diff options
Diffstat (limited to 'waagent')
-rwxr-xr-x | waagent | 523 |
1 files changed, 423 insertions, 100 deletions
@@ -2,7 +2,7 @@ # # Windows Azure Linux Agent # -# Copyright 2012 Microsoft Corporation +# 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. @@ -45,6 +45,9 @@ import time import traceback import xml.dom.minidom import fcntl +import inspect +import zipfile +import json if not hasattr(subprocess,'check_output'): def check_output(*popenargs, **kwargs): @@ -75,7 +78,7 @@ if not hasattr(subprocess,'check_output'): GuestAgentName = "WALinuxAgent" GuestAgentLongName = "Windows Azure Linux Agent" -GuestAgentVersion = "WALinuxAgent-2.0.3" +GuestAgentVersion = "WALinuxAgent-2.0.4" ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol. Config = None @@ -83,7 +86,7 @@ WaAgent = None DiskActivated = False Openssl = "openssl" Children = [] - +ExtensionChildren = [] VMM_STARTUP_SCRIPT_NAME='install' VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml' global RulesFiles @@ -117,7 +120,7 @@ ResourceDisk.SwapSizeMB=0 # Size of the swapfile. LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure. -Logs.Verbose=n # +Logs.Verbose=n # Enable verbose logs OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds. OS.OpensslPath=None # If "None", the system default version is used. @@ -403,6 +406,7 @@ class AbstractDistro(object): Return the ip of the first active non-loopback interface. """ + addr='' iface,addr=GetFirstActiveNetworkInterfaceNonLoopback() return addr @@ -470,8 +474,10 @@ class AbstractDistro(object): def Install(self): return Install() - def dvdHasMedia(self,dvd): - if Run("LC_ALL=C fdisk -l " + dvd + " | grep Disk"): + def mediaHasFilesystem(self,dsk): + if len(dsk) == 0 : + return False + if Run("LC_ALL=C fdisk -l " + dsk + " | grep Disk"): return False return True @@ -1155,6 +1161,18 @@ class LinuxMintDistro(UbuntuDistro): super(LinuxMintDistro,self).__init__() ############################################################ +# fedoraDistro +############################################################ + +class fedoraDistro(redhatDistro): + """ + FedoraDistro concrete class + Put Fedora specific behavior here... + """ + def __init__(self): + super(fedoraDistro,self).__init__() + +############################################################ # FreeBSD ############################################################ FreeBSDWaagentConf = """\ @@ -1181,7 +1199,7 @@ ResourceDisk.SwapSizeMB=0 # Size of the swapfile. LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure. -Logs.Verbose=n # +Logs.Verbose=n # Enable verbose logs OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds. OS.OpensslPath=None # If "None", the system default version is used. @@ -1196,7 +1214,7 @@ bsd_init_file="""\ # KEYWORD: nojail . /etc/rc.subr - +export PATH=$PATH:/usr/local/bin name="waagent" rcvar="waagent_enable" command="/usr/sbin/${name}" @@ -1209,6 +1227,54 @@ load_rc_config $name run_rc_command "$1" """ +bsd_activate_resource_disk_txt="""\ +#!/usr/bin/env python + +import os +import sys +import imp + +# waagent has no '.py' therefore create waagent module import manually. +__name__='setupmain' #prevent waagent.__main__ from executing +waagent=imp.load_source('waagent','/tmp/waagent') +waagent.LoggerInit('/var/log/waagent.log','/dev/console') +from waagent import RunGetOutput,Run +Config=waagent.ConfigurationProvider() +format = Config.get("ResourceDisk.Format") +if format == None or format.lower().startswith("n"): + sys.exit(0) +device_base = 'da1' +device = "/dev/" + device_base +for entry in RunGetOutput("mount")[1].split(): + if entry.startswith(device + "s1"): + waagent.Log("ActivateResourceDisk: " + device + "s1 is already mounted.") + sys.exit(0) +mountpoint = Config.get("ResourceDisk.MountPoint") +if mountpoint == None: + mountpoint = "/mnt/resource" +waagent.CreateDir(mountpoint, "root", 0755) +fs = Config.get("ResourceDisk.Filesystem") +if waagent.FreeBSDDistro().mediaHasFilesystem(device) == False : + Run("newfs " + device + "s1") +if Run("mount " + device + "s1 " + mountpoint): + waagent.Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "s1).") + sys.exit(0) +waagent.Log("Resource disk (" + device + "s1) is mounted at " + mountpoint + " with fstype " + fs) +swap = Config.get("ResourceDisk.EnableSwap") +if swap == None or swap.lower().startswith("n"): + sys.exit(0) +sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024 +if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024): + os.remove(mountpoint + "/swapfile") +if not os.path.isfile(mountpoint + "/swapfile"): + Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB)) +if Run("mdconfig -a -t vnode -f " + mountpoint + "/swapfile -u 0"): + waagent.Error("ActivateResourceDisk: Configuring swap - Failed to create md0") +if not Run("swapon /dev/md0"): + waagent.Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile") +else: + waagent.Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile") +""" class FreeBSDDistro(AbstractDistro): """ @@ -1218,6 +1284,7 @@ class FreeBSDDistro(AbstractDistro): Generic Attributes go here. These are based on 'majority rules'. This __init__() may be called or overriden by the child. """ + super(FreeBSDDistro,self).__init__() self.agent_service_name = os.path.basename(sys.argv[0]) self.selinux=False self.ssh_service_name='sshd' @@ -1234,7 +1301,7 @@ class FreeBSDDistro(AbstractDistro): self.grubKernelBootOptionsFile = '/boot/loader.conf' self.grubKernelBootOptionsLine = '' self.getpidcmd = 'pgrep -n' - self.mount_dvd_cmd = 'dd bs=2048 count=1 skip=295 if=' + self.mount_dvd_cmd = 'dd bs=2048 count=33 skip=295 if=' # custom data max len is 64k self.sudoers_dir_base = '/usr/local/etc' self.waagent_conf_file = FreeBSDWaagentConf @@ -1248,13 +1315,6 @@ class FreeBSDDistro(AbstractDistro): self.installAgentServiceScriptFiles() return Run("services_mkdb " + self.init_script_file) -# def uninstallAgentService(self): -# return Run('chkconfig --del ' + self.agent_service_name) - -# def unregisterAgentService(self): -# self.stopAgentService() -# return self.uninstallAgentService() - def restartSshService(self): """ Service call to re(start) the SSH service @@ -1358,6 +1418,14 @@ class FreeBSDDistro(AbstractDistro): retries-=1 if code > 0 and retries > 0 : Log("GetFreeBSDEthernetInfo - Error: retry ethernet detection " + str(retries)) + if retries == 9 : + c,o=RunGetOutput("ifconfig | grep -A1 -B2 ether",chk_err=False) + if c == 0: + t=o.replace('\n',' ') + t=t.split() + i=t[0][:-1] + Log(RunGetOutput('id')[1]) + Run('dhclient '+i) time.sleep(10) j=output.replace('\n',' ') @@ -1468,52 +1536,19 @@ class FreeBSDDistro(AbstractDistro): pass return - def ActivateResourceDisk(self): + def ActivateResourceDiskNoThread(self): """ Format, mount, and if specified in the configuration set resource disk as swap. """ global DiskActivated - format = Config.get("ResourceDisk.Format") - if format == None or format.lower().startswith("n"): - DiskActivated = True - return - #device = DeviceForIdePort(1) - device_base = 'ada1' -# if device == None: -# Error("ActivateResourceDisk: Unable to detect disk topology.") -# return - device = "/dev/" + device_base - for entry in RunGetOutput("mount")[1].split(): - if entry.startswith(device + "s1"): - Log("ActivateResourceDisk: " + device + "s1 is already mounted.") - DiskActivated = True - return - mountpoint = Config.get("ResourceDisk.MountPoint") - if mountpoint == None: - mountpoint = "/mnt/resource" - CreateDir(mountpoint, "root", 0755) - fs = Config.get("ResourceDisk.Filesystem") - Run("newfs " + device + "s1") - if Run("mount " + device + "s1 " + mountpoint): - Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).") - return - Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs) + Run('cp /usr/sbin/waagent /tmp/') + SetFileContents('/tmp/bsd_activate_resource_disk.py',bsd_activate_resource_disk_txt) + Run('chmod +x /tmp/bsd_activate_resource_disk.py') + pid = subprocess.Popen(["/tmp/bsd_activate_resource_disk.py", ""]).pid + Log("Spawning bsd_activate_resource_disk.py") DiskActivated = True - swap = Config.get("ResourceDisk.EnableSwap") - if swap == None or swap.lower().startswith("n"): - return - sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024 - if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024): - os.remove(mountpoint + "/swapfile") - if not os.path.isfile(mountpoint + "/swapfile"): - Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB)) - if Run("mdconfig -a -t vnode -f " + mountpoint + "/swapfile -u 0"): - Error("ActivateResourceDisk: Configuring swap - Failed to create md0") - if not Run("swapon /dev/md0"): - Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile") - else: - Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile") + return def Install(self): """ @@ -1558,8 +1593,8 @@ class FreeBSDDistro(AbstractDistro): #ApplyVNUMAWorkaround() return 0 - def dvdHasMedia(self,dvd): - if Run('LC_ALL=C fdisk -p ' + dvd + ' | grep "invalid fdisk partition table found" '): + def mediaHasFilesystem(self,dsk): + if Run('LC_ALL=C fdisk -p ' + dsk + ' | grep "invalid fdisk partition table found" ',False): return False return True @@ -1949,22 +1984,20 @@ class Logger(object): """ if self.file_path: with open(self.file_path, "a") as F : - F.write(message + "\n") + F.write(message.encode('ascii','ignore') + "\n") F.close() def LogToCon(self,message): - """ + """ Write 'message' to /dev/console. This supports serial port logging if the /dev/console is redirected to ttys0 in kernel boot options. """ - if self.con_path: - with open(self.con_path, "w") as C : -# if isinstance(message,str): -# message=message.encode('latin-1')) - C.write(message + "\n") - C.close() - + if self.con_path: + with open(self.con_path, "w") as C : + C.write(message.encode('ascii','ignore') + "\n") + C.close() + def Log(self,message): """ Standard Log function. @@ -1993,15 +2026,12 @@ class Logger(object): def LogIfVerbose(self,message): """ Only log 'message' if global Verbose is True. - Verbose messages are assumed to be undesiarable in the - serial logs, so do not send the verbose logging to /dev/console """ self.LogWithPrefixIfVerbose('',message) def LogWithPrefixIfVerbose(self,prefix, message): """ Only log 'message' if global Verbose is True. - Log to logfile, ignoring /dev/console Prefix each line of 'message' with current time+'prefix'. """ if self.verbose == True: @@ -2011,7 +2041,8 @@ class Logger(object): for line in message.split('\n'): line = t + line self.LogToFile(line) - + self.LogToCon(line) + def Warn(self,message): """ Prepend the text "WARNING:" to the prefix for each line in 'message'. @@ -2052,11 +2083,11 @@ def GetFirstActiveNetworkInterfaceNonLoopback(): Return the interface name, and ip addr of the first active non-loopback interface. """ + iface='' expected=16 # how many devices should I expect... struct_size=40 # for 64bit the size is 40 bytes s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) buff=array.array('B', b'\0' * (expected*struct_size)) -# retsize=(struct.unpack('iL', fcntl.ioctl(s.fileno().to_bytes(1,'little'), 0x8912, struct.pack('iL',expected*struct_size,buff.buffer_info()[0]))))[0] retsize=(struct.unpack('iL', fcntl.ioctl(s.fileno(), 0x8912, struct.pack('iL',expected*struct_size,buff.buffer_info()[0]))))[0] if retsize == (expected*struct_size) : Warn('SIOCGIFCONF returned more than ' + str(expected) + ' up network interfaces.') @@ -2069,7 +2100,6 @@ def GetFirstActiveNetworkInterfaceNonLoopback(): break return iface.decode('latin-1'), socket.inet_ntoa(s[i+20:i+24]) - def GetIpv4Address(): """ Return the ip of the @@ -2092,8 +2122,10 @@ def GetMacAddress(): Convienience function, returns mac addr bound to first non-loobback interface. """ - ifname=GetFirstActiveNetworkInterfaceNonLoopback()[0] - a = Linux_ioctl_GetInterfaceMac(ifname) + ifname='' + while len(ifname) < 2 : + ifname=GetFirstActiveNetworkInterfaceNonLoopback()[0] + a = Linux_ioctl_GetInterfaceMac(ifname) return HexStringToByteArray(a) def DeviceForIdePort(n): @@ -2558,7 +2590,270 @@ class SharedConfig(object): except OSError, e : ErrorWithPrefix('Agent.Run','Exception: '+ str(e) +' occured launching ' + program ) return self + +class ExtensionsConfig(object): + """ + Parse ExtensionsConfig, downloading and unpacking them to /var/lib/waagent. + Install if <enabled>true</enabled>, remove if it is set to false. + """ + #<?xml version="1.0" encoding="utf-8"?> + #<Extensions version="1.0.0.0" goalStateIncarnation="6"><Plugins> + # <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.5" + #location="http://previewusnorthcache.blob.core.test-cint.azure-test.net/d84b216d00bf4d96982be531539e1513/OSTCExtensions_ExampleHandlerLinux_usnorth_manifest.xml" + #config="" state="enabled" autoUpgrade="false" runAsStartupTask="false" isJson="true" /> + #</Plugins> + #<PluginSettings> + # <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.5"> + # <RuntimeSettings seqNo="2">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"1BE9A13AA1321C7C515EF109746998BAB6D86FD1", + #"protectedSettings":"MIIByAYJKoZIhvcNAQcDoIIBuTCCAbUCAQAxggFxMIIBbQIBADBVMEExPzA9BgoJkiaJk/IsZAEZFi9XaW5kb3dzIEF6dXJlIFNlcnZpY2UgTWFuYWdlbWVudCBmb3IgR + #Xh0ZW5zaW9ucwIQZi7dw+nhc6VHQTQpCiiV2zANBgkqhkiG9w0BAQEFAASCAQCKr09QKMGhwYe+O4/a8td+vpB4eTR+BQso84cV5KCAnD6iUIMcSYTrn9aveY6v6ykRLEw8GRKfri2d6 + #tvVDggUrBqDwIgzejGTlCstcMJItWa8Je8gHZVSDfoN80AEOTws9Fp+wNXAbSuMJNb8EnpkpvigAWU2v6pGLEFvSKC0MCjDTkjpjqciGMcbe/r85RG3Zo21HLl0xNOpjDs/qqikc/ri43Y76E/X + #v1vBSHEGMFprPy/Hwo3PqZCnulcbVzNnaXN3qi/kxV897xGMPPC3IrO7Nc++AT9qRLFI0841JLcLTlnoVG1okPzK9w6ttksDQmKBSHt3mfYV+skqs+EOMDsGCSqGSIb3DQEHATAUBggqh + #kiG9w0DBwQITgu0Nu3iFPuAGD6/QzKdtrnCI5425fIUy7LtpXJGmpWDUA==","publicSettings":{"port":"3000"}}}]}</RuntimeSettings> + # </Plugin> + #</PluginSettings> + #<StatusUploadBlob>https://ostcextensions.blob.core.test-cint.azure-test.net/vhds/eg-plugin7-vm.eg-plugin7-vm.eg-plugin7-vm.status?sr=b&sp=rw& + #se=9999-01-01&sk=key1&sv=2012-02-12&sig=wRUIDN1x2GC06FWaetBP9sjjifOWvRzS2y2XBB4qoBU%3D</StatusUploadBlob></Extensions> + + def __init__(self): + self.reinitialize() + + def reinitialize(self): + """ + Reset members. + """ + self.Extensions = None + self.Plugins = None + self.Util = None + def Parse(self, xmlText): + """ + Write configuration to file ExtensionsConfig.xml. + If state is enabled: + if the plugin is installed: + if the new plugin's version is higher: + download the new archive + do the updateCommand. + + if the 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 + + if state is disabled: + call disableCommand + Create execuatable shell script containig 'installCommand'. + Spawn the script and report the PID to the Log. + """ + 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) + for p in self.Plugins: + if len(p.getAttribute("location"))<1: # this plugin is inside the PluginSettings + continue + previous_version = None + version=p.getAttribute("version") + name=p.getAttribute("name") + Log("Found Plugin: " + name + ' version: ' + version) + if p.getAttribute("state") == 'disabled' : + #disable + if self.launchCommand(name,version,'disableCommand') == None : + Error('Unable to disable '+name) + continue + else : + Log(name+' is disabled') + 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 + plg_dir=None + for root, dirs, files in os.walk(LibDir): + for d in dirs: + if name in d: + plg_dir=os.path.join(root,d) + if plg_dir != None: + break + if plg_dir != None : + previous_version=plg_dir.rsplit('-')[-1] + if plg_dir == None or version > previous_version : + location=p.getAttribute("location") + Log("Downloading plugin manifest: " + name + " from " + location) + self.Util.Endpoint=location.split('/')[2] + 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.") + failoverlocation=p.getAttribute("failoverlocation") + self.Util.Endpoint=failoverlocation.split('/')[2] + 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))) + 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 + man_dom = xml.dom.minidom.parseString(manifest) + bundle_uri = GetNodeTextData(man_dom.getElementsByTagName("Uri")[0]) + 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 ) + 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))) + # unpack the archive + z=zipfile.ZipFile(filepath) + zip_dir=LibDir+"/" + name + '-' + version + z.extractall(zip_dir) + 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 ") + #write out the base64 config data so the plugin can process it. + 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) + # 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]) + if config == '': + Error("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"}}' + SetFileContents(root+'/HandlerEnvironment.json',handler_env) + 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 : + Error('Unable to disable old plugin '+name+' version ' + previous_version) + else : + Log(name+' version ' + previous_version + ' is disabled') + + if getcmd=='updateCommand': + if self.launchCommand(name,version,getcmd,previous_version) == None : + Error('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 : + Error('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 : + Error('Installation failed for '+name+'-'+version) + else : + Log('Installation completed for '+name+'-'+version) + #end if zip_dir == none or version > = prev + # state is still enable + if self.launchCommand(name,version,'enableCommand') == None : + Error('Enable failed for '+name+'-'+version) + else : + Log('Enable completed for '+name+'-'+version) + # this plugin processing is complete + Log('Processing completed for '+name+'-'+version) + #end plugin processing loop + Log('Finished processing ExtensionsConfig.xml') + return self + + def launchCommand(self,name,version,command,prev_version=None): + # get the manifest and read the command + mfile=None + zip_dir=LibDir+"/" + name + '-' + version + 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.') + return None + manifest = GetFileContents(mfile) + try: + jsn = json.loads(manifest) + except: + Error('Error parsing HandlerManifest.json.') + return None + if jsn.has_key('handlerManifest') : + cmd = jsn['handlerManifest'][command] + else : + Error('Key handlerManifest not found. Handler cannot be installed.') + if len(cmd) == 0 : + Error('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) + # launch + pid=None + try: + pid = subprocess.Popen(filepath+arg,shell=True,cwd=dirpath).pid + except Exception as e: + Error('Exception launching ' + filepath + str(e)) + if pid == None or pid < 1 : + ExtensionChildren.append((-1,root)) + Error('Error launching ' + filepath + '.') + else : + ExtensionChildren.append((pid,root)) + Log("Spawned "+ filepath + " 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 + retry-=1 + + if retry==0: + Error('More than five minutes has passed. Killing ' + str(pid)) + os.kill(pid,9) + return None + Log(command + ' completed.') + return 0 + class HostingEnvironmentConfig(object): """ Parse Hosting enviromnet config and store in @@ -2583,12 +2878,7 @@ class HostingEnvironmentConfig(object): # <ApplicationSettings> # <Setting name="__ModelData" value="<m role="MachineRole" xmlns="urn:azure:m:v1"><r name="MachineRole"><e name="a" /><e name="b" /><e name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" /><e name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" /></r></m>" /> # <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." /> - # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword" value="MIIBnQYJKoZIhvcN..." /> - # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration" value="2022-07-23T23:59:59.0000000-07:00" /> - # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername" value="test" /> - # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Enabled" value="true" /> # <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" /> - # <Setting name="Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" value="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" /> # </ApplicationSettings> # <ResourceReferences> # <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" /> @@ -2652,9 +2942,17 @@ class HostingEnvironmentConfig(object): Create the user account. Launch ConfigurationConsumer if specified in the config. """ + no_thread = False if DiskActivated == False: - diskThread = threading.Thread(target = self.ActivateResourceDisk) - diskThread.start() + for m in inspect.getmembers(MyDistro): + if 'ActivateResourceDiskNoThread' in m: + no_thread = True + break + if no_thread == True : + MyDistro.ActivateResourceDiskNoThread() + else : + diskThread = threading.Thread(target = self.ActivateResourceDisk) + diskThread.start() User = None Pass = None Expiration = None @@ -2662,14 +2960,6 @@ class HostingEnvironmentConfig(object): for b in self.ApplicationSettings: sname = b.getAttribute("name") svalue = b.getAttribute("value") - if sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountEncryptedPassword": - Pass = self.DecryptPassword(svalue) - elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountUsername": - User = svalue - elif sname == "Microsoft.WindowsAzure.Plugins.RemoteAccess.AccountExpiration": - Expiration = svalue - elif sname == "Certificate|Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption": - Thumbprint = svalue.split(':')[1].upper() if User != None and Pass != None: if User != "root" and User != "" and Pass != "": CreateAccount(User, Pass, Expiration, Thumbprint) @@ -2695,6 +2985,7 @@ class GoalState(Util): Initializes and populates: self.HostingEnvironmentConfig self.SharedConfig + self.ExtensionsConfig self.Certificates """ # @@ -2717,6 +3008,9 @@ class GoalState(Util): # <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=hostingEnvironmentConfig&incarnation=1</HostingEnvironmentConfig> # <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=sharedConfig&incarnation=1</SharedConfig> # <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&incarnation=1</Certificates> + # <ExtensionsConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&type=extensionsConfig&incarnation=2</ExtensionsConfig> + # <FullConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&type=fullConfig&incarnation=2</FullConfig> + # </Configuration> # </RoleInstance> # </RoleInstanceList> @@ -2748,6 +3042,9 @@ class GoalState(Util): self.CertificatesUrl = None self.CertificatesXml = None self.Certificates = None + self.ExtensionsConfigUrl = None + self.ExtensionsConfigXml = None + self.ExtensionsConfig = None self.RoleInstanceId = None self.ContainerId = None self.LoadBalancerProbePort = None # integer, ?list of integers @@ -2758,9 +3055,11 @@ class GoalState(Util): Parse and populate contained configuration objects. Calls Certificates().Parse() Calls SharedConfig().Parse + Calls ExtensionsConfig().Parse Calls HostingEnvironmentConfig().Parse """ self.reinitialize() + LogIfVerbose(xmlText) node = xml.dom.minidom.parseString(xmlText).childNodes[0] if node.localName != "GoalState": Error("GoalState.Parse: root not GoalState") @@ -2798,6 +3097,7 @@ class GoalState(Util): elif d.localName == "Configuration": for e in d.childNodes: if e.nodeType == node.ELEMENT_NODE: + LogIfVerbose(e.localName) if e.localName == "HostingEnvironmentConfig": self.HostingEnvironmentConfigUrl = GetNodeTextData(e) LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl) @@ -2808,6 +3108,10 @@ class GoalState(Util): LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl) self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl) self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml) + elif e.localName == "ExtensionsConfig": + self.ExtensionsConfigUrl = GetNodeTextData(e) + LogIfVerbose("ExtensionsConfigUrl:" + self.ExtensionsConfigUrl) + self.ExtensionsConfigXml = self.HttpGetWithHeaders(self.ExtensionsConfigUrl) elif e.localName == "Certificates": self.CertificatesUrl = GetNodeTextData(e) LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl) @@ -2893,6 +3197,7 @@ class OvfEnv(object): Return self. """ self.reinitialize() + LogIfVerbose(xmlText) dom = xml.dom.minidom.parseString(xmlText) if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1: Error("Unable to parse OVF XML.") @@ -2938,22 +3243,28 @@ class OvfEnv(object): if len(disableSshPass) != 0: self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true") for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"): + LogIfVerbose(repr(pkey)) fp = None path = None for c in pkey.childNodes: if c.localName == "Fingerprint": fp = GetNodeTextData(c).upper() + LogIfVerbose(fp) if c.localName == "Path": path = GetNodeTextData(c) + LogIfVerbose(path) self.SshPublicKeys += [[fp, path]] for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"): fp = None path = None + LogIfVerbose(repr(keyp)) for c in keyp.childNodes: if c.localName == "Fingerprint": fp = GetNodeTextData(c).upper() + LogIfVerbose(fp) if c.localName == "Path": path = GetNodeTextData(c) + LogIfVerbose(path) self.SshKeyPairs += [[fp, path]] return self @@ -3657,11 +3968,12 @@ class Agent(Util): Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key") MyDistro.restartSshService() #SetFileContents(LibDir + "/provisioned", "") + dvd = None for dvds in [re.match(r'(sr[0-9]|hd[c-z]|cdrom[0-9]|cd[0-9]?)',x) for x in os.listdir('/dev/')]: - if dvds == None: + if dvds == None : continue dvd = '/dev/'+dvds.group(0) - if MyDistro.dvdHasMedia(dvd) is False : + if MyDistro.mediaHasFilesystem(dvd) is False : out=MyDistro.load_ata_piix() if out: return out @@ -3693,6 +4005,7 @@ class Agent(Util): 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)) # we may have NULLs. + ovfxml=ovfxml[ovfxml.find('<?'):] # chop leading text if present SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml)) Run("umount " + dvd,chk_err=False) MyDistro.unload_ata_piix() @@ -3738,15 +4051,19 @@ class Agent(Util): # Determine if we are in VMM. Spawn VMM_STARTUP_SCRIPT_NAME if found. self.SearchForVMMStartup() - - if MyDistro.GetIpv4Address() == None: - Log("Waiting for network.") - while(MyDistro.GetIpv4Address() == None): + ipv4='' + while ipv4 == '' or ipv4 == '0.0.0.0' : + ipv4=MyDistro.GetIpv4Address() + if ipv4 == '' or ipv4 == '0.0.0.0' : + Log("Waiting for network.") time.sleep(10) - Log("IPv4 address: " + MyDistro.GetIpv4Address()) - Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in MyDistro.GetMacAddress()])) - + Log("IPv4 address: " + ipv4) + mac='' + mac=MyDistro.GetMacAddress() + if len(mac)>0 : + Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in mac])) + # Consume Entropy in ACPI table provided by Hyper-V try: SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0")) @@ -3836,7 +4153,11 @@ class Agent(Util): if provisionError != None: incarnation = self.ReportNotReady("ProvisioningFailed", provisionError) else: - incarnation = self.ReportReady() + incarnation = self.ReportReady() + # 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) WaagentLogrotate = """\ @@ -4002,6 +4323,8 @@ def GetMyDistro(dist_class_name=''): else : # I know this is not Linux! if 'FreeBSD' in platform.system(): Distro=platform.system() + Distro=Distro.strip('"') + Distro=Distro.strip(' ') dist_class_name=Distro+'Distro' else: Distro=dist_class_name |