summaryrefslogtreecommitdiff
path: root/waagent
diff options
context:
space:
mode:
Diffstat (limited to 'waagent')
-rwxr-xr-xwaagent523
1 files changed, 423 insertions, 100 deletions
diff --git a/waagent b/waagent
index f6ae95d..5be3288 100755
--- a/waagent
+++ b/waagent
@@ -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&amp;sp=rw&amp;
+ #se=9999-01-01&amp;sk=key1&amp;sv=2012-02-12&amp;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="&lt;m role=&quot;MachineRole&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;MachineRole&quot;>&lt;e name=&quot;a&quot; />&lt;e name=&quot;b&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;/r>&lt;/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&amp;type=hostingEnvironmentConfig&amp;incarnation=1</HostingEnvironmentConfig>
# <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=sharedConfig&amp;incarnation=1</SharedConfig>
# <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&amp;incarnation=1</Certificates>
+ # <ExtensionsConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&amp;type=extensionsConfig&amp;incarnation=2</ExtensionsConfig>
+ # <FullConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&amp;type=fullConfig&amp;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