summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorBen Howard <ben.howard@ubuntu.com>2015-08-14 16:40:41 -0600
committerusd-importer <ubuntu-server@lists.ubuntu.com>2015-08-15 14:33:21 +0000
commitf78b9650d0e7b008d430673a075aad95dda863be (patch)
treea6749619e78483d45a66d4bad4d6e922391541fc /bin
parent0afc048f2a6ff3638ecfa33e7ded5dc8dddf041a (diff)
downloadvyos-walinuxagent-f78b9650d0e7b008d430673a075aad95dda863be.tar.gz
vyos-walinuxagent-f78b9650d0e7b008d430673a075aad95dda863be.zip
Import patches-unapplied version 2.1.1-0ubuntu1 to ubuntu/wily-proposed
Imported using git-ubuntu import. Changelog parent: 0afc048f2a6ff3638ecfa33e7ded5dc8dddf041a New changelog entries: * New upstream release for Ubuntu. - Switch to Python3 - Applies Ubuntu specific patches
Diffstat (limited to 'bin')
-rwxr-xr-xbin/waagent49
-rwxr-xr-xbin/waagent2.06068
2 files changed, 6117 insertions, 0 deletions
diff --git a/bin/waagent b/bin/waagent
new file mode 100755
index 0000000..e65bc0c
--- /dev/null
+++ b/bin/waagent
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+#
+# Azure Linux Agent
+#
+# Copyright 2015 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.6+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+#
+
+import os
+import imp
+import sys
+
+if __name__ == '__main__' :
+ import azurelinuxagent.agent as agent
+ """
+ Invoke main method of agent
+ """
+ agent.main()
+
+if __name__ == 'waagent':
+ """
+ Load waagent2.0 to support old version of extensions
+ """
+ if sys.version_info[0] == 3:
+ raise ImportError("waagent2.0 doesn't support python3")
+ bin_path = os.path.dirname(os.path.abspath(__file__))
+ agent20_path = os.path.join(bin_path, "waagent2.0")
+ if not os.path.isfile(agent20_path):
+ raise ImportError("Can't load waagent")
+ agent20 = imp.load_source('waagent', agent20_path)
+ __all__ = dir(agent20)
+
diff --git a/bin/waagent2.0 b/bin/waagent2.0
new file mode 100755
index 0000000..52e022f
--- /dev/null
+++ b/bin/waagent2.0
@@ -0,0 +1,6068 @@
+#!/usr/bin/env python
+#
+# Windows Azure Linux Agent
+#
+# Copyright 2015 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.6+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+#
+
+import array
+import base64
+import httplib
+import os
+import os.path
+import platform
+import pwd
+import re
+import shutil
+import socket
+import SocketServer
+import struct
+import string
+import subprocess
+import sys
+import tempfile
+import textwrap
+import threading
+import time
+import traceback
+import xml.dom.minidom
+import fcntl
+import inspect
+import zipfile
+import json
+import datetime
+import xml.sax.saxutils
+
+if not hasattr(subprocess,'check_output'):
+ def check_output(*popenargs, **kwargs):
+ r"""Backport from subprocess module from python 2.7"""
+ if 'stdout' in kwargs:
+ raise ValueError('stdout argument not allowed, it will be overridden.')
+ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+ output, unused_err = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ raise subprocess.CalledProcessError(retcode, cmd, output=output)
+ return output
+
+ # Exception classes used by this module.
+ class CalledProcessError(Exception):
+ def __init__(self, returncode, cmd, output=None):
+ self.returncode = returncode
+ self.cmd = cmd
+ self.output = output
+ def __str__(self):
+ return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+
+ subprocess.check_output=check_output
+ subprocess.CalledProcessError=CalledProcessError
+
+GuestAgentName = "WALinuxAgent"
+GuestAgentLongName = "Windows Azure Linux Agent"
+GuestAgentVersion = "WALinuxAgent-2.0.15-pre"
+ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol.
+
+Config = None
+WaAgent = None
+DiskActivated = False
+Openssl = "openssl"
+Children = []
+ExtensionChildren = []
+VMM_STARTUP_SCRIPT_NAME='install'
+VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml'
+global RulesFiles
+RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
+ "/etc/udev/rules.d/70-persistent-net.rules" ]
+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
+HandlerStatusToAggStatus = {"installed":"Installing", "enabled":"Ready", "unintalled":"NotReady", "disabled":"NotReady"}
+
+WaagentConf = """\
+#
+# Windows Azure Linux Agent Configuration
+#
+
+Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status
+ # to the endpoint server.
+Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
+Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
+
+Provisioning.Enabled=y #
+Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
+Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
+Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
+Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
+
+ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
+ResourceDisk.Filesystem=ext4 # Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
+ResourceDisk.MountPoint=/mnt/resource #
+ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
+ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
+
+LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
+
+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.
+"""
+README_FILENAME="DATALOSS_WARNING_README.txt"
+README_FILECONTENT="""\
+WARNING: THIS IS A TEMPORARY DISK.
+
+Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT.
+
+Please do not use this disk for storing any personal or application data.
+
+For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx
+"""
+
+############################################################
+# BEGIN DISTRO CLASS DEFS
+############################################################
+############################################################
+# AbstractDistro
+############################################################
+class AbstractDistro(object):
+ """
+ AbstractDistro defines a skeleton neccesary for a concrete Distro class.
+
+ 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.linux_distribution()[0].
+ So for CentOS the derived class is called 'centosDistro'.
+ """
+
+ def __init__(self):
+ """
+ Generic Attributes go here. These are based on 'majority rules'.
+ This __init__() may be called or overriden by the child.
+ """
+ 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'
+ self.dhcp_client_name='dhclient'
+ self.requiredDeps = [ 'route', 'shutdown', 'ssh-keygen', 'useradd',
+ 'openssl', 'sfdisk', 'fdisk', 'mkfs', 'chpasswd',
+ 'sed', 'grep', 'sudo', 'parted' ]
+ self.init_script_file='/etc/init.d/waagent'
+ self.agent_package_name='WALinuxAgent'
+ self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log",'/etc/resolv.conf' ]
+ self.agent_files_to_uninstall = ["/etc/waagent.conf", "/etc/logrotate.d/waagent"]
+ self.grubKernelBootOptionsFile = '/etc/default/grub'
+ self.grubKernelBootOptionsLine = 'GRUB_CMDLINE_LINUX_DEFAULT='
+ self.getpidcmd = 'pidof'
+ self.mount_dvd_cmd = 'mount'
+ self.sudoers_dir_base = '/etc'
+ self.waagent_conf_file = WaagentConf
+ self.shadow_file_mode=0600
+ self.dhcp_enabled = False
+
+ def isSelinuxSystem(self):
+ """
+ Checks and sets self.selinux = True if SELinux is available on system.
+ """
+ if self.selinux == None:
+ if Run("which getenforce",chk_err=False):
+ self.selinux = False
+ else:
+ self.selinux = True
+ return self.selinux
+
+ def isSelinuxRunning(self):
+ """
+ Calls shell command 'getenforce' and returns True if 'Enforcing'.
+ """
+ if self.isSelinuxSystem():
+ return RunGetOutput("getenforce")[1].startswith("Enforcing")
+ else:
+ return False
+
+ def setSelinuxEnforce(self,state):
+ """
+ Calls shell command 'setenforce' with 'state' and returns resulting exit code.
+ """
+ if self.isSelinuxSystem():
+ if state: s = '1'
+ else: s='0'
+ return Run("setenforce "+s)
+
+ def setSelinuxContext(self,path,cn):
+ """
+ Calls shell 'chcon' with 'path' and 'cn' context.
+ Returns exit result.
+ """
+ if self.isSelinuxSystem():
+ return Run('chcon ' + cn + ' ' + path)
+
+ def setHostname(self,name):
+ """
+ Shell call to hostname.
+ Returns resulting exit code.
+ """
+ return Run('hostname ' + name)
+
+ def publishHostname(self,name):
+ """
+ Set the contents of the hostname file to 'name'.
+ Return 1 on failure.
+ """
+ try:
+ r=SetFileContents(self.hostname_file_path, name)
+ for f in EtcDhcpClientConfFiles:
+ if os.path.exists(f) and FindStringInFile(f,r'^[^#]*?send\s*host-name.*?(<hostname>|gethostname[(,)])') == None :
+ r=ReplaceFileContentsAtomic('/etc/dhcp/dhclient.conf', "send host-name \"" + name + "\";\n"
+ + "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents('/etc/dhcp/dhclient.conf').split('\n'))))
+ except:
+ return 1
+ return r
+
+ def installAgentServiceScriptFiles(self):
+ """
+ Create the waagent support files for service installation.
+ Called by registerAgentService()
+ Abstract Virtual Function. Over-ridden in concrete Distro classes.
+ """
+ pass
+
+ def registerAgentService(self):
+ """
+ Calls installAgentService to create service files.
+ Shell exec service registration commands. (e.g. chkconfig --add waagent)
+ Abstract Virtual Function. Over-ridden in concrete Distro classes.
+ """
+ pass
+
+ def uninstallAgentService(self):
+ """
+ Call service subsystem to remove waagent script.
+ Abstract Virtual Function. Over-ridden in concrete Distro classes.
+ """
+ pass
+
+ def unregisterAgentService(self):
+ """
+ Calls self.stopAgentService and call self.uninstallAgentService()
+ """
+ self.stopAgentService()
+ self.uninstallAgentService()
+
+ def startAgentService(self):
+ """
+ Service call to start the Agent service
+ """
+ return Run(self.service_cmd + ' ' + self.agent_service_name + ' start')
+
+ def stopAgentService(self):
+ """
+ Service call to stop the Agent service
+ """
+ return Run(self.service_cmd + ' ' + self.agent_service_name + ' stop',False)
+
+ def restartSshService(self):
+ """
+ Service call to re(start) the SSH service
+ """
+ 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:" + str(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
+ """
+ error=0
+ SshPubKey = OvfEnv().OpensslToSsh(fprint)
+ if SshPubKey != None:
+ AppendFileContents(path, SshPubKey)
+ else:
+ Error("Failed: " + fprint + ".crt -> " + path)
+ error = 1
+ return error
+
+ def checkPackageInstalled(self,p):
+ """
+ Query package database for prescence of an installed package.
+ Abstract Virtual Function. Over-ridden in concrete Distro classes.
+ """
+ pass
+
+ def checkPackageUpdateable(self,p):
+ """
+ Online check if updated package of walinuxagent is available.
+ Abstract Virtual Function. Over-ridden in concrete Distro classes.
+ """
+ pass
+
+ def deleteRootPassword(self):
+ """
+ Generic root password removal.
+ """
+ filepath="/etc/shadow"
+ ReplaceFileContentsAtomic(filepath,"root:*LOCK*:14600::::::\n"
+ + "\n".join(filter(lambda a: not a.startswith("root:"),GetFileContents(filepath).split('\n'))))
+ os.chmod(filepath,self.shadow_file_mode)
+ if self.isSelinuxSystem():
+ self.setSelinuxContext(filepath,'system_u:object_r:shadow_t:s0')
+ Log("Root password deleted.")
+ return 0
+
+ def changePass(self,user,password):
+ return RunSendStdin("chpasswd",(user + ":" + password + "\n"))
+
+ def load_ata_piix(self):
+ return WaAgent.TryLoadAtapiix()
+
+ def unload_ata_piix(self):
+ """
+ Generic function to remove ata_piix.ko.
+ """
+ return WaAgent.TryUnloadAtapiix()
+
+ def deprovisionWarnUser(self):
+ """
+ Generic user warnings used at deprovision.
+ """
+ print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
+
+ def deprovisionDeleteFiles(self):
+ """
+ Files to delete when VM is deprovisioned
+ """
+ for a in VarLibDhcpDirectories:
+ Run("rm -f " + a + "/*")
+
+ # Clear LibDir, remove nameserver and root bash history
+
+ for f in os.listdir(LibDir) + self.fileBlackList:
+ try:
+ os.remove(f)
+ except:
+ pass
+ return 0
+
+ def uninstallDeleteFiles(self):
+ """
+ Files to delete when agent is uninstalled.
+ """
+ for f in self.agent_files_to_uninstall:
+ try:
+ os.remove(f)
+ except:
+ pass
+ return 0
+
+ def checkDependencies(self):
+ """
+ Generic dependency check.
+ Return 1 unless all dependencies are satisfied.
+ """
+ if self.checkPackageInstalled('NetworkManager'):
+ Error(GuestAgentLongName + " is not compatible with network-manager.")
+ return 1
+ try:
+ m= __import__('pyasn1')
+ except ImportError:
+ Error(GuestAgentLongName + " requires python-pyasn1 for your Linux distribution.")
+ return 1
+ for a in self.requiredDeps:
+ if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
+ Error("Missing required dependency: " + a)
+ return 1
+ return 0
+
+ def packagedInstall(self,buildroot):
+ """
+ Called from setup.py for use by RPM.
+ Copies generated files waagent.conf, under the buildroot.
+ """
+ if not os.path.exists(buildroot+'/etc'):
+ os.mkdir(buildroot+'/etc')
+ SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
+
+ if not os.path.exists(buildroot+'/etc/logrotate.d'):
+ os.mkdir(buildroot+'/etc/logrotate.d')
+ SetFileContents(buildroot+'/etc/logrotate.d/waagent', WaagentLogrotate)
+
+ self.init_script_file=buildroot+self.init_script_file
+ # this allows us to call installAgentServiceScriptFiles()
+ if not os.path.exists(os.path.dirname(self.init_script_file)):
+ os.mkdir(os.path.dirname(self.init_script_file))
+ self.installAgentServiceScriptFiles()
+
+ def GetIpv4Address(self):
+ """
+ Return the ip of the
+ first active non-loopback interface.
+ """
+ addr=''
+ iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
+ return addr
+
+ def GetMacAddress(self):
+ return GetMacAddress()
+
+ def GetInterfaceName(self):
+ return GetFirstActiveNetworkInterfaceNonLoopback()[0]
+
+ def RestartInterface(self, iface):
+ Run("ifdown " + iface + " && ifup " + iface)
+
+ def CreateAccount(self,user, password, expiration, thumbprint):
+ return CreateAccount(user, password, expiration, thumbprint)
+
+ def DeleteAccount(self,user):
+ return DeleteAccount(user)
+
+ def ActivateResourceDisk(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)
+ if device == None:
+ Error("ActivateResourceDisk: Unable to detect disk topology.")
+ return
+ device = "/dev/" + device
+
+ mountlist = RunGetOutput("mount")[1]
+ mountpoint = GetMountPoint(mountlist, device)
+
+ if(mountpoint):
+ Log("ActivateResourceDisk: " + device + "1 is already mounted.")
+ else:
+ mountpoint = Config.get("ResourceDisk.MountPoint")
+ if mountpoint == None:
+ mountpoint = "/mnt/resource"
+ CreateDir(mountpoint, "root", 0755)
+ fs = Config.get("ResourceDisk.Filesystem")
+ if fs == None:
+ fs = "ext3"
+
+ partition = device + "1"
+
+ #Check partition type
+ Log("Detect GPT...")
+ ret = RunGetOutput("parted {0} print".format(device))
+ if ret[0] == 0 and "gpt" in ret[1]:
+ Log("GPT detected.")
+ #GPT(Guid Partition Table) is used.
+ #Get partitions.
+ parts = filter(lambda x : re.match("^\s*[0-9]+", x), ret[1].split("\n"))
+ #If there are more than 1 partitions, remove all partitions
+ #and create a new one using the entire disk space.
+ if len(parts) > 1:
+ for i in range(1, len(parts) + 1):
+ Run("parted {0} rm {1}".format(device, i))
+ Run("parted {0} mkpart primary 0% 100%".format(device))
+ Run("mkfs." + fs + " " + partition + " -F")
+ else:
+ existingFS = RunGetOutput("sfdisk -q -c " + device + " 1", chk_err=False)[1].rstrip()
+ if existingFS == "7" and fs != "ntfs":
+ Run("sfdisk -c " + device + " 1 83")
+ Run("mkfs." + fs + " " + partition)
+ if Run("mount " + partition + " " + mountpoint, chk_err=False):
+ #If mount failed, try to format the partition and mount again
+ Warn("Failed to mount resource disk. Retry mounting.")
+ Run("mkfs." + fs + " " + partition + " -F")
+ if Run("mount " + partition + " " + mountpoint):
+ Error("ActivateResourceDisk: Failed to mount resource disk (" + partition + ").")
+ return
+ Log("Resource disk (" + partition + ") is mounted at " + mountpoint + " with fstype " + fs)
+
+ #Create README file under the root of resource disk
+ SetFileContents(os.path.join(mountpoint,README_FILENAME), README_FILECONTENT)
+ DiskActivated = True
+
+ #Create swap space
+ 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))
+ Run("mkswap " + mountpoint + "/swapfile")
+ if not Run("swapon " + mountpoint + "/swapfile"):
+ Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
+ else:
+ Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
+
+ def Install(self):
+ return Install()
+
+ def mediaHasFilesystem(self,dsk):
+ if len(dsk) == 0 :
+ return False
+ if Run("LC_ALL=C fdisk -l " + dsk + " | grep Disk"):
+ return False
+ return True
+
+ def mountDVD(self,dvd,location):
+ return RunGetOutput(self.mount_dvd_cmd + ' ' + dvd + ' ' + location)
+
+ def GetHome(self):
+ return GetHome()
+
+ 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)
+
+ def waitForSshHostKey(self, path):
+ """
+ Provide a dummy waiting, since by default, ssh host key is created by waagent and the key
+ should already been created.
+ """
+ if(os.path.isfile(path)):
+ return True
+ else:
+ Error("Can't find host key: {0}".format(path))
+ return False
+
+ def isDHCPEnabled(self):
+ return self.dhcp_enabled
+
+ def stopDHCP(self):
+ """
+ Stop the system DHCP client so that the agent can bind on its port. If
+ the distro has set dhcp_enabled to True, it will need to provide an
+ implementation of this method.
+ """
+ raise NotImplementedError('stopDHCP method missing')
+
+ def startDHCP(self):
+ """
+ Start the system DHCP client. If the distro has set dhcp_enabled to
+ True, it will need to provide an implementation of this method.
+ """
+ raise NotImplementedError('startDHCP method missing')
+
+ def translateCustomData(self, data):
+ """
+ Translate the custom data from a Base64 encoding. Default to no-op.
+ """
+ decodeCustomData = Config.get("Provisioning.DecodeCustomData")
+ if decodeCustomData != None and decodeCustomData.lower().startswith("y"):
+ return base64.b64decode(data)
+ return data
+
+ def getConfigurationPath(self):
+ return "/etc/waagent.conf"
+
+ def getProcessorCores(self):
+ return int(RunGetOutput("grep 'processor.*:' /proc/cpuinfo |wc -l")[1])
+
+ def getTotalMemory(self):
+ return int(RunGetOutput("grep MemTotal /proc/meminfo |awk '{print $2}'")[1])/1024
+
+ def getInterfaceNameByMac(self, mac):
+ ret, output = RunGetOutput("ifconfig -a")
+ if ret != 0:
+ raise Exception("Failed to get network interface info")
+ output = output.replace('\n', '')
+ match = re.search(r"(eth\d).*(HWaddr|ether) {0}".format(mac),
+ output, re.IGNORECASE)
+ if match is None:
+ raise Exception("Failed to get ifname with mac: {0}".format(mac))
+ output = match.group(0)
+ eths = re.findall(r"eth\d", output)
+ if eths is None or len(eths) == 0:
+ raise Exception("Failed to get ifname with mac: {0}".format(mac))
+ return eths[-1]
+
+ def configIpV4(self, ifName, addr, netmask=24):
+ ret, output = RunGetOutput("ifconfig {0} up".format(ifName))
+ if ret != 0:
+ raise Exception("Failed to bring up {0}: {1}".format(ifName,
+ output))
+ ret, output = RunGetOutput("ifconfig {0} {1}/{2}".format(ifName, addr,
+ netmask))
+ if ret != 0:
+ raise Exception("Failed to config ipv4 for {0}: {1}".format(ifName,
+ output))
+
+############################################################
+# GentooDistro
+############################################################
+gentoo_init_file = """\
+#!/sbin/runscript
+
+command=/usr/sbin/waagent
+pidfile=/var/run/waagent.pid
+command_args=-daemon
+command_background=true
+name="Windows Azure Linux Agent"
+
+depend()
+{
+ need localmount
+ use logger network
+ after bootmisc modules
+}
+
+"""
+class gentooDistro(AbstractDistro):
+ """
+ Gentoo distro concrete class
+ """
+
+ def __init__(self): #
+ super(gentooDistro,self).__init__()
+ self.service_cmd='/sbin/service'
+ self.ssh_service_name='sshd'
+ self.hostname_file_path='/etc/conf.d/hostname'
+ self.dhcp_client_name='dhcpcd'
+ self.shadow_file_mode=0640
+ self.init_file=gentoo_init_file
+
+ def publishHostname(self,name):
+ try:
+ if (os.path.isfile(self.hostname_file_path)):
+ r=ReplaceFileContentsAtomic(self.hostname_file_path, "hostname=\"" + name + "\"\n"
+ + "\n".join(filter(lambda a: not a.startswith("hostname="), GetFileContents(self.hostname_file_path).split("\n"))))
+ except:
+ return 1
+ return r
+
+ def installAgentServiceScriptFiles(self):
+ SetFileContents(self.init_script_file, self.init_file)
+ os.chmod(self.init_script_file, 0755)
+
+ def registerAgentService(self):
+ self.installAgentServiceScriptFiles()
+ return Run('rc-update add ' + self.agent_service_name + ' default')
+
+ def uninstallAgentService(self):
+ return Run('rc-update del ' + self.agent_service_name + ' default')
+
+ def unregisterAgentService(self):
+ self.stopAgentService()
+ return self.uninstallAgentService()
+
+ def checkPackageInstalled(self,p):
+ if Run('eix -I ^' + p + '$',chk_err=False):
+ return 0
+ else:
+ return 1
+
+ def checkPackageUpdateable(self,p):
+ if Run('eix -u ^' + p + '$',chk_err=False):
+ return 0
+ else:
+ return 1
+
+ def RestartInterface(self, iface):
+ Run("/etc/init.d/net." + iface + " restart")
+
+############################################################
+# SuSEDistro
+############################################################
+suse_init_file = """\
+#! /bin/sh
+#
+# Windows Azure Linux Agent sysV init script
+#
+# Copyright 2013 Microsoft Corporation
+# Copyright SUSE LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# /etc/init.d/waagent
+#
+# and symbolic link
+#
+# /usr/sbin/rcwaagent
+#
+# System startup script for the waagent
+#
+### BEGIN INIT INFO
+# Provides: WindowsAzureLinuxAgent
+# Required-Start: $network sshd
+# Required-Stop: $network sshd
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Description: Start the WindowsAzureLinuxAgent
+### END INIT INFO
+
+PYTHON=/usr/bin/python
+WAZD_BIN=/usr/sbin/waagent
+WAZD_CONF=/etc/waagent.conf
+WAZD_PIDFILE=/var/run/waagent.pid
+
+test -x "$WAZD_BIN" || { echo "$WAZD_BIN not installed"; exit 5; }
+test -e "$WAZD_CONF" || { echo "$WAZD_CONF not found"; exit 6; }
+
+. /etc/rc.status
+
+# First reset status of this service
+rc_reset
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - misc error
+# 2 - invalid or excess args
+# 3 - unimplemented feature (e.g. reload)
+# 4 - insufficient privilege
+# 5 - program not installed
+# 6 - program not configured
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+
+
+case "$1" in
+ start)
+ echo -n "Starting WindowsAzureLinuxAgent"
+ ## Start daemon with startproc(8). If this fails
+ ## the echo return value is set appropriate.
+ startproc -f ${PYTHON} ${WAZD_BIN} -daemon
+ rc_status -v
+ ;;
+ stop)
+ echo -n "Shutting down WindowsAzureLinuxAgent"
+ ## Stop daemon with killproc(8) and if this fails
+ ## set echo the echo return value.
+ killproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
+ rc_status -v
+ ;;
+ try-restart)
+ ## Stop the service and if this succeeds (i.e. the
+ ## service was running before), start it again.
+ $0 status >/dev/null && $0 restart
+ rc_status
+ ;;
+ restart)
+ ## Stop the service and regardless of whether it was
+ ## running or not, start it again.
+ $0 stop
+ sleep 1
+ $0 start
+ rc_status
+ ;;
+ force-reload|reload)
+ rc_status
+ ;;
+ status)
+ echo -n "Checking for service WindowsAzureLinuxAgent "
+ ## Check status with checkproc(8), if process is running
+ ## checkproc will return with exit status 0.
+
+ checkproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
+ rc_status -v
+ ;;
+ probe)
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
+ exit 1
+ ;;
+esac
+rc_exit
+"""
+class SuSEDistro(AbstractDistro):
+ """
+ SuSE Distro concrete class
+ Put SuSE specific behavior here...
+ """
+ def __init__(self):
+ super(SuSEDistro,self).__init__()
+ self.service_cmd='/sbin/service'
+ self.ssh_service_name='sshd'
+ self.kernel_boot_options_file='/boot/grub/menu.lst'
+ self.hostname_file_path='/etc/HOSTNAME'
+ 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') or \
+ (DistInfo(fullname=1)[0] == 'openSUSE' and DistInfo()[1] >= '13.2')):
+ self.dhcp_client_name='wickedd-dhcp4'
+ self.grubKernelBootOptionsFile = '/boot/grub/menu.lst'
+ self.grubKernelBootOptionsLine = 'kernel'
+ self.getpidcmd='pidof '
+ self.dhcp_enabled=True
+
+ def checkPackageInstalled(self,p):
+ if Run("rpm -q " + p,chk_err=False):
+ return 0
+ else:
+ return 1
+
+ def checkPackageUpdateable(self,p):
+ if Run("zypper list-updates | grep " + p,chk_err=False):
+ return 1
+ else:
+ return 0
+
+
+ def installAgentServiceScriptFiles(self):
+ try:
+ SetFileContents(self.init_script_file, self.init_file)
+ os.chmod(self.init_script_file, 0744)
+ except:
+ pass
+
+ def registerAgentService(self):
+ self.installAgentServiceScriptFiles()
+ return Run('insserv ' + self.agent_service_name)
+
+ def uninstallAgentService(self):
+ return Run('insserv -r ' + self.agent_service_name)
+
+ def unregisterAgentService(self):
+ self.stopAgentService()
+ return self.uninstallAgentService()
+
+ def startDHCP(self):
+ Run("service " + self.dhcp_client_name + " start", chk_err=False)
+
+ def stopDHCP(self):
+ Run("service " + self.dhcp_client_name + " stop", chk_err=False)
+
+############################################################
+# redhatDistro
+############################################################
+
+redhat_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 redhatDistro(AbstractDistro):
+ """
+ Redhat Distro concrete class
+ Put Redhat specific behavior here...
+ """
+ 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 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)
+ 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
+ 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
+
+
+
+############################################################
+# centosDistro
+############################################################
+
+class centosDistro(redhatDistro):
+ """
+ CentOS Distro concrete class
+ Put CentOS specific behavior here...
+ """
+ def __init__(self):
+ super(centosDistro,self).__init__()
+
+
+############################################################
+# asianuxDistro
+############################################################
+
+class asianuxDistro(redhatDistro):
+ """
+ Asianux Distro concrete class
+ Put Asianux specific behavior here...
+ """
+ def __init__(self):
+ super(asianuxDistro,self).__init__()
+
+
+############################################################
+# CoreOSDistro
+############################################################
+
+class CoreOSDistro(AbstractDistro):
+ """
+ CoreOS Distro concrete class
+ Put CoreOS specific behavior here...
+ """
+ CORE_UID = 500
+
+ def __init__(self):
+ super(CoreOSDistro,self).__init__()
+ self.requiredDeps += [ "/usr/bin/systemctl" ]
+ self.agent_service_name = 'waagent'
+ self.init_script_file='/etc/systemd/system/waagent.service'
+ self.fileBlackList.append("/etc/machine-id")
+ self.dhcp_client_name='systemd-networkd'
+ self.getpidcmd='pidof '
+ self.shadow_file_mode=0640
+ self.waagent_path='/usr/share/oem/bin'
+ self.python_path='/usr/share/oem/python/bin'
+ self.dhcp_enabled=True
+ if 'PATH' in os.environ:
+ os.environ['PATH'] = "{0}:{1}".format(os.environ['PATH'], self.python_path)
+ else:
+ os.environ['PATH'] = self.python_path
+
+ if 'PYTHONPATH' in os.environ:
+ os.environ['PYTHONPATH'] = "{0}:{1}".format(os.environ['PYTHONPATH'], self.waagent_path)
+ else:
+ os.environ['PYTHONPATH'] = self.waagent_path
+
+ def checkPackageInstalled(self,p):
+ """
+ There is no package manager in CoreOS. Return 1 since it must be preinstalled.
+ """
+ return 1
+
+ def checkDependencies(self):
+ for a in self.requiredDeps:
+ if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
+ Error("Missing required dependency: " + a)
+ return 1
+ return 0
+
+
+ def checkPackageUpdateable(self,p):
+ """
+ There is no package manager in CoreOS. Return 0 since it can't be updated via package.
+ """
+ return 0
+
+ def startAgentService(self):
+ return Run('systemctl start ' + self.agent_service_name)
+
+ def stopAgentService(self):
+ return Run('systemctl stop ' + self.agent_service_name)
+
+ def restartSshService(self):
+ """
+ SSH is socket activated on CoreOS. No need to restart it.
+ """
+ return 0
+
+ def sshDeployPublicKey(self,fprint,path):
+ """
+ We support PKCS8.
+ """
+ if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
+ return 1
+ else :
+ return 0
+
+ def RestartInterface(self, iface):
+ Run("systemctl restart systemd-networkd")
+
+ def CreateAccount(self, user, password, expiration, thumbprint):
+ """
+ Create a user account, with 'user', 'password', 'expiration', ssh keys
+ and sudo permissions.
+ Returns None if successful, error string on failure.
+ """
+ userentry = None
+ try:
+ userentry = pwd.getpwnam(user)
+ except:
+ pass
+ uidmin = None
+ try:
+ uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
+ except:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry != None and userentry[2] < uidmin and userentry[2] != self.CORE_UID:
+ Error("CreateAccount: " + user + " is a system user. Will not set password.")
+ return "Failed to set password for system user: " + user + " (0x06)."
+ if userentry == None:
+ command = "useradd --create-home --password '*' " + user
+ if expiration != None:
+ command += " --expiredate " + expiration.split('.')[0]
+ if Run(command):
+ Error("Failed to create user account: " + user)
+ return "Failed to create user account: " + user + " (0x07)."
+ else:
+ Log("CreateAccount: " + user + " already exists. Will update password.")
+ if password != None:
+ RunSendStdin("chpasswd", user + ":" + password + "\n")
+ try:
+ if password == None:
+ SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
+ else:
+ SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
+ os.chmod("/etc/sudoers.d/waagent", 0440)
+ except:
+ Error("CreateAccount: Failed to configure sudo access for user.")
+ return "Failed to configure sudo privileges (0x08)."
+ home = MyDistro.GetHome()
+ if thumbprint != None:
+ dir = home + "/" + user + "/.ssh"
+ CreateDir(dir, user, 0700)
+ pub = dir + "/id_rsa.pub"
+ prv = dir + "/id_rsa"
+ Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
+ SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
+ for f in [pub, prv]:
+ os.chmod(f, 0600)
+ ChangeOwner(f, user)
+ SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
+ ChangeOwner(dir + "/authorized_keys", user)
+ Log("Created user account: " + user)
+ return None
+
+ def startDHCP(self):
+ Run("systemctl start " + self.dhcp_client_name, chk_err=False)
+
+ def stopDHCP(self):
+ Run("systemctl stop " + self.dhcp_client_name, chk_err=False)
+
+ def translateCustomData(self, data):
+ return base64.b64decode(data)
+
+ def getConfigurationPath(self):
+ return "/usr/share/oem/waagent.conf"
+
+############################################################
+# debianDistro
+############################################################
+debian_init_file = """\
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: WindowsAzureLinuxAgent
+# Required-Start: $network $syslog
+# Required-Stop: $network $syslog
+# Should-Start: $network $syslog
+# Should-Stop: $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: WindowsAzureLinuxAgent
+# Description: WindowsAzureLinuxAgent
+### END INIT INFO
+
+. /lib/lsb/init-functions
+
+OPTIONS="-daemon"
+WAZD_BIN=/usr/sbin/waagent
+WAZD_PID=/var/run/waagent.pid
+
+case "$1" in
+ start)
+ log_begin_msg "Starting WindowsAzureLinuxAgent..."
+ pid=$( pidofproc $WAZD_BIN )
+ if [ -n "$pid" ] ; then
+ log_begin_msg "Already running."
+ log_end_msg 0
+ exit 0
+ fi
+ start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
+ log_end_msg $?
+ ;;
+
+ stop)
+ log_begin_msg "Stopping WindowsAzureLinuxAgent..."
+ start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
+ ret=$?
+ rm -f $WAZD_PID
+ log_end_msg $ret
+ ;;
+ force-reload)
+ $0 restart
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ status)
+ status_of_proc $WAZD_BIN && exit 0 || exit $?
+ ;;
+ *)
+ log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
+ exit 1
+ ;;
+esac
+
+exit 0
+"""
+
+class debianDistro(AbstractDistro):
+ """
+ debian Distro concrete class
+ Put debian specific behavior here...
+ """
+ def __init__(self):
+ super(debianDistro,self).__init__()
+ self.requiredDeps += [ "/usr/sbin/update-rc.d" ]
+ self.init_file=debian_init_file
+ self.agent_package_name='walinuxagent'
+ self.dhcp_client_name='dhclient'
+ self.getpidcmd='pidof '
+ self.shadow_file_mode=0640
+
+ def checkPackageInstalled(self,p):
+ """
+ Check that the package is installed.
+ Return 1 if installed, 0 if not installed.
+ This method of using dpkg-query
+ allows wildcards to be present in the
+ package name.
+ """
+ if not Run("dpkg-query -W -f='${Status}\n' '" + p + "' | grep ' installed' 2>&1",chk_err=False):
+ return 1
+ else:
+ return 0
+
+ def checkDependencies(self):
+ """
+ Debian dependency check. python-pyasn1 is NOT needed.
+ Return 1 unless all dependencies are satisfied.
+ NOTE: using network*manager will catch either package name in Ubuntu or debian.
+ """
+ if self.checkPackageInstalled('network*manager'):
+ Error(GuestAgentLongName + " is not compatible with network-manager.")
+ return 1
+ for a in self.requiredDeps:
+ if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
+ Error("Missing required dependency: " + a)
+ return 1
+ return 0
+
+ def checkPackageUpdateable(self,p):
+ if Run("apt-get update ; apt-get upgrade -us | grep " + p,chk_err=False):
+ return 1
+ else:
+ return 0
+
+ def installAgentServiceScriptFiles(self):
+ """
+ If we are packaged - the service name is walinuxagent, do nothing.
+ """
+ if self.agent_service_name == 'walinuxagent':
+ return 0
+ try:
+ SetFileContents(self.init_script_file, self.init_file)
+ os.chmod(self.init_script_file, 0744)
+ except OSError, e:
+ ErrorWithPrefix('installAgentServiceScriptFiles','Exception: '+str(e)+' occured creating ' + self.init_script_file)
+ return 1
+ return 0
+
+ def registerAgentService(self):
+ if self.installAgentServiceScriptFiles() == 0:
+ return Run('update-rc.d waagent defaults')
+ else :
+ return 1
+
+ def uninstallAgentService(self):
+ return Run('update-rc.d -f ' + self.agent_service_name + ' remove')
+
+ def unregisterAgentService(self):
+ self.stopAgentService()
+ return self.uninstallAgentService()
+
+ def sshDeployPublicKey(self,fprint,path):
+ """
+ We support PKCS8.
+ """
+ if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
+ return 1
+ else :
+ return 0
+
+############################################################
+# KaliDistro - WIP
+# Functioning on Kali 1.1.0a so far
+############################################################
+class KaliDistro(debianDistro):
+ """
+ Kali Distro concrete class
+ Put Kali specific behavior here...
+ """
+ def __init__(self):
+ super(KaliDistro,self).__init__()
+
+############################################################
+# UbuntuDistro
+############################################################
+ubuntu_upstart_file = """\
+#walinuxagent - start Windows Azure agent
+
+description "walinuxagent"
+author "Ben Howard <ben.howard@canonical.com>"
+
+start on (filesystem and started rsyslog)
+
+pre-start script
+
+ WALINUXAGENT_ENABLED=1
+ [ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent
+
+ if [ "$WALINUXAGENT_ENABLED" != "1" ]; then
+ exit 1
+ fi
+
+ if [ ! -x /usr/sbin/waagent ]; then
+ exit 1
+ fi
+
+ #Load the udf module
+ modprobe -b udf
+end script
+
+exec /usr/sbin/waagent -daemon
+"""
+
+class UbuntuDistro(debianDistro):
+ """
+ Ubuntu Distro concrete class
+ Put Ubuntu specific behavior here...
+ """
+ def __init__(self):
+ super(UbuntuDistro,self).__init__()
+ self.init_script_file='/etc/init/waagent.conf'
+ self.init_file=ubuntu_upstart_file
+ self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log"]
+ self.dhcp_client_name=None
+ self.getpidcmd='pidof '
+
+ def registerAgentService(self):
+ return self.installAgentServiceScriptFiles()
+
+ def uninstallAgentService(self):
+ """
+ If we are packaged - the service name is walinuxagent, do nothing.
+ """
+ if self.agent_service_name == 'walinuxagent':
+ return 0
+ os.remove('/etc/init/' + self.agent_service_name + '.conf')
+
+ def unregisterAgentService(self):
+ """
+ If we are packaged - the service name is walinuxagent, do nothing.
+ """
+ if self.agent_service_name == 'walinuxagent':
+ return
+ self.stopAgentService()
+ return self.uninstallAgentService()
+
+ def deprovisionWarnUser(self):
+ """
+ Ubuntu specific warning string from Deprovision.
+ """
+ print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,original} will be deleted.")
+
+ def deprovisionDeleteFiles(self):
+ """
+ Ubuntu uses resolv.conf by default, so removing /etc/resolv.conf will
+ break resolvconf. Therefore, we check to see if resolvconf is in use,
+ and if so, we remove the resolvconf artifacts.
+ """
+ if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
+ Log("resolvconf is not configured. Removing /etc/resolv.conf")
+ self.fileBlackList.append('/etc/resolv.conf')
+ else:
+ Log("resolvconf is enabled; leaving /etc/resolv.conf intact")
+ resolvConfD = '/etc/resolvconf/resolv.conf.d/'
+ self.fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'original'])
+ for f in os.listdir(LibDir)+self.fileBlackList:
+ try:
+ os.remove(f)
+ except:
+ pass
+ return 0
+
+ def getDhcpClientName(self):
+ if self.dhcp_client_name != None :
+ return self.dhcp_client_name
+ if DistInfo()[1] == '12.04' :
+ self.dhcp_client_name='dhclient3'
+ else :
+ self.dhcp_client_name='dhclient'
+ return self.dhcp_client_name
+
+ def waitForSshHostKey(self, path):
+ """
+ Wait until the ssh host key is generated by cloud init.
+ """
+ for retry in range(0, 10):
+ if(os.path.isfile(path)):
+ return True
+ time.sleep(1)
+ Error("Can't find host key: {0}".format(path))
+ return False
+
+
+############################################################
+# LinuxMintDistro
+############################################################
+
+class LinuxMintDistro(UbuntuDistro):
+ """
+ LinuxMint Distro concrete class
+ Put LinuxMint specific behavior here...
+ """
+ def __init__(self):
+ super(LinuxMintDistro,self).__init__()
+
+############################################################
+# fedoraDistro
+############################################################
+fedora_systemd_service = """\
+[Unit]
+Description=Windows Azure Linux Agent
+After=network.target
+After=sshd.service
+ConditionFileIsExecutable=/usr/sbin/waagent
+ConditionPathExists=/etc/waagent.conf
+
+[Service]
+Type=simple
+ExecStart=/usr/sbin/waagent -daemon
+
+[Install]
+WantedBy=multi-user.target
+"""
+
+class fedoraDistro(redhatDistro):
+ """
+ FedoraDistro concrete class
+ Put Fedora specific behavior here...
+ """
+ def __init__(self):
+ super(fedoraDistro,self).__init__()
+ self.service_cmd = '/usr/bin/systemctl'
+ self.hostname_file_path = '/etc/hostname'
+ self.init_script_file = '/usr/lib/systemd/system/' + self.agent_service_name + '.service'
+ self.init_file = fedora_systemd_service
+ self.grubKernelBootOptionsFile = '/etc/default/grub'
+ self.grubKernelBootOptionsLine = 'GRUB_CMDLINE_LINUX='
+
+ def publishHostname(self, name):
+ SetFileContents(self.hostname_file_path, name + '\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, 0644)
+ return Run(self.service_cmd + ' daemon-reload')
+
+ def registerAgentService(self):
+ self.installAgentServiceScriptFiles()
+ return Run(self.service_cmd + ' enable ' + self.agent_service_name)
+
+ def uninstallAgentService(self):
+ """
+ Call service subsystem to remove waagent script.
+ """
+ return Run(self.service_cmd + ' disable ' + self.agent_service_name)
+
+ def unregisterAgentService(self):
+ """
+ Calls self.stopAgentService and call self.uninstallAgentService()
+ """
+ self.stopAgentService()
+ self.uninstallAgentService()
+
+ def startAgentService(self):
+ """
+ Service call to start the Agent service
+ """
+ return Run(self.service_cmd + ' start ' + self.agent_service_name)
+
+ def stopAgentService(self):
+ """
+ Service call to stop the Agent service
+ """
+ return Run(self.service_cmd + ' stop ' + self.agent_service_name, False)
+
+ def restartSshService(self):
+ """
+ Service call to re(start) the SSH service
+ """
+ sshRestartCmd = self.service_cmd + " " + self.ssh_service_restart_option + " " + self.ssh_service_name
+ retcode = Run(sshRestartCmd)
+ if retcode > 0:
+ Error("Failed to restart SSH service with return code:" + str(retcode))
+ return retcode
+
+ def checkPackageInstalled(self, p):
+ """
+ Query package database for prescence of an installed package.
+ """
+ import rpm
+ ts = rpm.TransactionSet()
+ rpms = ts.dbMatch(rpm.RPMTAG_PROVIDES, p)
+ return bool(len(rpms) > 0)
+
+ def deleteRootPassword(self):
+ return Run("/sbin/usermod root -p '!!'")
+
+ def packagedInstall(self,buildroot):
+ """
+ Called from setup.py for use by RPM.
+ Copies generated files waagent.conf, under the buildroot.
+ """
+ if not os.path.exists(buildroot+'/etc'):
+ os.mkdir(buildroot+'/etc')
+ SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
+
+ if not os.path.exists(buildroot+'/etc/logrotate.d'):
+ os.mkdir(buildroot+'/etc/logrotate.d')
+ SetFileContents(buildroot+'/etc/logrotate.d/WALinuxAgent', WaagentLogrotate)
+
+ self.init_script_file=buildroot+self.init_script_file
+ # this allows us to call installAgentServiceScriptFiles()
+ if not os.path.exists(os.path.dirname(self.init_script_file)):
+ os.mkdir(os.path.dirname(self.init_script_file))
+ self.installAgentServiceScriptFiles()
+
+ def CreateAccount(self, user, password, expiration, thumbprint):
+ super(fedoraDistro, self).CreateAccount(user, password, expiration, thumbprint)
+ Run('/sbin/usermod ' + user + ' -G wheel')
+
+ def DeleteAccount(self, user):
+ Run('/sbin/usermod ' + user + ' -G ""')
+ super(fedoraDistro, self).DeleteAccount(user)
+
+############################################################
+# FreeBSD
+############################################################
+FreeBSDWaagentConf = """\
+#
+# Windows Azure Linux Agent Configuration
+#
+
+Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status
+ # to the endpoint server.
+Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
+Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
+
+Provisioning.Enabled=y #
+Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
+Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
+Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
+Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
+
+ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
+ResourceDisk.Filesystem=ufs2 #
+ResourceDisk.MountPoint=/mnt/resource #
+ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
+ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
+
+LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
+
+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.
+"""
+
+bsd_init_file="""\
+#! /bin/sh
+
+# PROVIDE: waagent
+# REQUIRE: DAEMON cleanvar sshd
+# BEFORE: LOGIN
+# KEYWORD: nojail
+
+. /etc/rc.subr
+export PATH=$PATH:/usr/local/bin
+name="waagent"
+rcvar="waagent_enable"
+command="/usr/sbin/${name}"
+command_interpreter="/usr/local/bin/python"
+waagent_flags=" daemon &"
+
+pidfile="/var/run/waagent.pid"
+
+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)
+waagent.SetFileContents(os.path.join(mountpoint,waagent.README_FILENAME), waagent.README_FILECONTENT)
+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):
+ """
+ """
+ def __init__(self):
+ """
+ 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'
+ self.ssh_config_file='/etc/ssh/sshd_config'
+ self.hostname_file_path='/etc/hostname'
+ self.dhcp_client_name='dhclient'
+ self.requiredDeps = [ 'route', 'shutdown', 'ssh-keygen', 'pw'
+ , 'openssl', 'fdisk', 'sed', 'grep' , 'sudo']
+ self.init_script_file='/etc/rc.d/waagent'
+ self.init_file=bsd_init_file
+ self.agent_package_name='WALinuxAgent'
+ self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log",'/etc/resolv.conf' ]
+ self.agent_files_to_uninstall = ["/etc/waagent.conf"]
+ self.grubKernelBootOptionsFile = '/boot/loader.conf'
+ self.grubKernelBootOptionsLine = ''
+ self.getpidcmd = 'pgrep -n'
+ 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
+
+ def installAgentServiceScriptFiles(self):
+ SetFileContents(self.init_script_file, self.init_file)
+ os.chmod(self.init_script_file, 0777)
+ AppendFileContents("/etc/rc.conf","waagent_enable='YES'\n")
+ return 0
+
+ def registerAgentService(self):
+ self.installAgentServiceScriptFiles()
+ return Run("services_mkdb " + self.init_script_file)
+
+
+ def sshDeployPublicKey(self,fprint,path):
+ """
+ We support PKCS8.
+ """
+ if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
+ return 1
+ else :
+ return 0
+
+ def deleteRootPassword(self):
+ """
+ BSD root password removal.
+ """
+ filepath="/etc/master.passwd"
+ ReplaceStringInFile(filepath,r'root:.*?:','root::')
+ #ReplaceFileContentsAtomic(filepath,"root:*LOCK*:14600::::::\n"
+ # + "\n".join(filter(lambda a: not a.startswith("root:"),GetFileContents(filepath).split('\n'))))
+ os.chmod(filepath,self.shadow_file_mode)
+ if self.isSelinuxSystem():
+ self.setSelinuxContext(filepath,'system_u:object_r:shadow_t:s0')
+ RunGetOutput("pwd_mkdb -u root /etc/master.passwd")
+ Log("Root password deleted.")
+ return 0
+
+ def changePass(self,user,password):
+ return RunSendStdin("pw usermod " + user + " -h 0 ",password)
+
+ def load_ata_piix(self):
+ return 0
+
+ def unload_ata_piix(self):
+ return 0
+
+ def checkDependencies(self):
+ """
+ FreeBSD dependency check.
+ Return 1 unless all dependencies are satisfied.
+ """
+ for a in self.requiredDeps:
+ if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
+ Error("Missing required dependency: " + a)
+ return 1
+ return 0
+
+ def packagedInstall(self,buildroot):
+ pass
+
+ def GetInterfaceName(self):
+ """
+ Return the ip of the
+ active ethernet interface.
+ """
+ iface,inet,mac=self.GetFreeBSDEthernetInfo()
+ return iface
+
+ def RestartInterface(self, iface):
+ Run("service netif restart")
+
+ def GetIpv4Address(self):
+ """
+ Return the ip of the
+ active ethernet interface.
+ """
+ iface,inet,mac=self.GetFreeBSDEthernetInfo()
+ return inet
+
+ def GetMacAddress(self):
+ """
+ Return the ip of the
+ active ethernet interface.
+ """
+ iface,inet,mac=self.GetFreeBSDEthernetInfo()
+ l=mac.split(':')
+ r=[]
+ for i in l:
+ r.append(string.atoi(i,16))
+ return r
+
+ def GetFreeBSDEthernetInfo(self):
+ """
+ There is no SIOCGIFCONF
+ on freeBSD - just parse ifconfig.
+ Returns strings: iface, inet4_addr, and mac
+ or 'None,None,None' if unable to parse.
+ We will sleep and retry as the network must be up.
+ """
+ code,output=RunGetOutput("ifconfig",chk_err=False)
+ Log(output)
+ retries=10
+ cmd='ifconfig | grep -A2 -B2 ether | grep -B3 inet | grep -A4 UP '
+ code=1
+
+ while code > 0 :
+ if code > 0 and retries == 0:
+ Error("GetFreeBSDEthernetInfo - Failed to detect ethernet interface")
+ return None, None, None
+ code,output=RunGetOutput(cmd,chk_err=False)
+ 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',' ')
+ j=j.split()
+ iface=j[0][:-1]
+
+ for i in range(len(j)):
+ if j[i] == 'inet' :
+ inet=j[i+1]
+ elif j[i] == 'ether' :
+ mac=j[i+1]
+
+ return iface, inet, mac
+
+ def CreateAccount(self,user, password, expiration, thumbprint):
+ """
+ Create a user account, with 'user', 'password', 'expiration', ssh keys
+ and sudo permissions.
+ Returns None if successful, error string on failure.
+ """
+ userentry = None
+ try:
+ userentry = pwd.getpwnam(user)
+ except:
+ pass
+ uidmin = None
+ try:
+ if os.path.isfile("/etc/login.defs"):
+ uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
+ except:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry != None and userentry[2] < uidmin:
+ Error("CreateAccount: " + user + " is a system user. Will not set password.")
+ return "Failed to set password for system user: " + user + " (0x06)."
+ if userentry == None:
+ command = "pw useradd " + user + " -m"
+ if expiration != None:
+ command += " -e " + expiration.split('.')[0]
+ if Run(command):
+ Error("Failed to create user account: " + user)
+ return "Failed to create user account: " + user + " (0x07)."
+ else:
+ Log("CreateAccount: " + user + " already exists. Will update password.")
+
+ if password != None:
+ self.changePass(user,password)
+ try:
+ # for older distros create sudoers.d
+ if not os.path.isdir(MyDistro.sudoers_dir_base+'/sudoers.d/'):
+ # create the /etc/sudoers.d/ directory
+ os.mkdir(MyDistro.sudoers_dir_base+'/sudoers.d')
+ # add the include of sudoers.d to the /etc/sudoers
+ SetFileContents(MyDistro.sudoers_dir_base+'/sudoers',GetFileContents(MyDistro.sudoers_dir_base+'/sudoers')+'\n#includedir ' + MyDistro.sudoers_dir_base + '/sudoers.d\n')
+ if password == None:
+ SetFileContents(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
+ else:
+ SetFileContents(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
+ os.chmod(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", 0440)
+ except:
+ Error("CreateAccount: Failed to configure sudo access for user.")
+ return "Failed to configure sudo privileges (0x08)."
+ home = MyDistro.GetHome()
+ if thumbprint != None:
+ dir = home + "/" + user + "/.ssh"
+ CreateDir(dir, user, 0700)
+ pub = dir + "/id_rsa.pub"
+ prv = dir + "/id_rsa"
+ Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
+ SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
+ for f in [pub, prv]:
+ os.chmod(f, 0600)
+ ChangeOwner(f, user)
+ SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
+ ChangeOwner(dir + "/authorized_keys", user)
+ Log("Created user account: " + user)
+ return None
+
+ def DeleteAccount(self,user):
+ """
+ Delete the 'user'.
+ Clear utmp first, to avoid error.
+ Removes the /etc/sudoers.d/waagent file.
+ """
+ userentry = None
+ try:
+ userentry = pwd.getpwnam(user)
+ except:
+ pass
+ if userentry == None:
+ Error("DeleteAccount: " + user + " not found.")
+ return
+ uidmin = None
+ try:
+ if os.path.isfile("/etc/login.defs"):
+ uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
+ except:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry[2] < uidmin:
+ Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
+ return
+ Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
+ pid = subprocess.Popen(['rmuser', '-y', user], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE).pid
+ try:
+ os.remove(MyDistro.sudoers_dir_base+"/sudoers.d/waagent")
+ except:
+ pass
+ return
+
+ def ActivateResourceDiskNoThread(self):
+ """
+ Format, mount, and if specified in the configuration
+ set resource disk as swap.
+ """
+ global DiskActivated
+ 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
+ return
+
+ def Install(self):
+ """
+ Install the agent service.
+ Check dependencies.
+ Create /etc/waagent.conf and move old version to
+ /etc/waagent.conf.old
+ Copy RulesFiles to /var/lib/waagent
+ Create /etc/logrotate.d/waagent
+ Set /etc/ssh/sshd_config ClientAliveInterval to 180
+ Call ApplyVNUMAWorkaround()
+ """
+ if MyDistro.checkDependencies():
+ return 1
+ os.chmod(sys.argv[0], 0755)
+ SwitchCwd()
+ for a in RulesFiles:
+ if os.path.isfile(a):
+ if os.path.isfile(GetLastPathElement(a)):
+ os.remove(GetLastPathElement(a))
+ shutil.move(a, ".")
+ Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
+ MyDistro.registerAgentService()
+ if os.path.isfile("/etc/waagent.conf"):
+ try:
+ os.remove("/etc/waagent.conf.old")
+ except:
+ pass
+ try:
+ os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
+ Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
+ except:
+ pass
+ SetFileContents("/etc/waagent.conf", self.waagent_conf_file)
+ if os.path.exists('/usr/local/etc/logrotate.d/'):
+ SetFileContents("/usr/local/etc/logrotate.d/waagent", WaagentLogrotate)
+ filepath = "/etc/ssh/sshd_config"
+ ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
+ a.startswith("ClientAliveInterval"),
+ GetFileContents(filepath).split('\n'))) + "\nClientAliveInterval 180\n")
+ Log("Configured SSH client probing to keep connections alive.")
+ #ApplyVNUMAWorkaround()
+ return 0
+
+ def mediaHasFilesystem(self,dsk):
+ if Run('LC_ALL=C fdisk -p ' + dsk + ' | grep "invalid fdisk partition table found" ',False):
+ return False
+ return True
+
+ 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
+ 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(r'</Environment>.*\Z','',ovfxml,0,re.DOTALL)
+ ovfxml += '</Environment>'
+ 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
+
+ def getProcessorCores(self):
+ return int(RunGetOutput("sysctl hw.ncpu | awk '{print $2}'")[1])
+
+ def getTotalMemory(self):
+ return int(RunGetOutput("sysctl hw.realmem | awk '{print $2}'")[1])/1024
+
+############################################################
+# END DISTRO CLASS DEFS
+############################################################
+
+# This lets us index into a string or an array of integers transparently.
+def Ord(a):
+ """
+ Allows indexing into a string or an array of integers transparently.
+ Generic utility function.
+ """
+ if type(a) == type("a"):
+ a = ord(a)
+ return a
+
+def IsLinux():
+ """
+ Returns True if platform is Linux.
+ Generic utility function.
+ """
+ return (platform.uname()[0] == "Linux")
+
+def GetLastPathElement(path):
+ """
+ Similar to basename.
+ Generic utility function.
+ """
+ return path.rsplit('/', 1)[1]
+
+def GetFileContents(filepath,asbin=False):
+ """
+ Read and return contents of 'filepath'.
+ """
+ mode='r'
+ if asbin:
+ mode+='b'
+ c=None
+ try:
+ with open(filepath, mode) as F :
+ c=F.read()
+ except IOError, e:
+ ErrorWithPrefix('GetFileContents','Reading from file ' + filepath + ' Exception is ' + str(e))
+ return None
+ return c
+
+def SetFileContents(filepath, contents):
+ """
+ Write 'contents' to 'filepath'.
+ """
+ if type(contents) == str :
+ contents=contents.encode('latin-1', 'ignore')
+ try:
+ with open(filepath, "wb+") as F :
+ F.write(contents)
+ except IOError, e:
+ ErrorWithPrefix('SetFileContents','Writing to file ' + filepath + ' Exception is ' + str(e))
+ return None
+ return 0
+
+def AppendFileContents(filepath, contents):
+ """
+ Append 'contents' to 'filepath'.
+ """
+ if type(contents) == str :
+ contents=contents.encode('latin-1')
+ try:
+ with open(filepath, "a+") as F :
+ F.write(contents)
+ except IOError, e:
+ ErrorWithPrefix('AppendFileContents','Appending to file ' + filepath + ' Exception is ' + str(e))
+ return None
+ return 0
+
+def ReplaceFileContentsAtomic(filepath, contents):
+ """
+ Write 'contents' to 'filepath' by creating a temp file, and replacing original.
+ """
+ handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
+ if type(contents) == str :
+ contents=contents.encode('latin-1')
+ try:
+ os.write(handle, contents)
+ except IOError, e:
+ ErrorWithPrefix('ReplaceFileContentsAtomic','Writing to file ' + filepath + ' Exception is ' + str(e))
+ return None
+ finally:
+ os.close(handle)
+ try:
+ os.rename(temp, filepath)
+ return None
+ except IOError, e:
+ ErrorWithPrefix('ReplaceFileContentsAtomic','Renaming ' + temp+ ' to ' + filepath + ' Exception is ' + str(e))
+ try:
+ os.remove(filepath)
+ except IOError, e:
+ ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
+ try:
+ os.rename(temp,filepath)
+ except IOError, e:
+ ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
+ return 1
+ return 0
+
+def GetLineStartingWith(prefix, filepath):
+ """
+ Return line from 'filepath' if the line startswith 'prefix'
+ """
+ for line in GetFileContents(filepath).split('\n'):
+ if line.startswith(prefix):
+ return line
+ return None
+
+def Run(cmd,chk_err=True):
+ """
+ Calls RunGetOutput on 'cmd', returning only the return code.
+ If chk_err=True then errors will be reported in the log.
+ If chk_err=False then errors will be suppressed from the log.
+ """
+ retcode,out=RunGetOutput(cmd,chk_err)
+ return retcode
+
+def RunGetOutput(cmd,chk_err=True):
+ """
+ Wrapper for subprocess.check_output.
+ Execute 'cmd'. Returns return code and STDOUT, trapping expected exceptions.
+ Reports exceptions to Error if chk_err parameter is True
+ """
+ LogIfVerbose(cmd)
+ try:
+ output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
+ except subprocess.CalledProcessError,e :
+ if chk_err :
+ Error('CalledProcessError. Error Code is ' + str(e.returncode) )
+ Error('CalledProcessError. Command string was ' + e.cmd )
+ Error('CalledProcessError. Command result was ' + (e.output[:-1]).decode('latin-1'))
+ return e.returncode,e.output.decode('latin-1')
+ return 0,output.decode('latin-1')
+
+def RunSendStdin(cmd,input,chk_err=True):
+ """
+ Wrapper for subprocess.Popen.
+ Execute 'cmd', sending 'input' to STDIN of 'cmd'.
+ Returns return code and STDOUT, trapping expected exceptions.
+ Reports exceptions to Error if chk_err parameter is True
+ """
+ LogIfVerbose(cmd+input)
+ try:
+ me=subprocess.Popen([cmd], shell=True, stdin=subprocess.PIPE,stderr=subprocess.STDOUT,stdout=subprocess.PIPE)
+ output=me.communicate(input)
+ except OSError , e :
+ if chk_err :
+ Error('CalledProcessError. Error Code is ' + str(me.returncode) )
+ Error('CalledProcessError. Command string was ' + cmd )
+ Error('CalledProcessError. Command result was ' + output[0].decode('latin-1'))
+ return 1,output[0].decode('latin-1')
+ if me.returncode is not 0 and chk_err is True:
+ Error('CalledProcessError. Error Code is ' + str(me.returncode) )
+ Error('CalledProcessError. Command string was ' + cmd )
+ Error('CalledProcessError. Command result was ' + output[0].decode('latin-1'))
+ return me.returncode,output[0].decode('latin-1')
+
+def GetNodeTextData(a):
+ """
+ Filter non-text nodes from DOM tree
+ """
+ for b in a.childNodes:
+ if b.nodeType == b.TEXT_NODE:
+ return b.data
+
+def GetHome():
+ """
+ Attempt to guess the $HOME location.
+ Return the path string.
+ """
+ home = None
+ try:
+ home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
+ except:
+ pass
+ if (home == None) or (home.startswith("/") == False):
+ home = "/home"
+ return home
+
+def ChangeOwner(filepath, user):
+ """
+ Lookup user. Attempt chown 'filepath' to 'user'.
+ """
+ p = None
+ try:
+ p = pwd.getpwnam(user)
+ except:
+ pass
+ if p != None:
+ os.chown(filepath, p[2], p[3])
+
+def CreateDir(dirpath, user, mode):
+ """
+ Attempt os.makedirs, catch all exceptions.
+ Call ChangeOwner afterwards.
+ """
+ try:
+ os.makedirs(dirpath, mode)
+ except:
+ pass
+ ChangeOwner(dirpath, user)
+
+def CreateAccount(user, password, expiration, thumbprint):
+ """
+ Create a user account, with 'user', 'password', 'expiration', ssh keys
+ and sudo permissions.
+ Returns None if successful, error string on failure.
+ """
+ userentry = None
+ try:
+ userentry = pwd.getpwnam(user)
+ except:
+ pass
+ uidmin = None
+ try:
+ uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
+ except:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry != None and userentry[2] < uidmin:
+ Error("CreateAccount: " + user + " is a system user. Will not set password.")
+ return "Failed to set password for system user: " + user + " (0x06)."
+ if userentry == None:
+ command = "useradd -m " + user
+ if expiration != None:
+ command += " -e " + expiration.split('.')[0]
+ if Run(command):
+ Error("Failed to create user account: " + user)
+ return "Failed to create user account: " + user + " (0x07)."
+ else:
+ Log("CreateAccount: " + user + " already exists. Will update password.")
+ if password != None:
+ RunSendStdin("chpasswd",(user + ":" + password + "\n"))
+ try:
+ # for older distros create sudoers.d
+ if not os.path.isdir('/etc/sudoers.d/'):
+ # create the /etc/sudoers.d/ directory
+ os.mkdir('/etc/sudoers.d/')
+ # add the include of sudoers.d to the /etc/sudoers
+ SetFileContents('/etc/sudoers',GetFileContents('/etc/sudoers')+'\n#includedir /etc/sudoers.d\n')
+ if password == None:
+ SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
+ else:
+ SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
+ os.chmod("/etc/sudoers.d/waagent", 0440)
+ except:
+ Error("CreateAccount: Failed to configure sudo access for user.")
+ return "Failed to configure sudo privileges (0x08)."
+ home = MyDistro.GetHome()
+ if thumbprint != None:
+ dir = home + "/" + user + "/.ssh"
+ CreateDir(dir, user, 0700)
+ pub = dir + "/id_rsa.pub"
+ prv = dir + "/id_rsa"
+ Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
+ SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
+ for f in [pub, prv]:
+ os.chmod(f, 0600)
+ ChangeOwner(f, user)
+ SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
+ ChangeOwner(dir + "/authorized_keys", user)
+ Log("Created user account: " + user)
+ return None
+
+def DeleteAccount(user):
+ """
+ Delete the 'user'.
+ Clear utmp first, to avoid error.
+ Removes the /etc/sudoers.d/waagent file.
+ """
+ userentry = None
+ try:
+ userentry = pwd.getpwnam(user)
+ except:
+ pass
+ if userentry == None:
+ Error("DeleteAccount: " + user + " not found.")
+ return
+ uidmin = None
+ try:
+ uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
+ except:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry[2] < uidmin:
+ Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
+ return
+ Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
+ Run("userdel -f -r " + user)
+ try:
+ os.remove("/etc/sudoers.d/waagent")
+ except:
+ pass
+ return
+
+def IsInRangeInclusive(a, low, high):
+ """
+ Return True if 'a' in 'low' <= a >= 'high'
+ """
+ return (a >= low and a <= high)
+
+def IsPrintable(ch):
+ """
+ Return True if character is displayable.
+ """
+ return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
+
+def HexDump(buffer, size):
+ """
+ Return Hex formated dump of a 'buffer' of 'size'.
+ """
+ if size < 0:
+ size = len(buffer)
+ result = ""
+ for i in range(0, size):
+ if (i % 16) == 0:
+ result += "%06X: " % i
+ byte = buffer[i]
+ if type(byte) == str:
+ byte = ord(byte.decode('latin1'))
+ result += "%02X " % byte
+ if (i & 15) == 7:
+ result += " "
+ if ((i + 1) % 16) == 0 or (i + 1) == size:
+ j = i
+ while ((j + 1) % 16) != 0:
+ result += " "
+ if (j & 7) == 7:
+ result += " "
+ j += 1
+ result += " "
+ for j in range(i - (i % 16), i + 1):
+ byte=buffer[j]
+ if type(byte) == str:
+ byte = ord(byte.decode('latin1'))
+ k = '.'
+ if IsPrintable(byte):
+ k = chr(byte)
+ result += k
+ if (i + 1) != 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 :
+ lines = filter(lambda x : x in string.printable, lines)
+ F.write(lines.encode('ascii','ignore') + "\n")
+
+class Logger(object):
+ """
+ The Agent's logging assumptions are:
+ For Log, and LogWithPrefix all messages are logged to the
+ self.file_path and to the self.con_path. Setting either path
+ parameter to None skips that log. If Verbose is enabled, messages
+ calling the LogIfVerbose method will be logged to file_path yet
+ not to con_path. Error and Warn messages are normal log messages
+ with the 'ERROR:' or 'WARNING:' prefix added.
+ """
+
+ def __init__(self,filepath,conpath,verbose=False):
+ """
+ Construct an instance of Logger.
+ """
+ self.file_path=filepath
+ self.con_path=conpath
+ self.verbose=verbose
+
+ def ThrottleLog(self,counter):
+ """
+ Log everything up to 10, every 10 up to 100, then every 100.
+ """
+ return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
+
+ def LogToFile(self,message):
+ """
+ Write 'message' to logfile.
+ """
+ if self.file_path:
+ try:
+ with open(self.file_path, "a") as F :
+ message = filter(lambda x : x in string.printable, message)
+ F.write(message.encode('ascii','ignore') + "\n")
+ except IOError, e:
+ print e
+ pass
+
+ 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:
+ try:
+ with open(self.con_path, "w") as C :
+ message = filter(lambda x : x in string.printable, message)
+ C.write(message.encode('ascii','ignore') + "\n")
+ except IOError, e:
+ print e
+ pass
+
+ def Log(self,message):
+ """
+ Standard Log function.
+ Logs to self.file_path, and con_path
+ """
+ self.LogWithPrefix("", message)
+
+ def LogWithPrefix(self,prefix, message):
+ """
+ Prefix each line of 'message' with current time+'prefix'.
+ """
+ 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)
+ t += prefix
+ for line in message.split('\n'):
+ line = t + line
+ self.LogToFile(line)
+ self.LogToCon(line)
+
+ def NoLog(self,message):
+ """
+ Don't Log.
+ """
+ pass
+
+ def LogIfVerbose(self,message):
+ """
+ Only log 'message' if global Verbose is True.
+ """
+ self.LogWithPrefixIfVerbose('',message)
+
+ def LogWithPrefixIfVerbose(self,prefix, message):
+ """
+ Only log 'message' if global Verbose is True.
+ Prefix each line of 'message' with current time+'prefix'.
+ """
+ if self.verbose == True:
+ 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)
+ t += prefix
+ 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'.
+ """
+ self.LogWithPrefix("WARNING:", message)
+
+ def Error(self,message):
+ """
+ Call ErrorWithPrefix(message).
+ """
+ ErrorWithPrefix("", message)
+
+ def ErrorWithPrefix(self,prefix, message):
+ """
+ Prepend the text "ERROR:" to the prefix for each line in 'message'.
+ Errors written to logfile, and /dev/console
+ """
+ self.LogWithPrefix("ERROR:", message)
+
+def LoggerInit(log_file_path,log_con_path,verbose=False):
+ """
+ Create log object and export its methods to global scope.
+ """
+ global Log,LogWithPrefix,LogIfVerbose,LogWithPrefixIfVerbose,Error,ErrorWithPrefix,Warn,NoLog,ThrottleLog,myLogger
+ l=Logger(log_file_path,log_con_path,verbose)
+ Log,LogWithPrefix,LogIfVerbose,LogWithPrefixIfVerbose,Error,ErrorWithPrefix,Warn,NoLog,ThrottleLog,myLogger = l.Log,l.LogWithPrefix,l.LogIfVerbose,l.LogWithPrefixIfVerbose,l.Error,l.ErrorWithPrefix,l.Warn,l.NoLog,l.ThrottleLog,l
+
+def Linux_ioctl_GetInterfaceMac(ifname):
+ """
+ Return the mac-address bound to the socket.
+ """
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', (ifname[:15]+('\0'*241)).encode('latin-1')))
+ return ''.join(['%02X' % Ord(char) for char in info[18:24]])
+
+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...
+ is_64bits = sys.maxsize > 2**32
+ struct_size=40 if is_64bits else 32 # for 64bit the size is 40 bytes, for 32bits it is 32 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(), 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.')
+ s=buff.tostring()
+ preferred_nic = Config.get("Network.Interface")
+ for i in range(0,struct_size*expected,struct_size):
+ iface=s[i:i+16].split(b'\0', 1)[0]
+ if iface == b'lo':
+ continue
+ elif preferred_nic is None:
+ break
+ elif iface == preferred_nic:
+ break
+ return iface.decode('latin-1'), socket.inet_ntoa(s[i+20:i+24])
+
+def GetIpv4Address():
+ """
+ Return the ip of the
+ first active non-loopback interface.
+ """
+ iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
+ return addr
+
+def HexStringToByteArray(a):
+ """
+ Return hex string packed into a binary struct.
+ """
+ b = b""
+ for c in range(0, len(a) // 2):
+ b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
+ return b
+
+def GetMacAddress():
+ """
+ Convienience function, returns mac addr bound to
+ first non-loobback interface.
+ """
+ ifname=''
+ while len(ifname) < 2 :
+ ifname=GetFirstActiveNetworkInterfaceNonLoopback()[0]
+ a = Linux_ioctl_GetInterfaceMac(ifname)
+ return HexStringToByteArray(a)
+
+def DeviceForIdePort(n):
+ """
+ Return device name attached to ide port 'n'.
+ """
+ if n > 3:
+ return None
+ g0 = "00000000"
+ if n > 1:
+ g0 = "00000001"
+ n = n - 2
+ device = None
+ path = "/sys/bus/vmbus/devices/"
+ for vmbus in os.listdir(path):
+ guid = GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
+ if guid[0] == g0 and guid[1] == "000" + str(n):
+ for root, dirs, files in os.walk(path + vmbus):
+ if root.endswith("/block"):
+ device = dirs[0]
+ break
+ else : #older distros
+ for d in dirs:
+ if ':' in d and "block" == d.split(':')[0]:
+ device = d.split(':')[1]
+ break
+ break
+ return device
+
+class HttpResourceGoneError(Exception):
+ pass
+
+class Util(object):
+ """
+ Http communication class.
+ Base of GoalState, and Agent classes.
+ """
+ RetryWaitingInterval=10
+
+ def __init__(self):
+ self.Endpoint = None
+
+ def _ParseUrl(self, url):
+ secure = False
+ host = self.Endpoint
+ path = url
+ port = None
+
+ #"http[s]://hostname[:port][/]"
+ if url.startswith("http://"):
+ url = url[7:]
+ if "/" in url:
+ host = url[0: url.index("/")]
+ path = url[url.index("/"):]
+ else:
+ host = url
+ path = "/"
+ elif url.startswith("https://"):
+ secure = True
+ url = url[8:]
+ if "/" in url:
+ host = url[0: url.index("/")]
+ path = url[url.index("/"):]
+ else:
+ host = url
+ path = "/"
+
+ if host is None:
+ raise ValueError("Host is invalid:{0}".format(url))
+
+ if(":" in host):
+ pos = host.rfind(":")
+ port = int(host[pos + 1:])
+ host = host[0:pos]
+
+ return host, port, secure, path
+
+ def GetHttpProxy(self, secure):
+ """
+ Get http_proxy and https_proxy from environment variables.
+ Username and password is not supported now.
+ """
+ host = Config.get("HttpProxy.Host")
+ port = Config.get("HttpProxy.Port")
+ return (host, port)
+
+ def _HttpRequest(self, method, host, path, port=None, data=None, secure=False,
+ headers=None, proxyHost=None, proxyPort=None):
+ resp = None
+ conn = None
+ try:
+ if secure:
+ port = 443 if port is None else port
+ if proxyHost is not None and proxyPort is not None:
+ conn = httplib.HTTPSConnection(proxyHost, proxyPort)
+ conn.set_tunnel(host, port)
+ #If proxy is used, full url is needed.
+ path = "https://{0}:{1}{2}".format(host, port, path)
+ else:
+ conn = httplib.HTTPSConnection(host, port)
+ else:
+ port = 80 if port is None else port
+ if proxyHost is not None and proxyPort is not None:
+ conn = httplib.HTTPConnection(proxyHost, proxyPort)
+ #If proxy is used, full url is needed.
+ path = "http://{0}:{1}{2}".format(host, port, path)
+ else:
+ conn = httplib.HTTPConnection(host, port)
+ if headers == None:
+ conn.request(method, path, data)
+ else:
+ conn.request(method, path, data, headers)
+ resp = conn.getresponse()
+ except httplib.HTTPException, e:
+ Error('HTTPException {0}, args:{1}'.format(e, repr(e.args)))
+ except IOError, e:
+ Error('Socket IOError {0}, args:{1}'.format(e, repr(e.args)))
+ return resp
+
+ def HttpRequest(self, method, url, data=None,
+ headers=None, maxRetry=3, chkProxy=False):
+ """
+ Sending http request to server
+ On error, sleep 10 and maxRetry times.
+ Return the output buffer or None.
+ """
+ LogIfVerbose("HTTP Req: {0} {1}".format(method, url))
+ LogIfVerbose("HTTP Req: Data={0}".format(data))
+ LogIfVerbose("HTTP Req: Header={0}".format(headers))
+ try:
+ host, port, secure, path = self._ParseUrl(url)
+ except ValueError, e:
+ Error("Failed to parse url:{0}".format(url))
+ return None
+
+ #Check proxy
+ proxyHost, proxyPort = (None, None)
+ if chkProxy:
+ proxyHost, proxyPort = self.GetHttpProxy(secure)
+
+ #If httplib module is not built with ssl support. Fallback to http
+ if secure and not hasattr(httplib, "HTTPSConnection"):
+ Warn("httplib is not built with ssl support")
+ secure = False
+ proxyHost, proxyPort = self.GetHttpProxy(secure)
+
+ #If httplib module doesn't support https tunnelling. Fallback to http
+ if secure and \
+ proxyHost is not None and \
+ proxyPort is not None and \
+ not hasattr(httplib.HTTPSConnection, "set_tunnel"):
+ Warn("httplib doesn't support https tunnelling(new in python 2.7)")
+ secure = False
+ proxyHost, proxyPort = self.GetHttpProxy(secure)
+
+ resp = self._HttpRequest(method, host, path, port=port, data=data,
+ secure=secure, headers=headers,
+ proxyHost=proxyHost, proxyPort=proxyPort)
+ for retry in range(0, maxRetry):
+ if resp is not None and \
+ (resp.status == httplib.OK or \
+ resp.status == httplib.CREATED or \
+ resp.status == httplib.ACCEPTED):
+ return resp;
+
+ if resp is not None and resp.status == httplib.GONE:
+ raise HttpResourceGoneError("Http resource gone.")
+
+ Error("Retry={0}".format(retry))
+ Error("HTTP Req: {0} {1}".format(method, url))
+ Error("HTTP Req: Data={0}".format(data))
+ Error("HTTP Req: Header={0}".format(headers))
+ if resp is None:
+ Error("HTTP Err: response is empty.".format(retry))
+ else:
+ Error("HTTP Err: Status={0}".format(resp.status))
+ Error("HTTP Err: Reason={0}".format(resp.reason))
+ Error("HTTP Err: Header={0}".format(resp.getheaders()))
+ Error("HTTP Err: Body={0}".format(resp.read()))
+
+ time.sleep(self.__class__.RetryWaitingInterval)
+ resp = self._HttpRequest(method, host, path, port=port, data=data,
+ secure=secure, headers=headers,
+ proxyHost=proxyHost, proxyPort=proxyPort)
+
+ return None
+
+ def HttpGet(self, url, headers=None, maxRetry=3, chkProxy=False):
+ return self.HttpRequest("GET", url, headers=headers,
+ maxRetry=maxRetry, chkProxy=chkProxy)
+
+ def HttpHead(self, url, headers=None, maxRetry=3, chkProxy=False):
+ return self.HttpRequest("HEAD", url, headers=headers,
+ maxRetry=maxRetry, chkProxy=chkProxy)
+
+ def HttpPost(self, url, data, headers=None, maxRetry=3, chkProxy=False):
+ return self.HttpRequest("POST", url, data=data, headers=headers,
+ maxRetry=maxRetry, chkProxy=chkProxy)
+
+ def HttpPut(self, url, data, headers=None, maxRetry=3, chkProxy=False):
+ return self.HttpRequest("PUT", url, data=data, headers=headers,
+ maxRetry=maxRetry, chkProxy=chkProxy)
+
+ def HttpDelete(self, url, headers=None, maxRetry=3, chkProxy=False):
+ return self.HttpRequest("DELETE", url, headers=headers,
+ maxRetry=maxRetry, chkProxy=chkProxy)
+
+ def HttpGetWithoutHeaders(self, url, maxRetry=3, chkProxy=False):
+ """
+ Return data from an HTTP get on 'url'.
+ """
+ resp = self.HttpGet(url, headers=None, maxRetry=maxRetry,
+ chkProxy=chkProxy)
+ return resp.read() if resp is not None else None
+
+ def HttpGetWithHeaders(self, url, maxRetry=3, chkProxy=False):
+ """
+ Return data from an HTTP get on 'url' with
+ x-ms-agent-name and x-ms-version
+ headers.
+ """
+ resp = self.HttpGet(url, headers={
+ "x-ms-agent-name": GuestAgentName,
+ "x-ms-version": ProtocolVersion
+ }, maxRetry=maxRetry, chkProxy=chkProxy)
+ return resp.read() if resp is not None else None
+
+ def HttpSecureGetWithHeaders(self, url, transportCert, maxRetry=3,
+ chkProxy=False):
+ """
+ Return output of get using ssl cert.
+ """
+ resp = self.HttpGet(url, headers={
+ "x-ms-agent-name": GuestAgentName,
+ "x-ms-version": ProtocolVersion,
+ "x-ms-cipher-name": "DES_EDE3_CBC",
+ "x-ms-guest-agent-public-x509-cert": transportCert
+ }, maxRetry=maxRetry, chkProxy=chkProxy)
+ return resp.read() if resp is not None else None
+
+ def HttpPostWithHeaders(self, url, data, maxRetry=3, chkProxy=False):
+ headers = {
+ "x-ms-agent-name": GuestAgentName,
+ "Content-Type": "text/xml; charset=utf-8",
+ "x-ms-version": ProtocolVersion
+ }
+ return self.HttpPost(url, data=data, headers=headers,
+ maxRetry=maxRetry, chkProxy=chkProxy)
+
+__StorageVersion="2014-02-14"
+
+def GetBlobType(url):
+ restutil = Util()
+ #Check blob type
+ LogIfVerbose("Check blob type.")
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ blobPropResp = restutil.HttpHead(url, {
+ "x-ms-date" : timestamp,
+ 'x-ms-version' : __StorageVersion
+ }, chkProxy=True);
+ blobType = None
+ if blobPropResp is None:
+ Error("Can't get status blob type.")
+ return None
+ blobType = blobPropResp.getheader("x-ms-blob-type")
+ LogIfVerbose("Blob type={0}".format(blobType))
+ return blobType
+
+def PutBlockBlob(url, data):
+ restutil = Util()
+ LogIfVerbose("Upload block blob")
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ ret = restutil.HttpPut(url, data, {
+ "x-ms-date" : timestamp,
+ "x-ms-blob-type" : "BlockBlob",
+ "Content-Length": str(len(data)),
+ "x-ms-version" : __StorageVersion
+ }, chkProxy=True)
+ if ret is None:
+ Error("Failed to upload block blob for status.")
+
+def PutPageBlob(url, data):
+ restutil = Util()
+ LogIfVerbose("Replace old page blob")
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ #Align to 512 bytes
+ pageBlobSize = ((len(data) + 511) / 512) * 512
+ ret = restutil.HttpPut(url, "", {
+ "x-ms-date" : timestamp,
+ "x-ms-blob-type" : "PageBlob",
+ "Content-Length": "0",
+ "x-ms-blob-content-length" : str(pageBlobSize),
+ "x-ms-version" : __StorageVersion
+ }, chkProxy=True)
+ if ret is None:
+ Error("Failed to clean up page blob for status")
+ return
+
+ if url.index('?') < 0:
+ url = "{0}?comp=page".format(url)
+ else:
+ url = "{0}&comp=page".format(url)
+
+ LogIfVerbose("Upload page blob")
+ pageMax = 4 * 1024 * 1024 #Max page size: 4MB
+ start = 0
+ end = 0
+ while end < len(data):
+ end = min(len(data), start + pageMax)
+ contentSize = end - start
+ #Align to 512 bytes
+ pageEnd = ((end + 511) / 512) * 512
+ bufSize = pageEnd - start
+ buf = bytearray(bufSize)
+ buf[0 : contentSize] = data[start : end]
+ ret = restutil.HttpPut(url, buffer(buf), {
+ "x-ms-date" : timestamp,
+ "x-ms-range" : "bytes={0}-{1}".format(start, pageEnd - 1),
+ "x-ms-page-write" : "update",
+ "x-ms-version" : __StorageVersion,
+ "Content-Length": str(pageEnd - start)
+ }, chkProxy=True)
+ if ret is None:
+ Error("Failed to upload page blob for status")
+ return
+ start = end
+
+def UploadStatusBlob(url, data):
+ LogIfVerbose("Upload status blob")
+ LogIfVerbose("Status={0}".format(data))
+ blobType = GetBlobType(url)
+
+ if blobType == "BlockBlob":
+ PutBlockBlob(url, data)
+ elif blobType == "PageBlob":
+ PutPageBlob(url, data)
+ else:
+ Error("Unknown blob type: {0}".format(blobType))
+ return None
+
+class TCPHandler(SocketServer.BaseRequestHandler):
+ """
+ Callback object for LoadBalancerProbeServer.
+ Recv and send LB probe messages.
+ """
+ def __init__(self,lb_probe):
+ super(TCPHandler,self).__init__()
+ self.lb_probe=lb_probe
+
+ def GetHttpDateTimeNow(self):
+ """
+ Return formatted gmtime "Date: Fri, 25 Mar 2011 04:53:10 GMT"
+ """
+ return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
+
+ def handle(self):
+ """
+ Log LB probe messages, read the socket buffer,
+ send LB probe response back to server.
+ """
+ self.lb_probe.ProbeCounter = (self.lb_probe.ProbeCounter + 1) % 1000000
+ log = [NoLog, LogIfVerbose][ThrottleLog(self.lb_probe.ProbeCounter)]
+ strCounter = str(self.lb_probe.ProbeCounter)
+ if self.lb_probe.ProbeCounter == 1:
+ Log("Receiving LB probes.")
+ log("Received LB probe # " + strCounter)
+ self.request.recv(1024)
+ self.request.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: text/html\r\nDate: " + self.GetHttpDateTimeNow() + "\r\n\r\nOK")
+
+class LoadBalancerProbeServer(object):
+ """
+ Threaded object to receive and send LB probe messages.
+ Load Balancer messages but be recv'd by
+ the load balancing server, or this node may be shut-down.
+ """
+ def __init__(self, port):
+ self.ProbeCounter = 0
+ self.server = SocketServer.TCPServer((self.get_ip(), port), TCPHandler)
+ self.server_thread = threading.Thread(target = self.server.serve_forever)
+ self.server_thread.setDaemon(True)
+ self.server_thread.start()
+
+ def shutdown(self):
+ self.server.shutdown()
+
+ def get_ip(self):
+ for retry in range(1,6):
+ ip = MyDistro.GetIpv4Address()
+ if ip == None :
+ Log("LoadBalancerProbeServer: GetIpv4Address() returned None, sleeping 10 before retry " + str(retry+1) )
+ time.sleep(10)
+ else:
+ return ip
+
+class ConfigurationProvider(object):
+ """
+ Parse amd store key:values in waagent.conf
+ """
+ def __init__(self, walaConfigFile):
+ self.values = dict()
+ if 'MyDistro' not in globals():
+ global MyDistro
+ MyDistro = GetMyDistro()
+ if walaConfigFile is None:
+ walaConfigFile = MyDistro.getConfigurationPath()
+ if os.path.isfile(walaConfigFile) == False:
+ raise Exception("Missing configuration in {0}".format(walaConfigFile))
+ try:
+ for line in GetFileContents(walaConfigFile).split('\n'):
+ if not line.startswith("#") and "=" in line:
+ parts = line.split()[0].split('=')
+ value = parts[1].strip("\" ")
+ if value != "None":
+ self.values[parts[0]] = value
+ else:
+ self.values[parts[0]] = None
+ except:
+ Error("Unable to parse {0}".format(walaConfigFile))
+ raise
+ return
+
+ def get(self, key):
+ return self.values.get(key)
+
+class EnvMonitor(object):
+ """
+ Montor changes to dhcp and hostname.
+ If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
+ """
+ def __init__(self):
+ self.shutdown = False
+ self.HostName = socket.gethostname()
+ self.server_thread = threading.Thread(target = self.monitor)
+ self.server_thread.setDaemon(True)
+ self.server_thread.start()
+ self.published = False
+
+ def monitor(self):
+ """
+ Monitor dhcp client pid and hostname.
+ If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
+ """
+ publish = Config.get("Provisioning.MonitorHostName")
+ dhcpcmd = MyDistro.getpidcmd+ ' ' + MyDistro.getDhcpClientName()
+ dhcppid = RunGetOutput(dhcpcmd)[1]
+ while not self.shutdown:
+ for a in RulesFiles:
+ if os.path.isfile(a):
+ if os.path.isfile(GetLastPathElement(a)):
+ 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:
+ Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
+ self.HostName = socket.gethostname()
+ WaAgent.UpdateAndPublishHostName(self.HostName)
+ dhcppid = RunGetOutput(dhcpcmd)[1]
+ self.published = True
+ except:
+ pass
+ else:
+ self.published = True
+ pid = ""
+ if not os.path.isdir("/proc/" + dhcppid.strip()):
+ pid = RunGetOutput(dhcpcmd)[1]
+ if pid != "" and pid != dhcppid:
+ Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
+ WaAgent.RestoreRoutes()
+ dhcppid = pid
+ for child in Children:
+ if child.poll() != None:
+ Children.remove(child)
+ time.sleep(5)
+
+ def SetHostName(self, name):
+ """
+ Generic call to MyDistro.setHostname(name).
+ Complian to Log on error.
+ """
+ if socket.gethostname() == name:
+ self.published = True
+ elif MyDistro.setHostname(name):
+ Error("Error: SetHostName: Cannot set hostname to " + name)
+ return ("Error: SetHostName: Cannot set hostname to " + name)
+
+ def IsHostnamePublished(self):
+ """
+ Return self.published
+ """
+ return self.published
+
+ def ShutdownService(self):
+ """
+ Stop server comminucation and join the thread to main thread.
+ """
+ self.shutdown = True
+ self.server_thread.join()
+
+class Certificates(object):
+ """
+ Object containing certificates of host and provisioned user.
+ Parses and splits certificates into files.
+ """
+ # <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
+ # <Version>2010-12-15</Version>
+ # <Incarnation>2</Incarnation>
+ # <Format>Pkcs7BlobWithPfxContents</Format>
+ # <Data>MIILTAY...
+ # </Data>
+ # </CertificateFile>
+
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ """
+ Reset the Role, Incarnation
+ """
+ self.Incarnation = None
+ self.Role = None
+
+ def Parse(self, xmlText):
+ """
+ Parse multiple certificates into seperate files.
+ """
+ self.reinitialize()
+ SetFileContents("Certificates.xml", xmlText)
+ dom = xml.dom.minidom.parseString(xmlText)
+ for a in [ "CertificateFile", "Version", "Incarnation",
+ "Format", "Data", ]:
+ if not dom.getElementsByTagName(a):
+ Error("Certificates.Parse: Missing " + a)
+ return None
+ node = dom.childNodes[0]
+ if node.localName != "CertificateFile":
+ Error("Certificates.Parse: root not CertificateFile")
+ return None
+ SetFileContents("Certificates.p7m",
+ "MIME-Version: 1.0\n"
+ + "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
+ + "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
+ + "Content-Transfer-Encoding: base64\n\n"
+ + GetNodeTextData(dom.getElementsByTagName("Data")[0]))
+ if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
+ Error("Certificates.Parse: Failed to extract certificates from CMS message.")
+ return self
+ # There may be multiple certificates in this package. Split them.
+ file = open("Certificates.pem")
+ pindex = 1
+ cindex = 1
+ output = open("temp.pem", "w")
+ for line in file.readlines():
+ output.write(line)
+ if re.match(r'[-]+END .*?(KEY|CERTIFICATE)[-]+$',line):
+ output.close()
+ if re.match(r'[-]+END .*?KEY[-]+$',line):
+ os.rename("temp.pem", str(pindex) + ".prv")
+ pindex += 1
+ else:
+ os.rename("temp.pem", str(cindex) + ".crt")
+ cindex += 1
+ output = open("temp.pem", "w")
+ output.close()
+ os.remove("temp.pem")
+ keys = dict()
+ index = 1
+ filename = str(index) + ".crt"
+ while os.path.isfile(filename):
+ thumbprint = (RunGetOutput(Openssl + " x509 -in " + filename + " -fingerprint -noout")[1]).rstrip().split('=')[1].replace(':', '').upper()
+ pubkey=RunGetOutput(Openssl + " x509 -in " + filename + " -pubkey -noout")[1]
+ keys[pubkey] = thumbprint
+ os.rename(filename, thumbprint + ".crt")
+ os.chmod(thumbprint + ".crt", 0600)
+ MyDistro.setSelinuxContext(thumbprint + '.crt','unconfined_u:object_r:ssh_home_t:s0')
+ index += 1
+ filename = str(index) + ".crt"
+ index = 1
+ filename = str(index) + ".prv"
+ while os.path.isfile(filename):
+ pubkey = RunGetOutput(Openssl + " rsa -in " + filename + " -pubout 2> /dev/null ")[1]
+ os.rename(filename, keys[pubkey] + ".prv")
+ os.chmod(keys[pubkey] + ".prv", 0600)
+ MyDistro.setSelinuxContext( keys[pubkey] + '.prv','unconfined_u:object_r:ssh_home_t:s0')
+ index += 1
+ filename = str(index) + ".prv"
+ return self
+
+class SharedConfig(object):
+ """
+ Parse role endpoint server and goal state config.
+ """
+ #
+ # <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
+ # <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
+ # <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
+ # <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
+ # </Deployment>
+ # <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
+ # <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
+ # <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
+ # <Probes>
+ # <Probe name="MachineRole" />
+ # <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
+ # <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
+ # </Probes>
+ # </LoadBalancerSettings>
+ # <OutputEndpoints>
+ # <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
+ # <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
+ # </Endpoint>
+ # </OutputEndpoints>
+ # <Instances>
+ # <Instance id="MachineRole_IN_0" address="10.115.153.75">
+ # <FaultDomains randomId="0" updateId="0" updateCount="0" />
+ # <InputEndpoints>
+ # <Endpoint name="a" address="10.115.153.75:80" protocol="http" isPublic="true" loadBalancedPublicAddress="70.37.106.197:80" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ # <LocalPorts>
+ # <LocalPortRange from="80" to="80" />
+ # </LocalPorts>
+ # </Endpoint>
+ # <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ # <LocalPorts>
+ # <LocalPortRange from="3389" to="3389" />
+ # </LocalPorts>
+ # </Endpoint>
+ # <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" address="10.115.153.75:20000" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.106.197:3389" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ # <LocalPorts>
+ # <LocalPortRange from="20000" to="20000" />
+ # </LocalPorts>
+ # </Endpoint>
+ # </InputEndpoints>
+ # </Instance>
+ # </Instances>
+ # </SharedConfig>
+ #
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ """
+ Reset members.
+ """
+ self.RdmaMacAddress = None
+ self.RdmaIPv4Address = None
+ self.xmlText = None
+
+ def Parse(self, xmlText):
+ """
+ Parse and write configuration to file SharedConfig.xml.
+ """
+ LogIfVerbose(xmlText)
+ self.reinitialize()
+ self.xmlText = xmlText
+ dom = xml.dom.minidom.parseString(xmlText)
+ for a in [ "SharedConfig", "Deployment", "Service",
+ "ServiceInstance", "Incarnation", "Role", ]:
+ if not dom.getElementsByTagName(a):
+ Error("SharedConfig.Parse: Missing " + a)
+
+ node = dom.childNodes[0]
+ if node.localName != "SharedConfig":
+ Error("SharedConfig.Parse: root not SharedConfig")
+
+ nodes = dom.getElementsByTagName("Instance")
+ if nodes is not None and len(nodes) != 0:
+ node = nodes[0]
+ if node.hasAttribute("rdmaMacAddress"):
+ addr = node.getAttribute("rdmaMacAddress")
+ self.RdmaMacAddress = addr[0:2]
+ for i in range(1, 6):
+ self.RdmaMacAddress += ":" + addr[2 * i : 2 *i + 2]
+ if node.hasAttribute("rdmaIPv4Address"):
+ self.RdmaIPv4Address = node.getAttribute("rdmaIPv4Address")
+ return self
+
+ def Save(self):
+ LogIfVerbose("Save SharedConfig.xml")
+ SetFileContents("SharedConfig.xml", self.xmlText)
+
+ def InvokeTopologyConsumer(self):
+ program = Config.get("Role.TopologyConsumer")
+ if program != None:
+ try:
+ Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"]))
+ except OSError, e :
+ ErrorWithPrefix('Agent.Run','Exception: '+ str(e) +' occured launching ' + program )
+
+ def Process(self):
+ global rdma_configured
+ if not rdma_configured and self.RdmaMacAddress is not None and self.RdmaIPv4Address is not None:
+ handler = RdmaHandler(self.RdmaMacAddress, self.RdmaIPv4Address)
+ handler.start()
+ rdma_configured = True
+ self.InvokeTopologyConsumer()
+
+rdma_configured = False
+
+class RdmaError(Exception):
+ pass
+
+class RdmaHandler(object):
+ """
+ Handle rdma configuration.
+ """
+
+ def __init__(self, mac, ip_addr, dev="/dev/hvnd_rdma",
+ dat_conf_files=['/etc/dat.conf', '/etc/rdma/dat.conf',
+ '/usr/local/etc/dat.conf']):
+ self.mac = mac
+ self.ip_addr = ip_addr
+ self.dev = dev
+ self.dat_conf_files = dat_conf_files
+ self.data = ('rdmaMacAddress="{0}" rdmaIPv4Address="{1}"'
+ '').format(self.mac, self.ip_addr)
+
+ def start(self):
+ """
+ Start a new thread to process rdma
+ """
+ threading.Thread(target=self.process).start()
+
+ def process(self):
+ try:
+ self.set_dat_conf()
+ self.set_rdma_dev()
+ self.set_rdma_ip()
+ except RdmaError as e:
+ Error("Failed to config rdma device: {0}".format(e))
+
+ def set_dat_conf(self):
+ """
+ Agent needs to search all possible locations for dat.conf
+ """
+ Log("Set dat.conf")
+ for dat_conf_file in self.dat_conf_files:
+ if not os.path.isfile(dat_conf_file):
+ continue
+ try:
+ self.write_dat_conf(dat_conf_file)
+ except IOError as e:
+ raise RdmaError("Failed to write to dat.conf: {0}".format(e))
+
+ def write_dat_conf(self, dat_conf_file):
+ Log("Write config to {0}".format(dat_conf_file))
+ old = ("ofa-v2-ib0 u2.0 nonthreadsafe default libdaplofa.so.2 "
+ "dapl.2.0 \"\S+ 0\"")
+ new = ("ofa-v2-ib0 u2.0 nonthreadsafe default libdaplofa.so.2 "
+ "dapl.2.0 \"{0} 0\"").format(self.ip_addr)
+ lines = GetFileContents(dat_conf_file)
+ lines = re.sub(old, new, lines)
+ SetFileContents(dat_conf_file, lines)
+
+ def set_rdma_dev(self):
+ """
+ Write config string to /dev/hvnd_rdma
+ """
+ Log("Set /dev/hvnd_rdma")
+ self.wait_rdma_dev()
+ self.write_rdma_dev_conf()
+
+ def write_rdma_dev_conf(self):
+ Log("Write rdma config to {0}: {1}".format(self.dev, self.data))
+ try:
+ with open(self.dev, "w") as c:
+ c.write(self.data)
+ except IOError, e:
+ raise RdmaError("Error writing {0}, {1}".format(self.dev, e))
+
+ def wait_rdma_dev(self):
+ Log("Wait for /dev/hvnd_rdma")
+ retry = 0
+ while retry < 120:
+ if os.path.exists(self.dev):
+ return
+ time.sleep(1)
+ retry += 1
+ raise RdmaError("The device doesn't show up in 120 seconds")
+
+ def set_rdma_ip(self):
+ Log("Set ip addr for rdma")
+ try:
+ if_name = MyDistro.getInterfaceNameByMac(self.mac)
+ #Azure is using 12 bits network mask for infiniband.
+ MyDistro.configIpV4(if_name, self.ip_addr, 12)
+ except Exception as e:
+ raise RdmaError("Failed to config rdma device: {0}".format(e))
+
+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.
+ 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 DisallowMajorVersionUpgrade is false or if true, the version is a minor version do upgrade:
+ download the new archive
+ do the updateCommand.
+ 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/Enable
+ if state is disabled:
+ call disableCommand
+ if state is uninstall:
+ call uninstallCommand
+ remove old plugin directory.
+ """
+ self.reinitialize()
+ self.Util=Util()
+ dom = xml.dom.minidom.parseString(xmlText)
+ LogIfVerbose(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")
+ if len(pg) > 0:
+ self.Plugins = pg[0].getElementsByTagName("Plugin")
+ else:
+ self.Plugins = []
+ incarnation=self.Extensions[0].getAttribute("goalStateIncarnation")
+ SetFileContents('ExtensionsConfig.'+incarnation+'.xml', xmlText)
+ except Exception, e:
+ Error('ERROR: Error parsing ExtensionsConfig: {0}.'.format(e))
+ 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' 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, 'Disabled')
+ Log(name+' is disabled')
+ 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
+ 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)
+ 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, chkProxy=True)
+ 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, chkProxy=True)
+ #if failoverlocation also fail what to do then?
+ if manifest == None:
+ AddExtensionEvent(name,WALAEventOperation.Download,False,0,version,"Download mainfest fail "+failoverlocation)
+ Log("Plugin manifest " + name + " downloading failed from failover location.")
+ SimpleLog(p.plugin_log,"Plugin manifest " + name + " downloading failed from failover location.")
+
+ 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 = ""
+ 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, chkProxy=True)
+ if bundle == None:
+ AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download zip fail "+bundle_uri)
+ Error("Unable to download plugin bundle" + bundle_uri )
+ SimpleLog(p.plugin_log,"Unable to download plugin bundle" + bundle_uri )
+ continue
+ AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download Success")
+ 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 +" -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):
+ 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)
+ # 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=''
+ 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 == '':
+ Log("No RuntimeSettings for " + name + " V " + version)
+ SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version)
+
+ SetFileContents(root +"/config/" + seqNo +".settings", config )
+ #create HandlerEnvironment.json
+ 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 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')
+
+ isupgradeSuccess = True
+ if getcmd=='updateCommand':
+ 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)
+ isupgradeSuccess=False
+ else :
+ Log('Update complete'+name+'-'+version)
+ 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)
+ isupgradeSuccess=False
+ else :
+ self.SetHandlerState(previous_handler, 'NotInstalled')
+ Log('Uninstall complete'+ previous_handler )
+ SimpleLog(p.plugin_log,'Uninstall complete'+ name +'-' + previous_version)
+ AddExtensionEvent(name,WALAEventOperation.Upgrade,isupgradeSuccess,0,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)
+ 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/" + seqNo +".settings", config )
+
+ # state is still enable
+ 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,plugin_log,name,version,command,prev_version=None):
+ commandToEventOperation={
+ "installCommand":WALAEventOperation.Install,
+ "uninstallCommand":WALAEventOperation.UnIsntall,
+ "updateCommand": WALAEventOperation.Upgrade,
+ "enableCommand": WALAEventOperation.Enable,
+ "disableCommand": WALAEventOperation.Disable,
+ }
+ isSuccess=True
+ start = datetime.datetime.now()
+ r=self.__launchCommandWithoutEventLog(plugin_log,name,version,command,prev_version)
+ if r==None:
+ isSuccess=False
+ Duration = int((datetime.datetime.now() - start).seconds)
+ if commandToEventOperation.get(command):
+ AddExtensionEvent(name,commandToEventOperation[command],isSuccess,Duration,version)
+ return r
+
+ def __launchCommandWithoutEventLog(self,plugin_log,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.')
+ 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
+ dirpath=os.path.dirname(mfile)
+ LogIfVerbose('Command is '+ dirpath+'/'+ cmd)
+ # launch
+ pid=None
+ try:
+ child = subprocess.Popen(dirpath+'/'+cmd+arg,shell=True,cwd=dirpath,stdout=subprocess.PIPE)
+ except Exception as 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 ' + cmd + '.')
+ SimpleLog(plugin_log,'Error launching ' + cmd + '.')
+
+ else :
+ ExtensionChildren.append((pid,root))
+ Log("Spawned "+ cmd + " PID " + str(pid))
+ SimpleLog(plugin_log,"Spawned "+ cmd + " PID " + str(pid))
+
+
+ # wait until install/upgrade is finished
+ 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('Process exceeded timeout of ' + str(timeout) + ' seconds. Terminating process ' + str(pid))
+ SimpleLog(plugin_log,'Process exceeded timeout of ' + str(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=''
+ 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
+ reportHeartbeat = False
+ if len(p.getAttribute("manifestdata"))<1:
+ Error("Failed to get manifestdata.")
+ else:
+ reportHeartbeat = json.loads(p.getAttribute("manifestdata"))[0]['handlerManifest']['reportHeartbeat']
+ if len(statuses)>0:
+ statuses+=','
+ statuses+=self.GenerateAggStatus(name, version, reportHeartbeat)
+ 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
+
+ UploadStatusBlob(uri, status.encode("utf-8"))
+ LogIfVerbose('Status report '+status+' sent to ' + uri)
+ return True
+
+ def GetCurrentSequenceNumber(self, plugin_base_dir):
+ """
+ Get the settings file with biggest file number in config folder
+ """
+ config_dir = os.path.join(plugin_base_dir, 'config')
+ seq_no = 0
+ for subdir, dirs, files in os.walk(config_dir):
+ for file in files:
+ try:
+ cur_seq_no = int(os.path.basename(file).split('.')[0])
+ if cur_seq_no > seq_no:
+ seq_no = cur_seq_no
+ except ValueError:
+ continue
+ return str(seq_no)
+
+
+ def GenerateAggStatus(self, name, version, reportHeartbeat = False):
+ """
+ Generate the status which Azure can understand by the status and heartbeat reported by extension
+ """
+ plugin_base_dir = LibDir+'/'+name+'-'+version+'/'
+ current_seq_no = self.GetCurrentSequenceNumber(plugin_base_dir)
+ status_file=os.path.join(plugin_base_dir, 'status/', current_seq_no +'.status')
+ heartbeat_file = os.path.join(plugin_base_dir, 'heartbeat.log')
+
+ handler_state_file = os.path.join(plugin_base_dir, 'config', 'HandlerState')
+ agg_state = 'NotReady'
+ handler_state = None
+ status_obj = None
+ status_code = None
+ formatted_message = None
+ localized_message = None
+
+ if os.path.exists(handler_state_file):
+ handler_state = GetFileContents(handler_state_file).lower()
+ if HandlerStatusToAggStatus.has_key(handler_state):
+ agg_state = HandlerStatusToAggStatus[handler_state]
+ if reportHeartbeat:
+ if os.path.exists(heartbeat_file):
+ d=int(time.time()-os.stat(heartbeat_file).st_mtime)
+ if d > 600 : # not updated for more than 10 min
+ agg_state = 'Unresponsive'
+ else:
+ try:
+ heartbeat = json.loads(GetFileContents(heartbeat_file))[0]["heartbeat"]
+ agg_state = heartbeat.get("status")
+ status_code = heartbeat.get("code")
+ formatted_message = heartbeat.get("formattedMessage")
+ localized_message = heartbeat.get("message")
+ except:
+ Error("Incorrect heartbeat file. Ignore it. ")
+ else:
+ agg_state = 'Unresponsive'
+ #get status file reported by extension
+ if os.path.exists(status_file):
+ # raw status generated by extension is an array, get the first item and remove the unnecessary element
+ try:
+ status_obj = json.loads(GetFileContents(status_file))[0]
+ del status_obj["version"]
+ except:
+ Error("Incorrect status file. Will NOT settingsStatus in settings. ")
+ agg_status_obj = {"handlerName": name, "handlerVersion": version, "status": agg_state, "runtimeSettingsStatus" :
+ {"sequenceNumber": current_seq_no}}
+ if status_obj:
+ agg_status_obj["runtimeSettingsStatus"]["settingsStatus"] = status_obj
+ if status_code != None:
+ agg_status_obj["code"] = status_code
+ if formatted_message:
+ agg_status_obj["formattedMessage"] = formatted_message
+ if localized_message:
+ agg_status_obj["message"] = localized_message
+ agg_status_string = json.dumps(agg_status_obj)
+ LogIfVerbose("Handler Aggregated Status:" + agg_status_string)
+ return agg_status_string
+
+
+ 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
+ HostingEnvironmentConfig.xml
+ """
+ #
+ # <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
+ # <StoredCertificates>
+ # <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
+ # </StoredCertificates>
+ # <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
+ # <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
+ # <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
+ # </Deployment>
+ # <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
+ # <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
+ # <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip">
+ # <CAS mode="full" />
+ # <PrivilegeLevel mode="max" />
+ # <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties>
+ # </HostingEnvironmentSettings>
+ # <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.RemoteForwarder.Enabled" value="true" />
+ # </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" />
+ # </ResourceReferences>
+ # </HostingEnvironmentConfig>
+ #
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ """
+ Reset Members.
+ """
+ self.StoredCertificates = None
+ self.Deployment = None
+ self.Incarnation = None
+ self.Role = None
+ self.HostingEnvironmentSettings = None
+ self.ApplicationSettings = None
+ self.Certificates = None
+ self.ResourceReferences = None
+
+ def Parse(self, xmlText):
+ """
+ Parse and create HostingEnvironmentConfig.xml.
+ """
+ self.reinitialize()
+ SetFileContents("HostingEnvironmentConfig.xml", xmlText)
+ dom = xml.dom.minidom.parseString(xmlText)
+ for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
+ "ServiceInstance", "Incarnation", "Role", ]:
+ if not dom.getElementsByTagName(a):
+ Error("HostingEnvironmentConfig.Parse: Missing " + a)
+ return None
+ node = dom.childNodes[0]
+ if node.localName != "HostingEnvironmentConfig":
+ Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
+ return None
+ self.ApplicationSettings = dom.getElementsByTagName("Setting")
+ self.Certificates = dom.getElementsByTagName("StoredCertificate")
+ return self
+
+ def DecryptPassword(self, e):
+ """
+ Return decrypted password.
+ """
+ SetFileContents("password.p7m",
+ "MIME-Version: 1.0\n"
+ + "Content-Disposition: attachment; filename=\"password.p7m\"\n"
+ + "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
+ + "Content-Transfer-Encoding: base64\n\n"
+ + textwrap.fill(e, 64))
+ return RunGetOutput(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem")[1]
+
+ def ActivateResourceDisk(self):
+ return MyDistro.ActivateResourceDisk()
+
+ def Process(self):
+ """
+ Execute ActivateResourceDisk in separate thread.
+ Create the user account.
+ Launch ConfigurationConsumer if specified in the config.
+ """
+ no_thread = False
+ if DiskActivated == False:
+ 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
+ Thumbprint = None
+ for b in self.ApplicationSettings:
+ sname = b.getAttribute("name")
+ svalue = b.getAttribute("value")
+ if User != None and Pass != None:
+ if User != "root" and User != "" and Pass != "":
+ CreateAccount(User, Pass, Expiration, Thumbprint)
+ else:
+ Error("Not creating user account: " + User)
+ for c in self.Certificates:
+ csha1 = c.getAttribute("certificateId").split(':')[1].upper()
+ if os.path.isfile(csha1 + ".prv"):
+ Log("Private key with thumbprint: " + csha1 + " was retrieved.")
+ if os.path.isfile(csha1 + ".crt"):
+ Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
+ program = Config.get("Role.ConfigurationConsumer")
+ if program != None:
+ try:
+ Children.append(subprocess.Popen([program, LibDir + "/HostingEnvironmentConfig.xml"]))
+ except OSError, e :
+ ErrorWithPrefix('HostingEnvironmentConfig.Process','Exception: '+ str(e) +' occured launching ' + program )
+
+class GoalState(Util):
+ """
+ Primary container for all configuration except OvfXml.
+ Encapsulates http communication with endpoint server.
+ Initializes and populates:
+ self.HostingEnvironmentConfig
+ self.SharedConfig
+ self.ExtensionsConfig
+ self.Certificates
+ """
+ #
+ # <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
+ # <Version>2010-12-15</Version>
+ # <Incarnation>1</Incarnation>
+ # <Machine>
+ # <ExpectedState>Started</ExpectedState>
+ # <LBProbePorts>
+ # <Port>16001</Port>
+ # </LBProbePorts>
+ # </Machine>
+ # <Container>
+ # <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
+ # <RoleInstanceList>
+ # <RoleInstance>
+ # <InstanceId>MachineRole_IN_0</InstanceId>
+ # <State>Started</State>
+ # <Configuration>
+ # <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>
+ # </Container>
+ # </GoalState>
+ #
+ # There is only one Role for VM images.
+ #
+ # Of primary interest is:
+ # LBProbePorts -- an http server needs to run here
+ # We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
+ # And of course, Incarnation
+ #
+ def __init__(self, Agent):
+ self.Agent = Agent
+ self.Endpoint = Agent.Endpoint
+ self.TransportCert = Agent.TransportCert
+ self.reinitialize()
+
+ def reinitialize(self):
+ self.Incarnation = None # integer
+ self.ExpectedState = None # "Started"
+ self.HostingEnvironmentConfigUrl = None
+ self.HostingEnvironmentConfigXml = None
+ self.HostingEnvironmentConfig = None
+ self.SharedConfigUrl = None
+ self.SharedConfigXml = None
+ self.SharedConfig = None
+ 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
+
+ def Parse(self, xmlText):
+ """
+ Request configuration data from endpoint server.
+ 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")
+ return None
+ for a in node.childNodes:
+ if a.nodeType == node.ELEMENT_NODE:
+ if a.localName == "Incarnation":
+ self.Incarnation = GetNodeTextData(a)
+ elif a.localName == "Machine":
+ for b in a.childNodes:
+ if b.nodeType == node.ELEMENT_NODE:
+ if b.localName == "ExpectedState":
+ self.ExpectedState = GetNodeTextData(b)
+ Log("ExpectedState: " + self.ExpectedState)
+ elif b.localName == "LBProbePorts":
+ for c in b.childNodes:
+ if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
+ self.LoadBalancerProbePort = int(GetNodeTextData(c))
+ elif a.localName == "Container":
+ for b in a.childNodes:
+ if b.nodeType == node.ELEMENT_NODE:
+ if b.localName == "ContainerId":
+ self.ContainerId = GetNodeTextData(b)
+ Log("ContainerId: " + self.ContainerId)
+ elif b.localName == "RoleInstanceList":
+ for c in b.childNodes:
+ if c.localName == "RoleInstance":
+ for d in c.childNodes:
+ if d.nodeType == node.ELEMENT_NODE:
+ if d.localName == "InstanceId":
+ self.RoleInstanceId = GetNodeTextData(d)
+ Log("RoleInstanceId: " + self.RoleInstanceId)
+ elif d.localName == "State":
+ pass
+ 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)
+ self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
+ self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
+ elif e.localName == "SharedConfig":
+ self.SharedConfigUrl = GetNodeTextData(e)
+ LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
+ self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
+ self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
+ self.SharedConfig.Save()
+ 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)
+ self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
+ self.Certificates = Certificates().Parse(self.CertificatesXml)
+ if self.Incarnation == None:
+ Error("GoalState.Parse: Incarnation missing")
+ return None
+ if self.ExpectedState == None:
+ Error("GoalState.Parse: ExpectedState missing")
+ return None
+ if self.RoleInstanceId == None:
+ Error("GoalState.Parse: RoleInstanceId missing")
+ return None
+ if self.ContainerId == None:
+ Error("GoalState.Parse: ContainerId missing")
+ return None
+ SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
+ return self
+
+ def Process(self):
+ """
+ Calls HostingEnvironmentConfig.Process()
+ """
+ LogIfVerbose("Process goalstate")
+ self.HostingEnvironmentConfig.Process()
+ self.SharedConfig.Process()
+
+class OvfEnv(object):
+ """
+ Read, and process provisioning info from provisioning file OvfEnv.xml
+ """
+ #
+ # <?xml version="1.0" encoding="utf-8"?>
+ # <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ # <wa:ProvisioningSection>
+ # <wa:Version>1.0</wa:Version>
+ # <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ # <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
+ # <HostName>HostName</HostName>
+ # <UserName>UserName</UserName>
+ # <UserPassword>UserPassword</UserPassword>
+ # <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
+ # <SSH>
+ # <PublicKeys>
+ # <PublicKey>
+ # <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
+ # <Path>$HOME/UserName/.ssh/authorized_keys</Path>
+ # </PublicKey>
+ # </PublicKeys>
+ # <KeyPairs>
+ # <KeyPair>
+ # <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
+ # <Path>$HOME/UserName/.ssh/id_rsa</Path>
+ # </KeyPair>
+ # </KeyPairs>
+ # </SSH>
+ # </LinuxProvisioningConfigurationSet>
+ # </wa:ProvisioningSection>
+ # </Environment>
+ #
+ def __init__(self):
+ self.reinitialize()
+
+ def reinitialize(self):
+ """
+ Reset members.
+ """
+ self.WaNs = "http://schemas.microsoft.com/windowsazure"
+ self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
+ self.MajorVersion = 1
+ self.MinorVersion = 0
+ self.ComputerName = None
+ self.AdminPassword = None
+ self.UserName = None
+ self.UserPassword = None
+ self.CustomData = None
+ self.DisableSshPasswordAuthentication = True
+ self.SshPublicKeys = []
+ self.SshKeyPairs = []
+
+ def Parse(self, xmlText, isDeprovision = False):
+ """
+ Parse xml tree, retreiving user and ssh key information.
+ Return self.
+ """
+ self.reinitialize()
+ LogIfVerbose(re.sub("<UserPassword>.*?<", "<UserPassword>*<", xmlText))
+ dom = xml.dom.minidom.parseString(xmlText)
+ if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
+ Error("Unable to parse OVF XML.")
+ section = None
+ newer = False
+ for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
+ for n in p.childNodes:
+ if n.localName == "Version":
+ verparts = GetNodeTextData(n).split('.')
+ major = int(verparts[0])
+ minor = int(verparts[1])
+ if major > self.MajorVersion:
+ newer = True
+ if major != self.MajorVersion:
+ break
+ if minor > self.MinorVersion:
+ newer = True
+ section = p
+ if newer == True:
+ Warn("Newer provisioning configuration detected. Please consider updating waagent.")
+ if section == None:
+ Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
+ return None
+ self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
+ self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
+ if isDeprovision == True:
+ return self
+ try:
+ self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
+ except:
+ pass
+ CDSection=None
+ try:
+ CDSection=section.getElementsByTagNameNS(self.WaNs, "CustomData")
+ if len(CDSection) > 0 :
+ self.CustomData=GetNodeTextData(CDSection[0])
+ if len(self.CustomData)>0:
+ SetFileContents(LibDir + '/CustomData', MyDistro.translateCustomData(self.CustomData))
+ Log('Wrote ' + LibDir + '/CustomData')
+ else :
+ Error('<CustomData> contains no data!')
+ except Exception, e:
+ Error( str(e)+' occured creating ' + LibDir + '/CustomData')
+ disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
+ 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
+
+ def PrepareDir(self, filepath):
+ """
+ Create home dir for self.UserName
+ Change owner and return path.
+ """
+ home = MyDistro.GetHome()
+ # Expand HOME variable if present in path
+ path = os.path.normpath(filepath.replace("$HOME", home))
+ if (path.startswith("/") == False) or (path.endswith("/") == True):
+ return None
+ dir = path.rsplit('/', 1)[0]
+ if dir != "":
+ CreateDir(dir, "root", 0700)
+ if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
+ ChangeOwner(dir, self.UserName)
+ return path
+
+ def NumberToBytes(self, i):
+ """
+ Pack number into bytes. Retun as string.
+ """
+ result = []
+ while i:
+ result.append(chr(i & 0xFF))
+ i >>= 8
+ result.reverse()
+ return ''.join(result)
+
+ def BitsToString(self, a):
+ """
+ Return string representation of bits in a.
+ """
+ index=7
+ s = ""
+ c = 0
+ for bit in a:
+ c = c | (bit << index)
+ index = index - 1
+ if index == -1:
+ s = s + struct.pack('>B', c)
+ c = 0
+ index = 7
+ return s
+
+ def OpensslToSsh(self, file):
+ """
+ Return base-64 encoded key appropriate for ssh.
+ """
+ from pyasn1.codec.der import decoder as der_decoder
+ try:
+ f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
+ k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
+ n=k[0]
+ e=k[1]
+ keydata=""
+ keydata += struct.pack('>I',len("ssh-rsa"))
+ keydata += "ssh-rsa"
+ keydata += struct.pack('>I',len(self.NumberToBytes(e)))
+ keydata += self.NumberToBytes(e)
+ keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
+ keydata += "\0"
+ keydata += self.NumberToBytes(n)
+ except Exception, e:
+ print("OpensslToSsh: Exception " + str(e))
+ return None
+ return "ssh-rsa " + base64.b64encode(keydata) + "\n"
+
+ def Process(self):
+ """
+ Process all certificate and key info.
+ DisableSshPasswordAuthentication if configured.
+ CreateAccount(user)
+ Wait for WaAgent.EnvMonitor.IsHostnamePublished().
+ Restart ssh service.
+ """
+ error = None
+ if self.ComputerName == None :
+ return "Error: Hostname missing"
+ error=WaAgent.EnvMonitor.SetHostName(self.ComputerName)
+ if error: return error
+ if self.DisableSshPasswordAuthentication:
+ filepath = "/etc/ssh/sshd_config"
+ # Disable RFC 4252 and RFC 4256 authentication schemes.
+ ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
+ (a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
+ GetFileContents(filepath).split('\n'))) + "\nPasswordAuthentication no\nChallengeResponseAuthentication no\n")
+ Log("Disabled SSH password-based authentication methods.")
+ if self.AdminPassword != None:
+ MyDistro.changePass('root',self.AdminPassword)
+ if self.UserName != None:
+ error = MyDistro.CreateAccount(self.UserName, self.UserPassword, None, None)
+ sel = MyDistro.isSelinuxRunning()
+ if sel :
+ MyDistro.setSelinuxEnforce(0)
+ home = MyDistro.GetHome()
+ for pkey in self.SshPublicKeys:
+ Log("Deploy public key:{0}".format(pkey[0]))
+ if not os.path.isfile(pkey[0] + ".crt"):
+ Error("PublicKey not found: " + pkey[0])
+ error = "Failed to deploy public key (0x09)."
+ continue
+ path = self.PrepareDir(pkey[1])
+ if path == None:
+ Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
+ error = "Invalid path for public key (0x03)."
+ continue
+ Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
+ MyDistro.setSelinuxContext(pkey[0] + '.pub','unconfined_u:object_r:ssh_home_t:s0')
+ MyDistro.sshDeployPublicKey(pkey[0] + '.pub',path)
+ MyDistro.setSelinuxContext(path,'unconfined_u:object_r:ssh_home_t:s0')
+ if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
+ ChangeOwner(path, self.UserName)
+ for keyp in self.SshKeyPairs:
+ Log("Deploy key pair:{0}".format(keyp[0]))
+ if not os.path.isfile(keyp[0] + ".prv"):
+ Error("KeyPair not found: " + keyp[0])
+ error = "Failed to deploy key pair (0x0A)."
+ continue
+ path = self.PrepareDir(keyp[1])
+ if path == None:
+ Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
+ error = "Invalid path for key pair (0x05)."
+ continue
+ SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
+ os.chmod(path, 0600)
+ Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
+ MyDistro.setSelinuxContext(path,'unconfined_u:object_r:ssh_home_t:s0')
+ MyDistro.setSelinuxContext(path + '.pub','unconfined_u:object_r:ssh_home_t:s0')
+ if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
+ ChangeOwner(path, self.UserName)
+ ChangeOwner(path + ".pub", self.UserName)
+ if sel :
+ MyDistro.setSelinuxEnforce(1)
+ while not WaAgent.EnvMonitor.IsHostnamePublished():
+ time.sleep(1)
+ MyDistro.restartSshService()
+ return error
+
+
+class WALAEvent(object):
+ def __init__(self):
+
+ self.providerId=""
+ self.eventId=1
+
+ self.OpcodeName=""
+ self.KeywordName=""
+ self.TaskName=""
+ self.TenantName=""
+ self.RoleName=""
+ self.RoleInstanceName=""
+ self.ContainerId=""
+ self.ExecutionMode="IAAS"
+ self.OSVersion=""
+ self.GAVersion=""
+ self.RAM=0
+ self.Processors=0
+
+
+ def ToXml(self):
+ strEventid=u'<Event id="{0}"/>'.format(self.eventId)
+ strProviderid=u'<Provider id="{0}"/>'.format(self.providerId)
+ strRecordFormat = u'<Param Name="{0}" Value="{1}" T="{2}" />'
+ strRecordNoQuoteFormat = u'<Param Name="{0}" Value={1} T="{2}" />'
+ strMtStr=u'mt:wstr'
+ strMtUInt64=u'mt:uint64'
+ strMtBool=u'mt:bool'
+ strMtFloat=u'mt:float64'
+ strEventsData=u""
+
+ for attName in self.__dict__:
+ if attName in ["eventId","filedCount","providerId"]:
+ continue
+
+ attValue = self.__dict__[attName]
+ if type(attValue) is int:
+ strEventsData+=strRecordFormat.format(attName,attValue,strMtUInt64)
+ continue
+ if type(attValue) is str:
+ attValue = xml.sax.saxutils.quoteattr(attValue)
+ strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
+ continue
+ if str(type(attValue)).count("'unicode'") >0 :
+ attValue = xml.sax.saxutils.quoteattr(attValue)
+ strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
+ continue
+ if type(attValue) is bool:
+ strEventsData+=strRecordFormat.format(attName,attValue,strMtBool)
+ continue
+ if type(attValue) is float:
+ strEventsData+=strRecordFormat.format(attName,attValue,strMtFloat)
+ continue
+
+ Log("Warning: property "+attName+":"+str(type(attValue))+":type"+str(type(attValue))+"Can't convert to events data:"+":type not supported")
+
+ return u"<Data>{0}{1}{2}</Data>".format(strProviderid,strEventid,strEventsData)
+
+ def Save(self):
+ eventfolder = LibDir+"/events"
+ if not os.path.exists(eventfolder):
+ os.mkdir(eventfolder)
+ os.chmod(eventfolder,0700)
+ if len(os.listdir(eventfolder)) > 1000:
+ raise Exception("WriteToFolder:Too many file under "+eventfolder+" exit")
+
+ filename = os.path.join(eventfolder,str(int(time.time()*1000000)))
+ with open(filename+".tmp",'wb+') as hfile:
+ hfile.write(self.ToXml().encode("utf-8"))
+ os.rename(filename+".tmp",filename+".tld")
+
+
+class WALAEventOperation:
+ HeartBeat="HeartBeat"
+ Provision = "Provision"
+ Install = "Install"
+ UnIsntall = "UnInstall"
+ Disable = "Disable"
+ Enable = "Enable"
+ Download = "Download"
+ Upgrade = "Upgrade"
+ Update = "Update"
+
+def AddExtensionEvent(name,op,isSuccess,duration=0,version="1.0",message="",type="",isInternal=False):
+ event = ExtensionEvent()
+ event.Name=name
+ event.Version=version
+ event.IsInternal=isInternal
+ event.Operation=op
+ event.OperationSuccess=isSuccess
+ event.Message=message
+ event.Duration=duration
+ event.ExtensionType=type
+ try:
+ event.Save()
+ except:
+ Error("Error "+traceback.format_exc())
+
+
+class ExtensionEvent(WALAEvent):
+ def __init__(self):
+
+ WALAEvent.__init__(self)
+ self.eventId=1
+ self.providerId="69B669B9-4AF8-4C50-BDC4-6006FA76E975"
+ self.Name=""
+ self.Version=""
+ self.IsInternal=False
+ self.Operation=""
+ self.OperationSuccess=True
+ self.ExtensionType=""
+ self.Message=""
+ self.Duration=0
+
+
+class WALAEventMonitor(WALAEvent):
+ def __init__(self,postMethod):
+ WALAEvent.__init__(self)
+ self.post = postMethod
+ self.sysInfo={}
+ self.eventdir = LibDir+"/events"
+ self.issysteminfoinitilized = False
+
+ def StartEventsLoop(self):
+ eventThread = threading.Thread(target = self.EventsLoop)
+ eventThread.setDaemon(True)
+ eventThread.start()
+
+ def EventsLoop(self):
+ LastReportHeartBeatTime = datetime.datetime.min
+ try:
+ while(True):
+ if (datetime.datetime.now()-LastReportHeartBeatTime) > datetime.timedelta(hours=12):
+ LastReportHeartBeatTime = datetime.datetime.now()
+ AddExtensionEvent(op=WALAEventOperation.HeartBeat,name="WALA",isSuccess=True)
+ self.postNumbersInOneLoop=0
+ self.CollectAndSendWALAEvents()
+ time.sleep(60)
+ except:
+ Error("Exception in events loop:"+traceback.format_exc())
+
+ def SendEvent(self,providerid,events):
+ dataFormat = u'<?xml version="1.0"?><TelemetryData version="1.0"><Provider id="{0}">{1}'\
+ '</Provider></TelemetryData>'
+ data = dataFormat.format(providerid,events)
+ self.post("/machine/?comp=telemetrydata", data)
+
+ def CollectAndSendWALAEvents(self):
+ if not os.path.exists(self.eventdir):
+ return
+ #Throtting, can't send more than 3 events in 15 seconds
+ eventSendNumber=0
+ eventFiles = os.listdir(self.eventdir)
+ events = {}
+ for file in eventFiles:
+ if not file.endswith(".tld"):
+ continue
+ with open(os.path.join(self.eventdir,file),"rb") as hfile:
+ #if fail to open or delete the file, throw exception
+ xmlStr = hfile.read().decode("utf-8",'ignore')
+ os.remove(os.path.join(self.eventdir,file))
+ params=""
+ eventid=""
+ providerid=""
+ #if exception happen during process an event, catch it and continue
+ try:
+ xmlStr = self.AddSystemInfo(xmlStr)
+ for node in xml.dom.minidom.parseString(xmlStr.encode("utf-8")).childNodes[0].childNodes:
+ if node.tagName == "Param":
+ params+=node.toxml()
+ if node.tagName == "Event":
+ eventid=node.getAttribute("id")
+ if node.tagName == "Provider":
+ providerid = node.getAttribute("id")
+ except:
+ Error(traceback.format_exc())
+ continue
+ if len(params)==0 or len(eventid)==0 or len(providerid)==0:
+ Error("Empty filed in params:"+params+" event id:"+eventid+" provider id:"+providerid)
+ continue
+
+ eventstr = u'<Event id="{0}"><![CDATA[{1}]]></Event>'.format(eventid,params)
+ if not events.get(providerid):
+ events[providerid]=""
+ if len(events[providerid]) >0 and len(events.get(providerid)+eventstr)>= 63*1024:
+ eventSendNumber+=1
+ self.SendEvent(providerid,events.get(providerid))
+ if eventSendNumber %3 ==0:
+ time.sleep(15)
+ events[providerid]=""
+ if len(eventstr) >= 63*1024:
+ Error("Signle event too large abort "+eventstr[:300])
+ continue
+
+ events[providerid]=events.get(providerid)+eventstr
+
+ for key in events.keys():
+ if len(events[key]) > 0:
+ eventSendNumber+=1
+ self.SendEvent(key,events[key])
+ if eventSendNumber%3 == 0:
+ time.sleep(15)
+
+
+ def AddSystemInfo(self,eventData):
+ if not self.issysteminfoinitilized:
+ self.issysteminfoinitilized=True
+ try:
+ self.sysInfo["OSVersion"]=platform.system()+":"+"-".join(DistInfo(1))+":"+platform.release()
+ self.sysInfo["GAVersion"]=GuestAgentVersion
+ self.sysInfo["RAM"]=MyDistro.getTotalMemory()
+ self.sysInfo["Processors"]=MyDistro.getProcessorCores()
+ sharedConfig = xml.dom.minidom.parse("/var/lib/waagent/SharedConfig.xml").childNodes[0]
+ hostEnvConfig= xml.dom.minidom.parse("/var/lib/waagent/HostingEnvironmentConfig.xml").childNodes[0]
+ gfiles = RunGetOutput("ls -t /var/lib/waagent/GoalState.*.xml")[1]
+ goalStateConfi = xml.dom.minidom.parse(gfiles.split("\n")[0]).childNodes[0]
+ self.sysInfo["TenantName"]=hostEnvConfig.getElementsByTagName("Deployment")[0].getAttribute("name")
+ self.sysInfo["RoleName"]=hostEnvConfig.getElementsByTagName("Role")[0].getAttribute("name")
+ self.sysInfo["RoleInstanceName"]=sharedConfig.getElementsByTagName("Instance")[0].getAttribute("id")
+ self.sysInfo["ContainerId"]=goalStateConfi.getElementsByTagName("ContainerId")[0].childNodes[0].nodeValue
+ except:
+ Error(traceback.format_exc())
+
+ eventObject = xml.dom.minidom.parseString(eventData.encode("utf-8")).childNodes[0]
+ for node in eventObject.childNodes:
+ if node.tagName == "Param":
+ name = node.getAttribute("Name")
+ if self.sysInfo.get(name):
+ node.setAttribute("Value",xml.sax.saxutils.escape(str(self.sysInfo[name])))
+
+ return eventObject.toxml()
+
+
+class Agent(Util):
+ """
+ Primary object container for the provisioning process.
+
+ """
+ def __init__(self):
+ self.GoalState = None
+ self.Endpoint = None
+ self.LoadBalancerProbeServer = None
+ self.HealthReportCounter = 0
+ self.TransportCert = ""
+ self.EnvMonitor = None
+ self.SendData = None
+ self.DhcpResponse = None
+
+ def CheckVersions(self):
+ """
+ Query endpoint server for wire protocol version.
+ Fail if our desired protocol version is not seen.
+ """
+ #<?xml version="1.0" encoding="utf-8"?>
+ #<Versions>
+ # <Preferred>
+ # <Version>2010-12-15</Version>
+ # </Preferred>
+ # <Supported>
+ # <Version>2010-12-15</Version>
+ # <Version>2010-28-10</Version>
+ # </Supported>
+ #</Versions>
+ global ProtocolVersion
+ protocolVersionSeen = False
+ node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
+ if node.localName != "Versions":
+ Error("CheckVersions: root not Versions")
+ return False
+ for a in node.childNodes:
+ if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
+ for b in a.childNodes:
+ if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
+ v = GetNodeTextData(b)
+ LogIfVerbose("Fabric supported wire protocol version: " + v)
+ if v == ProtocolVersion:
+ protocolVersionSeen = True
+ if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
+ v = GetNodeTextData(a.getElementsByTagName("Version")[0])
+ Log("Fabric preferred wire protocol version: " + v)
+ if not protocolVersionSeen:
+ Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
+ else:
+ Log("Negotiated wire protocol version: " + ProtocolVersion)
+ return True
+
+ def Unpack(self, buffer, offset, range):
+ """
+ Unpack bytes into python values.
+ """
+ result = 0
+ for i in range:
+ result = (result << 8) | Ord(buffer[offset + i])
+ return result
+
+ def UnpackLittleEndian(self, buffer, offset, length):
+ """
+ Unpack little endian bytes into python values.
+ """
+ return self.Unpack(buffer, offset, list(range(length - 1, -1, -1)))
+
+ def UnpackBigEndian(self, buffer, offset, length):
+ """
+ Unpack big endian bytes into python values.
+ """
+ return self.Unpack(buffer, offset, list(range(0, length)))
+
+ def HexDump3(self, buffer, offset, length):
+ """
+ Dump range of buffer in formatted hex.
+ """
+ return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
+
+ def HexDump2(self, buffer):
+ """
+ Dump buffer in formatted hex.
+ """
+ return self.HexDump3(buffer, 0, len(buffer))
+
+ def BuildDhcpRequest(self):
+ """
+ Build DHCP request string.
+ """
+ #
+ # typedef struct _DHCP {
+ # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
+ # UINT8 HardwareAddressType; /* htype: ethernet */
+ # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
+ # UINT8 Hops; /* hops: 0 */
+ # UINT8 TransactionID[4]; /* xid: random */
+ # UINT8 Seconds[2]; /* secs: 0 */
+ # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
+ # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
+ # UINT8 YourIpAddress[4]; /* yiaddr: 0 */
+ # UINT8 ServerIpAddress[4]; /* siaddr: 0 */
+ # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
+ # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */
+ # UINT8 ServerName[64]; /* sname: 0 */
+ # UINT8 BootFileName[128]; /* file: 0 */
+ # UINT8 MagicCookie[4]; /* 99 130 83 99 */
+ # /* 0x63 0x82 0x53 0x63 */
+ # /* options -- hard code ours */
+ #
+ # UINT8 MessageTypeCode; /* 53 */
+ # UINT8 MessageTypeLength; /* 1 */
+ # UINT8 MessageType; /* 1 for DISCOVER */
+ # UINT8 End; /* 255 */
+ # } DHCP;
+ #
+
+ # tuple of 244 zeros
+ # (struct.pack_into would be good here, but requires Python 2.5)
+ sendData = [0] * 244
+
+ transactionID = os.urandom(4)
+ macAddress = MyDistro.GetMacAddress()
+
+ # Opcode = 1
+ # HardwareAddressType = 1 (ethernet/MAC)
+ # HardwareAddressLength = 6 (ethernet/MAC/48 bits)
+ for a in range(0, 3):
+ sendData[a] = [1, 1, 6][a]
+
+ # fill in transaction id (random number to ensure response matches request)
+ for a in range(0, 4):
+ sendData[4 + a] = Ord(transactionID[a])
+
+ LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
+
+ # fill in ClientHardwareAddress
+ for a in range(0, 6):
+ sendData[0x1C + a] = Ord(macAddress[a])
+
+ # DHCP Magic Cookie: 99, 130, 83, 99
+ # MessageTypeCode = 53 DHCP Message Type
+ # MessageTypeLength = 1
+ # MessageType = DHCPDISCOVER
+ # End = 255 DHCP_END
+ for a in range(0, 8):
+ sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
+ return array.array("B", sendData)
+
+ def IntegerToIpAddressV4String(self, a):
+ """
+ Build DHCP request string.
+ """
+ return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
+
+ def RouteAdd(self, net, mask, gateway):
+ """
+ Add specified route using /sbin/route add -net.
+ """
+ net = self.IntegerToIpAddressV4String(net)
+ mask = self.IntegerToIpAddressV4String(mask)
+ gateway = self.IntegerToIpAddressV4String(gateway)
+ Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway,chk_err=False)
+
+ def HandleDhcpResponse(self, sendData, receiveBuffer):
+ """
+ Parse DHCP response:
+ Set default gateway.
+ Set default routes.
+ Retrieve endpoint server.
+ Returns endpoint server or None on error.
+ """
+ LogIfVerbose("HandleDhcpResponse")
+ bytesReceived = len(receiveBuffer)
+ if bytesReceived < 0xF6:
+ Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
+ return None
+
+ LogIfVerbose("BytesReceived: " + hex(bytesReceived))
+ LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
+
+ # check transactionId, cookie, MAC address
+ # cookie should never mismatch
+ # transactionId and MAC address may mismatch if we see a response meant from another machine
+
+ for offsets in [list(range(4, 4 + 4)), list(range(0x1C, 0x1C + 6)), list(range(0xEC, 0xEC + 4))]:
+ for offset in offsets:
+ sentByte = Ord(sendData[offset])
+ receivedByte = Ord(receiveBuffer[offset])
+ if sentByte != receivedByte:
+ LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
+ LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
+ LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
+ LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
+ LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
+ LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
+ LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
+ return None
+ endpoint = None
+
+ #
+ # Walk all the returned options, parsing out what we need, ignoring the others.
+ # We need the custom option 245 to find the the endpoint we talk to,
+ # as well as, to handle some Linux DHCP client incompatibilities,
+ # options 3 for default gateway and 249 for routes. And 255 is end.
+ #
+
+ i = 0xF0 # offset to first option
+ while i < bytesReceived:
+ option = Ord(receiveBuffer[i])
+ length = 0
+ if (i + 1) < bytesReceived:
+ length = Ord(receiveBuffer[i + 1])
+ LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
+ if option == 255:
+ LogIfVerbose("DHCP packet ended at offset " + hex(i))
+ break
+ elif option == 249:
+ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+ LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
+ if length < 5:
+ Error("Data too small for option " + str(option))
+ j = i + 2
+ while j < (i + length + 2):
+ maskLengthBits = Ord(receiveBuffer[j])
+ maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
+ mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
+ j += 1
+ net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
+ net <<= (32 - maskLengthBytes * 8)
+ net &= mask
+ j += maskLengthBytes
+ gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
+ j += 4
+ self.RouteAdd(net, mask, gateway)
+ if j != (i + length + 2):
+ Error("HandleDhcpResponse: Unable to parse routes")
+ elif option == 3 or option == 245:
+ if i + 5 < bytesReceived:
+ if length != 4:
+ Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
+ return None
+ gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
+ IpAddress = self.IntegerToIpAddressV4String(gateway)
+ if option == 3:
+ self.RouteAdd(0, 0, gateway)
+ name = "DefaultGateway"
+ else:
+ endpoint = IpAddress
+ name = "Windows Azure wire protocol endpoint"
+ LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
+ else:
+ Error("HandleDhcpResponse: Data too small for option " + str(option))
+ else:
+ LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
+ i += length + 2
+ return endpoint
+
+ def DoDhcpWork(self):
+ """
+ Discover the wire server via DHCP option 245.
+ And workaround incompatibility with Windows Azure DHCP servers.
+ """
+ ShortSleep = False # Sleep 1 second before retrying DHCP queries.
+ ifname=None
+
+ sleepDurations = [0, 10, 30, 60, 60]
+ maxRetry = len(sleepDurations)
+ lastTry = (maxRetry - 1)
+ for retry in range(0, maxRetry):
+ try:
+ #Open DHCP port if iptables is enabled.
+ Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT",chk_err=False) # We supress error logging on error.
+ Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT",chk_err=False) # We supress error logging on error.
+ strRetry = str(retry)
+ prefix = "DoDhcpWork: try=" + strRetry
+ LogIfVerbose(prefix)
+ sendData = self.BuildDhcpRequest()
+ LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ missingDefaultRoute = True
+ try:
+ if DistInfo()[0] == 'FreeBSD':
+ missingDefaultRoute = True
+ 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
+ if missingDefaultRoute:
+ # This is required because sending after binding to 0.0.0.0 fails with
+ # network unreachable when the default gateway is not set up.
+ ifname=MyDistro.GetInterfaceName()
+ Log("DoDhcpWork: Missing default route - adding broadcast route for DHCP.")
+ if DistInfo()[0] == 'FreeBSD':
+ Run("route add -net 255.255.255.255 -iface " + ifname,chk_err=False)
+ else:
+ Run("route add 255.255.255.255 dev " + ifname,chk_err=False)
+ if MyDistro.isDHCPEnabled():
+ MyDistro.stopDHCP()
+ sock.bind(("0.0.0.0", 68))
+ sock.sendto(sendData, ("<broadcast>", 67))
+ sock.settimeout(10)
+ Log("DoDhcpWork: Setting socket.timeout=10, entering recv")
+ receiveBuffer = sock.recv(1024)
+ endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
+ if endpoint == None:
+ LogIfVerbose("DoDhcpWork: No endpoint found")
+ if endpoint != None or retry == lastTry:
+ if endpoint != None:
+ self.SendData = sendData
+ self.DhcpResponse = receiveBuffer
+ if retry == lastTry:
+ LogIfVerbose("DoDhcpWork: try=" + strRetry)
+ return endpoint
+ sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
+ LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
+ time.sleep(sleepDuration)
+ except Exception, e:
+ ErrorWithPrefix(prefix, str(e))
+ ErrorWithPrefix(prefix, traceback.format_exc())
+ finally:
+ sock.close()
+ if missingDefaultRoute:
+ #We added this route - delete it
+ Log("DoDhcpWork: Removing broadcast route for DHCP.")
+ if DistInfo()[0] == 'FreeBSD':
+ Run("route del -net 255.255.255.255 -iface " + ifname,chk_err=False)
+ else:
+ Run("route del 255.255.255.255 dev " + ifname,chk_err=False) # We supress error logging on error.
+ if MyDistro.isDHCPEnabled():
+ MyDistro.startDHCP()
+ return None
+
+ def UpdateAndPublishHostName(self, name):
+ """
+ Set hostname locally and publish to iDNS
+ """
+ Log("Setting host name: " + name)
+ MyDistro.publishHostname(name)
+ ethernetInterface = MyDistro.GetInterfaceName()
+ MyDistro.RestartInterface(ethernetInterface)
+ self.RestoreRoutes()
+
+ def RestoreRoutes(self):
+ """
+ If there is a DHCP response, then call HandleDhcpResponse.
+ """
+ if self.SendData != None and self.DhcpResponse != None:
+ self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
+
+ def UpdateGoalState(self):
+ """
+ Retreive goal state information from endpoint server.
+ Parse xml and initialize Agent.GoalState object.
+ Return object or None on error.
+ """
+ goalStateXml = None
+ maxRetry = 9
+ log = NoLog
+ for retry in range(1, maxRetry + 1):
+ strRetry = str(retry)
+ log("retry UpdateGoalState,retry=" + strRetry)
+ goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
+ if goalStateXml != None:
+ break
+ log = Log
+ time.sleep(retry)
+ if not goalStateXml:
+ Error("UpdateGoalState failed.")
+ return
+ Log("Retrieved GoalState from Windows Azure Fabric.")
+ self.GoalState = GoalState(self).Parse(goalStateXml)
+ return self.GoalState
+
+ def ReportReady(self):
+ """
+ Send health report 'Ready' to server.
+ This signals the fabric that our provosion is completed,
+ and the host is ready for operation.
+ """
+ counter = (self.HealthReportCounter + 1) % 1000000
+ self.HealthReportCounter = counter
+ healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
+ + self.GoalState.Incarnation
+ + "</GoalStateIncarnation><Container><ContainerId>"
+ + self.GoalState.ContainerId
+ + "</ContainerId><RoleInstanceList><Role><InstanceId>"
+ + self.GoalState.RoleInstanceId
+ + "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>")
+ a = self.HttpPostWithHeaders("/machine?comp=health", healthReport)
+ if a != None:
+ return a.getheader("x-ms-latest-goal-state-incarnation-number")
+ return None
+
+ def ReportNotReady(self, status, desc):
+ """
+ Send health report 'Provisioning' to server.
+ This signals the fabric that our provosion is starting.
+ """
+ healthReport = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><Health xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"><GoalStateIncarnation>"
+ + self.GoalState.Incarnation
+ + "</GoalStateIncarnation><Container><ContainerId>"
+ + self.GoalState.ContainerId
+ + "</ContainerId><RoleInstanceList><Role><InstanceId>"
+ + self.GoalState.RoleInstanceId
+ + "</InstanceId><Health><State>NotReady</State>"
+ + "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>"
+ + "</Health></Role></RoleInstanceList></Container></Health>")
+ a = self.HttpPostWithHeaders("/machine?comp=health", healthReport)
+ if a != None:
+ return a.getheader("x-ms-latest-goal-state-incarnation-number")
+ return None
+
+ def ReportRoleProperties(self, thumbprint):
+ """
+ Send roleProperties and thumbprint to server.
+ """
+ roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>"
+ + "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>"
+ + "<RoleInstances><RoleInstance>"
+ + "<Id>" + self.GoalState.RoleInstanceId + "</Id>"
+ + "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>"
+ + "</RoleInstance></RoleInstances></Container></RoleProperties>")
+ a = self.HttpPostWithHeaders("/machine?comp=roleProperties",
+ roleProperties)
+ Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
+ return a
+
+ def LoadBalancerProbeServer_Shutdown(self):
+ """
+ Shutdown the LoadBalancerProbeServer.
+ """
+ if self.LoadBalancerProbeServer != None:
+ self.LoadBalancerProbeServer.shutdown()
+ self.LoadBalancerProbeServer = None
+
+ def GenerateTransportCert(self):
+ """
+ Create ssl certificate for https communication with endpoint server.
+ """
+ Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
+ cert = ""
+ for line in GetFileContents("TransportCert.pem").split('\n'):
+ if not "CERTIFICATE" in line:
+ cert += line.rstrip()
+ return cert
+
+ def DoVmmStartup(self):
+ """
+ Spawn the VMM startup script.
+ """
+ Log("Starting Microsoft System Center VMM Initialization Process")
+ pid = subprocess.Popen(["/bin/bash","/mnt/cdrom/secure/"+VMM_STARTUP_SCRIPT_NAME,"-p /mnt/cdrom/secure/ "]).pid
+ time.sleep(5)
+ sys.exit(0)
+
+ def TryUnloadAtapiix(self):
+ """
+ If global modloaded is True, then we loaded the ata_piix kernel module, unload it.
+ """
+ if modloaded:
+ Run("rmmod ata_piix.ko",chk_err=False)
+ Log("Unloaded ata_piix.ko driver for ATAPI CD-ROM")
+
+ def TryLoadAtapiix(self):
+ """
+ Load the ata_piix kernel module if it exists.
+ If successful, set global modloaded to True.
+ If unable to load module leave modloaded False.
+ """
+ global modloaded
+ modloaded=False
+ retcode,krn=RunGetOutput('uname -r')
+ krn_pth='/lib/modules/'+krn.strip('\n')+'/kernel/drivers/ata/ata_piix.ko'
+ if Run("lsmod | grep ata_piix",chk_err=False) == 0 :
+ Log("Module " + krn_pth + " driver for ATAPI CD-ROM is already present.")
+ return 0
+ if retcode:
+ Error("Unable to provision: Failed to call uname -r")
+ return "Unable to provision: Failed to call uname"
+ if os.path.isfile(krn_pth):
+ retcode,output=RunGetOutput("insmod " + krn_pth,chk_err=False)
+ else:
+ Log("Module " + krn_pth + " driver for ATAPI CD-ROM does not exist.")
+ return 1
+ if retcode != 0:
+ Error('Error calling insmod for '+ krn_pth + ' driver for ATAPI CD-ROM')
+ return retcode
+ time.sleep(1)
+ # check 3 times if the mod is loaded
+ for i in range(3):
+ if Run('lsmod | grep ata_piix'):
+ continue
+ else :
+ modloaded=True
+ break
+ if not modloaded:
+ Error('Unable to load '+ krn_pth + ' driver for ATAPI CD-ROM')
+ return 1
+
+ Log("Loaded " + krn_pth + " driver for ATAPI CD-ROM")
+
+ # we have succeeded loading the ata_piix mod if it can be done.
+
+ def SearchForVMMStartup(self):
+ """
+ Search for a DVD/CDROM containing VMM's VMM_CONFIG_FILE_NAME.
+ Call TryLoadAtapiix in case we must load the ata_piix module first.
+
+ If VMM_CONFIG_FILE_NAME is found, call DoVmmStartup.
+ Else, return to Azure Provisioning process.
+ """
+ self.TryLoadAtapiix()
+ if os.path.exists('/mnt/cdrom/secure') == False:
+ CreateDir("/mnt/cdrom/secure", "root", 0700)
+ mounted=False
+ 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:
+ continue
+ dvd = '/dev/'+dvds.group(0)
+ if Run("LC_ALL=C fdisk -l " + dvd + " | grep Disk",chk_err=False):
+ continue # Not mountable
+ else:
+ for retry in range(1,6):
+ retcode,output=RunGetOutput("mount -v " + dvd + " /mnt/cdrom/secure")
+ Log(output[:-1])
+ if retcode == 0:
+ Log("mount succeeded on attempt #" + str(retry) )
+ mounted=True
+ break
+ if 'is already mounted on /mnt/cdrom/secure' in output:
+ Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
+ mounted=True
+ break
+ Log("mount failed on attempt #" + str(retry) )
+ Log("mount loop sleeping 5...")
+ time.sleep(5)
+ if not mounted:
+ # unable to mount
+ continue
+ if not os.path.isfile("/mnt/cdrom/secure/"+VMM_CONFIG_FILE_NAME):
+ #nope - mount the next drive
+ if mounted:
+ Run("umount "+dvd,chk_err=False)
+ mounted=False
+ continue
+ else : # it is the vmm startup
+ self.DoVmmStartup()
+
+ Log("VMM Init script not found. Provisioning for Azure")
+ return
+
+ def Provision(self):
+ """
+ Responible for:
+ Regenerate ssh keys,
+ Mount, read, and parse ovfenv.xml from provisioning dvd rom
+ Process the ovfenv.xml info
+ Call ReportRoleProperties
+ If configured, delete root password.
+ Return None on success, error string on error.
+ """
+ enabled = Config.get("Provisioning.Enabled")
+ if enabled != None and enabled.lower().startswith("n"):
+ return
+ Log("Provisioning image started.")
+ type = Config.get("Provisioning.SshHostKeyPairType")
+ if type == None:
+ type = "rsa"
+ regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
+ if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
+ Run("rm -f /etc/ssh/ssh_host_*key*")
+ 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 :
+ 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:
+ return out
+ for i in range(10): # we may have to wait
+ if os.path.exists(dvd):
+ break
+ Log("Waiting for DVD - sleeping 1 - "+str(i+1)+" try...")
+ time.sleep(1)
+ if os.path.exists('/mnt/cdrom/secure') == False:
+ CreateDir("/mnt/cdrom/secure", "root", 0700)
+ #begin mount loop - 5 tries - 5 sec wait between
+ for retry in range(1,6):
+ location='/mnt/cdrom/secure'
+ retcode,output=MyDistro.mountDVD(dvd,location)
+ Log(output[:-1])
+ if retcode == 0:
+ Log("mount succeeded on attempt #" + str(retry) )
+ break
+ if 'is already mounted on /mnt/cdrom/secure' in output:
+ Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
+ break
+ Log("mount failed on attempt #" + str(retry) )
+ Log("mount loop sleeping 5...")
+ time.sleep(5)
+ if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
+ Error("Unable to provision: Missing ovf-env.xml on DVD.")
+ return "Failed to retrieve provisioning data (0x02)."
+ ovfxml = (GetFileContents(u"/mnt/cdrom/secure/ovf-env.xml",asbin=False)) # use unicode here to ensure correct codec gets used.
+ 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()
+ error = None
+ if ovfxml != None:
+ Log("Provisioning image using OVF settings in the DVD.")
+ ovfobj = OvfEnv().Parse(ovfxml)
+ if ovfobj != None:
+ error = ovfobj.Process()
+ if error :
+ Error ("Provisioning image FAILED " + error)
+ return ("Provisioning image FAILED " + error)
+ Log("Ovf XML process finished")
+ # 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)
+ delRootPass = Config.get("Provisioning.DeleteRootPassword")
+ if delRootPass != None and delRootPass.lower().startswith("y"):
+ MyDistro.deleteRootPassword()
+ Log("Provisioning image completed.")
+ return error
+
+ def Run(self):
+ """
+ Called by 'waagent -daemon.'
+ Main loop to process the goal state. State is posted every 25 seconds
+ when provisioning has been completed.
+
+ Search for VMM enviroment, start VMM script if found.
+ Perform DHCP and endpoint server discovery by calling DoDhcpWork().
+ Check wire protocol versions.
+ Set SCSI timeout on root device.
+ Call GenerateTransportCert() to create ssl certs for server communication.
+ Call UpdateGoalState().
+ If not provisioned, call ReportNotReady("Provisioning", "Starting")
+ Call Provision(), set global provisioned = True if successful.
+ Call goalState.Process()
+ Start LBProbeServer if indicated in waagent.conf.
+ Start the StateConsumer if indicated in waagent.conf.
+ ReportReady if provisioning is complete.
+ If provisioning failed, call ReportNotReady("ProvisioningFailed", provisionError)
+ """
+ SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
+
+ # Determine if we are in VMM. Spawn VMM_STARTUP_SCRIPT_NAME if found.
+ self.SearchForVMMStartup()
+ 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: " + 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"))
+ except:
+ pass
+
+ Log("Probing for Windows Azure environment.")
+ self.Endpoint = self.DoDhcpWork()
+
+ if self.Endpoint == None:
+ Log("Windows Azure environment not detected.")
+ while True:
+ time.sleep(60)
+
+ Log("Discovered Windows Azure endpoint: " + self.Endpoint)
+ if not self.CheckVersions():
+ Error("Agent.CheckVersions failed")
+ sys.exit(1)
+
+ self.EnvMonitor = EnvMonitor()
+
+ # Set SCSI timeout on SCSI disks
+ MyDistro.initScsiDiskTimeout()
+ global provisioned
+ global provisionError
+
+ global Openssl
+ Openssl = Config.get("OS.OpensslPath")
+ if Openssl == None:
+ Openssl = "openssl"
+
+ self.TransportCert = self.GenerateTransportCert()
+
+ eventMonitor = None
+ incarnation = None # goalStateIncarnationFromHealthReport
+ currentPort = None # loadBalancerProbePort
+ goalState = None # self.GoalState, instance of GoalState
+ provisioned = os.path.exists(LibDir + "/provisioned")
+ program = Config.get("Role.StateConsumer")
+ provisionError = None
+ lbProbeResponder = True
+ setting = Config.get("LBProbeResponder")
+ if setting != None and setting.lower().startswith("n"):
+ lbProbeResponder = False
+ while True:
+ if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
+ try:
+ goalState = self.UpdateGoalState()
+ except HttpResourceGoneError as e:
+ Warn("Incarnation is out of date:{0}".format(e))
+ incarnation = None
+ continue
+
+ if goalState == None :
+ Warn("Failed to fetch goalstate")
+ continue
+
+ if provisioned == False:
+ self.ReportNotReady("Provisioning", "Starting")
+
+ goalState.Process()
+
+ if provisioned == False:
+ provisionError = self.Provision()
+ if provisionError == None :
+ provisioned = True
+ SetFileContents(LibDir + "/provisioned", "")
+ lastCtime = "NOTFIND"
+ try:
+ walaConfigFile = MyDistro.getConfigurationPath()
+ lastCtime = time.ctime(os.path.getctime(walaConfigFile))
+ except:
+ pass
+ #Get Ctime of wala config, can help identify the base image of this VM
+ AddExtensionEvent(name="WALA",op=WALAEventOperation.Provision,isSuccess=True,
+ message="WALA Config Ctime:"+lastCtime)
+
+ executeCustomData = Config.get("Provisioning.ExecuteCustomData")
+ if executeCustomData != None and executeCustomData.lower().startswith("y"):
+ if os.path.exists(LibDir + '/CustomData'):
+ Run('chmod +x ' + LibDir + '/CustomData')
+ Run(LibDir + '/CustomData')
+ else:
+ Error(LibDir + '/CustomData does not exist.')
+
+ #
+ # only one port supported
+ # restart server if new port is different than old port
+ # stop server if no longer a port
+ #
+ goalPort = goalState.LoadBalancerProbePort
+ if currentPort != goalPort:
+ try:
+ self.LoadBalancerProbeServer_Shutdown()
+ currentPort = goalPort
+ if currentPort != None and lbProbeResponder == True:
+ self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)
+ if self.LoadBalancerProbeServer == None :
+ lbProbeResponder = False
+ Log("Unable to create LBProbeResponder.")
+ except Exception, e:
+ Error("Failed to launch LBProbeResponder: {0}".format(e))
+ currentPort = None
+
+ # Report SSH key fingerprint
+ type = Config.get("Provisioning.SshHostKeyPairType")
+ if type == None:
+ type = "rsa"
+
+ host_key_path = "/etc/ssh/ssh_host_" + type + "_key.pub"
+ if(MyDistro.waitForSshHostKey(host_key_path)):
+ 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"]))
+ except OSError, e :
+ ErrorWithPrefix('SharedConfig.Parse','Exception: '+ str(e) +' occured launching ' + program )
+ program = None
+
+ sleepToReduceAccessDenied = 3
+ time.sleep(sleepToReduceAccessDenied)
+ if provisionError != None:
+ incarnation = self.ReportNotReady("ProvisioningFailed", provisionError)
+ else:
+ incarnation = self.ReportReady()
+ # Process our extensions.
+ if goalState.ExtensionsConfig == None and goalState.ExtensionsConfigXml != None :
+ goalState.ExtensionsConfig = ExtensionsConfig().Parse(goalState.ExtensionsConfigXml)
+
+ # report the status/heartbeat results of extension processing
+ if goalState.ExtensionsConfig != None :
+ goalState.ExtensionsConfig.ReportHandlerStatus()
+
+ if not eventMonitor:
+ eventMonitor = WALAEventMonitor(self.HttpPostWithHeaders)
+ eventMonitor.StartEventsLoop()
+
+ time.sleep(25 - sleepToReduceAccessDenied)
+
+
+WaagentLogrotate = """\
+/var/log/waagent.log {
+ monthly
+ rotate 6
+ notifempty
+ missingok
+}
+"""
+
+def GetMountPoint(mountlist, device):
+ """
+ Example of mountlist:
+ /dev/sda1 on / type ext4 (rw)
+ proc on /proc type proc (rw)
+ sysfs on /sys type sysfs (rw)
+ devpts on /dev/pts type devpts (rw,gid=5,mode=620)
+ tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
+ none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
+ /dev/sdb1 on /mnt/resource type ext4 (rw)
+ """
+ if (mountlist and device):
+ for entry in mountlist.split('\n'):
+ if(re.search(device, entry)):
+ tokens = entry.split()
+ #Return the 3rd column of this line
+ return tokens[2] if len(tokens) > 2 else None
+ return None
+
+def FindInLinuxKernelCmdline(option):
+ """
+ Return match object if 'option' is present in the kernel boot options
+ of the grub configuration.
+ """
+ m=None
+ matchs=r'^.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?'+option+r'.*$'
+ try:
+ m=FindStringInFile(MyDistro.grubKernelBootOptionsFile,matchs)
+ except IOError, e:
+ Error('FindInLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
+
+ return m
+
+def AppendToLinuxKernelCmdline(option):
+ """
+ Add 'option' to the kernel boot options of the grub configuration.
+ """
+ if not FindInLinuxKernelCmdline(option):
+ src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r')(.*?)("?)$'
+ rep=r'\1\2 '+ option + r'\3'
+ try:
+ ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
+ except IOError, e :
+ Error('AppendToLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
+ return 1
+ Run("update-grub",chk_err=False)
+ return 0
+
+def RemoveFromLinuxKernelCmdline(option):
+ """
+ Remove 'option' to the kernel boot options of the grub configuration.
+ """
+ if FindInLinuxKernelCmdline(option):
+ src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?)('+option+r')(.*?)("?)$'
+ rep=r'\1\3\4'
+ try:
+ ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
+ except IOError, e :
+ Error('RemoveFromLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
+ return 1
+ Run("update-grub",chk_err=False)
+ return 0
+
+def FindStringInFile(fname,matchs):
+ """
+ Return match object if found in file.
+ """
+ try:
+ ms=re.compile(matchs)
+ for l in (open(fname,'r')).readlines():
+ m=re.search(ms,l)
+ if m:
+ return m
+ except:
+ raise
+
+ return None
+
+def ReplaceStringInFile(fname,src,repl):
+ """
+ Replace 'src' with 'repl' in file.
+ """
+ try:
+ sr=re.compile(src)
+ if FindStringInFile(fname,src):
+ updated=''
+ for l in (open(fname,'r')).readlines():
+ n=re.sub(sr,repl,l)
+ updated+=n
+ ReplaceFileContentsAtomic(fname,updated)
+ except :
+ raise
+ return
+
+def ApplyVNUMAWorkaround():
+ """
+ If kernel version has NUMA bug, add 'numa=off' to
+ kernel boot options.
+ """
+ VersionParts = platform.release().replace('-', '.').split('.')
+ if int(VersionParts[0]) > 2:
+ return
+ if int(VersionParts[1]) > 6:
+ return
+ if int(VersionParts[2]) > 37:
+ return
+ if AppendToLinuxKernelCmdline("numa=off") == 0 :
+ Log("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
+ else :
+ "Error adding 'numa=off'. NUMA has not been disabled."
+
+def RevertVNUMAWorkaround():
+ """
+ Remove 'numa=off' from kernel boot options.
+ """
+ if RemoveFromLinuxKernelCmdline("numa=off") == 0 :
+ Log('NUMA has been re-enabled')
+ else :
+ Log('NUMA has not been re-enabled')
+
+def Install():
+ """
+ Install the agent service.
+ Check dependencies.
+ Create /etc/waagent.conf and move old version to
+ /etc/waagent.conf.old
+ Copy RulesFiles to /var/lib/waagent
+ Create /etc/logrotate.d/waagent
+ Set /etc/ssh/sshd_config ClientAliveInterval to 180
+ Call ApplyVNUMAWorkaround()
+ """
+ if MyDistro.checkDependencies():
+ return 1
+ os.chmod(sys.argv[0], 0755)
+ SwitchCwd()
+ for a in RulesFiles:
+ if os.path.isfile(a):
+ if os.path.isfile(GetLastPathElement(a)):
+ os.remove(GetLastPathElement(a))
+ shutil.move(a, ".")
+ Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
+ MyDistro.registerAgentService()
+ if os.path.isfile("/etc/waagent.conf"):
+ try:
+ os.remove("/etc/waagent.conf.old")
+ except:
+ pass
+ try:
+ os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
+ Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
+ except:
+ pass
+ SetFileContents("/etc/waagent.conf", MyDistro.waagent_conf_file)
+ SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
+ filepath = "/etc/ssh/sshd_config"
+ ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
+ a.startswith("ClientAliveInterval"),
+ GetFileContents(filepath).split('\n'))) + "\nClientAliveInterval 180\n")
+ Log("Configured SSH client probing to keep connections alive.")
+ ApplyVNUMAWorkaround()
+ return 0
+
+def GetMyDistro(dist_class_name=''):
+ """
+ Return MyDistro object.
+ NOTE: Logging is not initialized at this point.
+ """
+ if dist_class_name == '':
+ if 'Linux' in platform.system():
+ Distro=DistInfo()[0]
+ 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
+ if not globals().has_key(dist_class_name):
+ print Distro+' is not a supported distribution.'
+ 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.
+ Generic implementation Creates directories and
+ files /etc/waagent.conf, /etc/init.d/waagent, /usr/sbin/waagent,
+ /etc/logrotate.d/waagent, /etc/sudoers.d/waagent under buildroot.
+ Copies generated files waagent.conf, into place and exits.
+ """
+ MyDistro=GetMyDistro()
+ if MyDistro == None :
+ sys.exit(1)
+ MyDistro.packagedInstall(buildroot)
+
+def LibraryInstall(buildroot):
+ pass
+
+def Uninstall():
+ """
+ Uninstall the agent service.
+ Copy RulesFiles back to original locations.
+ Delete agent-related files.
+ Call RevertVNUMAWorkaround().
+ """
+ SwitchCwd()
+ for a in RulesFiles:
+ if os.path.isfile(GetLastPathElement(a)):
+ try:
+ shutil.move(GetLastPathElement(a), a)
+ Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
+ except:
+ pass
+ MyDistro.unregisterAgentService()
+ MyDistro.uninstallDeleteFiles()
+ RevertVNUMAWorkaround()
+ return 0
+
+def Deprovision(force, deluser):
+ """
+ Remove user accounts created by provisioning.
+ Disables root password if Provisioning.DeleteRootPassword = 'y'
+ Stop agent service.
+ Remove SSH host keys if they were generated by the provision.
+ Set hostname to 'localhost.localdomain'.
+ Delete cached system configuration files in /var/lib and /var/lib/waagent.
+ """
+
+ #Append blank line at the end of file, so the ctime of this file is changed every time
+ Run("echo ''>>"+ MyDistro.getConfigurationPath())
+
+ SwitchCwd()
+ ovfxml = GetFileContents(LibDir+"/ovf-env.xml")
+ ovfobj = None
+ if ovfxml != None:
+ ovfobj = OvfEnv().Parse(ovfxml, True)
+
+ print("WARNING! The waagent service will be stopped.")
+ print("WARNING! All SSH host key pairs will be deleted.")
+ print("WARNING! Cached DHCP leases will be deleted.")
+ MyDistro.deprovisionWarnUser()
+ delRootPass = Config.get("Provisioning.DeleteRootPassword")
+ if delRootPass != None and delRootPass.lower().startswith("y"):
+ print("WARNING! root password will be disabled. You will not be able to login as root.")
+
+ if ovfobj != None and deluser == True:
+ print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
+
+ if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
+ return 1
+
+ MyDistro.stopAgentService()
+
+ # Remove SSH host keys
+ regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
+ if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
+ Run("rm -f /etc/ssh/ssh_host_*key*")
+
+ # Remove root password
+ if delRootPass != None and delRootPass.lower().startswith("y"):
+ MyDistro.deleteRootPassword()
+ # Remove distribution specific networking configuration
+
+ MyDistro.publishHostname('localhost.localdomain')
+ MyDistro.deprovisionDeleteFiles()
+ if deluser == True:
+ MyDistro.DeleteAccount(ovfobj.UserName)
+ return 0
+
+def SwitchCwd():
+ """
+ Switch to cwd to /var/lib/waagent.
+ Create if not present.
+ """
+ CreateDir(LibDir, "root", 0700)
+ os.chdir(LibDir)
+
+def Usage():
+ """
+ Print the arguments to waagent.
+ """
+ print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
+ return 0
+
+
+
+def main():
+ """
+ Instantiate MyDistro, exit if distro class is not defined.
+ Parse command-line arguments, exit with usage() on error.
+ Instantiate ConfigurationProvider.
+ Call appropriate non-daemon methods and exit.
+ If daemon mode, enter Agent.Run() loop.
+ """
+ if GuestAgentVersion == "":
+ print("WARNING! This is a non-standard agent that does not include a valid version string.")
+
+ if len(sys.argv) == 1:
+ sys.exit(Usage())
+
+ LoggerInit('/var/log/waagent.log','/dev/console')
+ global LinuxDistro
+ LinuxDistro=DistInfo()[0]
+
+ #The platform.py lib has issue with detecting oracle linux distribution.
+ #Merge the following patch provided by oracle as a temparory fix.
+ if os.path.exists("/etc/oracle-release"):
+ LinuxDistro="Oracle Linux"
+
+ global MyDistro
+ MyDistro=GetMyDistro()
+ if MyDistro == None :
+ sys.exit(1)
+ args = []
+ conf_file = None
+ global force
+ force = False
+ for a in sys.argv[1:]:
+ if re.match("^([-/]*)(help|usage|\?)", a):
+ sys.exit(Usage())
+ elif re.match("^([-/]*)version", a):
+ print(GuestAgentVersion + " running on " + LinuxDistro)
+ sys.exit(0)
+ elif re.match("^([-/]*)verbose", a):
+ myLogger.verbose = True
+ elif re.match("^([-/]*)force", a):
+ force = True
+ elif re.match("^(?:[-/]*)conf=.+", a):
+ conf_file = re.match("^(?:[-/]*)conf=(.+)", a).groups()[0]
+ elif re.match("^([-/]*)(setup|install)", a):
+ sys.exit(MyDistro.Install())
+ elif re.match("^([-/]*)(uninstall)", a):
+ sys.exit(Uninstall())
+ else:
+ args.append(a)
+ global Config
+ Config = ConfigurationProvider(conf_file)
+
+ logfile = Config.get("Logs.File")
+ if logfile is not None:
+ myLogger.file_path = logfile
+ logconsole = Config.get("Logs.Console")
+ if logconsole is not None and logconsole.lower().startswith("n"):
+ myLogger.con_path = None
+ verbose = Config.get("Logs.Verbose")
+ if verbose != None and verbose.lower().startswith("y"):
+ myLogger.verbose=True
+ global daemon
+ daemon = False
+ for a in args:
+ if re.match("^([-/]*)deprovision\+user", a):
+ sys.exit(Deprovision(force, True))
+ elif re.match("^([-/]*)deprovision", a):
+ sys.exit(Deprovision(force, False))
+ elif re.match("^([-/]*)daemon", a):
+ daemon = True
+ elif re.match("^([-/]*)serialconsole", a):
+ AppendToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
+ Log("Configured kernel to use ttyS0 as the boot console.")
+ sys.exit(0)
+ else:
+ print("Invalid command line parameter:" + a)
+ sys.exit(1)
+
+ if daemon == False:
+ sys.exit(Usage())
+ global modloaded
+ modloaded = False
+ try:
+ SwitchCwd()
+ Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
+ if IsLinux():
+ Log("Linux Distribution Detected : " + LinuxDistro)
+ global WaAgent
+ WaAgent = Agent()
+ WaAgent.Run()
+ except Exception, e:
+ Error(traceback.format_exc())
+ Error("Exception: " + str(e))
+ sys.exit(1)
+
+if __name__ == '__main__' :
+ main()