summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Howard <ben.howard@ubuntu.com>2014-05-20 08:26:50 -0600
committerusd-importer <ubuntu-server@lists.ubuntu.com>2014-05-20 14:53:44 +0000
commit320f1b54bf746a6664e17df719a20f5c14a5ea2f (patch)
tree116650330685e98abfd3d0f3f7cde100ef8f8f24
parentb2eb317cf1393f7d58f842a8dc1eabc81f03166b (diff)
parent00c97042e31a0b55b34e0fc9ed8773651a708bfe (diff)
downloadvyos-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--Changelog13
-rw-r--r--README7
-rw-r--r--debian/changelog16
-rw-r--r--debian/control2
-rw-r--r--debian/patches/sshd_config_newline_fix.patch2
-rw-r--r--[-rwxr-xr-x]waagent826
6 files changed, 651 insertions, 215 deletions
diff --git a/Changelog b/Changelog
index 0df9f2b..40e0fdc 100644
--- a/Changelog
+++ b/Changelog
@@ -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
diff --git a/README b/README
index f16e164..22274ba 100644
--- a/README
+++ b/README
@@ -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")),
diff --git a/waagent b/waagent
index 621f939..6d9b4d4 100755..100644
--- a/waagent
+++ b/waagent
@@ -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('&amp;','&')
+ 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 :