diff options
author | Ben Howard <ben.howard@ubuntu.com> | 2014-05-20 08:26:50 -0600 |
---|---|---|
committer | usd-importer <ubuntu-server@lists.ubuntu.com> | 2014-05-20 14:53:44 +0000 |
commit | 320f1b54bf746a6664e17df719a20f5c14a5ea2f (patch) | |
tree | 116650330685e98abfd3d0f3f7cde100ef8f8f24 | |
parent | b2eb317cf1393f7d58f842a8dc1eabc81f03166b (diff) | |
parent | 00c97042e31a0b55b34e0fc9ed8773651a708bfe (diff) | |
download | vyos-walinuxagent-320f1b54bf746a6664e17df719a20f5c14a5ea2f.tar.gz vyos-walinuxagent-320f1b54bf746a6664e17df719a20f5c14a5ea2f.zip |
Import patches-applied version 2.0.5-0ubuntu1 to applied/ubuntu/utopic-proposed
Imported using git-ubuntu import.
Changelog parent: b2eb317cf1393f7d58f842a8dc1eabc81f03166b
Unapplied parent: 00c97042e31a0b55b34e0fc9ed8773651a708bfe
New changelog entries:
* New upstream release (LP: #1321024).
- Multiple fixes for extension handler framework
- New logging support
- Add state handling for each extension
- Properly handle non-JSON extensions
- Several other bugfixes
- Replace platform.* calls with DistInfo() function
- EnvMonitor - Set SCSI I/O timeout for all attached disks
* Packaging changes
- Rebased Ubuntu packaging patches from 2.0.4.
- Updated Debian standards from 3.9.4 to 3.9.5.
-rw-r--r-- | Changelog | 13 | ||||
-rw-r--r-- | README | 7 | ||||
-rw-r--r-- | debian/changelog | 16 | ||||
-rw-r--r-- | debian/control | 2 | ||||
-rw-r--r-- | debian/patches/sshd_config_newline_fix.patch | 2 | ||||
-rw-r--r--[-rwxr-xr-x] | waagent | 826 |
6 files changed, 651 insertions, 215 deletions
@@ -1,5 +1,18 @@ WALinuxAgent Changelog ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +19 May 2014, WALinuxAgent 2.0.5 + . Multiple fixes for FreeBSD provisioning + . Add support for SLES 12 + . Multiple fixes for extension handler framework + . New logging support + . Add state handling for each extension + . Properly handle non-JSON extensions + . Several other bugfixes + . Replace platform.* calls with DistInfo() function + . Inherit from redhatDistro in centosDistro class + . Fix hostname configuration for RHEL7-based systems + . EnvMonitor - Set SCSI I/O timeout for all attached disks + 02 Apr 2014, WALinuxAgent 2.0.4 . Fix encoding issue in LogToFile() & LogToCon() . Add support for parsing ExtensionsConfiguration from GoalState document @@ -33,6 +33,11 @@ functionality for Linux and FreeBSD IaaS deployments: - Detect and bootstrap the VMM agent for Linux when running in a System Center Virtual Machine Manager 2012R2 environment + * VM Extension + - Inject component authored by Microsoft and Partners into Linux VM (IaaS) + to enable software and configuration automation + - VM Extension reference implementation on https://github.com/Azure/azure-linux-extensions + COMMUNICATION @@ -61,7 +66,7 @@ http://support.microsoft.com/kb/2805216 * SLES 11 SP2+ * Oracle Linux 6.4+ - Other Supported Syst4ems: + Other Supported Systems: * FreeBSD 9+ Waagent depends on some system packages in order to function properly: diff --git a/debian/changelog b/debian/changelog index dac7683..94ba03e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,19 @@ +walinuxagent (2.0.5-0ubuntu1) utopic; urgency=medium + + * New upstream release (LP: #1321024). + - Multiple fixes for extension handler framework + - New logging support + - Add state handling for each extension + - Properly handle non-JSON extensions + - Several other bugfixes + - Replace platform.* calls with DistInfo() function + - EnvMonitor - Set SCSI I/O timeout for all attached disks + * Packaging changes + - Rebased Ubuntu packaging patches from 2.0.4. + - Updated Debian standards from 3.9.4 to 3.9.5. + + -- Ben Howard <ben.howard@ubuntu.com> Tue, 20 May 2014 08:26:50 -0600 + walinuxagent (2.0.4-0ubuntu2) trusty; urgency=low * Fix for broken sshd configuration (LP: #1305418) diff --git a/debian/control b/debian/control index e3d3d6c..7cd9c8c 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Priority: extra Maintainer: Ben Howard <ben.howard@ubuntu.com> XSBC-Original-Maintainer: Microsoft Corporation <walinuxagent@microsoft.com> Build-Depends: debhelper (>= 8), python-all, python-setuptools -Standards-Version: 3.9.4 +Standards-Version: 3.9.5 XS-Python-Version: all Homepage: http://go.microsoft.com/fwlink/?LinkId=250998 diff --git a/debian/patches/sshd_config_newline_fix.patch b/debian/patches/sshd_config_newline_fix.patch index a599c81..97c7b5d 100644 --- a/debian/patches/sshd_config_newline_fix.patch +++ b/debian/patches/sshd_config_newline_fix.patch @@ -11,7 +11,7 @@ Bug-Ubuntu: https://bugs.launchpad.net/bugs/1305418 --- a/waagent +++ b/waagent -@@ -3353,7 +3353,7 @@ +@@ -3719,7 +3719,7 @@ # Disable RFC 4252 and RFC 4256 authentication schemes. ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")), @@ -78,7 +78,7 @@ if not hasattr(subprocess,'check_output'): GuestAgentName = "WALinuxAgent" GuestAgentLongName = "Windows Azure Linux Agent" -GuestAgentVersion = "WALinuxAgent-2.0.4" +GuestAgentVersion = "WALinuxAgent-2.0.5" ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol. Config = None @@ -95,6 +95,10 @@ VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp" EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"] global LibDir LibDir = "/var/lib/waagent" +global provisioned +provisioned=False +global provisionError +provisionError=None WaagentConf = """\ # @@ -138,8 +142,8 @@ class AbstractDistro(object): Generic methods and attributes are kept here, distribution specific attributes and behavior are to be placed in the concrete child named distroDistro, where - distro is the string returned by calling python platform.dist()[0]. So for CentOS - the derived class is called 'centosDistro'. + distro is the string returned by calling python platform.linux_distribution()[0]. + So for CentOS the derived class is called 'centosDistro'. """ def __init__(self): @@ -150,6 +154,7 @@ class AbstractDistro(object): self.agent_service_name = os.path.basename(sys.argv[0]) self.selinux=None self.service_cmd='/usr/sbin/service' + self.ssh_service_restart_option='restart' self.ssh_service_name='ssh' self.ssh_config_file='/etc/ssh/sshd_config' self.hostname_file_path='/etc/hostname' @@ -273,11 +278,12 @@ class AbstractDistro(object): """ Service call to re(start) the SSH service """ - if not Run(self.service_cmd + " " + self.ssh_service_name + " status | grep running"): - return Run(self.service_cmd + " " + self.ssh_service_name + " reload") - else: - return 0 - + sshRestartCmd = self.service_cmd + " " + self.ssh_service_name + " " + self.ssh_service_restart_option + retcode = Run(sshRestartCmd) + if retcode > 0: + Error("Failed to restart SSH service with return code:" + retcode) + return retcode + def sshDeployPublicKey(self,fprint,path): """ Generic sshDeployPublicKey - over-ridden in some concrete Distro classes due to minor differences in openssl packages deployed @@ -489,7 +495,34 @@ class AbstractDistro(object): def getDhcpClientName(self): return self.dhcp_client_name - + + def initScsiDiskTimeout(self): + """ + Set the SCSI disk timeout when the agent starts running + """ + self.setScsiDiskTimeout() + + def setScsiDiskTimeout(self): + """ + Iterate all SCSI disks(include hot-add) and set their timeout if their value are different from the OS.RootDeviceScsiTimeout + """ + try: + scsiTimeout = Config.get("OS.RootDeviceScsiTimeout") + for diskName in [disk for disk in os.listdir("/sys/block") if disk.startswith("sd")]: + self.setBlockDeviceTimeout(diskName, scsiTimeout) + except: + pass + + def setBlockDeviceTimeout(self, device, timeout): + """ + Set SCSI disk timeout by set /sys/block/sd*/device/timeout + """ + if timeout != None and device: + filePath = "/sys/block/" + device + "/device/timeout" + if(GetFileContents(filePath).splitlines()[0].rstrip() != timeout): + SetFileContents(filePath,timeout) + Log("SetBlockDeviceTimeout: Update the device " + device + " with timeout " + timeout) + ############################################################ # SuSEDistro ############################################################ @@ -621,6 +654,8 @@ class SuSEDistro(AbstractDistro): self.requiredDeps += [ "/sbin/insserv" ] self.init_file=suse_init_file self.dhcp_client_name='dhcpcd' + if (DistInfo(fullname=1)[0] == 'SUSE Linux Enterprise Server') and (DistInfo()[1] >= '12'): + self.dhcp_client_name='wickedd-dhcp4' self.grubKernelBootOptionsFile = '/boot/grub/menu.lst' self.grubKernelBootOptionsLine = 'kernel' self.getpidcmd='pidof ' @@ -725,18 +760,20 @@ class redhatDistro(AbstractDistro): def __init__(self): super(redhatDistro,self).__init__() self.service_cmd='/sbin/service' + self.ssh_service_restart_option='condrestart' self.ssh_service_name='sshd' - self.hostname_file_path=None + self.hostname_file_path= None if DistInfo()[1] < '7.0' else '/etc/hostname' self.init_file=redhat_init_file self.grubKernelBootOptionsFile = '/boot/grub/menu.lst' self.grubKernelBootOptionsLine = 'kernel' def publishHostname(self,name): super(redhatDistro,self).publishHostname(name) - filepath = "/etc/sysconfig/network" - if os.path.isfile(filepath): - ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n" - + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n')))) + if DistInfo()[1] < '7.0' : + filepath = "/etc/sysconfig/network" + if os.path.isfile(filepath): + ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n" + + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n')))) ethernetInterface = MyDistro.GetInterfaceName() filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface @@ -774,122 +811,18 @@ class redhatDistro(AbstractDistro): return 0 - + ############################################################ # centosDistro ############################################################ -centos_init_file= """\ -#!/bin/bash -# -# Init file for WindowsAzureLinuxAgent. -# -# chkconfig: 2345 60 80 -# description: WindowsAzureLinuxAgent -# - -# source function library -. /etc/rc.d/init.d/functions - -RETVAL=0 -FriendlyName="WindowsAzureLinuxAgent" -WAZD_BIN=/usr/sbin/waagent - -start() -{ - echo -n $"Starting $FriendlyName: " - $WAZD_BIN -daemon & -} - -stop() -{ - echo -n $"Stopping $FriendlyName: " - killproc -p /var/run/waagent.pid $WAZD_BIN - RETVAL=$? - echo - return $RETVAL -} - -case "$1" in - start) - start - ;; - stop) - stop - ;; - restart) - stop - start - ;; - reload) - ;; - report) - ;; - status) - status $WAZD_BIN - RETVAL=$? - ;; - *) - echo $"Usage: $0 {start|stop|restart|status}" - RETVAL=1 -esac -exit $RETVAL -""" - -class centosDistro(AbstractDistro): +class centosDistro(redhatDistro): """ CentOS Distro concrete class Put CentOS specific behavior here... """ def __init__(self): super(centosDistro,self).__init__() - self.service_cmd='/sbin/service' - self.ssh_service_name='sshd' - self.hostname_file_path=None - self.init_file=centos_init_file - self.grubKernelBootOptionsFile = '/boot/grub/menu.lst' - self.grubKernelBootOptionsLine = 'kernel' - - def publishHostname(self,name): - super(centosDistro,self).publishHostname(name) - filepath = "/etc/sysconfig/network" - if os.path.isfile(filepath): - ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n" - + "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n')))) - ethernetInterface = MyDistro.GetInterfaceName() - filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface - if os.path.isfile(filepath): - ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n" - + "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n')))) - return 0 - - def installAgentServiceScriptFiles(self): - SetFileContents(self.init_script_file, self.init_file) - os.chmod(self.init_script_file, 0744) - return 0 - - def registerAgentService(self): - self.installAgentServiceScriptFiles() - return Run('chkconfig --add waagent') - - def uninstallAgentService(self): - return Run('chkconfig --del ' + self.agent_service_name) - - def unregisterAgentService(self): - self.stopAgentService() - return self.uninstallAgentService() - - def checkPackageInstalled(self,p): - if Run("yum list installed " + p,chk_err=False): - return 0 - else: - return 1 - - def checkPackageUpdateable(self,p): - if Run("yum check-update | grep "+ p,chk_err=False): - return 1 - else: - return 0 ############################################################ # debianDistro @@ -1142,7 +1075,7 @@ class UbuntuDistro(debianDistro): def getDhcpClientName(self): if self.dhcp_client_name != None : return self.dhcp_client_name - if platform.dist()[1] == '12.04' : + if DistInfo()[1] == '12.04' : self.dhcp_client_name='dhclient3' else : self.dhcp_client_name='dhclient' @@ -1314,12 +1247,7 @@ class FreeBSDDistro(AbstractDistro): def registerAgentService(self): self.installAgentServiceScriptFiles() return Run("services_mkdb " + self.init_script_file) - - def restartSshService(self): - """ - Service call to re(start) the SSH service - """ - return Run(self.service_cmd + " " + self.ssh_service_name + " restart") + def sshDeployPublicKey(self,fprint,path): """ @@ -1600,11 +1528,37 @@ class FreeBSDDistro(AbstractDistro): def mountDVD(self,dvd,location): #At this point we cannot read a joliet option udf DVD in freebsd10 - so we 'dd' it into our location - return RunGetOutput(self.mount_dvd_cmd + dvd + ' of=' + location + '/ovf-env.xml') + retcode,out = RunGetOutput(self.mount_dvd_cmd + dvd + ' of=' + location + '/ovf-env.xml') + if retcode != 0: + return retcode,out + + ovfxml = (GetFileContents(location+"/ovf-env.xml",asbin=False)) + if ord(ovfxml[0]) > 128 and ord(ovfxml[1]) > 128 and ord(ovfxml[2]) > 128 : + ovfxml = ovfxml[3:] # BOM is not stripped. First three bytes are > 128 and not unicode chars so we ignore them. + ovfxml = ovfxml.strip(chr(0x00)) + ovfxml = "".join(filter(lambda x: ord(x)<128, ovfxml)) + ovfxml = re.sub('>.*\Z', '', ovfxml) + ovfxml += '>' + SetFileContents(location+"/ovf-env.xml", ovfxml) + return retcode,out def GetHome(self): return '/home' + def initScsiDiskTimeout(self): + """ + Set the SCSI disk timeout by updating the kernal config + """ + timeout = Config.get("OS.RootDeviceScsiTimeout") + if timeout: + Run("sysctl kern.cam.da.default_timeout=" + timeout) + + def setScsiDiskTimeout(self): + return + + def setBlockDeviceTimeout(self, device, timeout): + return + ############################################################ # END DISTRO CLASS DEFS ############################################################ @@ -1654,7 +1608,7 @@ def SetFileContents(filepath, contents): Write 'contents' to 'filepath'. """ if type(contents) == str : - contents=contents.encode('latin-1') + contents=contents.encode('latin-1', 'ignore') try: with open(filepath, "wb+") as F : F.write(contents) @@ -1953,6 +1907,15 @@ def HexDump(buffer, size): result += "\n" return result +def SimpleLog(file_path,message): + if not file_path or len(message) < 1: + return + t = time.localtime() + t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec) + lines=re.sub(re.compile(r'^(.)',re.MULTILINE),t+r'\1',message) + with open(file_path, "a") as F : + F.write(lines.encode('ascii','ignore') + "\n") + class Logger(object): """ The Agent's logging assumptions are: @@ -2261,6 +2224,40 @@ class Util(object): log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus) return response + def HttpPutBlockBlob(self, url, data): + """ + Send http PUT to server, sleeping 10 retrying maxRetry times upon error. + """ + LogIfVerbose("HttpPutBlockBlob(" + url + ")") + maxRetry = 2 + for retry in range(0, maxRetry + 1): + strRetry = str(retry) + log = [NoLog, Error][retry > 0] + log("retry HttpPutBlockBlob(" + url + "),retry=" + strRetry) + response = None + strStatus = "None" + try: + httpConnection = httplib.HTTPConnection(self.Endpoint) + request = httpConnection.request("PUT", url, data, {"x-ms-blob-type" : "BlockBlob", "x-ms-date" : time.strftime("%Y-%M-%dT%H:%M:%SZ", time.gmtime()) ,"Content-Length": str(len(data))} ) + response = httpConnection.getresponse() + strStatus = str(response.status) + except httplib.HTTPException, e: + Error('HTTPException ' + e.message + ' args: ' + repr(e.args)) + except IOError, e: + Error('socket IOError ' + e.message + ' args: ' + repr(e.args)) + log("response HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) + if response == None or (response.status != httplib.OK and response.status != httplib.CREATED): + Error("HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) + if retry == maxRetry: + Error("return HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) + return None + else: + Error("sleep 10 seconds HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) + time.sleep(10) + else: + log("return HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus) + return response + class TCPHandler(SocketServer.BaseRequestHandler): """ Callback object for LoadBalancerProbeServer. @@ -2368,6 +2365,7 @@ class EnvMonitor(object): os.remove(GetLastPathElement(a)) shutil.move(a, ".") Log("EnvMonitor: Moved " + a + " -> " + LibDir) + MyDistro.setScsiDiskTimeout() if publish != None and publish.lower().startswith("y"): try: if socket.gethostname() != self.HostName: @@ -2629,46 +2627,97 @@ class ExtensionsConfig(object): def Parse(self, xmlText): """ Write configuration to file ExtensionsConfig.xml. + Log plugin specific activity to /var/log/azure/<Publisher>.<PluginName>/<Version>/CommandExecution.log. If state is enabled: if the plugin is installed: - if the new plugin's version is higher: + if the new plugin's version is higher + if DisallowMajorVersionUpgrade is false or if true, the version is a minor version do upgrade: download the new archive do the updateCommand. - - if the version is the same or lower: + disable the old plugin and remove + enable the new plugin + if the new plugin's version is the same or lower: create the new .settings file from the configuration received do the enableCommand if the plugin is not installed: - download/unpack archive and call the installCommand - + download/unpack archive and call the installCommand/Enable if state is disabled: call disableCommand - Create execuatable shell script containig 'installCommand'. - Spawn the script and report the PID to the Log. + if state is uninstall: + call uninstallCommand + remove old plugin directory. """ self.reinitialize() self.Util=Util() dom = xml.dom.minidom.parseString(xmlText) LogIfVerbose(xmlText) - self.Extensions=dom.getElementsByTagName("Extensions") - self.Plugins = dom.getElementsByTagName("Plugin") - incarnation=self.Extensions[0].getAttribute("goalStateIncarnation") - SetFileContents('ExtensionsConfig.'+incarnation+'.xml', xmlText) + self.plugin_log_dir='/var/log/azure' + if not os.path.exists(self.plugin_log_dir): + os.mkdir(self.plugin_log_dir) + try: + self.Extensions=dom.getElementsByTagName("Extensions") + pg = dom.getElementsByTagName("Plugins") + self.Plugins = pg[0].getElementsByTagName("Plugin") + incarnation=self.Extensions[0].getAttribute("goalStateIncarnation") + SetFileContents('ExtensionsConfig.'+incarnation+'.xml', xmlText) + except: + LogIfVerbose('ERROR: Error parsing ExtensionsConfig.') + return None for p in self.Plugins: if len(p.getAttribute("location"))<1: # this plugin is inside the PluginSettings continue + p.setAttribute('restricted','false') previous_version = None version=p.getAttribute("version") name=p.getAttribute("name") + plog_dir=self.plugin_log_dir+'/'+name +'/'+ version + if not os.path.exists(plog_dir): + os.makedirs(plog_dir) + p.plugin_log=plog_dir+'/CommandExecution.log' + handler=name + '-' + version + if p.getAttribute("isJson") != 'true': + Error("Plugin " + name+" version: " +version+" is not a JSON Extension. Skipping.") + continue Log("Found Plugin: " + name + ' version: ' + version) - if p.getAttribute("state") == 'disabled' : - #disable - if self.launchCommand(name,version,'disableCommand') == None : - Error('Unable to disable '+name) + if p.getAttribute("state") == 'disabled' or p.getAttribute("state") == 'uninstall': + #disable + zip_dir=LibDir+"/" + name + '-' + version + mfile=None + for root, dirs, files in os.walk(zip_dir): + for f in files: + if f in ('HandlerManifest.json'): + mfile=os.path.join(root,f) + if mfile != None: + break + if mfile == None : + Error('HandlerManifest.json not found.') continue + manifest = GetFileContents(mfile) + p.setAttribute('manifestdata',manifest) + if self.launchCommand(p.plugin_log,name,version,'disableCommand') == None : + self.SetHandlerState(handler, 'Enabled') + Error('Unable to disable '+name) + SimpleLog(p.plugin_log,'ERROR: Unable to disable '+name) else : + self.SetHandlerState(handler, 'Installed') Log(name+' is disabled') - continue + SimpleLog(p.plugin_log,name+' is disabled') + + # uninstall if needed + if p.getAttribute("state") == 'uninstall': + if self.launchCommand(p.plugin_log,name,version,'uninstallCommand') == None : + self.SetHandlerState(handler, 'Installed') + Error('Unable to uninstall '+name) + SimpleLog(p.plugin_log,'Unable to uninstall '+name) + else : + self.SetHandlerState(handler, 'NotInstalled') + Log(name+' uninstallCommand completed .') + # remove the plugin + Run('rm -rf ' + LibDir + '/' + name +'-'+ version + '*') + Log(name +'-'+ version + ' extension files deleted.') + SimpleLog(p.plugin_log,name +'-'+ version + ' extension files deleted.') + + continue # state is enabled # if the same plugin exists and the version is newer or # does not exist then download and unzip the new plugin @@ -2684,44 +2733,73 @@ class ExtensionsConfig(object): if plg_dir == None or version > previous_version : location=p.getAttribute("location") Log("Downloading plugin manifest: " + name + " from " + location) + SimpleLog(p.plugin_log,"Downloading plugin manifest: " + name + " from " + location) + self.Util.Endpoint=location.split('/')[2] Log("Plugin server is: " + self.Util.Endpoint) + SimpleLog(p.plugin_log,"Plugin server is: " + self.Util.Endpoint) + manifest=self.Util.HttpGetWithoutHeaders(location) if manifest == None: Error("Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.") + SimpleLog(p.plugin_log,"Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.") failoverlocation=p.getAttribute("failoverlocation") self.Util.Endpoint=failoverlocation.split('/')[2] Log("Plugin failover server is: " + self.Util.Endpoint) + SimpleLog(p.plugin_log,"Plugin failover server is: " + self.Util.Endpoint) + manifest=self.Util.HttpGetWithoutHeaders(failoverlocation) if manifest == None: - Error("Unable to download plugin manifest" + name + " from failover location") - continue - Log("Plugin manifest" + name + "downloaded successfully length = " + str(len(manifest))) + Log("Plugin manifest" + name + "downloaded successfully length = " + str(len(manifest))) + SimpleLog(p.plugin_log,"Plugin manifest" + name + "downloaded successfully length = " + str(len(manifest))) + filepath=LibDir+"/" + name + '.' + incarnation + '.manifest' if os.path.splitext(location)[-1] == '.xml' : #if this is an xml file we may have a BOM if ord(manifest[0]) > 128 and ord(manifest[1]) > 128 and ord(manifest[2]) > 128: manifest=manifest[3:] SetFileContents(filepath,manifest) #Get the bundle url from the manifest + p.setAttribute('manifestdata',manifest) man_dom = xml.dom.minidom.parseString(manifest) - bundle_uri = GetNodeTextData(man_dom.getElementsByTagName("Uri")[0]) + bundle_uri = "" + for mp in man_dom.getElementsByTagName("Plugin"): + if GetNodeTextData(mp.getElementsByTagName("Version")[0]) == version: + bundle_uri = GetNodeTextData(mp.getElementsByTagName("Uri")[0]) + break + if len(mp.getElementsByTagName("DisallowMajorVersionUpgrade")): + if GetNodeTextData(mp.getElementsByTagName("DisallowMajorVersionUpgrade")[0]) == 'true' and previous_version !=None and previous_version.split('.')[0] != version.split('.')[0] : + Log('DisallowMajorVersionUpgrade is true, this major version is restricted from upgrade.') + SimpleLog(p.plugin_log,'DisallowMajorVersionUpgrade is true, this major version is restricted from upgrade.') + p.setAttribute('restricted','true') + continue + if len(bundle_uri) < 1 : + Error("Unable to fetch Bundle URI from manifest for " + name + " v " + version) + SimpleLog(p.plugin_log,"Unable to fetch Bundle URI from manifest for " + name + " v " + version) + continue Log("Bundle URI = " + bundle_uri) + SimpleLog(p.plugin_log,"Bundle URI = " + bundle_uri) + # Download the zipfile archive and save as '.zip' bundle=self.Util.HttpGetWithoutHeaders(bundle_uri) if bundle == None: Error("Unable to download plugin bundle" + bundle_uri ) + SimpleLog(p.plugin_log,"Unable to download plugin bundle" + bundle_uri ) continue b=bytearray(bundle) filepath=LibDir+"/" + os.path.basename(bundle_uri) + '.zip' SetFileContents(filepath,b) Log("Plugin bundle" + bundle_uri + "downloaded successfully length = " + str(len(bundle))) + SimpleLog(p.plugin_log,"Plugin bundle" + bundle_uri + "downloaded successfully length = " + str(len(bundle))) + # unpack the archive z=zipfile.ZipFile(filepath) zip_dir=LibDir+"/" + name + '-' + version z.extractall(zip_dir) Log('Extracted ' + bundle_uri + ' to ' + zip_dir) + SimpleLog(p.plugin_log,'Extracted ' + bundle_uri + ' to ' + zip_dir) + # zip no file perms in .zip so set all the scripts to +x - Run( "find " + zip_dir +" | egrep '.sh|.py' | xargs chmod 0700 ") + Run( "find " + zip_dir +" -type f | xargs chmod u+x ") #write out the base64 config data so the plugin can process it. mfile=None for root, dirs, files in os.walk(zip_dir): @@ -2732,65 +2810,163 @@ class ExtensionsConfig(object): break if mfile == None : Error('HandlerManifest.json not found.') + SimpleLog(p.plugin_log,'HandlerManifest.json not found.') continue manifest = GetFileContents(mfile) + p.setAttribute('manifestdata',manifest) # create the status and config dirs Run('mkdir -p ' + root + '/status') Run('mkdir -p ' + root + '/config') # write out the configuration data to goalStateIncarnation.settings file in the config path. config='' - pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin") - for ps in pslist: - if name == ps.getAttribute("name") and version == ps.getAttribute("version"): - Log("Found RuntimeSettings for " + name + " V " + version) - config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0]) + seqNo='0' + if len(dom.getElementsByTagName("PluginSettings")) != 0 : + pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin") + for ps in pslist: + if name == ps.getAttribute("name") and version == ps.getAttribute("version"): + Log("Found RuntimeSettings for " + name + " V " + version) + SimpleLog(p.plugin_log,"Found RuntimeSettings for " + name + " V " + version) + + config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0]) + seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo") + break if config == '': - Error("No RuntimeSettings for " + name + " V " + version) + Log("No RuntimeSettings for " + name + " V " + version) + SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version) + SetFileContents(root +"/config/" + incarnation +".settings", config ) #create HandlerEnvironment.json - handler_env='{ "name": "'+name+'", "version": 1.0, "handlerEnvironment": { "logFolder": "/var/log", "configFolder": "' + root + '/config", "statusFolder": "' + root + '/status", "heartbeatFile": "'+ root + '/heartbeat.log"}}' + handler_env='[{ "name": "'+name+'", "seqNo": "'+seqNo+'", "version": 1.0, "handlerEnvironment": { "logFolder": "'+os.path.dirname(p.plugin_log)+'", "configFolder": "' + root + '/config", "statusFolder": "' + root + '/status", "heartbeatFile": "'+ root + '/heartbeat.log"}}]' SetFileContents(root+'/HandlerEnvironment.json',handler_env) + self.SetHandlerState(handler, 'NotInstalled') + cmd = '' getcmd='installCommand' - if plg_dir != None and version > plg_dir.rsplit('-')[-1]: - getcmd='updateCommand' - # disable the old plugin if it exists - if previous_version != None: - if self.launchCommand(name,previous_version,'disableCommand') == None : + if plg_dir != None and previous_version != None and version > previous_version : + previous_handler=name+'-'+previous_version + if self.GetHandlerState(previous_handler) != 'NotInstalled': + getcmd='updateCommand' + # disable the old plugin if it exists + if self.launchCommand(p.plugin_log,name,previous_version,'disableCommand') == None : + self.SetHandlerState(previous_handler, 'Enabled') Error('Unable to disable old plugin '+name+' version ' + previous_version) + SimpleLog(p.plugin_log,'Unable to disable old plugin '+name+' version ' + previous_version) else : + self.SetHandlerState(previous_handler, 'Disabled') Log(name+' version ' + previous_version + ' is disabled') - + SimpleLog(p.plugin_log,name+' version ' + previous_version + ' is disabled') + + if getcmd=='updateCommand': - if self.launchCommand(name,version,getcmd,previous_version) == None : + if self.launchCommand(p.plugin_log,name,version,getcmd,previous_version) == None : Error('Update failed for '+name+'-'+version) + SimpleLog(p.plugin_log,'Update failed for '+name+'-'+version) + else : Log('Update complete'+name+'-'+version) - # if we updated - call unistall for the old plugin - remove old plugin dir and zipfile - if self.launchCommand(name,previous_version,'uninstallCommand') == None : + SimpleLog(p.plugin_log,'Update complete'+name+'-'+version) + + # if we updated - call unistall for the old plugin + if self.launchCommand(p.plugin_log,name,previous_version,'uninstallCommand') == None : + self.SetHandlerState(previous_handler, 'Installed') Error('Uninstall failed for '+name+'-'+previous_version) + SimpleLog(p.plugin_log,'Uninstall failed for '+name+'-'+previous_version) + else : - Log('Uninstall complete'+ name +'-' + previous_version) - # remove the old plugin - Run('rm -rf ' + LibDir + '/' + name +'-'+ previous_version + '*') - else : # run install - if self.launchCommand(name,version,getcmd) == None : + self.SetHandlerState(previous_handler, 'NotInstalled') + Log('Uninstall complete'+ previous_handler ) + SimpleLog(p.plugin_log,'Uninstall complete'+ name +'-' + previous_version) + + else : # run install + if self.launchCommand(p.plugin_log,name,version,getcmd) == None : + self.SetHandlerState(handler, 'NotInstalled') Error('Installation failed for '+name+'-'+version) + SimpleLog(p.plugin_log,'Installation failed for '+name+'-'+version) + else : + self.SetHandlerState(handler, 'Installed') Log('Installation completed for '+name+'-'+version) - #end if zip_dir == none or version > = prev + SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version) + + #end if plg_dir == none or version > = prev + # change incarnation of settings file so it knows how to name status... + zip_dir=LibDir+"/" + name + '-' + version + mfile=None + for root, dirs, files in os.walk(zip_dir): + for f in files: + if f in ('HandlerManifest.json'): + mfile=os.path.join(root,f) + if mfile != None: + break + if mfile == None : + Error('HandlerManifest.json not found.') + SimpleLog(p.plugin_log,'HandlerManifest.json not found.') + + continue + manifest = GetFileContents(mfile) + p.setAttribute('manifestdata',manifest) + config='' + seqNo='0' + if len(dom.getElementsByTagName("PluginSettings")) != 0 : + try: + pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin") + except: + Error('Error parsing ExtensionsConfig.') + SimpleLog(p.plugin_log,'Error parsing ExtensionsConfig.') + + continue + for ps in pslist: + if name == ps.getAttribute("name") and version == ps.getAttribute("version"): + Log("Found RuntimeSettings for " + name + " V " + version) + SimpleLog(p.plugin_log,"Found RuntimeSettings for " + name + " V " + version) + + config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0]) + seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo") + break + if config == '': + Error("No RuntimeSettings for " + name + " V " + version) + SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version) + + SetFileContents(root +"/config/" + incarnation +".settings", config ) + # state is still enable - if self.launchCommand(name,version,'enableCommand') == None : - Error('Enable failed for '+name+'-'+version) - else : - Log('Enable completed for '+name+'-'+version) + if (self.GetHandlerState(handler) == 'NotInstalled'): # run install first if true + if self.launchCommand(p.plugin_log,name,version,'installCommand') == None : + self.SetHandlerState(handler, 'NotInstalled') + Error('Installation failed for '+name+'-'+version) + SimpleLog(p.plugin_log,'Installation failed for '+name+'-'+version) + + else : + self.SetHandlerState(handler, 'Installed') + Log('Installation completed for '+name+'-'+version) + SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version) + + + if (self.GetHandlerState(handler) != 'NotInstalled'): + if self.launchCommand(p.plugin_log,name,version,'enableCommand') == None : + self.SetHandlerState(handler, 'Installed') + Error('Enable failed for '+name+'-'+version) + SimpleLog(p.plugin_log,'Enable failed for '+name+'-'+version) + + else : + self.SetHandlerState(handler, 'Enabled') + Log('Enable completed for '+name+'-'+version) + SimpleLog(p.plugin_log,'Enable completed for '+name+'-'+version) + # this plugin processing is complete Log('Processing completed for '+name+'-'+version) + SimpleLog(p.plugin_log,'Processing completed for '+name+'-'+version) + #end plugin processing loop Log('Finished processing ExtensionsConfig.xml') + try: + SimpleLog(p.plugin_log,'Finished processing ExtensionsConfig.xml') + except: + pass + return self - def launchCommand(self,name,version,command,prev_version=None): + def launchCommand(self,plugin_log,name,version,command,prev_version=None): # get the manifest and read the command mfile=None zip_dir=LibDir+"/" + name + '-' + version @@ -2802,58 +2978,248 @@ class ExtensionsConfig(object): break if mfile == None : Error('HandlerManifest.json not found.') + SimpleLog(plugin_log,'HandlerManifest.json not found.') + return None manifest = GetFileContents(mfile) try: jsn = json.loads(manifest) except: Error('Error parsing HandlerManifest.json.') + SimpleLog(plugin_log,'Error parsing HandlerManifest.json.') + return None + if type(jsn)==list: + jsn=jsn[0] if jsn.has_key('handlerManifest') : cmd = jsn['handlerManifest'][command] else : Error('Key handlerManifest not found. Handler cannot be installed.') + SimpleLog(plugin_log,'Key handlerManifest not found. Handler cannot be installed.') + if len(cmd) == 0 : Error('Unable to read ' + command ) + SimpleLog(plugin_log,'Unable to read ' + command ) + return None + # for update we send the path of the old installation arg='' if prev_version != None : arg=' ' + LibDir+'/' + name + '-' + prev_version - filepath='./'+os.path.basename(cmd) - dirpath=zip_dir+'/'+os.path.dirname(cmd).split('/')[1] - Log(command+ ' = ' + dirpath+'/'+filepath + arg) + dirpath=os.path.dirname(mfile) + LogIfVerbose('Command is '+ dirpath+'/'+ cmd) # launch pid=None try: - pid = subprocess.Popen(filepath+arg,shell=True,cwd=dirpath).pid + child = subprocess.Popen(dirpath+'/'+cmd+arg,shell=True,cwd=dirpath) except Exception as e: - Error('Exception launching ' + filepath + str(e)) + Error('Exception launching ' + cmd + str(e)) + SimpleLog(plugin_log,'Exception launching ' + cmd + str(e)) + + pid = child.pid if pid == None or pid < 1 : ExtensionChildren.append((-1,root)) - Error('Error launching ' + filepath + '.') + Error('Error launching ' + cmd + '.') + SimpleLog(plugin_log,'Error launching ' + cmd + '.') + else : ExtensionChildren.append((pid,root)) - Log("Spawned "+ filepath + " PID " + str(pid)) + Log("Spawned "+ cmd + " PID " + str(pid)) + SimpleLog(plugin_log,"Spawned "+ cmd + " PID " + str(pid)) + + # wait until install/upgrade is finished - retry = 5*60/10 - while pid !=None and pid > 0: - if retry==0: - break - if Run("ps " + str(pid)) != 0: - Log(filepath + ' still running with PID ' + str(pid)) - time.sleep(10) - else : - pid=0 + timeout = 300 # 5 minutes + retry = timeout/5 + while retry > 0 and child.poll() == None: + LogIfVerbose(cmd + ' still running with PID ' + str(pid)) + time.sleep(5) retry-=1 - if retry==0: - Error('More than five minutes has passed. Killing ' + str(pid)) + Error('Process exceeded timeout of ' + timeout + ' seconds. Terminating process ' + str(pid)) + SimpleLog(plugin_log,'Process exceeded timeout of ' + timeout + ' seconds. Terminating process ' + str(pid)) + os.kill(pid,9) return None + code = child.wait() + if code == None or code != 0: + Error('Process ' + str(pid) + ' returned non-zero exit code (' + str(code) + ')') + SimpleLog(plugin_log,'Process ' + str(pid) + ' returned non-zero exit code (' + str(code) + ')') + + return None Log(command + ' completed.') + SimpleLog(plugin_log,command + ' completed.') + return 0 + + def ReportHandlerStatus(self): + """ + Collect all status reports. + """ + # { "version": "1.0", "timestampUTC": "2014-03-31T21:28:58Z", + # "aggregateStatus": { + # "guestAgentStatus": { "version": "2.0.4PRE", "status": "Ready", "formattedMessage": { "lang": "en-US", "message": "GuestAgent is running and accepting new configurations." } }, + # "handlerAggregateStatus": [{ + # "handlerName": "ExampleHandlerLinux", "handlerVersion": "1.0", "status": "Ready", "runtimeSettingsStatus": { + # "sequenceNumber": "2", "settingsStatus": { "timestampUTC": "2014-03-31T23:46:00Z", "status": { "name": "ExampleHandlerLinux", "operation": "Command Execution Finished", "configurationAppliedTime": "2014-03-31T23:46:00Z", "status": "success", "formattedMessage": { "lang": "en-US", "message": "Finished executing command" }, + # "substatus": [ + # { "name": "StdOut", "status": "success", "formattedMessage": { "lang": "en-US", "message": "Goodbye world!" } }, + # { "name": "StdErr", "status": "success", "formattedMessage": { "lang": "en-US", "message": "" } } + # ] + # } } } } + # ] + # }} + + try: + incarnation=self.Extensions[0].getAttribute("goalStateIncarnation") + except: + Error('Error parsing ExtensionsConfig. Unable to send status reports') + return None + status='' + statuses='' + sent_suffix = '_sent' + for p in self.Plugins: + if p.getAttribute("state") == 'uninstall' or p.getAttribute("restricted") == 'true' : + continue + version=p.getAttribute("version") + name=p.getAttribute("name") + if p.getAttribute("isJson") != 'true': + LogIfVerbose("Plugin " + name+" version: " +version+" is not a JSON Extension. Skipping.") + continue + status_file=LibDir+'/'+name+'-'+version+'/status/'+incarnation+'.status' + if os.path.exists(status_file) !=True and os.path.exists(status_file+sent_suffix) != True: + if p.getAttribute("state") == 'disabled' : + LogIfVerbose(name+'-'+version+' is disabled. No status to report.') + continue + Error("Unable to locate " + status_file) + Error('Status report '+status_file+' Not sent!') + continue + elif os.path.exists(status_file) !=True and os.path.exists(status_file+sent_suffix) == True: + status_file = status_file+sent_suffix + if len(statuses)>0: + statuses+=',' + statuses+=GetFileContents(status_file) + if status_file.find(sent_suffix) < 0: + if os.path.exists(status_file + sent_suffix): + os.remove(status_file + sent_suffix) + os.rename(status_file,status_file+sent_suffix) + + if len(statuses)<1: + LogIfVerbose('No Handler status to report') + return None + tstamp=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) + #header + #agent state + if provisioned == False: + if provisionError == None : + agent_state='Provisioning' + agent_msg='Guest Agent is starting.' + else: + agent_state='Provisioning Error.' + agent_msg=provisionError + else: + agent_state='Ready' + agent_msg='GuestAgent is running and accepting new configurations.' + + status='{"version":"1.0","timestampUTC":"'+tstamp+'","aggregateStatus":{"guestAgentStatus":{"version":"'+GuestAgentVersion+'","status":"'+agent_state+'","formattedMessage":{"lang":"en-US","message":"'+agent_msg+'"}},"handlerAggregateStatus":['+statuses+']}}' + try: + uri=GetNodeTextData(self.Extensions[0].getElementsByTagName("StatusUploadBlob")[0]).replace('&','&') + except: + Error('Error parsing ExtensionsConfig. Unable to send status reports') + return None + self.Util.Endpoint=uri.split('/')[2] + self.Util.HttpPutBlockBlob(uri, status) + Log('Status report '+status+' sent to ' + uri) + return True + def CheckHeartbeat(self): + try: + incarnation=self.Extensions[0].getAttribute("goalStateIncarnation") + uri=GetNodeTextData(self.Extensions[0].getElementsByTagName("StatusUploadBlob")[0]) + except: + Error('Error parsing ExtensionsConfig. Unable to check hearbeat.') + return None + for p in self.Plugins: + if p.getAttribute("state") == 'disabled' or p.getAttribute("state") == 'uninstall' or p.getAttribute("restricted") == 'true' : + continue + version=p.getAttribute("version") + name=p.getAttribute("name") + if p.getAttribute("isJson") != 'true': + Error("Plugin " + name+" version: " +version+" is not a JSON Extension. Skipping.") + continue + try: + if len(p.getAttribute("manifestdata"))<1 or json.loads(p.getAttribute("manifestdata"))[0]['handlerManifest']['reportHeartbeat']!=True : + Error("JSON error, unable to process manifestdata") + continue + except: + continue + + heartbeat_file=LibDir+'/'+name+'-'+version+'/heartbeat.log' + status_file=LibDir+'/'+name+'-'+version+'/status/'+incarnation+'.status' + if not os.path.exists(heartbeat_file): + Error('Missing '+ heartbeat_file) + continue + else: + heartbeat=GetFileContents(heartbeat_file) + try: + hb=json.loads(heartbeat) + except: + Error("JSON error, unable to process " + heartbeat_file) + try: + d=int(time.time()-os.stat(heartbeat_file).st_mtime) + except: + Error("Unable to stat " + heartbeat_file) + continue + + if d >= 700: # stop sending heartbeats + return 'NotReady' + if d < 120: # within 2 mins considered active + return 'Ready' + if d < 600: # less than 10 mins unknown + state='Unknown' + else: # more than 10 mins with no update considered notready + state='NotReady' + try: + stat_rept='{"handlerName":"' + name + '","handlerVersion":"'+version+ '","status":"' +hb[0]['heartbeat']['status'] + '","code":' + hb[0]['heartbeat']['code'] + ',"formattedMessage":{"lang":"en-US","message":"' + hb[0]['heartbeat']['Message'] + '"}}' + cur_file=status_file+'_current' + with open(cur_file,'w+') as f: + f.write(stat_rept) + # if inc.status exists, rename the inc.status to inc.status_sent + if os.path.exists(status_file) == True: + os.rename(status_file,status_file+'_sent') + # rename inc.status_current to inc.status + os.rename(cur_file,status_file) + # remove inc.status_sent + if os.path.exists(status_file+'_sent') == True: + os.unlink(status_file+'_sent') + except: + Error("Unable to create " + status_file) + continue + + def SetHandlerState(self, handler, state=''): + zip_dir=LibDir+"/" + handler + mfile=None + for root, dirs, files in os.walk(zip_dir): + for f in files: + if f in ('HandlerManifest.json'): + mfile=os.path.join(root,f) + if mfile != None: + break + if mfile == None : + Error('SetHandlerState(): HandlerManifest.json not found, cannot set HandlerState.') + return None + Log("SetHandlerState: "+handler+", "+state) + return SetFileContents(os.path.dirname(mfile)+'/config/HandlerState', state) + + def GetHandlerState(self, handler): + handlerState = GetFileContents(handler+'/config/HandlerState') + if (handlerState): + return handlerState.rstrip('\r\n') + else: + return 'NotInstalled' + + class HostingEnvironmentConfig(object): """ Parse Hosting enviromnet config and store in @@ -3687,8 +4053,12 @@ class Agent(Util): sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) missingDefaultRoute = True try: - for line in RunGetOutput("route -n")[1].split('\n'): - if line.startswith("0.0.0.0 "): + if DistInfo()[0] == 'FreeBSD': + routes = RunGetOutput("netstat -nr")[1] + else: + routes = RunGetOutput("route -n")[1] + for line in routes.split('\n'): + if line.startswith("0.0.0.0 ") or line.startswith("default "): missingDefaultRoute = False except: pass @@ -3698,6 +4068,8 @@ class Agent(Util): ifname=MyDistro.GetInterfaceName() Log("DoDhcpWork: Missing default route - adding broadcast route for DHCP.") Run("route add 255.255.255.255 dev " + ifname,chk_err=False) # We supress error logging on error. + if MyDistro.dhcp_client_name == 'wickedd-dhcp4': + Run("service " + MyDistro.dhcp_client_name + " stop",chk_err=False) sock.bind(("0.0.0.0", 68)) sock.sendto(sendData, ("<broadcast>", 67)) sock.settimeout(10) @@ -3725,6 +4097,8 @@ class Agent(Util): #We added this route - delete it Run("route del 255.255.255.255 dev " + ifname,chk_err=False) # We supress error logging on error. Log("DoDhcpWork: Removing broadcast route for DHCP.") + if MyDistro.dhcp_client_name == 'wickedd-dhcp4': + Run("service " + MyDistro.dhcp_client_name + " start",chk_err=False) return None def UpdateAndPublishHostName(self, name): @@ -3734,7 +4108,10 @@ class Agent(Util): Log("Setting host name: " + name) MyDistro.publishHostname(name) ethernetInterface = MyDistro.GetInterfaceName() - Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface) + if DistInfo()[0] == 'FreeBSD': + Run("service netif restart") + else: + Run("ifdown " + ethernetInterface + " && ifup " + ethernetInterface) self.RestoreRoutes() def RestoreRoutes(self): @@ -3973,6 +4350,10 @@ class Agent(Util): if dvds == None : continue dvd = '/dev/'+dvds.group(0) + if dvd == None: + # No DVD device detected + Error("No DVD device detected, unable to provision.") + return "No DVD device detected, unable to provision." if MyDistro.mediaHasFilesystem(dvd) is False : out=MyDistro.load_ata_piix() if out: @@ -4016,8 +4397,8 @@ class Agent(Util): if ovfobj != None: error = ovfobj.Process() if error : - Error ("Provisioninig image FAILED " + error) - return ("Provisioninig image FAILED " + error) + Error ("Provisioning image FAILED " + error) + return ("Provisioning image FAILED " + error) # This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml fingerprint = RunGetOutput("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub")[1].rstrip().split()[1].replace(':','') self.ReportRoleProperties(fingerprint) @@ -4085,14 +4466,11 @@ class Agent(Util): self.EnvMonitor = EnvMonitor() - # Set SCSI timeout on root device - try: - timeout = Config.get("OS.RootDeviceScsiTimeout") - if timeout != None: - SetFileContents("/sys/block/" + DeviceForIdePort(0) + "/device/timeout", timeout) - except: - pass - + # Set SCSI timeout on SCSI disks + MyDistro.initScsiDiskTimeout() + global provisioned + global provisionError + global Openssl Openssl = Config.get("OS.OpensslPath") if Openssl == None: @@ -4141,6 +4519,13 @@ class Agent(Util): lbProbeResponder = False Log("Unable to create LBProbeResponder.") + # Report SSH key fingerprint + type = Config.get("Provisioning.SshHostKeyPairType") + if type == None: + type = "rsa" + fingerprint = RunGetOutput("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub")[1].rstrip().split()[1].replace(':','') + self.ReportRoleProperties(fingerprint) + if program != None and DiskActivated == True: try: Children.append(subprocess.Popen([program, "Ready"])) @@ -4157,8 +4542,13 @@ class Agent(Util): # Process our extensions. if goalState.ExtensionsConfig == None and goalState.ExtensionsConfigXml != None : goalState.ExtensionsConfig = ExtensionsConfig().Parse(goalState.ExtensionsConfigXml) - # TODO report the status/heartbeat results of extension processing - time.sleep(25 - sleepToReduceAccessDenied) + + # report the status/heartbeat results of extension processing + if goalState.ExtensionsConfig != None : + goalState.ExtensionsConfig.ReportHandlerStatus() + goalState.ExtensionsConfig.CheckHeartbeat() + + time.sleep(25 - sleepToReduceAccessDenied) WaagentLogrotate = """\ /var/log/waagent.log { @@ -4319,7 +4709,7 @@ def GetMyDistro(dist_class_name=''): """ if dist_class_name == '': if 'Linux' in platform.system(): - Distro=platform.dist()[0] + Distro=DistInfo()[0] else : # I know this is not Linux! if 'FreeBSD' in platform.system(): Distro=platform.system() @@ -4333,6 +4723,18 @@ def GetMyDistro(dist_class_name=''): return None return globals()[dist_class_name]() # the distro class inside this module. +def DistInfo(fullname=0): + if 'FreeBSD' in platform.system(): + release = re.sub('\-.*\Z', '', str(platform.release())) + distinfo = ['FreeBSD', release] + return distinfo + if 'linux_distribution' in dir(platform): + distinfo = list(platform.linux_distribution(full_distribution_name=fullname)) + distinfo[0] = distinfo[0].strip() # remove trailing whitespace in distro name + return distinfo + else: + return platform.dist() + def PackagedInstall(buildroot): """ Called from setup.py for use by RPM. @@ -4447,7 +4849,7 @@ def main(): LoggerInit('/var/log/waagent.log','/dev/console') global LinuxDistro - LinuxDistro=platform.dist()[0] + LinuxDistro=DistInfo()[0] global MyDistro MyDistro=GetMyDistro() if MyDistro == None : |