summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Changelog6
-rw-r--r--README13
-rw-r--r--config/waagent.conf2
-rw-r--r--debian/changelog7
-rw-r--r--debian/patches/disable-udev-rules.patch2
-rw-r--r--rpm/walinuxagent.spec12
-rwxr-xr-xwaagent523
7 files changed, 454 insertions, 111 deletions
diff --git a/Changelog b/Changelog
index 33d76fe..0df9f2b 100644
--- a/Changelog
+++ b/Changelog
@@ -1,5 +1,11 @@
WALinuxAgent Changelog
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+02 Apr 2014, WALinuxAgent 2.0.4
+ . Fix encoding issue in LogToFile() & LogToCon()
+ . Add support for parsing ExtensionsConfiguration from GoalState document
+ . Add support for the Fedora distribution
+ . Several fixes to FreeBSDDistro class
+
16 Jan 2014, WALinuxAgent 2.0.3
. Add exception awareness to GetFileContents, SetFileContents, and
AppendFileContents
diff --git a/README b/README
index 477d3d5..f16e164 100644
--- a/README
+++ b/README
@@ -22,9 +22,9 @@ functionality for Linux and FreeBSD IaaS deployments:
- Ensures the stability of the network interface name
* Kernel
- - Configuring virtual NUMA
+ - Configure virtual NUMA (disable for kernel <2.6.37)
- Consume Hyper-V entropy for /dev/random
- - Configuring SCSI timeouts for the root device (which could be remote)
+ - Configure SCSI timeouts for the root device (which could be remote)
* Diagnostics
- Console redirection to the serial port
@@ -50,7 +50,7 @@ REQUIREMENTS
The following systems have been tested and are known to work with the Windows
Azure Linux Agent. Please note that this list may differ from the official
-list of supported systems on the Windows Azure Platform described here:
+list of supported systems on the Windows Azure Platform as described here:
http://support.microsoft.com/kb/2805216
Supported Linux Distributions:
@@ -66,7 +66,7 @@ http://support.microsoft.com/kb/2805216
Waagent depends on some system packages in order to function properly:
- * Python 2.4+
+ * Python 2.5+
* OpenSSL 1.0+
* OpenSSH 5.3+
* Filesystem utilities: sfdisk, fdisk, mkfs
@@ -201,7 +201,7 @@ Role.TopologyConsumer:
Type: String Default: None
If a path to an executable program is specified, the program is invoked when the
-Fabric indicates that a new network topology layout is available for the VM.The
+Fabric indicates that a new network topology layout is available for the VM. The
path to the XML configuration file is provided as an argument to the executable.
This may be invoked multiple times whenever the network topology changes (due to
service healing for example). A sample file is provided in the Appendix. Please
@@ -264,7 +264,8 @@ Type: String Default: ext4
This specifies the filesystem type for the resource disk. Supported values vary
by Linux distribution. If the string is X, then mkfs.X should be present on the
-Linux image. FreeBSD images should use 'ufs2' here.
+Linux image. SLES 11 images should typically use 'ext3'. FreeBSD images should
+use 'ufs2' here.
ResourceDisk.MountPoint:
Type: String Default: /mnt/resource
diff --git a/config/waagent.conf b/config/waagent.conf
index 364c2c1..c7cb96a 100644
--- a/config/waagent.conf
+++ b/config/waagent.conf
@@ -47,7 +47,7 @@ ResourceDisk.SwapSizeMB=0
# Respond to load balancer probes if requested by Windows Azure.
LBProbeResponder=y
-# Logging level
+# Enable verbose logging (y|n)
Logs.Verbose=n
# Root device timeout in seconds.
diff --git a/debian/changelog b/debian/changelog
index 6cd9f8b..c42e5f4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+walinuxagent (2.0.4-0ubuntu1) trusty; urgency=medium
+
+ * Update to latest upstream version 2.0.4 (LP: #1304023).
+ - Includes ability for agent injection.
+
+ -- Ben Howard <ben.howard@ubuntu.com> Mon, 07 Apr 2014 16:48:16 -0600
+
walinuxagent (2.0.3-0ubuntu2) trusty; urgency=low
* Change /etc/dhcp/dhclient.conf 'send host-name' during post-inst to
diff --git a/debian/patches/disable-udev-rules.patch b/debian/patches/disable-udev-rules.patch
index ba96a2d..56c5acf 100644
--- a/debian/patches/disable-udev-rules.patch
+++ b/debian/patches/disable-udev-rules.patch
@@ -5,7 +5,7 @@ Author: Ben Howard
Last-Update: 2013-07-11
--- a/waagent
+++ b/waagent
-@@ -87,8 +87,7 @@
+@@ -90,8 +90,7 @@
VMM_STARTUP_SCRIPT_NAME='install'
VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml'
global RulesFiles
diff --git a/rpm/walinuxagent.spec b/rpm/walinuxagent.spec
index 0ba8cec..f20cb64 100644
--- a/rpm/walinuxagent.spec
+++ b/rpm/walinuxagent.spec
@@ -2,18 +2,18 @@
# Name: walinuxagent.spec
#-------------------------------------------------------------------------------
# Purpose : RPM Spec file for Python script packaging
-# Version : 2.0.0
+# Version : 2.0.4
# Created : April 20 2012
#===============================================================================
Name: WALinuxAgent
Summary: The Windows Azure Linux Agent
-Version: 2.0.3
+Version: 2.0.4
Release: 1
License: Apache License Version 2.0
Group: System/Daemons
Url: http://go.microsoft.com/fwlink/?LinkId=250998
-Source0: WALinuxAgent-2.0.3.tar.gz
+Source0: WALinuxAgent-2.0.4.tar.gz
Requires: python python-pyasn1 openssh openssl util-linux sed grep sudo iptables
Conflicts: NetworkManager
BuildRoot: %{_tmppath}/%{name}-%{version}-build
@@ -39,6 +39,7 @@ find . -type f -exec chmod 0644 {} +
%install
python setup.py install --prefix=%{_prefix} --lnx-distro='redhat' --init-system='sysV' --root=%{buildroot}
mkdir -p %{buildroot}/%{_localstatedir}/log
+mkdir -p -m 0700 %{buildroot}/%{_sharedstatedir}/waagent
touch %{buildroot}/%{_localstatedir}/log/waagent.log
%post
@@ -64,9 +65,14 @@ fi
%config(noreplace) %{_sysconfdir}/logrotate.d/waagent
%config %{_sysconfdir}/waagent.conf
%ghost %{_localstatedir}/log/waagent.log
+%dir %attr(0700, root, root) %{_sharedstatedir}/waagent
%changelog
+* Thu Mar 25 2014 - walinuxagent@microsoft.com
+- Create directory /var/lib/waagent
+- Updated version to 2.0.4 for release
+
* Thu Jan 16 2014 - walinuxagent@microsoft.com
- Updated version to 2.0.3 for release
diff --git a/waagent b/waagent
index f80aaf5..c04ab3e 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
@@ -118,7 +121,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.
@@ -404,6 +407,7 @@ class AbstractDistro(object):
Return the ip of the
first active non-loopback interface.
"""
+ addr=''
iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
return addr
@@ -471,8 +475,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
@@ -1156,6 +1162,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 = """\
@@ -1182,7 +1200,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.
@@ -1197,7 +1215,7 @@ bsd_init_file="""\
# KEYWORD: nojail
. /etc/rc.subr
-
+export PATH=$PATH:/usr/local/bin
name="waagent"
rcvar="waagent_enable"
command="/usr/sbin/${name}"
@@ -1210,6 +1228,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):
"""
@@ -1219,6 +1285,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'
@@ -1235,7 +1302,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
@@ -1249,13 +1316,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
@@ -1359,6 +1419,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',' ')
@@ -1469,52 +1537,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):
"""
@@ -1559,8 +1594,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
@@ -1950,22 +1985,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.
@@ -1994,15 +2027,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:
@@ -2012,7 +2042,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'.
@@ -2053,11 +2084,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.')
@@ -2070,7 +2101,6 @@ def GetFirstActiveNetworkInterfaceNonLoopback():
break
return iface.decode('latin-1'), socket.inet_ntoa(s[i+20:i+24])
-
def GetIpv4Address():
"""
Return the ip of the
@@ -2093,8 +2123,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):
@@ -2559,7 +2591,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
@@ -2584,12 +2879,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" />
@@ -2653,9 +2943,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
@@ -2663,14 +2961,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)
@@ -2696,6 +2986,7 @@ class GoalState(Util):
Initializes and populates:
self.HostingEnvironmentConfig
self.SharedConfig
+ self.ExtensionsConfig
self.Certificates
"""
#
@@ -2718,6 +3009,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>
@@ -2749,6 +3043,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
@@ -2759,9 +3056,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")
@@ -2799,6 +3098,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)
@@ -2809,6 +3109,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)
@@ -2894,6 +3198,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.")
@@ -2939,22 +3244,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
@@ -3658,11 +3969,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
@@ -3694,6 +4006,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()
@@ -3739,15 +4052,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"))
@@ -3837,7 +4154,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 = """\
@@ -4003,6 +4324,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