support both python2/3
This commit is contained in:
Sunbin Zhu 2021-12-23 19:08:41 +08:00
Родитель 832d81dc64
Коммит 70554a6a76
9 изменённых файлов: 7272 добавлений и 106 удалений

Просмотреть файл

@ -2,11 +2,11 @@
"name": "HPCNodeManager", "name": "HPCNodeManager",
"version": 1.0, "version": 1.0,
"handlerManifest": { "handlerManifest": {
"installCommand": "hpcnodemanager.py -install", "installCommand": "./shim.sh -install",
"uninstallCommand": "hpcnodemanager.py -uninstall", "uninstallCommand": "./shim.sh -uninstall",
"updateCommand": "hpcnodemanager.py -update", "updateCommand": "./shim.sh -update",
"enableCommand": "hpcnodemanager.py -enable", "enableCommand": "./shim.sh -enable",
"disableCommand": "hpcnodemanager.py -disable", "disableCommand": "./shim.sh -disable",
"rebootAfterInstall": false, "rebootAfterInstall": false,
"reportHeartbeat": false, "reportHeartbeat": false,
"updateMode": "UpdateWithInstall" "updateMode": "UpdateWithInstall"

Просмотреть файл

@ -1,4 +1,4 @@
# #
# Handler library for Linux IaaS # Handler library for Linux IaaS
# #
# Copyright 2014 Microsoft Corporation # Copyright 2014 Microsoft Corporation
@ -14,8 +14,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#
# Requires Python 2.7+
""" """
@ -53,7 +51,6 @@ Example Status Report:
""" """
import os import os
import os.path import os.path
import sys import sys
@ -61,15 +58,20 @@ import imp
import base64 import base64
import json import json
import time import time
import re
from xml.etree import ElementTree
from os.path import join from os.path import join
from Utils.WAAgentUtil import waagent from Utils.WAAgentUtil import waagent
from waagent import LoggerInit from waagent import LoggerInit
DateTimeFormat = "%Y-%m-%dT%H:%M:%SZ" DateTimeFormat = "%Y-%m-%dT%H:%M:%SZ"
MANIFEST_XML = "manifest.xml"
class HandlerContext: class HandlerContext:
def __init__(self,name): def __init__(self, name):
self._name = name self._name = name
self._version = '0.0' self._version = '0.0'
self._config_dir = None self._config_dir = None
@ -83,14 +85,47 @@ class HandlerContext:
self._config = None self._config = None
return return
class HandlerUtility: class HandlerUtility:
def __init__(self, log, error, short_name): def __init__(self, log, error, s_name=None, l_name=None, extension_version=None, logFileName='extension.log',
console_logger=None, file_logger=None):
self._log = log self._log = log
self._log_to_con = console_logger
self._log_to_file = file_logger
self._error = error self._error = error
self._short_name = short_name self._logFileName = logFileName
if s_name is None or l_name is None or extension_version is None:
(l_name, s_name, extension_version) = self._get_extension_info()
self._short_name = s_name
self._extension_version = extension_version
self._log_prefix = '[%s-%s] ' % (l_name, extension_version)
def get_extension_version(self):
return self._extension_version
def _get_log_prefix(self): def _get_log_prefix(self):
return '[%s-%s]' %(self._context._name, self._context._version) return self._log_prefix
def _get_extension_info(self):
if os.path.isfile(MANIFEST_XML):
return self._get_extension_info_manifest()
ext_dir = os.path.basename(os.getcwd())
(long_name, version) = ext_dir.split('-')
short_name = long_name.split('.')[-1]
return long_name, short_name, version
def _get_extension_info_manifest(self):
with open(MANIFEST_XML) as fh:
doc = ElementTree.parse(fh)
namespace = doc.find('{http://schemas.microsoft.com/windowsazure}ProviderNameSpace').text
short_name = doc.find('{http://schemas.microsoft.com/windowsazure}Type').text
version = doc.find('{http://schemas.microsoft.com/windowsazure}Version').text
long_name = "%s.%s" % (namespace, short_name)
return (long_name, short_name, version)
def _get_current_seq_no(self, config_folder): def _get_current_seq_no(self, config_folder):
seq_no = -1 seq_no = -1
@ -100,13 +135,13 @@ class HandlerUtility:
for file in files: for file in files:
try: try:
cur_seq_no = int(os.path.basename(file).split('.')[0]) cur_seq_no = int(os.path.basename(file).split('.')[0])
if(freshest_time == None): if (freshest_time == None):
freshest_time = os.path.getmtime(join(config_folder,file)) freshest_time = os.path.getmtime(join(config_folder, file))
seq_no = cur_seq_no seq_no = cur_seq_no
else: else:
current_file_m_time = os.path.getmtime(join(config_folder,file)) current_file_m_time = os.path.getmtime(join(config_folder, file))
if(current_file_m_time > freshest_time): if (current_file_m_time > freshest_time):
freshest_time=current_file_m_time freshest_time = current_file_m_time
seq_no = cur_seq_no seq_no = cur_seq_no
except ValueError: except ValueError:
continue continue
@ -115,69 +150,87 @@ class HandlerUtility:
def log(self, message): def log(self, message):
self._log(self._get_log_prefix() + message) self._log(self._get_log_prefix() + message)
def log_to_console(self, message):
if self._log_to_con is not None:
self._log_to_con(self._get_log_prefix() + message)
else:
self.error("Unable to log to console, console log method not set")
def log_to_file(self, message):
if self._log_to_file is not None:
self._log_to_file(self._get_log_prefix() + message)
else:
self.error("Unable to log to file, file log method not set")
def error(self, message): def error(self, message):
self._error(self._get_log_prefix() + message) self._error(self._get_log_prefix() + message)
@staticmethod
def redact_protected_settings(content):
redacted_tmp = re.sub('"protectedSettings":\s*"[^"]+=="', '"protectedSettings": "*** REDACTED ***"', content)
redacted = re.sub('"protectedSettingsCertThumbprint":\s*"[^"]+"', '"protectedSettingsCertThumbprint": "*** REDACTED ***"', redacted_tmp)
return redacted
def _parse_config(self, ctxt): def _parse_config(self, ctxt):
config = None config = None
try: try:
config=json.loads(ctxt) config = json.loads(ctxt)
except: except:
self.error('JSON exception decoding ' + ctxt) self.error('JSON exception decoding ' + HandlerUtility.redact_protected_settings(ctxt))
if config == None: if config is None:
self.error("JSON error processing settings file:" + ctxt) self.error("JSON error processing settings file:" + HandlerUtility.redact_protected_settings(ctxt))
else: else:
handlerSettings = config['runtimeSettings'][0]['handlerSettings'] handlerSettings = config['runtimeSettings'][0]['handlerSettings']
if handlerSettings.has_key('protectedSettings') and \ if 'protectedSettings' in handlerSettings and \
handlerSettings.has_key("protectedSettingsCertThumbprint") and \ 'protectedSettingsCertThumbprint' in handlerSettings and \
handlerSettings['protectedSettings'] is not None and \ handlerSettings['protectedSettings'] is not None and \
handlerSettings["protectedSettingsCertThumbprint"] is not None: handlerSettings["protectedSettingsCertThumbprint"] is not None:
protectedSettings = handlerSettings['protectedSettings'] protectedSettings = handlerSettings['protectedSettings']
thumb=handlerSettings['protectedSettingsCertThumbprint'] thumb = handlerSettings['protectedSettingsCertThumbprint']
cert=waagent.LibDir+'/'+thumb+'.crt' cert = waagent.LibDir + '/' + thumb + '.crt'
pkey=waagent.LibDir+'/'+thumb+'.prv' pkey = waagent.LibDir + '/' + thumb + '.prv'
unencodedSettings = base64.standard_b64decode(protectedSettings) unencodedSettings = base64.standard_b64decode(protectedSettings)
openSSLcmd = "openssl smime -inform DER -decrypt -recip {0} -inkey {1}" openSSLcmd = "openssl smime -inform DER -decrypt -recip {0} -inkey {1}"
cleartxt = waagent.RunSendStdin(openSSLcmd.format(cert, pkey), unencodedSettings)[1] cleartxt = waagent.RunSendStdin(openSSLcmd.format(cert, pkey), unencodedSettings)[1]
if cleartxt == None: if cleartxt is None:
self.error("OpenSSh decode error using thumbprint " + thumb ) self.error("OpenSSL decode error using thumbprint " + thumb)
self.do_exit(1,operation,'error','1', 'Failed decrypting protectedSettings') self.do_exit(1, "Enable", 'error', '1', 'Failed to decrypt protectedSettings')
jctxt='' jctxt = ''
try: try:
jctxt=json.loads(cleartxt) jctxt = json.loads(cleartxt)
except: except:
self.error('JSON exception decoding ' + cleartxt) self.error('JSON exception decoding ' + HandlerUtility.redact_protected_settings(cleartxt))
handlerSettings['protectedSettings']=jctxt handlerSettings['protectedSettings']=jctxt
self.log('Config decoded correctly.') self.log('Config decoded correctly.')
return config return config
def do_parse_context(self,operation): def do_parse_context(self, operation):
_context = self.try_parse_context() _context = self.try_parse_context()
if not _context: if not _context:
self.do_exit(1,operation,'error','1', operation + ' Failed') self.do_exit(1, operation, 'error', '1', operation + ' Failed')
return _context return _context
def try_parse_context(self): def try_parse_context(self):
self._context = HandlerContext(self._short_name) self._context = HandlerContext(self._short_name)
handler_env=None handler_env = None
config=None config = None
ctxt=None ctxt = None
code=0 code = 0
# get the HandlerEnvironment.json. According to the extension handler spec, it is always in the ./ directory # get the HandlerEnvironment.json. According to the extension handler spec, it is always in the ./ directory
self.log('cwd is ' + os.path.realpath(os.path.curdir)) self.log('cwd is ' + os.path.realpath(os.path.curdir))
handler_env_file='./HandlerEnvironment.json' handler_env_file = './HandlerEnvironment.json'
if not os.path.isfile(handler_env_file): if not os.path.isfile(handler_env_file):
self.error("Unable to locate " + handler_env_file) self.error("Unable to locate " + handler_env_file)
return None return None
ctxt = waagent.GetFileContents(handler_env_file) ctxt = waagent.GetFileContents(handler_env_file)
if ctxt == None : if ctxt == None:
self.error("Unable to read " + handler_env_file) self.error("Unable to read " + handler_env_file)
try: try:
handler_env=json.loads(ctxt) handler_env = json.loads(ctxt)
except: except:
pass pass
if handler_env == None : if handler_env == None:
self.log("JSON error processing " + handler_env_file) self.log("JSON error processing " + handler_env_file)
return None return None
if type(handler_env) == list: if type(handler_env) == list:
@ -185,41 +238,41 @@ class HandlerUtility:
self._context._name = handler_env['name'] self._context._name = handler_env['name']
self._context._version = str(handler_env['version']) self._context._version = str(handler_env['version'])
self._context._config_dir=handler_env['handlerEnvironment']['configFolder'] self._context._config_dir = handler_env['handlerEnvironment']['configFolder']
self._context._log_dir= handler_env['handlerEnvironment']['logFolder'] self._context._log_dir = handler_env['handlerEnvironment']['logFolder']
self._context._log_file= os.path.join(handler_env['handlerEnvironment']['logFolder'],'extension.log')
self._context._log_file = os.path.join(handler_env['handlerEnvironment']['logFolder'], self._logFileName)
self._change_log_file() self._change_log_file()
self._context._status_dir=handler_env['handlerEnvironment']['statusFolder'] self._context._status_dir = handler_env['handlerEnvironment']['statusFolder']
self._context._heartbeat_file=handler_env['handlerEnvironment']['heartbeatFile'] self._context._heartbeat_file = handler_env['handlerEnvironment']['heartbeatFile']
self._context._seq_no = self._get_current_seq_no(self._context._config_dir) self._context._seq_no = self._get_current_seq_no(self._context._config_dir)
if self._context._seq_no < 0: if self._context._seq_no < 0:
self.error("Unable to locate a .settings file!") self.error("Unable to locate a .settings file!")
return None return None
self._context._seq_no = str(self._context._seq_no) self._context._seq_no = str(self._context._seq_no)
self.log('sequence number is ' + self._context._seq_no) self.log('sequence number is ' + self._context._seq_no)
self._context._status_file= os.path.join(self._context._status_dir, self._context._seq_no +'.status') self._context._status_file = os.path.join(self._context._status_dir, self._context._seq_no + '.status')
self._context._settings_file = os.path.join(self._context._config_dir, self._context._seq_no + '.settings') self._context._settings_file = os.path.join(self._context._config_dir, self._context._seq_no + '.settings')
self.log("setting file path is" + self._context._settings_file) self.log("setting file path is" + self._context._settings_file)
ctxt=None ctxt = None
ctxt=waagent.GetFileContents(self._context._settings_file) ctxt = waagent.GetFileContents(self._context._settings_file)
if ctxt == None : if ctxt == None:
error_msg = 'Unable to read ' + self._context._settings_file + '. ' error_msg = 'Unable to read ' + self._context._settings_file + '. '
self.error(error_msg) self.error(error_msg)
return None return None
self.log("JSON config: " + ctxt) self.log("JSON config: " + HandlerUtility.redact_protected_settings(ctxt))
self._context._config = self._parse_config(ctxt) self._context._config = self._parse_config(ctxt)
return self._context return self._context
def _change_log_file(self): def _change_log_file(self):
self.log("Change log file to " + self._context._log_file) self.log("Change log file to " + self._context._log_file)
LoggerInit(self._context._log_file,'/dev/stdout') LoggerInit(self._context._log_file, '/dev/stdout')
self._log = waagent.Log self._log = waagent.Log
self._error = waagent.Error self._error = waagent.Error
def set_verbose_log(self, verbose): def set_verbose_log(self, verbose):
if(verbose == "1" or verbose == 1): if (verbose == "1" or verbose == 1):
self.log("Enable verbose log") self.log("Enable verbose log")
LoggerInit(self._context._log_file, '/dev/stdout', verbose=True) LoggerInit(self._context._log_file, '/dev/stdout', verbose=True)
else: else:
@ -233,19 +286,22 @@ class HandlerUtility:
self._set_most_recent_seq(self._context._seq_no) self._set_most_recent_seq(self._context._seq_no)
self.log("set most recent sequence number to " + self._context._seq_no) self.log("set most recent sequence number to " + self._context._seq_no)
def exit_if_enabled(self): def exit_if_enabled(self, remove_protected_settings=False):
self.exit_if_seq_smaller() self.exit_if_seq_smaller(remove_protected_settings)
def exit_if_seq_smaller(self): def exit_if_seq_smaller(self, remove_protected_settings):
if(self.is_seq_smaller()): if(self.is_seq_smaller()):
self.log("Current sequence number, " + self._context._seq_no + ", is not greater than the sequnce number of the most recent executed configuration. Exiting...") self.log("Current sequence number, " + self._context._seq_no + ", is not greater than the sequence number of the most recent executed configuration. Exiting...")
sys.exit(0) sys.exit(0)
self.save_seq() self.save_seq()
if remove_protected_settings:
self.scrub_settings_file()
def _get_most_recent_seq(self): def _get_most_recent_seq(self):
if(os.path.isfile('mrseq')): if (os.path.isfile('mrseq')):
seq = waagent.GetFileContents('mrseq') seq = waagent.GetFileContents('mrseq')
if(seq): if (seq):
return int(seq) return int(seq)
return -1 return -1
@ -256,47 +312,47 @@ class HandlerUtility:
def get_inused_config_seq(self): def get_inused_config_seq(self):
return self._get_most_recent_seq() return self._get_most_recent_seq()
def set_inused_config_seq(self,seq): def set_inused_config_seq(self, seq):
self._set_most_recent_seq(seq) self._set_most_recent_seq(seq)
def _set_most_recent_seq(self,seq): def _set_most_recent_seq(self, seq):
waagent.SetFileContents('mrseq', str(seq)) waagent.SetFileContents('mrseq', str(seq))
def do_status_report(self, operation, status, status_code, message): def do_status_report(self, operation, status, status_code, message):
self.log("{0},{1},{2},{3}".format(operation, status, status_code, message)) self.log("{0},{1},{2},{3}".format(operation, status, status_code, message))
tstamp=time.strftime(DateTimeFormat, time.gmtime()) tstamp = time.strftime(DateTimeFormat, time.gmtime())
stat = [{ stat = [{
"version" : self._context._version, "version": self._context._version,
"timestampUTC" : tstamp, "timestampUTC": tstamp,
"status" : { "status": {
"name" : self._context._name, "name": self._context._name,
"operation" : operation, "operation": operation,
"status" : status, "status": status,
"code" : status_code, "code": status_code,
"formattedMessage" : { "formattedMessage": {
"lang" : "en-US", "lang": "en-US",
"message" : message "message": message
} }
} }
}] }]
stat_rept = json.dumps(stat) stat_rept = json.dumps(stat)
if self._context._status_file: if self._context._status_file:
tmp = "%s.tmp" %(self._context._status_file) tmp = "%s.tmp" % (self._context._status_file)
with open(tmp,'w+') as f: with open(tmp, 'w+') as f:
f.write(stat_rept) f.write(stat_rept)
os.rename(tmp, self._context._status_file) os.rename(tmp, self._context._status_file)
def do_heartbeat_report(self, heartbeat_file,status,code,message): def do_heartbeat_report(self, heartbeat_file, status, code, message):
# heartbeat # heartbeat
health_report='[{"version":"1.0","heartbeat":{"status":"' + status+ '","code":"'+ code + '","Message":"' + message + '"}}]' health_report = '[{"version":"1.0","heartbeat":{"status":"' + status + '","code":"' + code + '","Message":"' + message + '"}}]'
if waagent.SetFileContents(heartbeat_file,health_report) == None : if waagent.SetFileContents(heartbeat_file, health_report) == None:
self.error('Unable to wite heartbeat info to ' + heartbeat_file) self.error('Unable to wite heartbeat info to ' + heartbeat_file)
def do_exit(self,exit_code,operation,status,code,message): def do_exit(self, exit_code, operation, status, code, message):
try: try:
self.do_status_report(operation, status,code,message) self.do_status_report(operation, status, code, message)
except Exception as e: except Exception as e:
self.log("Can't update status: "+str(e)) self.log("Can't update status: " + str(e))
sys.exit(exit_code) sys.exit(exit_code)
def get_name(self): def get_name(self):
@ -324,3 +380,8 @@ class HandlerUtility:
return self.get_handler_settings().get('publicSettings') return self.get_handler_settings().get('publicSettings')
return None return None
def scrub_settings_file(self):
content = waagent.GetFileContents(self._context._settings_file)
redacted = HandlerUtility.redact_protected_settings(content)
waagent.SetFileContents(self._context._settings_file, redacted)

Просмотреть файл

@ -0,0 +1,50 @@
# Logging utilities
#
# Copyright 2014 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import os.path
import string
import sys
OutputSize = 4 * 1024
def tail(log_file, output_size = OutputSize):
pos = min(output_size, os.path.getsize(log_file))
with open(log_file, "r") as log:
log.seek(0, os.SEEK_END)
log.seek(log.tell() - pos, os.SEEK_SET)
buf = log.read(output_size)
buf = filter(lambda x: x in string.printable, buf)
# encoding works different for between interpreter version, we are keeping separate implementation to ensure
# backward compatibility
if sys.version_info[0] == 3:
buf = ''.join(list(buf)).encode('ascii', 'ignore').decode("ascii", "ignore")
elif sys.version_info[0] == 2:
buf = buf.decode("ascii", "ignore")
return buf
def get_formatted_log(summary, stdout, stderr):
msg_format = ("{0}\n"
"---stdout---\n"
"{1}\n"
"---errout---\n"
"{2}\n")
return msg_format.format(summary, stdout, stderr)

Просмотреть файл

@ -0,0 +1,140 @@
# Script utilities
#
# Copyright 2014 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import os.path
import time
import subprocess
import traceback
import string
import shlex
import sys
from Utils import LogUtil
from Utils.WAAgentUtil import waagent
DefaultStdoutFile = "stdout"
DefaultErroutFile = "errout"
def run_command(hutil, args, cwd, operation, extension_short_name, version, exit_after_run=True, interval=30,
std_out_file_name=DefaultStdoutFile, std_err_file_name=DefaultErroutFile):
std_out_file = os.path.join(cwd, std_out_file_name)
err_out_file = os.path.join(cwd, std_err_file_name)
std_out = None
err_out = None
try:
std_out = open(std_out_file, "w")
err_out = open(err_out_file, "w")
start_time = time.time()
child = subprocess.Popen(args,
cwd=cwd,
stdout=std_out,
stderr=err_out)
time.sleep(1)
while child.poll() is None:
msg = "Command is running..."
msg_with_cmd_output = LogUtil.get_formatted_log(msg, LogUtil.tail(std_out_file), LogUtil.tail(err_out_file))
msg_without_cmd_output = msg + " Stdout/Stderr omitted from output."
hutil.log_to_file(msg_with_cmd_output)
hutil.log_to_console(msg_without_cmd_output)
hutil.do_status_report(operation, 'transitioning', '0', msg_without_cmd_output)
time.sleep(interval)
exit_code = child.returncode
if child.returncode and child.returncode != 0:
msg = "Command returned an error."
msg_with_cmd_output = LogUtil.get_formatted_log(msg, LogUtil.tail(std_out_file), LogUtil.tail(err_out_file))
msg_without_cmd_output = msg + " Stdout/Stderr omitted from output."
hutil.error(msg_without_cmd_output)
waagent.AddExtensionEvent(name=extension_short_name,
op=operation,
isSuccess=False,
version=version,
message="(01302)" + msg_without_cmd_output)
else:
msg = "Command is finished."
msg_with_cmd_output = LogUtil.get_formatted_log(msg, LogUtil.tail(std_out_file), LogUtil.tail(err_out_file))
msg_without_cmd_output = msg + " Stdout/Stderr omitted from output."
hutil.log_to_file(msg_with_cmd_output)
hutil.log_to_console(msg_without_cmd_output)
waagent.AddExtensionEvent(name=extension_short_name,
op=operation,
isSuccess=True,
version=version,
message="(01302)" + msg_without_cmd_output)
end_time = time.time()
waagent.AddExtensionEvent(name=extension_short_name,
op=operation,
isSuccess=True,
version=version,
message=("(01304)Command execution time: "
"{0}s").format(str(end_time - start_time)))
log_or_exit(hutil, exit_after_run, exit_code, operation, msg_with_cmd_output)
except Exception as e:
error_msg = ("Failed to launch command with error: {0},"
"stacktrace: {1}").format(e, traceback.format_exc())
hutil.error(error_msg)
waagent.AddExtensionEvent(name=extension_short_name,
op=operation,
isSuccess=False,
version=version,
message="(01101)" + error_msg)
exit_code = 1
msg = 'Launch command failed: {0}'.format(e)
log_or_exit(hutil, exit_after_run, exit_code, operation, msg)
finally:
if std_out:
std_out.close()
if err_out:
err_out.close()
return exit_code
# do_exit calls sys.exit which raises an exception so we do not call it from the finally block
def log_or_exit(hutil, exit_after_run, exit_code, operation, msg):
status = 'success' if exit_code == 0 else 'failed'
if exit_after_run:
hutil.do_exit(exit_code, operation, status, str(exit_code), msg)
else:
hutil.do_status_report(operation, status, str(exit_code), msg)
def parse_args(cmd):
cmd = filter(lambda x: x in string.printable, cmd)
# encoding works different for between interpreter version, we are keeping separate implementation to ensure
# backward compatibility
if sys.version_info[0] == 3:
cmd = ''.join(list(cmd)).encode('ascii', 'ignore').decode("ascii", "ignore")
elif sys.version_info[0] == 2:
cmd = cmd.decode("ascii", "ignore")
args = shlex.split(cmd)
# From python 2.6 to python 2.7.2, shlex.split output UCS-4 result like
# '\x00\x00a'. Temp workaround is to replace \x00
for idx, val in enumerate(args):
if '\x00' in args[idx]:
args[idx] = args[idx].replace('\x00', '')
return args

Просмотреть файл

@ -16,10 +16,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#
# Requires Python 2.7+
#
import imp import imp
import os import os
@ -74,11 +70,55 @@ if not hasattr(waagent, "WALAEventOperation"):
Update = "Update" Update = "Update"
waagent.WALAEventOperation = _WALAEventOperation waagent.WALAEventOperation = _WALAEventOperation
__ExtensionName__=None # Better deal with the silly waagent typo, in anticipation of a proper fix of the typo later on waagent
if not hasattr(waagent.WALAEventOperation, 'Uninstall'):
if hasattr(waagent.WALAEventOperation, 'UnIsntall'):
waagent.WALAEventOperation.Uninstall = waagent.WALAEventOperation.UnIsntall
else: # This shouldn't happen, but just in case...
waagent.WALAEventOperation.Uninstall = 'Uninstall'
def GetWaagentHttpProxyConfigString():
"""
Get http_proxy and https_proxy from waagent config.
Username and password is not supported now.
This code is adopted from /usr/sbin/waagent
"""
host = None
port = None
try:
waagent.Config = waagent.ConfigurationProvider(None) # Use default waagent conf file (most likely /etc/waagent.conf)
host = waagent.Config.get("HttpProxy.Host")
port = waagent.Config.get("HttpProxy.Port")
except Exception as e:
# waagent.ConfigurationProvider(None) will throw an exception on an old waagent
# Has to silently swallow because logging is not yet available here
# and we don't want to bring that in here. Also if the call fails, then there's
# no proxy config in waagent.conf anyway, so it's safe to silently swallow.
pass
result = ''
if host is not None:
result = "http://" + host
if port is not None:
result += ":" + port
return result
waagent.HttpProxyConfigString = GetWaagentHttpProxyConfigString()
# end: waagent http proxy config stuff
__ExtensionName__ = None
def InitExtensionEventLog(name): def InitExtensionEventLog(name):
global __ExtensionName__ global __ExtensionName__
__ExtensionName__ = name __ExtensionName__ = name
def AddExtensionEvent(name=__ExtensionName__, def AddExtensionEvent(name=__ExtensionName__,
op=waagent.WALAEventOperation.Enable, op=waagent.WALAEventOperation.Enable,
isSuccess=False, isSuccess=False,

Просмотреть файл

@ -12,8 +12,5 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#
# Requires Python 2.7+
#

Просмотреть файл

@ -15,10 +15,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#
# Requires Python 2.7+
import os import os
import sys import sys
import json import json
@ -47,10 +43,10 @@ RestartIntervalInSeconds = 60
def main(): def main():
waagent.LoggerInit('/var/log/waagent.log','/dev/stdout') waagent.LoggerInit('/var/log/waagent.log','/dev/stdout')
waagent.Log("%s started to handle." %(ExtensionShortName)) waagent.Log('Microsoft.HpcPack Linux NodeAgent started to handle.')
waagent.MyDistro = waagent.GetMyDistro() waagent.MyDistro = waagent.GetMyDistro()
global DistroName, DistroVersion global DistroName, DistroVersion
distro = platform.dist() distro = get_dist_info()
DistroName = distro[0].lower() DistroName = distro[0].lower()
DistroVersion = distro[1] DistroVersion = distro[1]
for a in sys.argv[1:]: for a in sys.argv[1:]:
@ -78,7 +74,7 @@ def _is_nodemanager_daemon(pid):
return False return False
def install_package(package_name): def install_package(package_name):
if DistroName == "centos" or DistroName == "redhat": if DistroName in ["centos", "redhat", "alma", "rocky"]:
cmd = "yum -y install " + package_name cmd = "yum -y install " + package_name
elif DistroName == "ubuntu": elif DistroName == "ubuntu":
waagent.Log("Updating apt package lists with command: apt-get -y update") waagent.Log("Updating apt package lists with command: apt-get -y update")
@ -271,7 +267,7 @@ def _update_dns_record(domain_fqdn):
try: try:
s.connect((domain_fqdn, 53)) s.connect((domain_fqdn, 53))
break break
except Exception, e: except Exception as e:
waagent.Log('Failed to connect to {0}:53: {1}'.format(domain_fqdn, e)) waagent.Log('Failed to connect to {0}:53: {1}'.format(domain_fqdn, e))
ipaddr = s.getsockname()[0] ipaddr = s.getsockname()[0]
host_fqdn = "{0}.{1}".format(socket.gethostname().split('.')[0], domain_fqdn) host_fqdn = "{0}.{1}".format(socket.gethostname().split('.')[0], domain_fqdn)
@ -338,7 +334,7 @@ def install():
try: try:
cleanup_host_entries() cleanup_host_entries()
_uninstall_nodemanager_files() _uninstall_nodemanager_files()
if DistroName == "centos" or DistroName == "redhat": if DistroName in ["centos", "redhat", "alma", "rocky"]:
waagent.Run("yum-config-manager --setopt=\\*.skip_if_unavailable=1 --save", chk_err=False) waagent.Run("yum-config-manager --setopt=\\*.skip_if_unavailable=1 --save", chk_err=False)
_install_cgroup_tool() _install_cgroup_tool()
_install_sysstat() _install_sysstat()
@ -459,7 +455,7 @@ def install():
shutil.copy2(configfile, backup_configfile) shutil.copy2(configfile, backup_configfile)
config_firewall_rules() config_firewall_rules()
hutil.do_exit(0, 'Install', 'success', '0', 'Install Succeeded.') hutil.do_exit(0, 'Install', 'success', '0', 'Install Succeeded.')
except Exception, e: except Exception as e:
hutil.do_exit(1, 'Install','error','1', '{0}'.format(e)) hutil.do_exit(1, 'Install','error','1', '{0}'.format(e))
def enable(): def enable():
@ -477,7 +473,7 @@ def enable():
os.killpg(int(pid), 9) os.killpg(int(pid), 9)
os.remove(DaemonPidFilePath) os.remove(DaemonPidFilePath)
args = [os.path.join(os.getcwd(), __file__), "daemon"] args = [get_python_executor(), os.path.join(os.getcwd(), __file__), "daemon"]
devnull = open(os.devnull, 'w') devnull = open(os.devnull, 'w')
child = subprocess.Popen(args, stdout=devnull, stderr=devnull, preexec_fn=os.setsid) child = subprocess.Popen(args, stdout=devnull, stderr=devnull, preexec_fn=os.setsid)
if child.pid is None or child.pid < 1: if child.pid is None or child.pid < 1:
@ -569,7 +565,7 @@ def daemon():
waagent.Log("Restart HPC node manager process after {0} seconds".format(RestartIntervalInSeconds)) waagent.Log("Restart HPC node manager process after {0} seconds".format(RestartIntervalInSeconds))
time.sleep(RestartIntervalInSeconds) time.sleep(RestartIntervalInSeconds)
except Exception, e: except Exception as e:
hutil.error("Failed to enable the extension with error: %s, stack trace: %s" %(str(e), traceback.format_exc())) hutil.error("Failed to enable the extension with error: %s, stack trace: %s" %(str(e), traceback.format_exc()))
hutil.do_exit(1, 'Enable','error','1', 'Enable failed.') hutil.do_exit(1, 'Enable','error','1', 'Enable failed.')
@ -616,6 +612,57 @@ def update():
waagent.MyDistro.publishHostname(confighostname) waagent.MyDistro.publishHostname(confighostname)
hutil.do_exit(0,'Update','success','0', 'Update Succeeded') hutil.do_exit(0,'Update','success','0', 'Update Succeeded')
def get_python_executor():
cmd = ''
if sys.version_info.major == 2:
cmd = 'python2'
elif sys.version_info.major == 3:
cmd = 'python3'
if waagent.Run("command -v {0}".format(cmd), chk_err=False) != 0:
# If a user-installed python isn't available, check for a platform-python. This is typically only used in RHEL 8.0.
if waagent.Run("command -v /usr/libexec/platform-python", chk_err=False) == 0:
cmd = '/usr/libexec/platform-python'
return cmd
def get_dist_info():
try:
return waagent.DistInfo()
except:
pass
errCode, info = waagent.RunGetOutput("cat /etc/*-release")
if errCode != 0:
raise Exception('Failed to get Linux Distro info by running command "cat /etc/*release", error code: {}'.format(errCode))
distroName = ''
distroVersion = ''
for line in info.splitlines():
if line.startswith('PRETTY_NAME='):
line = line.lower()
if 'ubuntu' in line:
distroName = 'ubuntu'
elif 'centos' in line:
distroName = 'centos'
elif 'red hat' in line:
distroName = 'redhat'
elif 'suse' in line:
distroName = 'suse'
elif 'alma' in line:
distroName = 'alma'
elif 'rocky' in line:
distroName = 'rocky'
elif 'fedora' in line:
distroName = 'fedora'
elif 'freebsd' in line:
distroName = 'freebsd'
else:
raise Exception('Unknown linux distribution with {}'.format(line))
if line.startswith('VERSION_ID='):
line = line.strip(' ')
quoteIndex = line.index('"')
if quoteIndex >= 0:
distroVersion = line[quoteIndex+1:-1]
return distroName, distroVersion, ""
if __name__ == '__main__' : if __name__ == '__main__' :
main() main()

36
VMExtension/shim.sh Normal file
Просмотреть файл

@ -0,0 +1,36 @@
#!/usr/bin/env bash
# This is the main driver file for HPC Pack Linux NodeAgent extension. This file first checks if Python 3 or 2 is available on the VM
# and if yes then uses that Python (if both are available then, default is set to python3) to run extension operations in hpcnodemanager.py
# Control arguments passed to the shim are redirected to hpcnodemanager.py without validation.
COMMAND="./hpcnodemanager.py"
PYTHON=""
ARG="$@"
function find_python() {
local python_exec_command=$1
if command -v python3 >/dev/null 2>&1 ; then
eval ${python_exec_command}="python3"
elif command -v python2 >/dev/null 2>&1 ; then
eval ${python_exec_command}="python2"
elif command -v /usr/libexec/platform-python >/dev/null 2>&1 ; then
# If a user-installed python isn't available, check for a platform-python. This is typically only used in RHEL 8.0.
echo "User-installed python not found. Using /usr/libexec/platform-python as the python interpreter."
eval ${python_exec_command}="/usr/libexec/platform-python"
fi
}
find_python PYTHON
if [ -z "$PYTHON" ] # If python is not installed, we will fail the install with the following error, requiring cx to have python pre-installed
then
echo "No Python interpreter found. Please install Python 3, or Python 2 if the former is unavailable." >&2
exit 52 # Missing Dependency
else
${PYTHON} --version 2>&1
fi
${PYTHON} ${COMMAND} ${ARG}
exit $?

6795
VMExtension/waagent Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу