3
# Windows Azure Linux Agent
5
# Copyright 2014 Microsoft Corporation
7
# Licensed under the Apache License, Version 2.0 (the "License");
8
# you may not use this file except in compliance with the License.
9
# You may obtain a copy of the License at
11
# http://www.apache.org/licenses/LICENSE-2.0
13
# Unless required by applicable law or agreed to in writing, software
14
# distributed under the License is distributed on an "AS IS" BASIS,
15
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
# See the License for the specific language governing permissions and
17
# limitations under the License.
19
# Requires Python 2.4+ and Openssl 1.0+
21
# Implements parts of RFC 2131, 1541, 1497 and
22
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
23
# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
26
if __name__ == '__main__' :
28
import azurelinuxagent.agent as agent
52
import xml.dom.minidom
58
import xml.sax.saxutils
60
if not hasattr(subprocess,'check_output'):
61
def check_output(*popenargs, **kwargs):
62
r"""Backport from subprocess module from python 2.7"""
63
if 'stdout' in kwargs:
64
raise ValueError('stdout argument not allowed, it will be overridden.')
65
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
66
output, unused_err = process.communicate()
67
retcode = process.poll()
69
cmd = kwargs.get("args")
72
raise subprocess.CalledProcessError(retcode, cmd, output=output)
75
# Exception classes used by this module.
76
class CalledProcessError(Exception):
77
def __init__(self, returncode, cmd, output=None):
78
self.returncode = returncode
82
return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
84
subprocess.check_output=check_output
85
subprocess.CalledProcessError=CalledProcessError
87
GuestAgentName = "WALinuxAgent"
88
GuestAgentLongName = "Windows Azure Linux Agent"
89
GuestAgentVersion = "WALinuxAgent-2.0.8"
90
ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol.
97
ExtensionChildren = []
98
VMM_STARTUP_SCRIPT_NAME='install'
99
VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml'
101
RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
102
"/etc/udev/rules.d/70-persistent-net.rules" ]
103
VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
104
EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
106
LibDir = "/var/lib/waagent"
109
global provisionError
111
HandlerStatusToAggStatus = {"installed":"Installing", "enabled":"Ready", "unintalled":"NotReady", "disabled":"NotReady"}
115
# Windows Azure Linux Agent Configuration
118
Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status
119
# to the endpoint server.
120
Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
121
Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
123
Provisioning.Enabled=y #
124
Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
125
Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
126
Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
127
Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
129
ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
130
ResourceDisk.Filesystem=ext4 # Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
131
ResourceDisk.MountPoint=/mnt/resource #
132
ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
133
ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
135
LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
137
Logs.Verbose=n # Enable verbose logs
139
OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
140
OS.OpensslPath=None # If "None", the system default version is used.
142
README_FILENAME="DATALOSS_WARNING_README.txt"
143
README_FILECONTENT="""\
144
WARNING: THIS IS A TEMPORARY DISK.
146
Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT.
148
Please do not use this disk for storing any personal or application data.
150
For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx
153
############################################################
154
# BEGIN DISTRO CLASS DEFS
155
############################################################
156
############################################################
158
############################################################
159
class AbstractDistro(object):
161
AbstractDistro defines a skeleton neccesary for a concrete Distro class.
163
Generic methods and attributes are kept here, distribution specific attributes
164
and behavior are to be placed in the concrete child named distroDistro, where
165
distro is the string returned by calling python platform.linux_distribution()[0].
166
So for CentOS the derived class is called 'centosDistro'.
171
Generic Attributes go here. These are based on 'majority rules'.
172
This __init__() may be called or overriden by the child.
174
self.agent_service_name = os.path.basename(sys.argv[0])
176
self.service_cmd='/usr/sbin/service'
177
self.ssh_service_restart_option='restart'
178
self.ssh_service_name='ssh'
179
self.ssh_config_file='/etc/ssh/sshd_config'
180
self.hostname_file_path='/etc/hostname'
181
self.dhcp_client_name='dhclient'
182
self.requiredDeps = [ 'route', 'shutdown', 'ssh-keygen', 'useradd'
183
, 'openssl', 'sfdisk', 'fdisk', 'mkfs', 'chpasswd', 'sed', 'grep', 'sudo' ]
184
self.init_script_file='/etc/init.d/waagent'
185
self.agent_package_name='WALinuxAgent'
186
self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log",'/etc/resolv.conf' ]
187
self.agent_files_to_uninstall = ["/etc/waagent.conf", "/etc/logrotate.d/waagent", "/etc/sudoers.d/waagent"]
188
self.grubKernelBootOptionsFile = '/etc/default/grub'
189
self.grubKernelBootOptionsLine = 'GRUB_CMDLINE_LINUX_DEFAULT='
190
self.getpidcmd = 'pidof'
191
self.mount_dvd_cmd = 'mount'
192
self.sudoers_dir_base = '/etc'
193
self.waagent_conf_file = WaagentConf
194
self.shadow_file_mode=0600
195
self.dhcp_enabled = False
197
def isSelinuxSystem(self):
199
Checks and sets self.selinux = True if SELinux is available on system.
201
if self.selinux == None:
202
if Run("which getenforce",chk_err=False):
208
def isSelinuxRunning(self):
210
Calls shell command 'getenforce' and returns True if 'Enforcing'.
212
if self.isSelinuxSystem():
213
return RunGetOutput("getenforce")[1].startswith("Enforcing")
217
def setSelinuxEnforce(self,state):
219
Calls shell command 'setenforce' with 'state' and returns resulting exit code.
221
if self.isSelinuxSystem():
224
return Run("setenforce "+s)
226
def setSelinuxContext(self,path,cn):
228
Calls shell 'chcon' with 'path' and 'cn' context.
231
if self.isSelinuxSystem():
232
return Run('chcon ' + cn + ' ' + path)
234
def setHostname(self,name):
236
Shell call to hostname.
237
Returns resulting exit code.
239
return Run('hostname ' + name)
241
def publishHostname(self,name):
243
Set the contents of the hostname file to 'name'.
247
r=SetFileContents(self.hostname_file_path, name)
248
for f in EtcDhcpClientConfFiles:
249
if os.path.exists(f) and FindStringInFile(f,r'^[^#]*?send\s*host-name.*?(<hostname>|gethostname[(,)])') == None :
250
r=ReplaceFileContentsAtomic('/etc/dhcp/dhclient.conf', "send host-name \"" + name + "\";\n"
251
+ "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents('/etc/dhcp/dhclient.conf').split('\n'))))
256
def installAgentServiceScriptFiles(self):
258
Create the waagent support files for service installation.
259
Called by registerAgentService()
260
Abstract Virtual Function. Over-ridden in concrete Distro classes.
264
def registerAgentService(self):
266
Calls installAgentService to create service files.
267
Shell exec service registration commands. (e.g. chkconfig --add waagent)
268
Abstract Virtual Function. Over-ridden in concrete Distro classes.
272
def uninstallAgentService(self):
274
Call service subsystem to remove waagent script.
275
Abstract Virtual Function. Over-ridden in concrete Distro classes.
279
def unregisterAgentService(self):
281
Calls self.stopAgentService and call self.uninstallAgentService()
283
self.stopAgentService()
284
self.uninstallAgentService()
286
def startAgentService(self):
288
Service call to start the Agent service
290
return Run(self.service_cmd + ' ' + self.agent_service_name + ' start')
292
def stopAgentService(self):
294
Service call to stop the Agent service
296
return Run(self.service_cmd + ' ' + self.agent_service_name + ' stop',False)
298
def restartSshService(self):
300
Service call to re(start) the SSH service
302
sshRestartCmd = self.service_cmd + " " + self.ssh_service_name + " " + self.ssh_service_restart_option
303
retcode = Run(sshRestartCmd)
305
Error("Failed to restart SSH service with return code:" + str(retcode))
308
def sshDeployPublicKey(self,fprint,path):
310
Generic sshDeployPublicKey - over-ridden in some concrete Distro classes due to minor differences in openssl packages deployed
313
SshPubKey = OvfEnv().OpensslToSsh(fprint)
314
if SshPubKey != None:
315
AppendFileContents(path, SshPubKey)
317
Error("Failed: " + fprint + ".crt -> " + path)
321
def checkPackageInstalled(self,p):
323
Query package database for prescence of an installed package.
324
Abstract Virtual Function. Over-ridden in concrete Distro classes.
328
def checkPackageUpdateable(self,p):
330
Online check if updated package of walinuxagent is available.
331
Abstract Virtual Function. Over-ridden in concrete Distro classes.
335
def deleteRootPassword(self):
337
Generic root password removal.
339
filepath="/etc/shadow"
340
ReplaceFileContentsAtomic(filepath,"root:*LOCK*:14600::::::\n"
341
+ "\n".join(filter(lambda a: not a.startswith("root:"),GetFileContents(filepath).split('\n'))))
342
os.chmod(filepath,self.shadow_file_mode)
343
if self.isSelinuxSystem():
344
self.setSelinuxContext(filepath,'system_u:object_r:shadow_t:s0')
345
Log("Root password deleted.")
348
def changePass(self,user,password):
349
return RunSendStdin("chpasswd",(user + ":" + password + "\n"))
351
def load_ata_piix(self):
352
return WaAgent.TryLoadAtapiix()
354
def unload_ata_piix(self):
356
Generic function to remove ata_piix.ko.
358
return WaAgent.TryUnloadAtapiix()
360
def deprovisionWarnUser(self):
362
Generic user warnings used at deprovision.
364
print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
366
def deprovisionDeleteFiles(self):
368
Files to delete when VM is deprovisioned
370
for a in VarLibDhcpDirectories:
371
Run("rm -f " + a + "/*")
373
# Clear LibDir, remove nameserver and root bash history
375
for f in os.listdir(LibDir) + self.fileBlackList:
382
def uninstallDeleteFiles(self):
384
Files to delete when agent is uninstalled.
386
for f in self.agent_files_to_uninstall:
393
def checkDependencies(self):
395
Generic dependency check.
396
Return 1 unless all dependencies are satisfied.
398
if self.checkPackageInstalled('NetworkManager'):
399
Error(GuestAgentLongName + " is not compatible with network-manager.")
402
m= __import__('pyasn1')
404
Error(GuestAgentLongName + " requires python-pyasn1 for your Linux distribution.")
406
for a in self.requiredDeps:
407
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
408
Error("Missing required dependency: " + a)
412
def packagedInstall(self,buildroot):
414
Called from setup.py for use by RPM.
415
Copies generated files waagent.conf, under the buildroot.
417
if not os.path.exists(buildroot+'/etc'):
418
os.mkdir(buildroot+'/etc')
419
SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
421
if not os.path.exists(buildroot+'/etc/logrotate.d'):
422
os.mkdir(buildroot+'/etc/logrotate.d')
423
SetFileContents(buildroot+'/etc/logrotate.d/waagent', WaagentLogrotate)
425
self.init_script_file=buildroot+self.init_script_file
426
# this allows us to call installAgentServiceScriptFiles()
427
if not os.path.exists(os.path.dirname(self.init_script_file)):
428
os.mkdir(os.path.dirname(self.init_script_file))
429
self.installAgentServiceScriptFiles()
431
def GetIpv4Address(self):
434
first active non-loopback interface.
437
iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
440
def GetMacAddress(self):
441
return GetMacAddress()
443
def GetInterfaceName(self):
444
return GetFirstActiveNetworkInterfaceNonLoopback()[0]
446
def RestartInterface(self, iface):
447
Run("ifdown " + iface + " && ifup " + iface)
449
def CreateAccount(self,user, password, expiration, thumbprint):
450
return CreateAccount(user, password, expiration, thumbprint)
452
def DeleteAccount(self,user):
453
return DeleteAccount(user)
455
def ActivateResourceDisk(self):
457
Format, mount, and if specified in the configuration
458
set resource disk as swap.
461
format = Config.get("ResourceDisk.Format")
462
if format == None or format.lower().startswith("n"):
465
device = DeviceForIdePort(1)
467
Error("ActivateResourceDisk: Unable to detect disk topology.")
469
device = "/dev/" + device
471
mountlist = RunGetOutput("mount")[1]
472
mountpoint = GetMountPoint(mountlist, device)
475
Log("ActivateResourceDisk: " + device + "1 is already mounted.")
477
mountpoint = Config.get("ResourceDisk.MountPoint")
478
if mountpoint == None:
479
mountpoint = "/mnt/resource"
480
CreateDir(mountpoint, "root", 0755)
481
fs = Config.get("ResourceDisk.Filesystem")
484
if RunGetOutput("sfdisk -q -c " + device + " 1")[1].rstrip() == "7" and fs != "ntfs":
485
Run("sfdisk -c " + device + " 1 83")
486
Run("mkfs." + fs + " " + device + "1")
487
if Run("mount " + device + "1 " + mountpoint):
488
Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "1).")
490
Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs)
492
#Create README file under the root of resource disk
493
SetFileContents(os.path.join(mountpoint,README_FILENAME), README_FILECONTENT)
497
swap = Config.get("ResourceDisk.EnableSwap")
498
if swap == None or swap.lower().startswith("n"):
500
sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
501
if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
502
os.remove(mountpoint + "/swapfile")
503
if not os.path.isfile(mountpoint + "/swapfile"):
504
Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
505
Run("mkswap " + mountpoint + "/swapfile")
506
if not Run("swapon " + mountpoint + "/swapfile"):
507
Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
509
Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
514
def mediaHasFilesystem(self,dsk):
517
if Run("LC_ALL=C fdisk -l " + dsk + " | grep Disk"):
521
def mountDVD(self,dvd,location):
522
return RunGetOutput(self.mount_dvd_cmd + ' ' + dvd + ' ' + location)
527
def getDhcpClientName(self):
528
return self.dhcp_client_name
530
def initScsiDiskTimeout(self):
532
Set the SCSI disk timeout when the agent starts running
534
self.setScsiDiskTimeout()
536
def setScsiDiskTimeout(self):
538
Iterate all SCSI disks(include hot-add) and set their timeout if their value are different from the OS.RootDeviceScsiTimeout
541
scsiTimeout = Config.get("OS.RootDeviceScsiTimeout")
542
for diskName in [disk for disk in os.listdir("/sys/block") if disk.startswith("sd")]:
543
self.setBlockDeviceTimeout(diskName, scsiTimeout)
547
def setBlockDeviceTimeout(self, device, timeout):
549
Set SCSI disk timeout by set /sys/block/sd*/device/timeout
551
if timeout != None and device:
552
filePath = "/sys/block/" + device + "/device/timeout"
553
if(GetFileContents(filePath).splitlines()[0].rstrip() != timeout):
554
SetFileContents(filePath,timeout)
555
Log("SetBlockDeviceTimeout: Update the device " + device + " with timeout " + timeout)
557
def waitForSshHostKey(self, path):
559
Provide a dummy waiting, since by default, ssh host key is created by waagent and the key
560
should already been created.
562
if(os.path.isfile(path)):
565
Error("Can't find host key: {0}".format(path))
568
def isDHCPEnabled(self):
569
return self.dhcp_enabled
573
Stop the system DHCP client so that tha agent can bind on its port. If
574
the distro has set dhcp_enabled to True, it will need to provide an
575
implementation of this method.
577
raise NotImplementedError('stopDHCP method missing')
581
Start the system DHCP client. If the distro has set dhcp_enabled to
582
True, it will need to provide an implementation of this method.
584
raise NotImplementedError('startDHCP method missing')
586
def translateCustomData(self, data):
588
Translate the custom data from a Base64 encoding. Default to no-op.
592
def getConfigurationPath(self):
593
return "/etc/waagent.conf"
595
############################################################
597
############################################################
598
gentoo_init_file = """\
601
command=/usr/sbin/waagent
602
pidfile=/var/run/waagent.pid
604
command_background=true
605
name="Windows Azure Linux Agent"
611
after bootmisc modules
615
class gentooDistro(AbstractDistro):
617
Gentoo distro concrete class
620
def __init__(self): #
621
super(gentooDistro,self).__init__()
622
self.service_cmd='/sbin/service'
623
self.ssh_service_name='sshd'
624
self.hostname_file_path='/etc/conf.d/hostname'
625
self.dhcp_client_name='dhcpcd'
626
self.shadow_file_mode=0640
627
self.init_file=gentoo_init_file
629
def publishHostname(self,name):
631
if (os.path.isfile(self.hostname_file_path)):
632
r=ReplaceFileContentsAtomic(self.hostname_file_path, "hostname=\"" + name + "\"\n"
633
+ "\n".join(filter(lambda a: not a.startswith("hostname="), GetFileContents(self.hostname_file_path).split("\n"))))
638
def installAgentServiceScriptFiles(self):
639
SetFileContents(self.init_script_file, self.init_file)
640
os.chmod(self.init_script_file, 0755)
642
def registerAgentService(self):
643
self.installAgentServiceScriptFiles()
644
return Run('rc-update add ' + self.agent_service_name + ' default')
646
def uninstallAgentService(self):
647
return Run('rc-update del ' + self.agent_service_name + ' default')
649
def unregisterAgentService(self):
650
self.stopAgentService()
651
return self.uninstallAgentService()
653
def checkPackageInstalled(self,p):
654
if Run('eix -I ^' + p + '$',chk_err=False):
659
def checkPackageUpdateable(self,p):
660
if Run('eix -u ^' + p + '$',chk_err=False):
665
def RestartInterface(self, iface):
666
Run("/etc/init.d/net." + iface + " restart")
668
############################################################
670
############################################################
671
suse_init_file = """\
674
# Windows Azure Linux Agent sysV init script
676
# Copyright 2013 Microsoft Corporation
679
# Licensed under the Apache License, Version 2.0 (the "License");
680
# you may not use this file except in compliance with the License.
681
# You may obtain a copy of the License at
683
# http://www.apache.org/licenses/LICENSE-2.0
685
# Unless required by applicable law or agreed to in writing, software
686
# distributed under the License is distributed on an "AS IS" BASIS,
687
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
688
# See the License for the specific language governing permissions and
689
# limitations under the License.
691
# /etc/init.d/waagent
695
# /usr/sbin/rcwaagent
697
# System startup script for the waagent
700
# Provides: WindowsAzureLinuxAgent
701
# Required-Start: $network sshd
702
# Required-Stop: $network sshd
704
# Default-Stop: 0 1 2 6
705
# Description: Start the WindowsAzureLinuxAgent
708
PYTHON=/usr/bin/python
709
WAZD_BIN=/usr/sbin/waagent
710
WAZD_CONF=/etc/waagent.conf
711
WAZD_PIDFILE=/var/run/waagent.pid
713
test -x "$WAZD_BIN" || { echo "$WAZD_BIN not installed"; exit 5; }
714
test -e "$WAZD_CONF" || { echo "$WAZD_CONF not found"; exit 6; }
718
# First reset status of this service
721
# Return values acc. to LSB for all commands but status:
724
# 2 - invalid or excess args
725
# 3 - unimplemented feature (e.g. reload)
726
# 4 - insufficient privilege
727
# 5 - program not installed
728
# 6 - program not configured
730
# Note that starting an already running service, stopping
731
# or restarting a not-running service as well as the restart
732
# with force-reload (in case signalling is not supported) are
733
# considered a success.
738
echo -n "Starting WindowsAzureLinuxAgent"
739
## Start daemon with startproc(8). If this fails
740
## the echo return value is set appropriate.
741
startproc -f ${PYTHON} ${WAZD_BIN} -daemon
745
echo -n "Shutting down WindowsAzureLinuxAgent"
746
## Stop daemon with killproc(8) and if this fails
747
## set echo the echo return value.
748
killproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
752
## Stop the service and if this succeeds (i.e. the
753
## service was running before), start it again.
754
$0 status >/dev/null && $0 restart
758
## Stop the service and regardless of whether it was
759
## running or not, start it again.
769
echo -n "Checking for service WindowsAzureLinuxAgent "
770
## Check status with checkproc(8), if process is running
771
## checkproc will return with exit status 0.
773
checkproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
779
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
785
class SuSEDistro(AbstractDistro):
787
SuSE Distro concrete class
788
Put SuSE specific behavior here...
791
super(SuSEDistro,self).__init__()
792
self.service_cmd='/sbin/service'
793
self.ssh_service_name='sshd'
794
self.kernel_boot_options_file='/boot/grub/menu.lst'
795
self.hostname_file_path='/etc/HOSTNAME'
796
self.requiredDeps += [ "/sbin/insserv" ]
797
self.init_file=suse_init_file
798
self.dhcp_client_name='dhcpcd'
799
if (DistInfo(fullname=1)[0] == 'SUSE Linux Enterprise Server') and (DistInfo()[1] >= '12'):
800
self.dhcp_client_name='wickedd-dhcp4'
801
self.grubKernelBootOptionsFile = '/boot/grub/menu.lst'
802
self.grubKernelBootOptionsLine = 'kernel'
803
self.getpidcmd='pidof '
804
self.dhcp_enabled=True
806
def checkPackageInstalled(self,p):
807
if Run("rpm -q " + p,chk_err=False):
812
def checkPackageUpdateable(self,p):
813
if Run("zypper list-updates | grep " + p,chk_err=False):
819
def installAgentServiceScriptFiles(self):
821
SetFileContents(self.init_script_file, self.init_file)
822
os.chmod(self.init_script_file, 0744)
826
def registerAgentService(self):
827
self.installAgentServiceScriptFiles()
828
return Run('insserv ' + self.agent_service_name)
830
def uninstallAgentService(self):
831
return Run('insserv -r ' + self.agent_service_name)
833
def unregisterAgentService(self):
834
self.stopAgentService()
835
return self.uninstallAgentService()
838
Run("service " + self.dhcp_client_name + " start", chk_err=False)
841
Run("service " + self.dhcp_client_name + " stop", chk_err=False)
843
############################################################
845
############################################################
847
redhat_init_file= """\
850
# Init file for WindowsAzureLinuxAgent.
852
# chkconfig: 2345 60 80
853
# description: WindowsAzureLinuxAgent
856
# source function library
857
. /etc/rc.d/init.d/functions
860
FriendlyName="WindowsAzureLinuxAgent"
861
WAZD_BIN=/usr/sbin/waagent
865
echo -n $"Starting $FriendlyName: "
871
echo -n $"Stopping $FriendlyName: "
872
killproc -p /var/run/waagent.pid $WAZD_BIN
898
echo $"Usage: $0 {start|stop|restart|status}"
904
class redhatDistro(AbstractDistro):
906
Redhat Distro concrete class
907
Put Redhat specific behavior here...
910
super(redhatDistro,self).__init__()
911
self.service_cmd='/sbin/service'
912
self.ssh_service_restart_option='condrestart'
913
self.ssh_service_name='sshd'
914
self.hostname_file_path= None if DistInfo()[1] < '7.0' else '/etc/hostname'
915
self.init_file=redhat_init_file
916
self.grubKernelBootOptionsFile = '/boot/grub/menu.lst'
917
self.grubKernelBootOptionsLine = 'kernel'
919
def publishHostname(self,name):
920
super(redhatDistro,self).publishHostname(name)
921
if DistInfo()[1] < '7.0' :
922
filepath = "/etc/sysconfig/network"
923
if os.path.isfile(filepath):
924
ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n"
925
+ "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n'))))
927
ethernetInterface = MyDistro.GetInterfaceName()
928
filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
929
if os.path.isfile(filepath):
930
ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
931
+ "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
934
def installAgentServiceScriptFiles(self):
935
SetFileContents(self.init_script_file, self.init_file)
936
os.chmod(self.init_script_file, 0744)
939
def registerAgentService(self):
940
self.installAgentServiceScriptFiles()
941
return Run('chkconfig --add waagent')
943
def uninstallAgentService(self):
944
return Run('chkconfig --del ' + self.agent_service_name)
946
def unregisterAgentService(self):
947
self.stopAgentService()
948
return self.uninstallAgentService()
950
def checkPackageInstalled(self,p):
951
if Run("yum list installed " + p,chk_err=False):
956
def checkPackageUpdateable(self,p):
957
if Run("yum check-update | grep "+ p,chk_err=False):
964
############################################################
966
############################################################
968
class centosDistro(redhatDistro):
970
CentOS Distro concrete class
971
Put CentOS specific behavior here...
974
super(centosDistro,self).__init__()
977
############################################################
979
############################################################
981
class CoreOSDistro(AbstractDistro):
983
CoreOS Distro concrete class
984
Put CoreOS specific behavior here...
989
super(CoreOSDistro,self).__init__()
990
self.requiredDeps += [ "/usr/bin/systemctl" ]
991
self.agent_service_name = 'waagent'
992
self.init_script_file='/etc/systemd/system/waagent.service'
993
self.fileBlackList.append("/etc/machine-id")
994
self.dhcp_client_name='systemd-networkd'
995
self.getpidcmd='pidof '
996
self.shadow_file_mode=0640
997
self.waagent_path='/usr/share/oem/waagent/bin'
998
self.python_path='/usr/share/oem/python/bin'
999
self.dhcp_enabled=True
1000
if 'PATH' in os.environ:
1001
os.environ['PATH'] = "{0}:{1}".format(os.environ['PATH'], self.python_path)
1003
os.environ['PATH'] = self.python_path
1005
if 'PYTHONPATH' in os.environ:
1006
os.environ['PYTHONPATH'] = "{0}:{1}".format(os.environ['PYTHONPATH'], self.waagent_path)
1008
os.environ['PYTHONPATH'] = self.waagent_path
1010
def checkPackageInstalled(self,p):
1012
There is no package manager in CoreOS. Return 1 since it must be preinstalled.
1016
def checkDependencies(self):
1017
for a in self.requiredDeps:
1018
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
1019
Error("Missing required dependency: " + a)
1024
def checkPackageUpdateable(self,p):
1026
There is no package manager in CoreOS. Return 0 since it can't be updated via package.
1030
def startAgentService(self):
1031
return Run('systemctl start ' + self.agent_service_name)
1033
def stopAgentService(self):
1034
return Run('systemctl stop ' + self.agent_service_name)
1036
def restartSshService(self):
1038
Service call to re(start) the SSH service
1040
retcode = Run("systemctl restart sshd")
1042
Error("Failed to restart SSH service with return code:" + str(retcode))
1045
def sshDeployPublicKey(self,fprint,path):
1049
if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
1054
def RestartInterface(self, iface):
1055
Run("systemctl restart systemd-networkd")
1057
def CreateAccount(self, user, password, expiration, thumbprint):
1059
Create a user account, with 'user', 'password', 'expiration', ssh keys
1060
and sudo permissions.
1061
Returns None if successful, error string on failure.
1065
userentry = pwd.getpwnam(user)
1070
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
1075
if userentry != None and userentry[2] < uidmin and userentry[2] != self.CORE_UID:
1076
Error("CreateAccount: " + user + " is a system user. Will not set password.")
1077
return "Failed to set password for system user: " + user + " (0x06)."
1078
if userentry == None:
1079
command = "useradd --create-home --password '*' " + user
1080
if expiration != None:
1081
command += " --expiredate " + expiration.split('.')[0]
1083
Error("Failed to create user account: " + user)
1084
return "Failed to create user account: " + user + " (0x07)."
1086
Log("CreateAccount: " + user + " already exists. Will update password.")
1087
if password != None:
1088
RunSendStdin("chpasswd", user + ":" + password + "\n")
1090
if password == None:
1091
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
1093
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
1094
os.chmod("/etc/sudoers.d/waagent", 0440)
1096
Error("CreateAccount: Failed to configure sudo access for user.")
1097
return "Failed to configure sudo privileges (0x08)."
1098
home = MyDistro.GetHome()
1099
if thumbprint != None:
1100
dir = home + "/" + user + "/.ssh"
1101
CreateDir(dir, user, 0700)
1102
pub = dir + "/id_rsa.pub"
1103
prv = dir + "/id_rsa"
1104
Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
1105
SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
1106
for f in [pub, prv]:
1108
ChangeOwner(f, user)
1109
SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
1110
ChangeOwner(dir + "/authorized_keys", user)
1111
Log("Created user account: " + user)
1114
def startDHCP(self):
1115
Run("systemctl start " + self.dhcp_client_name, chk_err=False)
1118
Run("systemctl stop " + self.dhcp_client_name, chk_err=False)
1120
def translateCustomData(self, data):
1121
return base64.b64decode(data)
1123
def getConfigurationPath(self):
1124
return "/usr/share/oem/waagent.conf"
1126
############################################################
1128
############################################################
1129
debian_init_file = """\
1132
# Provides: WindowsAzureLinuxAgent
1133
# Required-Start: $network $syslog
1134
# Required-Stop: $network $syslog
1135
# Should-Start: $network $syslog
1136
# Should-Stop: $network $syslog
1137
# Default-Start: 2 3 4 5
1138
# Default-Stop: 0 1 6
1139
# Short-Description: WindowsAzureLinuxAgent
1140
# Description: WindowsAzureLinuxAgent
1143
. /lib/lsb/init-functions
1146
WAZD_BIN=/usr/sbin/waagent
1147
WAZD_PID=/var/run/waagent.pid
1151
log_begin_msg "Starting WindowsAzureLinuxAgent..."
1152
pid=$( pidofproc $WAZD_BIN )
1153
if [ -n "$pid" ] ; then
1154
log_begin_msg "Already running."
1158
start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
1163
log_begin_msg "Stopping WindowsAzureLinuxAgent..."
1164
start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
1177
status_of_proc $WAZD_BIN && exit 0 || exit $?
1180
log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
1188
class debianDistro(AbstractDistro):
1190
debian Distro concrete class
1191
Put debian specific behavior here...
1194
super(debianDistro,self).__init__()
1195
self.requiredDeps += [ "/usr/sbin/update-rc.d" ]
1196
self.init_file=debian_init_file
1197
self.agent_package_name='walinuxagent'
1198
self.dhcp_client_name='dhclient'
1199
self.getpidcmd='pidof '
1200
self.shadow_file_mode=0640
1202
def checkPackageInstalled(self,p):
1204
Check that the package is installed.
1205
Return 1 if installed, 0 if not installed.
1206
This method of using dpkg-query
1207
allows wildcards to be present in the
1210
if not Run("dpkg-query -W -f='${Status}\n' '" + p + "' | grep ' installed' 2>&1",chk_err=False):
1215
def checkDependencies(self):
1217
Debian dependency check. python-pyasn1 is NOT needed.
1218
Return 1 unless all dependencies are satisfied.
1219
NOTE: using network*manager will catch either package name in Ubuntu or debian.
1221
if self.checkPackageInstalled('network*manager'):
1222
Error(GuestAgentLongName + " is not compatible with network-manager.")
1224
for a in self.requiredDeps:
1225
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
1226
Error("Missing required dependency: " + a)
1230
def checkPackageUpdateable(self,p):
1231
if Run("apt-get update ; apt-get upgrade -us | grep " + p,chk_err=False):
1236
def installAgentServiceScriptFiles(self):
1238
If we are packaged - the service name is walinuxagent, do nothing.
1240
if self.agent_service_name == 'walinuxagent':
1243
SetFileContents(self.init_script_file, self.init_file)
1244
os.chmod(self.init_script_file, 0744)
1246
ErrorWithPrefix('installAgentServiceScriptFiles','Exception: '+str(e)+' occured creating ' + self.init_script_file)
1250
def registerAgentService(self):
1251
if self.installAgentServiceScriptFiles() == 0:
1252
return Run('update-rc.d waagent defaults')
1256
def uninstallAgentService(self):
1257
return Run('update-rc.d -f ' + self.agent_service_name + ' remove')
1259
def unregisterAgentService(self):
1260
self.stopAgentService()
1261
return self.uninstallAgentService()
1263
def sshDeployPublicKey(self,fprint,path):
1267
if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
1272
############################################################
1274
############################################################
1275
ubuntu_upstart_file = """\
1276
#walinuxagent - start Windows Azure agent
1278
description "walinuxagent"
1279
author "Ben Howard <ben.howard@canonical.com>"
1281
start on (filesystem and started rsyslog)
1285
WALINUXAGENT_ENABLED=1
1286
[ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent
1288
if [ "$WALINUXAGENT_ENABLED" != "1" ]; then
1292
if [ ! -x /usr/sbin/waagent ]; then
1296
#Load the udf module
1300
exec /usr/sbin/waagent -daemon
1303
class UbuntuDistro(debianDistro):
1305
Ubuntu Distro concrete class
1306
Put Ubuntu specific behavior here...
1309
super(UbuntuDistro,self).__init__()
1310
self.init_script_file='/etc/init/waagent.conf'
1311
self.init_file=ubuntu_upstart_file
1312
self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log"]
1313
self.dhcp_client_name=None
1314
self.getpidcmd='pidof '
1316
def registerAgentService(self):
1317
return self.installAgentServiceScriptFiles()
1319
def startAgentService(self):
1323
return Run('start ' + self.agent_service_name)
1325
def stopAgentService(self):
1329
return Run('stop ' + self.agent_service_name)
1331
def uninstallAgentService(self):
1333
If we are packaged - the service name is walinuxagent, do nothing.
1335
if self.agent_service_name == 'walinuxagent':
1337
os.remove('/etc/init/' + self.agent_service_name + '.conf')
1339
def unregisterAgentService(self):
1341
If we are packaged - the service name is walinuxagent, do nothing.
1343
if self.agent_service_name == 'walinuxagent':
1345
self.stopAgentService()
1346
return self.uninstallAgentService()
1348
def deprovisionWarnUser(self):
1350
Ubuntu specific warning string from Deprovision.
1352
print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.")
1354
def deprovisionDeleteFiles(self):
1356
Ubuntu uses resolv.conf by default, so removing /etc/resolv.conf will
1357
break resolvconf. Therefore, we check to see if resolvconf is in use,
1358
and if so, we remove the resolvconf artifacts.
1360
if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
1361
Log("resolvconf is not configured. Removing /etc/resolv.conf")
1362
self.fileBlackList.append('/etc/resolv.conf')
1364
Log("resolvconf is enabled; leaving /etc/resolv.conf intact")
1365
resolvConfD = '/etc/resolvconf/resolv.conf.d/'
1366
self.fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'originial'])
1367
for f in os.listdir(LibDir)+self.fileBlackList:
1374
def getDhcpClientName(self):
1375
if self.dhcp_client_name != None :
1376
return self.dhcp_client_name
1377
if DistInfo()[1] == '12.04' :
1378
self.dhcp_client_name='dhclient3'
1380
self.dhcp_client_name='dhclient'
1381
return self.dhcp_client_name
1383
def waitForSshHostKey(self, path):
1385
Wait until the ssh host key is generated by cloud init.
1387
for retry in range(0, 10):
1388
if(os.path.isfile(path)):
1391
Error("Can't find host key: {0}".format(path))
1395
############################################################
1397
############################################################
1399
class LinuxMintDistro(UbuntuDistro):
1401
LinuxMint Distro concrete class
1402
Put LinuxMint specific behavior here...
1405
super(LinuxMintDistro,self).__init__()
1407
############################################################
1409
############################################################
1410
fedora_systemd_service = """\
1412
Description=Windows Azure Linux Agent
1413
After=network.target
1415
ConditionFileIsExecutable=/usr/sbin/waagent
1416
ConditionPathExists=/etc/waagent.conf
1420
ExecStart=/usr/sbin/waagent -daemon
1423
WantedBy=multi-user.target
1426
class fedoraDistro(redhatDistro):
1428
FedoraDistro concrete class
1429
Put Fedora specific behavior here...
1432
super(fedoraDistro,self).__init__()
1433
self.service_cmd = '/usr/bin/systemctl'
1434
self.hostname_file_path = '/etc/hostname'
1435
self.init_script_file = '/usr/lib/systemd/system/' + self.agent_service_name + '.service'
1436
self.init_file = fedora_systemd_service
1437
self.grubKernelBootOptionsFile = '/etc/default/grub'
1438
self.grubKernelBootOptionsLine = 'GRUB_CMDLINE_LINUX='
1440
def publishHostname(self, name):
1441
SetFileContents(self.hostname_file_path, name + '\n')
1442
ethernetInterface = MyDistro.GetInterfaceName()
1443
filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
1444
if os.path.isfile(filepath):
1445
ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
1446
+ "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
1449
def installAgentServiceScriptFiles(self):
1450
SetFileContents(self.init_script_file, self.init_file)
1451
os.chmod(self.init_script_file, 0644)
1452
return Run(self.service_cmd + ' daemon-reload')
1454
def registerAgentService(self):
1455
self.installAgentServiceScriptFiles()
1456
return Run(self.service_cmd + ' enable ' + self.agent_service_name)
1458
def uninstallAgentService(self):
1460
Call service subsystem to remove waagent script.
1462
return Run(self.service_cmd + ' disable ' + self.agent_service_name)
1464
def unregisterAgentService(self):
1466
Calls self.stopAgentService and call self.uninstallAgentService()
1468
self.stopAgentService()
1469
self.uninstallAgentService()
1471
def startAgentService(self):
1473
Service call to start the Agent service
1475
return Run(self.service_cmd + ' start ' + self.agent_service_name)
1477
def stopAgentService(self):
1479
Service call to stop the Agent service
1481
return Run(self.service_cmd + ' stop ' + self.agent_service_name, False)
1483
def restartSshService(self):
1485
Service call to re(start) the SSH service
1487
sshRestartCmd = self.service_cmd + " " + self.ssh_service_restart_option + " " + self.ssh_service_name
1488
retcode = Run(sshRestartCmd)
1490
Error("Failed to restart SSH service with return code:" + str(retcode))
1493
def checkPackageInstalled(self, p):
1495
Query package database for prescence of an installed package.
1498
ts = rpm.TransactionSet()
1499
rpms = ts.dbMatch(rpm.RPMTAG_PROVIDES, p)
1500
return bool(len(rpms) > 0)
1502
def deleteRootPassword(self):
1503
return Run("/sbin/usermod root -p '!!'")
1505
def packagedInstall(self,buildroot):
1507
Called from setup.py for use by RPM.
1508
Copies generated files waagent.conf, under the buildroot.
1510
if not os.path.exists(buildroot+'/etc'):
1511
os.mkdir(buildroot+'/etc')
1512
SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
1514
if not os.path.exists(buildroot+'/etc/logrotate.d'):
1515
os.mkdir(buildroot+'/etc/logrotate.d')
1516
SetFileContents(buildroot+'/etc/logrotate.d/WALinuxAgent', WaagentLogrotate)
1518
self.init_script_file=buildroot+self.init_script_file
1519
# this allows us to call installAgentServiceScriptFiles()
1520
if not os.path.exists(os.path.dirname(self.init_script_file)):
1521
os.mkdir(os.path.dirname(self.init_script_file))
1522
self.installAgentServiceScriptFiles()
1524
def CreateAccount(self, user, password, expiration, thumbprint):
1525
super(fedoraDistro, self).CreateAccount(user, password, expiration, thumbprint)
1526
Run('/sbin/usermod ' + user + ' -G wheel')
1528
def DeleteAccount(self, user):
1529
Run('/sbin/usermod ' + user + ' -G ""')
1530
super(fedoraDistro, self).DeleteAccount(user)
1532
############################################################
1534
############################################################
1535
FreeBSDWaagentConf = """\
1537
# Windows Azure Linux Agent Configuration
1540
Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status
1541
# to the endpoint server.
1542
Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
1543
Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
1545
Provisioning.Enabled=y #
1546
Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
1547
Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
1548
Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
1549
Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
1551
ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
1552
ResourceDisk.Filesystem=ufs2 #
1553
ResourceDisk.MountPoint=/mnt/resource #
1554
ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
1555
ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
1557
LBProbeResponder=y # Respond to load balancer probes if requested by Windows Azure.
1559
Logs.Verbose=n # Enable verbose logs
1561
OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
1562
OS.OpensslPath=None # If "None", the system default version is used.
1569
# REQUIRE: DAEMON cleanvar sshd
1574
export PATH=$PATH:/usr/local/bin
1576
rcvar="waagent_enable"
1577
command="/usr/sbin/${name}"
1578
command_interpreter="/usr/local/bin/python"
1579
waagent_flags=" daemon &"
1581
pidfile="/var/run/waagent.pid"
1583
load_rc_config $name
1587
bsd_activate_resource_disk_txt="""\
1588
#!/usr/bin/env python
1594
# waagent has no '.py' therefore create waagent module import manually.
1595
__name__='setupmain' #prevent waagent.__main__ from executing
1596
waagent=imp.load_source('waagent','/tmp/waagent')
1597
waagent.LoggerInit('/var/log/waagent.log','/dev/console')
1598
from waagent import RunGetOutput,Run
1599
Config=waagent.ConfigurationProvider()
1600
format = Config.get("ResourceDisk.Format")
1601
if format == None or format.lower().startswith("n"):
1604
device = "/dev/" + device_base
1605
for entry in RunGetOutput("mount")[1].split():
1606
if entry.startswith(device + "s1"):
1607
waagent.Log("ActivateResourceDisk: " + device + "s1 is already mounted.")
1609
mountpoint = Config.get("ResourceDisk.MountPoint")
1610
if mountpoint == None:
1611
mountpoint = "/mnt/resource"
1612
waagent.CreateDir(mountpoint, "root", 0755)
1613
fs = Config.get("ResourceDisk.Filesystem")
1614
if waagent.FreeBSDDistro().mediaHasFilesystem(device) == False :
1615
Run("newfs " + device + "s1")
1616
if Run("mount " + device + "s1 " + mountpoint):
1617
waagent.Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "s1).")
1619
waagent.Log("Resource disk (" + device + "s1) is mounted at " + mountpoint + " with fstype " + fs)
1620
swap = Config.get("ResourceDisk.EnableSwap")
1621
if swap == None or swap.lower().startswith("n"):
1623
sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
1624
if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
1625
os.remove(mountpoint + "/swapfile")
1626
if not os.path.isfile(mountpoint + "/swapfile"):
1627
Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
1628
if Run("mdconfig -a -t vnode -f " + mountpoint + "/swapfile -u 0"):
1629
waagent.Error("ActivateResourceDisk: Configuring swap - Failed to create md0")
1630
if not Run("swapon /dev/md0"):
1631
waagent.Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
1633
waagent.Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
1636
class FreeBSDDistro(AbstractDistro):
1641
Generic Attributes go here. These are based on 'majority rules'.
1642
This __init__() may be called or overriden by the child.
1644
super(FreeBSDDistro,self).__init__()
1645
self.agent_service_name = os.path.basename(sys.argv[0])
1647
self.ssh_service_name='sshd'
1648
self.ssh_config_file='/etc/ssh/sshd_config'
1649
self.hostname_file_path='/etc/hostname'
1650
self.dhcp_client_name='dhclient'
1651
self.requiredDeps = [ 'route', 'shutdown', 'ssh-keygen', 'pw'
1652
, 'openssl', 'fdisk', 'sed', 'grep' , 'sudo']
1653
self.init_script_file='/etc/rc.d/waagent'
1654
self.init_file=bsd_init_file
1655
self.agent_package_name='WALinuxAgent'
1656
self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log",'/etc/resolv.conf' ]
1657
self.agent_files_to_uninstall = ["/etc/waagent.conf", "/usr/local/etc/sudoers.d/waagent"]
1658
self.grubKernelBootOptionsFile = '/boot/loader.conf'
1659
self.grubKernelBootOptionsLine = ''
1660
self.getpidcmd = 'pgrep -n'
1661
self.mount_dvd_cmd = 'dd bs=2048 count=33 skip=295 if=' # custom data max len is 64k
1662
self.sudoers_dir_base = '/usr/local/etc'
1663
self.waagent_conf_file = FreeBSDWaagentConf
1665
def installAgentServiceScriptFiles(self):
1666
SetFileContents(self.init_script_file, self.init_file)
1667
os.chmod(self.init_script_file, 0777)
1668
AppendFileContents("/etc/rc.conf","waagent_enable='YES'\n")
1671
def registerAgentService(self):
1672
self.installAgentServiceScriptFiles()
1673
return Run("services_mkdb " + self.init_script_file)
1676
def sshDeployPublicKey(self,fprint,path):
1680
if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
1685
def deleteRootPassword(self):
1687
BSD root password removal.
1689
filepath="/etc/master.passwd"
1690
ReplaceStringInFile(filepath,r'root:.*?:','root::')
1691
#ReplaceFileContentsAtomic(filepath,"root:*LOCK*:14600::::::\n"
1692
# + "\n".join(filter(lambda a: not a.startswith("root:"),GetFileContents(filepath).split('\n'))))
1693
os.chmod(filepath,self.shadow_file_mode)
1694
if self.isSelinuxSystem():
1695
self.setSelinuxContext(filepath,'system_u:object_r:shadow_t:s0')
1696
RunGetOutput("pwd_mkdb -u root /etc/master.passwd")
1697
Log("Root password deleted.")
1700
def changePass(self,user,password):
1701
return RunSendStdin("pw usermod " + user + " -h 0 ",password)
1703
def load_ata_piix(self):
1706
def unload_ata_piix(self):
1709
def checkDependencies(self):
1711
FreeBSD dependency check.
1712
Return 1 unless all dependencies are satisfied.
1714
for a in self.requiredDeps:
1715
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
1716
Error("Missing required dependency: " + a)
1720
def packagedInstall(self,buildroot):
1723
def GetInterfaceName(self):
1725
Return the ip of the
1726
active ethernet interface.
1728
iface,inet,mac=self.GetFreeBSDEthernetInfo()
1731
def RestartInterface(self, iface):
1732
Run("service netif restart")
1734
def GetIpv4Address(self):
1736
Return the ip of the
1737
active ethernet interface.
1739
iface,inet,mac=self.GetFreeBSDEthernetInfo()
1742
def GetMacAddress(self):
1744
Return the ip of the
1745
active ethernet interface.
1747
iface,inet,mac=self.GetFreeBSDEthernetInfo()
1751
r.append(string.atoi(i,16))
1754
def GetFreeBSDEthernetInfo(self):
1756
There is no SIOCGIFCONF
1757
on freeBSD - just parse ifconfig.
1758
Returns strings: iface, inet4_addr, and mac
1759
or 'None,None,None' if unable to parse.
1760
We will sleep and retry as the network must be up.
1762
code,output=RunGetOutput("ifconfig",chk_err=False)
1765
cmd='ifconfig | grep -A1 -B2 ether | grep -B3 inet | grep -A3 UP '
1769
if code > 0 and retries == 0:
1770
Error("GetFreeBSDEthernetInfo - Failed to detect ethernet interface")
1771
return None, None, None
1772
code,output=RunGetOutput(cmd,chk_err=False)
1774
if code > 0 and retries > 0 :
1775
Log("GetFreeBSDEthernetInfo - Error: retry ethernet detection " + str(retries))
1777
c,o=RunGetOutput("ifconfig | grep -A1 -B2 ether",chk_err=False)
1779
t=o.replace('\n',' ')
1782
Log(RunGetOutput('id')[1])
1786
j=output.replace('\n',' ')
1790
for i in range(len(j)):
1793
elif j[i] == 'ether' :
1796
return iface, inet, mac
1798
def CreateAccount(self,user, password, expiration, thumbprint):
1800
Create a user account, with 'user', 'password', 'expiration', ssh keys
1801
and sudo permissions.
1802
Returns None if successful, error string on failure.
1806
userentry = pwd.getpwnam(user)
1811
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
1816
if userentry != None and userentry[2] < uidmin:
1817
Error("CreateAccount: " + user + " is a system user. Will not set password.")
1818
return "Failed to set password for system user: " + user + " (0x06)."
1819
if userentry == None:
1820
command = "pw useradd " + user + " -m"
1821
if expiration != None:
1822
command += " -e " + expiration.split('.')[0]
1824
Error("Failed to create user account: " + user)
1825
return "Failed to create user account: " + user + " (0x07)."
1827
Log("CreateAccount: " + user + " already exists. Will update password.")
1829
if password != None:
1830
self.changePass(user,password)
1832
# for older distros create sudoers.d
1833
if not os.path.isdir(MyDistro.sudoers_dir_base+'/sudoers.d/'):
1834
# create the /etc/sudoers.d/ directory
1835
os.mkdir(MyDistro.sudoers_dir_base+'/sudoers.d')
1836
# add the include of sudoers.d to the /etc/sudoers
1837
SetFileContents(MyDistro.sudoers_dir_base+'/sudoers',GetFileContents(MyDistro.sudoers_dir_base+'/sudoers')+'\n#includedir ' + MyDistro.sudoers_dir_base + '/sudoers.d\n')
1838
if password == None:
1839
SetFileContents(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
1841
SetFileContents(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
1842
os.chmod(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", 0440)
1844
Error("CreateAccount: Failed to configure sudo access for user.")
1845
return "Failed to configure sudo privileges (0x08)."
1846
home = MyDistro.GetHome()
1847
if thumbprint != None:
1848
dir = home + "/" + user + "/.ssh"
1849
CreateDir(dir, user, 0700)
1850
pub = dir + "/id_rsa.pub"
1851
prv = dir + "/id_rsa"
1852
Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
1853
SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
1854
for f in [pub, prv]:
1856
ChangeOwner(f, user)
1857
SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
1858
ChangeOwner(dir + "/authorized_keys", user)
1859
Log("Created user account: " + user)
1862
def DeleteAccount(self,user):
1865
Clear utmp first, to avoid error.
1866
Removes the /etc/sudoers.d/waagent file.
1870
userentry = pwd.getpwnam(user)
1873
if userentry == None:
1874
Error("DeleteAccount: " + user + " not found.")
1878
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
1883
if userentry[2] < uidmin:
1884
Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
1886
Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
1887
Run("rmuser -y " + user)
1889
os.remove(MyDistro.sudoers_dir_base+"/sudoers.d/waagent")
1894
def ActivateResourceDiskNoThread(self):
1896
Format, mount, and if specified in the configuration
1897
set resource disk as swap.
1899
global DiskActivated
1900
Run('cp /usr/sbin/waagent /tmp/')
1901
SetFileContents('/tmp/bsd_activate_resource_disk.py',bsd_activate_resource_disk_txt)
1902
Run('chmod +x /tmp/bsd_activate_resource_disk.py')
1903
pid = subprocess.Popen(["/tmp/bsd_activate_resource_disk.py", ""]).pid
1904
Log("Spawning bsd_activate_resource_disk.py")
1905
DiskActivated = True
1910
Install the agent service.
1912
Create /etc/waagent.conf and move old version to
1913
/etc/waagent.conf.old
1914
Copy RulesFiles to /var/lib/waagent
1915
Create /etc/logrotate.d/waagent
1916
Set /etc/ssh/sshd_config ClientAliveInterval to 180
1917
Call ApplyVNUMAWorkaround()
1919
if MyDistro.checkDependencies():
1921
os.chmod(sys.argv[0], 0755)
1923
for a in RulesFiles:
1924
if os.path.isfile(a):
1925
if os.path.isfile(GetLastPathElement(a)):
1926
os.remove(GetLastPathElement(a))
1928
Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
1929
MyDistro.registerAgentService()
1930
if os.path.isfile("/etc/waagent.conf"):
1932
os.remove("/etc/waagent.conf.old")
1936
os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
1937
Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
1940
SetFileContents("/etc/waagent.conf", self.waagent_conf_file)
1941
if os.path.exists('/usr/local/etc/logrotate.d/'):
1942
SetFileContents("/usr/local/etc/logrotate.d/waagent", WaagentLogrotate)
1943
filepath = "/etc/ssh/sshd_config"
1944
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
1945
a.startswith("ClientAliveInterval"),
1946
GetFileContents(filepath).split('\n'))) + "\nClientAliveInterval 180\n")
1947
Log("Configured SSH client probing to keep connections alive.")
1948
#ApplyVNUMAWorkaround()
1951
def mediaHasFilesystem(self,dsk):
1952
if Run('LC_ALL=C fdisk -p ' + dsk + ' | grep "invalid fdisk partition table found" ',False):
1956
def mountDVD(self,dvd,location):
1957
#At this point we cannot read a joliet option udf DVD in freebsd10 - so we 'dd' it into our location
1958
retcode,out = RunGetOutput(self.mount_dvd_cmd + dvd + ' of=' + location + '/ovf-env.xml')
1962
ovfxml = (GetFileContents(location+"/ovf-env.xml",asbin=False))
1963
if ord(ovfxml[0]) > 128 and ord(ovfxml[1]) > 128 and ord(ovfxml[2]) > 128 :
1964
ovfxml = ovfxml[3:] # BOM is not stripped. First three bytes are > 128 and not unicode chars so we ignore them.
1965
ovfxml = ovfxml.strip(chr(0x00))
1966
ovfxml = "".join(filter(lambda x: ord(x)<128, ovfxml))
1967
ovfxml = re.sub(r'</Environment>.*\Z','',ovfxml,0,re.DOTALL)
1968
ovfxml += '</Environment>'
1969
SetFileContents(location+"/ovf-env.xml", ovfxml)
1975
def initScsiDiskTimeout(self):
1977
Set the SCSI disk timeout by updating the kernal config
1979
timeout = Config.get("OS.RootDeviceScsiTimeout")
1981
Run("sysctl kern.cam.da.default_timeout=" + timeout)
1983
def setScsiDiskTimeout(self):
1986
def setBlockDeviceTimeout(self, device, timeout):
1989
############################################################
1990
# END DISTRO CLASS DEFS
1991
############################################################
1993
# This lets us index into a string or an array of integers transparently.
1996
Allows indexing into a string or an array of integers transparently.
1997
Generic utility function.
1999
if type(a) == type("a"):
2005
Returns True if platform is Linux.
2006
Generic utility function.
2008
return (platform.uname()[0] == "Linux")
2010
def GetLastPathElement(path):
2012
Similar to basename.
2013
Generic utility function.
2015
return path.rsplit('/', 1)[1]
2017
def GetFileContents(filepath,asbin=False):
2019
Read and return contents of 'filepath'.
2026
with open(filepath, mode) as F :
2029
ErrorWithPrefix('GetFileContents','Reading from file ' + filepath + ' Exception is ' + str(e))
2033
def SetFileContents(filepath, contents):
2035
Write 'contents' to 'filepath'.
2037
if type(contents) == str :
2038
contents=contents.encode('latin-1', 'ignore')
2040
with open(filepath, "wb+") as F :
2043
ErrorWithPrefix('SetFileContents','Writing to file ' + filepath + ' Exception is ' + str(e))
2047
def AppendFileContents(filepath, contents):
2049
Append 'contents' to 'filepath'.
2051
if type(contents) == str :
2052
contents=contents.encode('latin-1')
2054
with open(filepath, "a+") as F :
2057
ErrorWithPrefix('AppendFileContents','Appending to file ' + filepath + ' Exception is ' + str(e))
2061
def ReplaceFileContentsAtomic(filepath, contents):
2063
Write 'contents' to 'filepath' by creating a temp file, and replacing original.
2065
handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
2066
if type(contents) == str :
2067
contents=contents.encode('latin-1')
2069
os.write(handle, contents)
2071
ErrorWithPrefix('ReplaceFileContentsAtomic','Writing to file ' + filepath + ' Exception is ' + str(e))
2076
os.rename(temp, filepath)
2079
ErrorWithPrefix('ReplaceFileContentsAtomic','Renaming ' + temp+ ' to ' + filepath + ' Exception is ' + str(e))
2083
ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
2085
os.rename(temp,filepath)
2087
ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
2091
def GetLineStartingWith(prefix, filepath):
2093
Return line from 'filepath' if the line startswith 'prefix'
2095
for line in GetFileContents(filepath).split('\n'):
2096
if line.startswith(prefix):
2100
def Run(cmd,chk_err=True):
2102
Calls RunGetOutput on 'cmd', returning only the return code.
2103
If chk_err=True then errors will be reported in the log.
2104
If chk_err=False then errors will be suppressed from the log.
2106
retcode,out=RunGetOutput(cmd,chk_err)
2109
def RunGetOutput(cmd,chk_err=True):
2111
Wrapper for subprocess.check_output.
2112
Execute 'cmd'. Returns return code and STDOUT, trapping expected exceptions.
2113
Reports exceptions to Error if chk_err parameter is True
2117
output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
2118
except subprocess.CalledProcessError,e :
2120
Error('CalledProcessError. Error Code is ' + str(e.returncode) )
2121
Error('CalledProcessError. Command string was ' + e.cmd )
2122
Error('CalledProcessError. Command result was ' + (e.output[:-1]).decode('latin-1'))
2123
return e.returncode,e.output.decode('latin-1')
2124
return 0,output.decode('latin-1')
2126
def RunSendStdin(cmd,input,chk_err=True):
2128
Wrapper for subprocess.Popen.
2129
Execute 'cmd', sending 'input' to STDIN of 'cmd'.
2130
Returns return code and STDOUT, trapping expected exceptions.
2131
Reports exceptions to Error if chk_err parameter is True
2133
LogIfVerbose(cmd+input)
2135
me=subprocess.Popen([cmd], shell=True, stdin=subprocess.PIPE,stderr=subprocess.STDOUT,stdout=subprocess.PIPE)
2136
output=me.communicate(input)
2137
except OSError , e :
2139
Error('CalledProcessError. Error Code is ' + str(me.returncode) )
2140
Error('CalledProcessError. Command string was ' + cmd )
2141
Error('CalledProcessError. Command result was ' + output[0].decode('latin-1'))
2142
return 1,output[0].decode('latin-1')
2143
if me.returncode is not 0 and chk_err is True:
2144
Error('CalledProcessError. Error Code is ' + str(me.returncode) )
2145
Error('CalledProcessError. Command string was ' + cmd )
2146
Error('CalledProcessError. Command result was ' + output[0].decode('latin-1'))
2147
return me.returncode,output[0].decode('latin-1')
2149
def GetNodeTextData(a):
2151
Filter non-text nodes from DOM tree
2153
for b in a.childNodes:
2154
if b.nodeType == b.TEXT_NODE:
2159
Attempt to guess the $HOME location.
2160
Return the path string.
2164
home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
2167
if (home == None) or (home.startswith("/") == False):
2171
def ChangeOwner(filepath, user):
2173
Lookup user. Attempt chown 'filepath' to 'user'.
2177
p = pwd.getpwnam(user)
2181
os.chown(filepath, p[2], p[3])
2183
def CreateDir(dirpath, user, mode):
2185
Attempt os.makedirs, catch all exceptions.
2186
Call ChangeOwner afterwards.
2189
os.makedirs(dirpath, mode)
2192
ChangeOwner(dirpath, user)
2194
def CreateAccount(user, password, expiration, thumbprint):
2196
Create a user account, with 'user', 'password', 'expiration', ssh keys
2197
and sudo permissions.
2198
Returns None if successful, error string on failure.
2202
userentry = pwd.getpwnam(user)
2207
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
2212
if userentry != None and userentry[2] < uidmin:
2213
Error("CreateAccount: " + user + " is a system user. Will not set password.")
2214
return "Failed to set password for system user: " + user + " (0x06)."
2215
if userentry == None:
2216
command = "useradd -m " + user
2217
if expiration != None:
2218
command += " -e " + expiration.split('.')[0]
2220
Error("Failed to create user account: " + user)
2221
return "Failed to create user account: " + user + " (0x07)."
2223
Log("CreateAccount: " + user + " already exists. Will update password.")
2224
if password != None:
2225
RunSendStdin("chpasswd",(user + ":" + password + "\n"))
2227
# for older distros create sudoers.d
2228
if not os.path.isdir('/etc/sudoers.d/'):
2229
# create the /etc/sudoers.d/ directory
2230
os.mkdir('/etc/sudoers.d/')
2231
# add the include of sudoers.d to the /etc/sudoers
2232
SetFileContents('/etc/sudoers',GetFileContents('/etc/sudoers')+'\n#includedir /etc/sudoers.d\n')
2233
if password == None:
2234
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
2236
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
2237
os.chmod("/etc/sudoers.d/waagent", 0440)
2239
Error("CreateAccount: Failed to configure sudo access for user.")
2240
return "Failed to configure sudo privileges (0x08)."
2241
home = MyDistro.GetHome()
2242
if thumbprint != None:
2243
dir = home + "/" + user + "/.ssh"
2244
CreateDir(dir, user, 0700)
2245
pub = dir + "/id_rsa.pub"
2246
prv = dir + "/id_rsa"
2247
Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
2248
SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
2249
for f in [pub, prv]:
2251
ChangeOwner(f, user)
2252
SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
2253
ChangeOwner(dir + "/authorized_keys", user)
2254
Log("Created user account: " + user)
2257
def DeleteAccount(user):
2260
Clear utmp first, to avoid error.
2261
Removes the /etc/sudoers.d/waagent file.
2265
userentry = pwd.getpwnam(user)
2268
if userentry == None:
2269
Error("DeleteAccount: " + user + " not found.")
2273
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
2278
if userentry[2] < uidmin:
2279
Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
2281
Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
2282
Run("userdel -f -r " + user)
2284
os.remove("/etc/sudoers.d/waagent")
2289
def IsInRangeInclusive(a, low, high):
2291
Return True if 'a' in 'low' <= a >= 'high'
2293
return (a >= low and a <= high)
2295
def IsPrintable(ch):
2297
Return True if character is displayable.
2299
return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
2301
def HexDump(buffer, size):
2303
Return Hex formated dump of a 'buffer' of 'size'.
2308
for i in range(0, size):
2310
result += "%06X: " % i
2312
if type(byte) == str:
2313
byte = ord(byte.decode('latin1'))
2314
result += "%02X " % byte
2317
if ((i + 1) % 16) == 0 or (i + 1) == size:
2319
while ((j + 1) % 16) != 0:
2325
for j in range(i - (i % 16), i + 1):
2327
if type(byte) == str:
2328
byte = ord(byte.decode('latin1'))
2330
if IsPrintable(byte):
2337
def SimpleLog(file_path,message):
2338
if not file_path or len(message) < 1:
2340
t = time.localtime()
2341
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)
2342
lines=re.sub(re.compile(r'^(.)',re.MULTILINE),t+r'\1',message)
2343
with open(file_path, "a") as F :
2344
lines = filter(lambda x : x in string.printable, lines)
2345
F.write(lines.encode('ascii','ignore') + "\n")
2347
class Logger(object):
2349
The Agent's logging assumptions are:
2350
For Log, and LogWithPrefix all messages are logged to the
2351
self.file_path and to the self.con_path. Setting either path
2352
parameter to None skips that log. If Verbose is enabled, messages
2353
calling the LogIfVerbose method will be logged to file_path yet
2354
not to con_path. Error and Warn messages are normal log messages
2355
with the 'ERROR:' or 'WARNING:' prefix added.
2358
def __init__(self,filepath,conpath,verbose=False):
2360
Construct an instance of Logger.
2362
self.file_path=filepath
2363
self.con_path=conpath
2364
self.verbose=verbose
2366
def ThrottleLog(self,counter):
2368
Log everything up to 10, every 10 up to 100, then every 100.
2370
return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
2372
def LogToFile(self,message):
2374
Write 'message' to logfile.
2378
with open(self.file_path, "a") as F :
2379
message = filter(lambda x : x in string.printable, message)
2380
F.write(message.encode('ascii','ignore') + "\n")
2385
def LogToCon(self,message):
2387
Write 'message' to /dev/console.
2388
This supports serial port logging if the /dev/console
2389
is redirected to ttys0 in kernel boot options.
2393
with open(self.con_path, "w") as C :
2394
message = filter(lambda x : x in string.printable, message)
2395
C.write(message.encode('ascii','ignore') + "\n")
2400
def Log(self,message):
2402
Standard Log function.
2403
Logs to self.file_path, and con_path
2405
self.LogWithPrefix("", message)
2407
def LogWithPrefix(self,prefix, message):
2409
Prefix each line of 'message' with current time+'prefix'.
2411
t = time.localtime()
2412
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)
2414
for line in message.split('\n'):
2416
self.LogToFile(line)
2419
def NoLog(self,message):
2425
def LogIfVerbose(self,message):
2427
Only log 'message' if global Verbose is True.
2429
self.LogWithPrefixIfVerbose('',message)
2431
def LogWithPrefixIfVerbose(self,prefix, message):
2433
Only log 'message' if global Verbose is True.
2434
Prefix each line of 'message' with current time+'prefix'.
2436
if self.verbose == True:
2437
t = time.localtime()
2438
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)
2440
for line in message.split('\n'):
2442
self.LogToFile(line)
2445
def Warn(self,message):
2447
Prepend the text "WARNING:" to the prefix for each line in 'message'.
2449
self.LogWithPrefix("WARNING:", message)
2451
def Error(self,message):
2453
Call ErrorWithPrefix(message).
2455
ErrorWithPrefix("", message)
2457
def ErrorWithPrefix(self,prefix, message):
2459
Prepend the text "ERROR:" to the prefix for each line in 'message'.
2460
Errors written to logfile, and /dev/console
2462
self.LogWithPrefix("ERROR:", message)
2464
def LoggerInit(log_file_path,log_con_path,verbose=False):
2466
Create log object and export its methods to global scope.
2468
global Log,LogWithPrefix,LogIfVerbose,LogWithPrefixIfVerbose,Error,ErrorWithPrefix,Warn,NoLog,ThrottleLog,myLogger
2469
l=Logger(log_file_path,log_con_path,verbose)
2470
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
2472
def Linux_ioctl_GetInterfaceMac(ifname):
2474
Return the mac-address bound to the socket.
2476
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2477
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', (ifname[:15]+('\0'*241)).encode('latin-1')))
2478
return ''.join(['%02X' % Ord(char) for char in info[18:24]])
2480
def GetFirstActiveNetworkInterfaceNonLoopback():
2482
Return the interface name, and ip addr of the
2483
first active non-loopback interface.
2486
expected=16 # how many devices should I expect...
2487
struct_size=40 # for 64bit the size is 40 bytes
2488
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2489
buff=array.array('B', b'\0' * (expected*struct_size))
2490
retsize=(struct.unpack('iL', fcntl.ioctl(s.fileno(), 0x8912, struct.pack('iL',expected*struct_size,buff.buffer_info()[0]))))[0]
2491
if retsize == (expected*struct_size) :
2492
Warn('SIOCGIFCONF returned more than ' + str(expected) + ' up network interfaces.')
2494
for i in range(0,struct_size*expected,struct_size):
2495
iface=s[i:i+16].split(b'\0', 1)[0]
2500
return iface.decode('latin-1'), socket.inet_ntoa(s[i+20:i+24])
2502
def GetIpv4Address():
2504
Return the ip of the
2505
first active non-loopback interface.
2507
iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
2510
def HexStringToByteArray(a):
2512
Return hex string packed into a binary struct.
2515
for c in range(0, len(a) // 2):
2516
b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
2519
def GetMacAddress():
2521
Convienience function, returns mac addr bound to
2522
first non-loobback interface.
2525
while len(ifname) < 2 :
2526
ifname=GetFirstActiveNetworkInterfaceNonLoopback()[0]
2527
a = Linux_ioctl_GetInterfaceMac(ifname)
2528
return HexStringToByteArray(a)
2530
def DeviceForIdePort(n):
2532
Return device name attached to ide port 'n'.
2541
path = "/sys/bus/vmbus/devices/"
2542
for vmbus in os.listdir(path):
2543
guid = GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
2544
if guid[0] == g0 and guid[1] == "000" + str(n):
2545
for root, dirs, files in os.walk(path + vmbus):
2546
if root.endswith("/block"):
2549
else : #older distros
2551
if ':' in d and "block" == d.split(':')[0]:
2552
device = d.split(':')[1]
2559
Http communication class.
2560
Base of GoalState, and Agent classes.
2562
def _HttpGet(self, url, headers):
2564
Do HTTP get on 'url' with 'headers'.
2565
On error, sleep 10 and maxRetry times.
2566
Return the output buffer or None.
2568
LogIfVerbose("HttpGet(" + url + ")")
2570
if url.startswith("http://"):
2572
url = url[url.index("/"):]
2573
for retry in range(0, maxRetry + 1):
2574
strRetry = str(retry)
2575
log = [NoLog, Error][retry > 0]
2576
log("retry HttpGet(" + url + "),retry=" + strRetry)
2580
httpConnection = httplib.HTTPConnection(self.Endpoint)
2582
request = httpConnection.request("GET", url)
2584
request = httpConnection.request("GET", url, None, headers)
2585
response = httpConnection.getresponse()
2586
strStatus = str(response.status)
2587
except httplib.HTTPException, e:
2588
Error('HTTPException ' + e.message + ' args: ' + repr(e.args))
2590
Error('socket IOError ' + e.message + ' args: ' + repr(e.args))
2591
log("response HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2592
if response == None or response.status != httplib.OK:
2593
Error("HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2594
if retry == maxRetry:
2595
Error("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2598
Error("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2601
log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2602
return response.read()
2604
def HttpGetWithoutHeaders(self, url):
2606
Return data from an HTTP get on 'url'.
2608
return self._HttpGet(url, None)
2610
def HttpGetWithHeaders(self, url):
2612
Return data from an HTTP get on 'url' with
2613
x-ms-agent-name and x-ms-version
2616
return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion})
2618
def HttpSecureGetWithHeaders(self, url, transportCert):
2620
Return output of get using ssl cert.
2622
return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName,
2623
"x-ms-version": ProtocolVersion,
2624
"x-ms-cipher-name": "DES_EDE3_CBC",
2625
"x-ms-guest-agent-public-x509-cert": transportCert})
2627
def HttpPost(self, url, data):
2629
Send http POST to server, sleeping 10 retrying maxRetry times upon error.
2631
LogIfVerbose("HttpPost(" + url + ")")
2633
for retry in range(0, maxRetry + 1):
2634
strRetry = str(retry)
2635
log = [NoLog, Error][retry > 0]
2636
log("retry HttpPost(" + url + "),retry=" + strRetry)
2640
httpConnection = httplib.HTTPConnection(self.Endpoint)
2641
request = httpConnection.request("POST", url, data, {"x-ms-agent-name": GuestAgentName,
2642
"Content-Type": "text/xml; charset=utf-8",
2643
"x-ms-version": ProtocolVersion})
2644
response = httpConnection.getresponse()
2645
strStatus = str(response.status)
2646
except httplib.HTTPException, e:
2647
Error('HTTPException ' + e.message + ' args: ' + repr(e.args))
2649
Error('socket IOError ' + e.message + ' args: ' + repr(e.args))
2650
log("response HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2651
if response == None or (response.status != httplib.OK and response.status != httplib.ACCEPTED):
2652
Error("HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2653
if retry == maxRetry:
2654
Error("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2657
Error("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2660
log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2663
def HttpPutBlockBlob(self, url, data):
2665
Send http PUT to server, sleeping 10 retrying maxRetry times upon error.
2667
LogIfVerbose("HttpPutBlockBlob(" + url + ")")
2669
for retry in range(0, maxRetry + 1):
2670
strRetry = str(retry)
2671
log = [NoLog, Error][retry > 0]
2672
log("retry HttpPutBlockBlob(" + url + "),retry=" + strRetry)
2676
httpConnection = httplib.HTTPConnection(self.Endpoint)
2677
request = httpConnection.request("PUT", url, data, {"x-ms-blob-type" : "BlockBlob", "x-ms-date" : time.strftime("%Y-%M-%dT%H:%M:%SZ", time.gmtime()) ,"Content-Length": str(len(data))} )
2678
response = httpConnection.getresponse()
2679
strStatus = str(response.status)
2680
except httplib.HTTPException, e:
2681
Error('HTTPException ' + e.message + ' args: ' + repr(e.args))
2683
Error('socket IOError ' + e.message + ' args: ' + repr(e.args))
2684
log("response HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2685
if response == None or (response.status != httplib.OK and response.status != httplib.CREATED):
2686
Error("HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2687
if retry == maxRetry:
2688
Error("return HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2691
Error("sleep 10 seconds HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2694
log("return HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus)
2697
class TCPHandler(SocketServer.BaseRequestHandler):
2699
Callback object for LoadBalancerProbeServer.
2700
Recv and send LB probe messages.
2702
def __init__(self,lb_probe):
2703
super(TCPHandler,self).__init__()
2704
self.lb_probe=lb_probe
2706
def GetHttpDateTimeNow(self):
2708
Return formatted gmtime "Date: Fri, 25 Mar 2011 04:53:10 GMT"
2710
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
2714
Log LB probe messages, read the socket buffer,
2715
send LB probe response back to server.
2717
self.lb_probe.ProbeCounter = (self.lb_probe.ProbeCounter + 1) % 1000000
2718
log = [NoLog, LogIfVerbose][ThrottleLog(self.lb_probe.ProbeCounter)]
2719
strCounter = str(self.lb_probe.ProbeCounter)
2720
if self.lb_probe.ProbeCounter == 1:
2721
Log("Receiving LB probes.")
2722
log("Received LB probe # " + strCounter)
2723
self.request.recv(1024)
2724
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")
2726
class LoadBalancerProbeServer(object):
2728
Threaded object to receive and send LB probe messages.
2729
Load Balancer messages but be recv'd by
2730
the load balancing server, or this node may be shut-down.
2732
def __init__(self, port):
2733
self.ProbeCounter = 0
2734
self.server = SocketServer.TCPServer((self.get_ip(), port), TCPHandler)
2735
self.server_thread = threading.Thread(target = self.server.serve_forever)
2736
self.server_thread.setDaemon(True)
2737
self.server_thread.start()
2740
self.server.shutdown()
2743
for retry in range(1,6):
2744
ip = MyDistro.GetIpv4Address()
2746
Log("LoadBalancerProbeServer: GetIpv4Address() returned None, sleeping 10 before retry " + str(retry+1) )
2751
class ConfigurationProvider(object):
2753
Parse amd store key:values in waagent.conf
2756
self.values = dict()
2757
walaConfigFile = MyDistro.getConfigurationPath()
2758
if os.path.isfile(walaConfigFile) == False:
2759
raise Exception("Missing configuration in {0}".format(walaConfigFile))
2761
for line in GetFileContents(walaConfigFile).split('\n'):
2762
if not line.startswith("#") and "=" in line:
2763
parts = line.split()[0].split('=')
2764
value = parts[1].strip("\" ")
2766
self.values[parts[0]] = value
2768
self.values[parts[0]] = None
2770
Error("Unable to parse {0}".format(walaConfigFile))
2775
return self.values.get(key)
2777
class EnvMonitor(object):
2779
Montor changes to dhcp and hostname.
2780
If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
2783
self.shutdown = False
2784
self.HostName = socket.gethostname()
2785
self.server_thread = threading.Thread(target = self.monitor)
2786
self.server_thread.setDaemon(True)
2787
self.server_thread.start()
2788
self.published = False
2792
Monitor dhcp client pid and hostname.
2793
If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
2795
publish = ConfigurationProvider().get("Provisioning.MonitorHostName")
2796
dhcpcmd = MyDistro.getpidcmd+ ' ' + MyDistro.getDhcpClientName()
2797
dhcppid = RunGetOutput(dhcpcmd)[1]
2798
while not self.shutdown:
2799
for a in RulesFiles:
2800
if os.path.isfile(a):
2801
if os.path.isfile(GetLastPathElement(a)):
2802
os.remove(GetLastPathElement(a))
2804
Log("EnvMonitor: Moved " + a + " -> " + LibDir)
2805
MyDistro.setScsiDiskTimeout()
2806
if publish != None and publish.lower().startswith("y"):
2808
if socket.gethostname() != self.HostName:
2809
Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
2810
self.HostName = socket.gethostname()
2811
WaAgent.UpdateAndPublishHostName(self.HostName)
2812
dhcppid = RunGetOutput(dhcpcmd)[1]
2813
self.published = True
2817
self.published = True
2819
if not os.path.isdir("/proc/" + dhcppid.strip()):
2820
pid = RunGetOutput(dhcpcmd)[1]
2821
if pid != "" and pid != dhcppid:
2822
Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
2823
WaAgent.RestoreRoutes()
2825
for child in Children:
2826
if child.poll() != None:
2827
Children.remove(child)
2830
def SetHostName(self, name):
2832
Generic call to MyDistro.setHostname(name).
2833
Complian to Log on error.
2835
if socket.gethostname() == name:
2836
self.published = True
2837
elif MyDistro.setHostname(name):
2838
Error("Error: SetHostName: Cannot set hostname to " + name)
2839
return ("Error: SetHostName: Cannot set hostname to " + name)
2841
def IsHostnamePublished(self):
2843
Return self.published
2845
return self.published
2847
def ShutdownService(self):
2849
Stop server comminucation and join the thread to main thread.
2851
self.shutdown = True
2852
self.server_thread.join()
2854
class Certificates(object):
2856
Object containing certificates of host and provisioned user.
2857
Parses and splits certificates into files.
2859
# <CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
2860
# <Version>2010-12-15</Version>
2861
# <Incarnation>2</Incarnation>
2862
# <Format>Pkcs7BlobWithPfxContents</Format>
2865
# </CertificateFile>
2870
def reinitialize(self):
2872
Reset the Role, Incarnation
2874
self.Incarnation = None
2877
def Parse(self, xmlText):
2879
Parse multiple certificates into seperate files.
2882
SetFileContents("Certificates.xml", xmlText)
2883
dom = xml.dom.minidom.parseString(xmlText)
2884
for a in [ "CertificateFile", "Version", "Incarnation",
2885
"Format", "Data", ]:
2886
if not dom.getElementsByTagName(a):
2887
Error("Certificates.Parse: Missing " + a)
2889
node = dom.childNodes[0]
2890
if node.localName != "CertificateFile":
2891
Error("Certificates.Parse: root not CertificateFile")
2893
SetFileContents("Certificates.p7m",
2894
"MIME-Version: 1.0\n"
2895
+ "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
2896
+ "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
2897
+ "Content-Transfer-Encoding: base64\n\n"
2898
+ GetNodeTextData(dom.getElementsByTagName("Data")[0]))
2899
if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
2900
Error("Certificates.Parse: Failed to extract certificates from CMS message.")
2902
# There may be multiple certificates in this package. Split them.
2903
file = open("Certificates.pem")
2906
output = open("temp.pem", "w")
2907
for line in file.readlines():
2909
if re.match(r'[-]+END .*?(KEY|CERTIFICATE)[-]+$',line):
2911
if re.match(r'[-]+END .*?KEY[-]+$',line):
2912
os.rename("temp.pem", str(pindex) + ".prv")
2915
os.rename("temp.pem", str(cindex) + ".crt")
2917
output = open("temp.pem", "w")
2919
os.remove("temp.pem")
2922
filename = str(index) + ".crt"
2923
while os.path.isfile(filename):
2924
thumbprint = (RunGetOutput(Openssl + " x509 -in " + filename + " -fingerprint -noout")[1]).rstrip().split('=')[1].replace(':', '').upper()
2925
pubkey=RunGetOutput(Openssl + " x509 -in " + filename + " -pubkey -noout")[1]
2926
keys[pubkey] = thumbprint
2927
os.rename(filename, thumbprint + ".crt")
2928
os.chmod(thumbprint + ".crt", 0600)
2929
MyDistro.setSelinuxContext(thumbprint + '.crt','unconfined_u:object_r:ssh_home_t:s0')
2931
filename = str(index) + ".crt"
2933
filename = str(index) + ".prv"
2934
while os.path.isfile(filename):
2935
pubkey = RunGetOutput(Openssl + " rsa -in " + filename + " -pubout 2> /dev/null ")[1]
2936
os.rename(filename, keys[pubkey] + ".prv")
2937
os.chmod(keys[pubkey] + ".prv", 0600)
2938
MyDistro.setSelinuxContext( keys[pubkey] + '.prv','unconfined_u:object_r:ssh_home_t:s0')
2940
filename = str(index) + ".prv"
2943
class SharedConfig(object):
2945
Parse role endpoint server and goal state config.
2948
# <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
2949
# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
2950
# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
2951
# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
2953
# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
2954
# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
2955
# <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
2957
# <Probe name="MachineRole" />
2958
# <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
2959
# <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
2961
# </LoadBalancerSettings>
2963
# <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
2964
# <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
2966
# </OutputEndpoints>
2968
# <Instance id="MachineRole_IN_0" address="10.115.153.75">
2969
# <FaultDomains randomId="0" updateId="0" updateCount="0" />
2971
# <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">
2973
# <LocalPortRange from="80" to="80" />
2976
# <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
2978
# <LocalPortRange from="3389" to="3389" />
2981
# <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">
2983
# <LocalPortRange from="20000" to="20000" />
2994
def reinitialize(self):
2998
self.Deployment = None
2999
self.Incarnation = None
3001
self.LoadBalancerSettings = None
3002
self.OutputEndpoints = None
3003
self.Instances = None
3005
def Parse(self, xmlText):
3007
Parse and write configuration to file SharedConfig.xml.
3010
SetFileContents("SharedConfig.xml", xmlText)
3011
dom = xml.dom.minidom.parseString(xmlText)
3012
for a in [ "SharedConfig", "Deployment", "Service",
3013
"ServiceInstance", "Incarnation", "Role", ]:
3014
if not dom.getElementsByTagName(a):
3015
Error("SharedConfig.Parse: Missing " + a)
3017
node = dom.childNodes[0]
3018
if node.localName != "SharedConfig":
3019
Error("SharedConfig.Parse: root not SharedConfig")
3021
program = Config.get("Role.TopologyConsumer")
3024
Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"]))
3026
ErrorWithPrefix('Agent.Run','Exception: '+ str(e) +' occured launching ' + program )
3029
class ExtensionsConfig(object):
3031
Parse ExtensionsConfig, downloading and unpacking them to /var/lib/waagent.
3032
Install if <enabled>true</enabled>, remove if it is set to false.
3034
#<?xml version="1.0" encoding="utf-8"?>
3035
#<Extensions version="1.0.0.0" goalStateIncarnation="6"><Plugins>
3036
# <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.5"
3037
#location="http://previewusnorthcache.blob.core.test-cint.azure-test.net/d84b216d00bf4d96982be531539e1513/OSTCExtensions_ExampleHandlerLinux_usnorth_manifest.xml"
3038
#config="" state="enabled" autoUpgrade="false" runAsStartupTask="false" isJson="true" />
3041
# <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.5">
3042
# <RuntimeSettings seqNo="2">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"1BE9A13AA1321C7C515EF109746998BAB6D86FD1",
3043
#"protectedSettings":"MIIByAYJKoZIhvcNAQcDoIIBuTCCAbUCAQAxggFxMIIBbQIBADBVMEExPzA9BgoJkiaJk/IsZAEZFi9XaW5kb3dzIEF6dXJlIFNlcnZpY2UgTWFuYWdlbWVudCBmb3IgR
3044
#Xh0ZW5zaW9ucwIQZi7dw+nhc6VHQTQpCiiV2zANBgkqhkiG9w0BAQEFAASCAQCKr09QKMGhwYe+O4/a8td+vpB4eTR+BQso84cV5KCAnD6iUIMcSYTrn9aveY6v6ykRLEw8GRKfri2d6
3045
#tvVDggUrBqDwIgzejGTlCstcMJItWa8Je8gHZVSDfoN80AEOTws9Fp+wNXAbSuMJNb8EnpkpvigAWU2v6pGLEFvSKC0MCjDTkjpjqciGMcbe/r85RG3Zo21HLl0xNOpjDs/qqikc/ri43Y76E/X
3046
#v1vBSHEGMFprPy/Hwo3PqZCnulcbVzNnaXN3qi/kxV897xGMPPC3IrO7Nc++AT9qRLFI0841JLcLTlnoVG1okPzK9w6ttksDQmKBSHt3mfYV+skqs+EOMDsGCSqGSIb3DQEHATAUBggqh
3047
#kiG9w0DBwQITgu0Nu3iFPuAGD6/QzKdtrnCI5425fIUy7LtpXJGmpWDUA==","publicSettings":{"port":"3000"}}}]}</RuntimeSettings>
3050
#<StatusUploadBlob>https://ostcextensions.blob.core.test-cint.azure-test.net/vhds/eg-plugin7-vm.eg-plugin7-vm.eg-plugin7-vm.status?sr=b&sp=rw&
3051
#se=9999-01-01&sk=key1&sv=2012-02-12&sig=wRUIDN1x2GC06FWaetBP9sjjifOWvRzS2y2XBB4qoBU%3D</StatusUploadBlob></Extensions>
3056
def reinitialize(self):
3060
self.Extensions = None
3064
def Parse(self, xmlText):
3066
Write configuration to file ExtensionsConfig.xml.
3067
Log plugin specific activity to /var/log/azure/<Publisher>.<PluginName>/<Version>/CommandExecution.log.
3068
If state is enabled:
3069
if the plugin is installed:
3070
if the new plugin's version is higher
3071
if DisallowMajorVersionUpgrade is false or if true, the version is a minor version do upgrade:
3072
download the new archive
3073
do the updateCommand.
3074
disable the old plugin and remove
3075
enable the new plugin
3076
if the new plugin's version is the same or lower:
3077
create the new .settings file from the configuration received
3078
do the enableCommand
3079
if the plugin is not installed:
3080
download/unpack archive and call the installCommand/Enable
3081
if state is disabled:
3083
if state is uninstall:
3084
call uninstallCommand
3085
remove old plugin directory.
3089
dom = xml.dom.minidom.parseString(xmlText)
3090
LogIfVerbose(xmlText)
3091
self.plugin_log_dir='/var/log/azure'
3092
if not os.path.exists(self.plugin_log_dir):
3093
os.mkdir(self.plugin_log_dir)
3095
self.Extensions=dom.getElementsByTagName("Extensions")
3096
pg = dom.getElementsByTagName("Plugins")
3097
self.Plugins = pg[0].getElementsByTagName("Plugin")
3098
incarnation=self.Extensions[0].getAttribute("goalStateIncarnation")
3099
SetFileContents('ExtensionsConfig.'+incarnation+'.xml', xmlText)
3101
LogIfVerbose('ERROR: Error parsing ExtensionsConfig.')
3103
for p in self.Plugins:
3104
if len(p.getAttribute("location"))<1: # this plugin is inside the PluginSettings
3106
p.setAttribute('restricted','false')
3107
previous_version = None
3108
version=p.getAttribute("version")
3109
name=p.getAttribute("name")
3110
plog_dir=self.plugin_log_dir+'/'+name +'/'+ version
3111
if not os.path.exists(plog_dir):
3112
os.makedirs(plog_dir)
3113
p.plugin_log=plog_dir+'/CommandExecution.log'
3114
handler=name + '-' + version
3115
if p.getAttribute("isJson") != 'true':
3116
Error("Plugin " + name+" version: " +version+" is not a JSON Extension. Skipping.")
3118
Log("Found Plugin: " + name + ' version: ' + version)
3119
if p.getAttribute("state") == 'disabled' or p.getAttribute("state") == 'uninstall':
3121
zip_dir=LibDir+"/" + name + '-' + version
3123
for root, dirs, files in os.walk(zip_dir):
3125
if f in ('HandlerManifest.json'):
3126
mfile=os.path.join(root,f)
3130
Error('HandlerManifest.json not found.')
3132
manifest = GetFileContents(mfile)
3133
p.setAttribute('manifestdata',manifest)
3134
if self.launchCommand(p.plugin_log,name,version,'disableCommand') == None :
3135
self.SetHandlerState(handler, 'Enabled')
3136
Error('Unable to disable '+name)
3137
SimpleLog(p.plugin_log,'ERROR: Unable to disable '+name)
3139
self.SetHandlerState(handler, 'Disabled')
3140
Log(name+' is disabled')
3141
SimpleLog(p.plugin_log,name+' is disabled')
3143
# uninstall if needed
3144
if p.getAttribute("state") == 'uninstall':
3145
if self.launchCommand(p.plugin_log,name,version,'uninstallCommand') == None :
3146
self.SetHandlerState(handler, 'Installed')
3147
Error('Unable to uninstall '+name)
3148
SimpleLog(p.plugin_log,'Unable to uninstall '+name)
3150
self.SetHandlerState(handler, 'NotInstalled')
3151
Log(name+' uninstallCommand completed .')
3153
Run('rm -rf ' + LibDir + '/' + name +'-'+ version + '*')
3154
Log(name +'-'+ version + ' extension files deleted.')
3155
SimpleLog(p.plugin_log,name +'-'+ version + ' extension files deleted.')
3159
# if the same plugin exists and the version is newer or
3160
# does not exist then download and unzip the new plugin
3162
for root, dirs, files in os.walk(LibDir):
3165
plg_dir=os.path.join(root,d)
3168
if plg_dir != None :
3169
previous_version=plg_dir.rsplit('-')[-1]
3170
if plg_dir == None or version > previous_version :
3171
location=p.getAttribute("location")
3172
Log("Downloading plugin manifest: " + name + " from " + location)
3173
SimpleLog(p.plugin_log,"Downloading plugin manifest: " + name + " from " + location)
3175
self.Util.Endpoint=location.split('/')[2]
3176
Log("Plugin server is: " + self.Util.Endpoint)
3177
SimpleLog(p.plugin_log,"Plugin server is: " + self.Util.Endpoint)
3179
manifest=self.Util.HttpGetWithoutHeaders(location)
3180
if manifest == None:
3181
Error("Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.")
3182
SimpleLog(p.plugin_log,"Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.")
3183
failoverlocation=p.getAttribute("failoverlocation")
3184
self.Util.Endpoint=failoverlocation.split('/')[2]
3185
Log("Plugin failover server is: " + self.Util.Endpoint)
3186
SimpleLog(p.plugin_log,"Plugin failover server is: " + self.Util.Endpoint)
3188
manifest=self.Util.HttpGetWithoutHeaders(failoverlocation)
3189
#if failoverlocation also fail what to do then?
3190
if manifest == None:
3191
AddExtensionEvent(name,WALAEventOperation.Download,False,0,version,"Download mainfest fail "+failoverlocation)
3192
Log("Plugin manifest" + name + "downloaded successfully length = " + str(len(manifest)))
3193
SimpleLog(p.plugin_log,"Plugin manifest" + name + "downloaded successfully length = " + str(len(manifest)))
3195
filepath=LibDir+"/" + name + '.' + incarnation + '.manifest'
3196
if os.path.splitext(location)[-1] == '.xml' : #if this is an xml file we may have a BOM
3197
if ord(manifest[0]) > 128 and ord(manifest[1]) > 128 and ord(manifest[2]) > 128:
3198
manifest=manifest[3:]
3199
SetFileContents(filepath,manifest)
3200
#Get the bundle url from the manifest
3201
p.setAttribute('manifestdata',manifest)
3202
man_dom = xml.dom.minidom.parseString(manifest)
3204
for mp in man_dom.getElementsByTagName("Plugin"):
3205
if GetNodeTextData(mp.getElementsByTagName("Version")[0]) == version:
3206
bundle_uri = GetNodeTextData(mp.getElementsByTagName("Uri")[0])
3208
if len(mp.getElementsByTagName("DisallowMajorVersionUpgrade")):
3209
if GetNodeTextData(mp.getElementsByTagName("DisallowMajorVersionUpgrade")[0]) == 'true' and previous_version !=None and previous_version.split('.')[0] != version.split('.')[0] :
3210
Log('DisallowMajorVersionUpgrade is true, this major version is restricted from upgrade.')
3211
SimpleLog(p.plugin_log,'DisallowMajorVersionUpgrade is true, this major version is restricted from upgrade.')
3212
p.setAttribute('restricted','true')
3214
if len(bundle_uri) < 1 :
3215
Error("Unable to fetch Bundle URI from manifest for " + name + " v " + version)
3216
SimpleLog(p.plugin_log,"Unable to fetch Bundle URI from manifest for " + name + " v " + version)
3218
Log("Bundle URI = " + bundle_uri)
3219
SimpleLog(p.plugin_log,"Bundle URI = " + bundle_uri)
3221
# Download the zipfile archive and save as '.zip'
3222
bundle=self.Util.HttpGetWithoutHeaders(bundle_uri)
3224
AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download zip fail "+bundle_uri)
3225
Error("Unable to download plugin bundle" + bundle_uri )
3226
SimpleLog(p.plugin_log,"Unable to download plugin bundle" + bundle_uri )
3228
AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download Success")
3230
filepath=LibDir+"/" + os.path.basename(bundle_uri) + '.zip'
3231
SetFileContents(filepath,b)
3232
Log("Plugin bundle" + bundle_uri + "downloaded successfully length = " + str(len(bundle)))
3233
SimpleLog(p.plugin_log,"Plugin bundle" + bundle_uri + "downloaded successfully length = " + str(len(bundle)))
3235
# unpack the archive
3236
z=zipfile.ZipFile(filepath)
3237
zip_dir=LibDir+"/" + name + '-' + version
3238
z.extractall(zip_dir)
3239
Log('Extracted ' + bundle_uri + ' to ' + zip_dir)
3240
SimpleLog(p.plugin_log,'Extracted ' + bundle_uri + ' to ' + zip_dir)
3242
# zip no file perms in .zip so set all the scripts to +x
3243
Run( "find " + zip_dir +" -type f | xargs chmod u+x ")
3244
#write out the base64 config data so the plugin can process it.
3246
for root, dirs, files in os.walk(zip_dir):
3248
if f in ('HandlerManifest.json'):
3249
mfile=os.path.join(root,f)
3253
Error('HandlerManifest.json not found.')
3254
SimpleLog(p.plugin_log,'HandlerManifest.json not found.')
3256
manifest = GetFileContents(mfile)
3257
p.setAttribute('manifestdata',manifest)
3258
# create the status and config dirs
3259
Run('mkdir -p ' + root + '/status')
3260
Run('mkdir -p ' + root + '/config')
3261
# write out the configuration data to goalStateIncarnation.settings file in the config path.
3264
if len(dom.getElementsByTagName("PluginSettings")) != 0 :
3265
pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin")
3267
if name == ps.getAttribute("name") and version == ps.getAttribute("version"):
3268
Log("Found RuntimeSettings for " + name + " V " + version)
3269
SimpleLog(p.plugin_log,"Found RuntimeSettings for " + name + " V " + version)
3271
config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0])
3272
seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo")
3275
Log("No RuntimeSettings for " + name + " V " + version)
3276
SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version)
3278
SetFileContents(root +"/config/" + seqNo +".settings", config )
3279
#create HandlerEnvironment.json
3280
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"}}]'
3281
SetFileContents(root+'/HandlerEnvironment.json',handler_env)
3282
self.SetHandlerState(handler, 'NotInstalled')
3285
getcmd='installCommand'
3286
if plg_dir != None and previous_version != None and version > previous_version :
3287
previous_handler=name+'-'+previous_version
3288
if self.GetHandlerState(previous_handler) != 'NotInstalled':
3289
getcmd='updateCommand'
3290
# disable the old plugin if it exists
3291
if self.launchCommand(p.plugin_log,name,previous_version,'disableCommand') == None :
3292
self.SetHandlerState(previous_handler, 'Enabled')
3293
Error('Unable to disable old plugin '+name+' version ' + previous_version)
3294
SimpleLog(p.plugin_log,'Unable to disable old plugin '+name+' version ' + previous_version)
3296
self.SetHandlerState(previous_handler, 'Disabled')
3297
Log(name+' version ' + previous_version + ' is disabled')
3298
SimpleLog(p.plugin_log,name+' version ' + previous_version + ' is disabled')
3300
isupgradeSuccess = True
3301
if getcmd=='updateCommand':
3302
if self.launchCommand(p.plugin_log,name,version,getcmd,previous_version) == None :
3303
Error('Update failed for '+name+'-'+version)
3304
SimpleLog(p.plugin_log,'Update failed for '+name+'-'+version)
3305
isupgradeSuccess=False
3307
Log('Update complete'+name+'-'+version)
3308
SimpleLog(p.plugin_log,'Update complete'+name+'-'+version)
3310
# if we updated - call unistall for the old plugin
3311
if self.launchCommand(p.plugin_log,name,previous_version,'uninstallCommand') == None :
3312
self.SetHandlerState(previous_handler, 'Installed')
3313
Error('Uninstall failed for '+name+'-'+previous_version)
3314
SimpleLog(p.plugin_log,'Uninstall failed for '+name+'-'+previous_version)
3315
isupgradeSuccess=False
3317
self.SetHandlerState(previous_handler, 'NotInstalled')
3318
Log('Uninstall complete'+ previous_handler )
3319
SimpleLog(p.plugin_log,'Uninstall complete'+ name +'-' + previous_version)
3320
AddExtensionEvent(name,WALAEventOperation.Upgrade,isupgradeSuccess,0,previous_version)
3321
else : # run install
3322
if self.launchCommand(p.plugin_log,name,version,getcmd) == None :
3323
self.SetHandlerState(handler, 'NotInstalled')
3324
Error('Installation failed for '+name+'-'+version)
3325
SimpleLog(p.plugin_log,'Installation failed for '+name+'-'+version)
3327
self.SetHandlerState(handler, 'Installed')
3328
Log('Installation completed for '+name+'-'+version)
3329
SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version)
3331
#end if plg_dir == none or version > = prev
3332
# change incarnation of settings file so it knows how to name status...
3333
zip_dir=LibDir+"/" + name + '-' + version
3335
for root, dirs, files in os.walk(zip_dir):
3337
if f in ('HandlerManifest.json'):
3338
mfile=os.path.join(root,f)
3342
Error('HandlerManifest.json not found.')
3343
SimpleLog(p.plugin_log,'HandlerManifest.json not found.')
3346
manifest = GetFileContents(mfile)
3347
p.setAttribute('manifestdata',manifest)
3350
if len(dom.getElementsByTagName("PluginSettings")) != 0 :
3352
pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin")
3354
Error('Error parsing ExtensionsConfig.')
3355
SimpleLog(p.plugin_log,'Error parsing ExtensionsConfig.')
3359
if name == ps.getAttribute("name") and version == ps.getAttribute("version"):
3360
Log("Found RuntimeSettings for " + name + " V " + version)
3361
SimpleLog(p.plugin_log,"Found RuntimeSettings for " + name + " V " + version)
3363
config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0])
3364
seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo")
3367
Error("No RuntimeSettings for " + name + " V " + version)
3368
SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version)
3370
SetFileContents(root +"/config/" + seqNo +".settings", config )
3372
# state is still enable
3373
if (self.GetHandlerState(handler) == 'NotInstalled'): # run install first if true
3374
if self.launchCommand(p.plugin_log,name,version,'installCommand') == None :
3375
self.SetHandlerState(handler, 'NotInstalled')
3376
Error('Installation failed for '+name+'-'+version)
3377
SimpleLog(p.plugin_log,'Installation failed for '+name+'-'+version)
3380
self.SetHandlerState(handler, 'Installed')
3381
Log('Installation completed for '+name+'-'+version)
3382
SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version)
3385
if (self.GetHandlerState(handler) != 'NotInstalled'):
3386
if self.launchCommand(p.plugin_log,name,version,'enableCommand') == None :
3387
self.SetHandlerState(handler, 'Installed')
3388
Error('Enable failed for '+name+'-'+version)
3389
SimpleLog(p.plugin_log,'Enable failed for '+name+'-'+version)
3392
self.SetHandlerState(handler, 'Enabled')
3393
Log('Enable completed for '+name+'-'+version)
3394
SimpleLog(p.plugin_log,'Enable completed for '+name+'-'+version)
3396
# this plugin processing is complete
3397
Log('Processing completed for '+name+'-'+version)
3398
SimpleLog(p.plugin_log,'Processing completed for '+name+'-'+version)
3400
#end plugin processing loop
3401
Log('Finished processing ExtensionsConfig.xml')
3403
SimpleLog(p.plugin_log,'Finished processing ExtensionsConfig.xml')
3408
def launchCommand(self,plugin_log,name,version,command,prev_version=None):
3409
commandToEventOperation={
3410
"installCommand":WALAEventOperation.Install,
3411
"uninstallCommand":WALAEventOperation.UnIsntall,
3412
"updateCommand": WALAEventOperation.Upgrade,
3413
"enableCommand": WALAEventOperation.Enable,
3414
"disableCommand": WALAEventOperation.Disable,
3417
start = datetime.datetime.now()
3418
r=self.__launchCommandWithoutEventLog(plugin_log,name,version,command,prev_version)
3421
Duration = int((datetime.datetime.now() - start).seconds)
3422
if commandToEventOperation.get(command):
3423
AddExtensionEvent(name,commandToEventOperation[command],isSuccess,Duration,version)
3426
def __launchCommandWithoutEventLog(self,plugin_log,name,version,command,prev_version=None):
3427
# get the manifest and read the command
3429
zip_dir=LibDir+"/" + name + '-' + version
3430
for root, dirs, files in os.walk(zip_dir):
3432
if f in ('HandlerManifest.json'):
3433
mfile=os.path.join(root,f)
3437
Error('HandlerManifest.json not found.')
3438
SimpleLog(plugin_log,'HandlerManifest.json not found.')
3441
manifest = GetFileContents(mfile)
3443
jsn = json.loads(manifest)
3445
Error('Error parsing HandlerManifest.json.')
3446
SimpleLog(plugin_log,'Error parsing HandlerManifest.json.')
3451
if jsn.has_key('handlerManifest') :
3452
cmd = jsn['handlerManifest'][command]
3454
Error('Key handlerManifest not found. Handler cannot be installed.')
3455
SimpleLog(plugin_log,'Key handlerManifest not found. Handler cannot be installed.')
3458
Error('Unable to read ' + command )
3459
SimpleLog(plugin_log,'Unable to read ' + command )
3463
# for update we send the path of the old installation
3465
if prev_version != None :
3466
arg=' ' + LibDir+'/' + name + '-' + prev_version
3467
dirpath=os.path.dirname(mfile)
3468
LogIfVerbose('Command is '+ dirpath+'/'+ cmd)
3472
child = subprocess.Popen(dirpath+'/'+cmd+arg,shell=True,cwd=dirpath,stdout=subprocess.PIPE)
3473
except Exception as e:
3474
Error('Exception launching ' + cmd + str(e))
3475
SimpleLog(plugin_log,'Exception launching ' + cmd + str(e))
3478
if pid == None or pid < 1 :
3479
ExtensionChildren.append((-1,root))
3480
Error('Error launching ' + cmd + '.')
3481
SimpleLog(plugin_log,'Error launching ' + cmd + '.')
3484
ExtensionChildren.append((pid,root))
3485
Log("Spawned "+ cmd + " PID " + str(pid))
3486
SimpleLog(plugin_log,"Spawned "+ cmd + " PID " + str(pid))
3489
# wait until install/upgrade is finished
3490
timeout = 300 # 5 minutes
3492
while retry > 0 and child.poll() == None:
3493
LogIfVerbose(cmd + ' still running with PID ' + str(pid))
3497
Error('Process exceeded timeout of ' + str(timeout) + ' seconds. Terminating process ' + str(pid))
3498
SimpleLog(plugin_log,'Process exceeded timeout of ' + str(timeout) + ' seconds. Terminating process ' + str(pid))
3503
if code == None or code != 0:
3504
Error('Process ' + str(pid) + ' returned non-zero exit code (' + str(code) + ')')
3505
SimpleLog(plugin_log,'Process ' + str(pid) + ' returned non-zero exit code (' + str(code) + ')')
3508
Log(command + ' completed.')
3509
SimpleLog(plugin_log,command + ' completed.')
3513
def ReportHandlerStatus(self):
3515
Collect all status reports.
3517
# { "version": "1.0", "timestampUTC": "2014-03-31T21:28:58Z",
3518
# "aggregateStatus": {
3519
# "guestAgentStatus": { "version": "2.0.4PRE", "status": "Ready", "formattedMessage": { "lang": "en-US", "message": "GuestAgent is running and accepting new configurations." } },
3520
# "handlerAggregateStatus": [{
3521
# "handlerName": "ExampleHandlerLinux", "handlerVersion": "1.0", "status": "Ready", "runtimeSettingsStatus": {
3522
# "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" },
3524
# { "name": "StdOut", "status": "success", "formattedMessage": { "lang": "en-US", "message": "Goodbye world!" } },
3525
# { "name": "StdErr", "status": "success", "formattedMessage": { "lang": "en-US", "message": "" } }
3532
incarnation=self.Extensions[0].getAttribute("goalStateIncarnation")
3534
Error('Error parsing ExtensionsConfig. Unable to send status reports')
3538
for p in self.Plugins:
3539
if p.getAttribute("state") == 'uninstall' or p.getAttribute("restricted") == 'true' :
3541
version=p.getAttribute("version")
3542
name=p.getAttribute("name")
3543
if p.getAttribute("isJson") != 'true':
3544
LogIfVerbose("Plugin " + name+" version: " +version+" is not a JSON Extension. Skipping.")
3546
reportHeartbeat = False
3547
if len(p.getAttribute("manifestdata"))<1:
3548
Error("Failed to get manifestdata.")
3550
reportHeartbeat = json.loads(p.getAttribute("manifestdata"))[0]['handlerManifest']['reportHeartbeat']
3553
statuses+=self.GenerateAggStatus(name, version, reportHeartbeat)
3554
tstamp=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
3557
if provisioned == False:
3558
if provisionError == None :
3559
agent_state='Provisioning'
3560
agent_msg='Guest Agent is starting.'
3562
agent_state='Provisioning Error.'
3563
agent_msg=provisionError
3566
agent_msg='GuestAgent is running and accepting new configurations.'
3568
status='{"version":"1.0","timestampUTC":"'+tstamp+'","aggregateStatus":{"guestAgentStatus":{"version":"'+GuestAgentVersion+'","status":"'+agent_state+'","formattedMessage":{"lang":"en-US","message":"'+agent_msg+'"}},"handlerAggregateStatus":['+statuses+']}}'
3570
uri=GetNodeTextData(self.Extensions[0].getElementsByTagName("StatusUploadBlob")[0]).replace('&','&')
3572
Error('Error parsing ExtensionsConfig. Unable to send status reports')
3574
self.Util.Endpoint=uri.split('/')[2]
3575
self.Util.HttpPutBlockBlob(uri, status)
3576
LogIfVerbose('Status report '+status+' sent to ' + uri)
3579
def GetCurrentSequenceNumber(self, plugin_base_dir):
3581
Get the settings file with biggest file number in config folder
3583
config_dir = os.path.join(plugin_base_dir, 'config')
3585
for subdir, dirs, files in os.walk(config_dir):
3588
cur_seq_no = int(os.path.basename(file).split('.')[0])
3589
if cur_seq_no > seq_no:
3596
def GenerateAggStatus(self, name, version, reportHeartbeat = False):
3598
Generate the status which Azure can understand by the status and heartbeat reported by extension
3600
plugin_base_dir = LibDir+'/'+name+'-'+version+'/'
3601
current_seq_no = self.GetCurrentSequenceNumber(plugin_base_dir)
3602
status_file=os.path.join(plugin_base_dir, 'status/', current_seq_no +'.status')
3603
heartbeat_file = os.path.join(plugin_base_dir, 'heartbeat.log')
3605
handler_state_file = os.path.join(plugin_base_dir, 'config', 'HandlerState')
3606
agg_state = 'NotReady'
3607
handler_state = None
3610
formatted_message = None
3611
localized_message = None
3613
if os.path.exists(handler_state_file):
3614
handler_state = GetFileContents(handler_state_file).lower()
3615
if HandlerStatusToAggStatus.has_key(handler_state):
3616
agg_state = HandlerStatusToAggStatus[handler_state]
3618
if os.path.exists(heartbeat_file):
3619
d=int(time.time()-os.stat(heartbeat_file).st_mtime)
3620
if d > 600 : # not updated for more than 10 min
3621
agg_state = 'Unresponsive'
3624
heartbeat = json.loads(GetFileContents(heartbeat_file))[0]["heartbeat"]
3625
agg_state = heartbeat.get("status")
3626
status_code = heartbeat.get("code")
3627
formatted_message = heartbeat.get("formattedMessage")
3628
localized_message = heartbeat.get("message")
3630
Error("Incorrect heartbeat file. Ignore it. ")
3632
agg_state = 'Unresponsive'
3633
#get status file reported by extension
3634
if os.path.exists(status_file):
3635
# raw status generated by extension is an array, get the first item and remove the unnecessary element
3637
status_obj = json.loads(GetFileContents(status_file))[0]
3638
del status_obj["version"]
3640
Error("Incorrect status file. Will NOT settingsStatus in settings. ")
3641
agg_status_obj = {"handlerName": name, "handlerVersion": version, "status": agg_state, "runtimeSettingsStatus" :
3642
{"sequenceNumber": current_seq_no}}
3644
agg_status_obj["runtimeSettingsStatus"]["settingsStatus"] = status_obj
3645
if status_code != None:
3646
agg_status_obj["code"] = status_code
3647
if formatted_message:
3648
agg_status_obj["formattedMessage"] = formatted_message
3649
if localized_message:
3650
agg_status_obj["message"] = localized_message
3651
agg_status_string = json.dumps(agg_status_obj)
3652
LogIfVerbose("Handler Aggregated Status:" + agg_status_string)
3653
return agg_status_string
3656
def SetHandlerState(self, handler, state=''):
3657
zip_dir=LibDir+"/" + handler
3659
for root, dirs, files in os.walk(zip_dir):
3661
if f in ('HandlerManifest.json'):
3662
mfile=os.path.join(root,f)
3666
Error('SetHandlerState(): HandlerManifest.json not found, cannot set HandlerState.')
3668
Log("SetHandlerState: "+handler+", "+state)
3669
return SetFileContents(os.path.dirname(mfile)+'/config/HandlerState', state)
3671
def GetHandlerState(self, handler):
3672
handlerState = GetFileContents(handler+'/config/HandlerState')
3674
return handlerState.rstrip('\r\n')
3676
return 'NotInstalled'
3679
class HostingEnvironmentConfig(object):
3681
Parse Hosting enviromnet config and store in
3682
HostingEnvironmentConfig.xml
3685
# <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
3686
# <StoredCertificates>
3687
# <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
3688
# </StoredCertificates>
3689
# <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
3690
# <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
3691
# <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
3693
# <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
3694
# <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
3695
# <HostingEnvironmentSettings name="full" Runtime="rd_fabric_stable.110217-1402.RuntimePackage_1.0.0.8.zip">
3696
# <CAS mode="full" />
3697
# <PrivilegeLevel mode="max" />
3698
# <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties>
3699
# </HostingEnvironmentSettings>
3700
# <ApplicationSettings>
3701
# <Setting name="__ModelData" value="<m role="MachineRole" xmlns="urn:azure:m:v1"><r name="MachineRole"><e name="a" /><e name="b" /><e name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" /><e name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" /></r></m>" />
3702
# <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." />
3703
# <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
3704
# </ApplicationSettings>
3705
# <ResourceReferences>
3706
# <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" />
3707
# </ResourceReferences>
3708
# </HostingEnvironmentConfig>
3713
def reinitialize(self):
3717
self.StoredCertificates = None
3718
self.Deployment = None
3719
self.Incarnation = None
3721
self.HostingEnvironmentSettings = None
3722
self.ApplicationSettings = None
3723
self.Certificates = None
3724
self.ResourceReferences = None
3726
def Parse(self, xmlText):
3728
Parse and create HostingEnvironmentConfig.xml.
3731
SetFileContents("HostingEnvironmentConfig.xml", xmlText)
3732
dom = xml.dom.minidom.parseString(xmlText)
3733
for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
3734
"ServiceInstance", "Incarnation", "Role", ]:
3735
if not dom.getElementsByTagName(a):
3736
Error("HostingEnvironmentConfig.Parse: Missing " + a)
3738
node = dom.childNodes[0]
3739
if node.localName != "HostingEnvironmentConfig":
3740
Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
3742
self.ApplicationSettings = dom.getElementsByTagName("Setting")
3743
self.Certificates = dom.getElementsByTagName("StoredCertificate")
3746
def DecryptPassword(self, e):
3748
Return decrypted password.
3750
SetFileContents("password.p7m",
3751
"MIME-Version: 1.0\n"
3752
+ "Content-Disposition: attachment; filename=\"password.p7m\"\n"
3753
+ "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
3754
+ "Content-Transfer-Encoding: base64\n\n"
3755
+ textwrap.fill(e, 64))
3756
return RunGetOutput(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem")[1]
3758
def ActivateResourceDisk(self):
3759
return MyDistro.ActivateResourceDisk()
3763
Execute ActivateResourceDisk in separate thread.
3764
Create the user account.
3765
Launch ConfigurationConsumer if specified in the config.
3768
if DiskActivated == False:
3769
for m in inspect.getmembers(MyDistro):
3770
if 'ActivateResourceDiskNoThread' in m:
3773
if no_thread == True :
3774
MyDistro.ActivateResourceDiskNoThread()
3776
diskThread = threading.Thread(target = self.ActivateResourceDisk)
3782
for b in self.ApplicationSettings:
3783
sname = b.getAttribute("name")
3784
svalue = b.getAttribute("value")
3785
if User != None and Pass != None:
3786
if User != "root" and User != "" and Pass != "":
3787
CreateAccount(User, Pass, Expiration, Thumbprint)
3789
Error("Not creating user account: " + User)
3790
for c in self.Certificates:
3791
csha1 = c.getAttribute("certificateId").split(':')[1].upper()
3792
if os.path.isfile(csha1 + ".prv"):
3793
Log("Private key with thumbprint: " + csha1 + " was retrieved.")
3794
if os.path.isfile(csha1 + ".crt"):
3795
Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
3796
program = Config.get("Role.ConfigurationConsumer")
3799
Children.append(subprocess.Popen([program, LibDir + "/HostingEnvironmentConfig.xml"]))
3801
ErrorWithPrefix('HostingEnvironmentConfig.Process','Exception: '+ str(e) +' occured launching ' + program )
3803
class GoalState(Util):
3805
Primary container for all configuration except OvfXml.
3806
Encapsulates http communication with endpoint server.
3807
Initializes and populates:
3808
self.HostingEnvironmentConfig
3810
self.ExtensionsConfig
3814
# <GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
3815
# <Version>2010-12-15</Version>
3816
# <Incarnation>1</Incarnation>
3818
# <ExpectedState>Started</ExpectedState>
3820
# <Port>16001</Port>
3824
# <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
3825
# <RoleInstanceList>
3827
# <InstanceId>MachineRole_IN_0</InstanceId>
3828
# <State>Started</State>
3830
# <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=hostingEnvironmentConfig&incarnation=1</HostingEnvironmentConfig>
3831
# <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=sharedConfig&incarnation=1</SharedConfig>
3832
# <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&incarnation=1</Certificates>
3833
# <ExtensionsConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&type=extensionsConfig&incarnation=2</ExtensionsConfig>
3834
# <FullConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&type=fullConfig&incarnation=2</FullConfig>
3838
# </RoleInstanceList>
3842
# There is only one Role for VM images.
3844
# Of primary interest is:
3845
# LBProbePorts -- an http server needs to run here
3846
# We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
3847
# And of course, Incarnation
3849
def __init__(self, Agent):
3851
self.Endpoint = Agent.Endpoint
3852
self.TransportCert = Agent.TransportCert
3855
def reinitialize(self):
3856
self.Incarnation = None # integer
3857
self.ExpectedState = None # "Started"
3858
self.HostingEnvironmentConfigUrl = None
3859
self.HostingEnvironmentConfigXml = None
3860
self.HostingEnvironmentConfig = None
3861
self.SharedConfigUrl = None
3862
self.SharedConfigXml = None
3863
self.SharedConfig = None
3864
self.CertificatesUrl = None
3865
self.CertificatesXml = None
3866
self.Certificates = None
3867
self.ExtensionsConfigUrl = None
3868
self.ExtensionsConfigXml = None
3869
self.ExtensionsConfig = None
3870
self.RoleInstanceId = None
3871
self.ContainerId = None
3872
self.LoadBalancerProbePort = None # integer, ?list of integers
3874
def Parse(self, xmlText):
3876
Request configuration data from endpoint server.
3877
Parse and populate contained configuration objects.
3878
Calls Certificates().Parse()
3879
Calls SharedConfig().Parse
3880
Calls ExtensionsConfig().Parse
3881
Calls HostingEnvironmentConfig().Parse
3884
LogIfVerbose(xmlText)
3885
node = xml.dom.minidom.parseString(xmlText).childNodes[0]
3886
if node.localName != "GoalState":
3887
Error("GoalState.Parse: root not GoalState")
3889
for a in node.childNodes:
3890
if a.nodeType == node.ELEMENT_NODE:
3891
if a.localName == "Incarnation":
3892
self.Incarnation = GetNodeTextData(a)
3893
elif a.localName == "Machine":
3894
for b in a.childNodes:
3895
if b.nodeType == node.ELEMENT_NODE:
3896
if b.localName == "ExpectedState":
3897
self.ExpectedState = GetNodeTextData(b)
3898
Log("ExpectedState: " + self.ExpectedState)
3899
elif b.localName == "LBProbePorts":
3900
for c in b.childNodes:
3901
if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
3902
self.LoadBalancerProbePort = int(GetNodeTextData(c))
3903
elif a.localName == "Container":
3904
for b in a.childNodes:
3905
if b.nodeType == node.ELEMENT_NODE:
3906
if b.localName == "ContainerId":
3907
self.ContainerId = GetNodeTextData(b)
3908
Log("ContainerId: " + self.ContainerId)
3909
elif b.localName == "RoleInstanceList":
3910
for c in b.childNodes:
3911
if c.localName == "RoleInstance":
3912
for d in c.childNodes:
3913
if d.nodeType == node.ELEMENT_NODE:
3914
if d.localName == "InstanceId":
3915
self.RoleInstanceId = GetNodeTextData(d)
3916
Log("RoleInstanceId: " + self.RoleInstanceId)
3917
elif d.localName == "State":
3919
elif d.localName == "Configuration":
3920
for e in d.childNodes:
3921
if e.nodeType == node.ELEMENT_NODE:
3922
LogIfVerbose(e.localName)
3923
if e.localName == "HostingEnvironmentConfig":
3924
self.HostingEnvironmentConfigUrl = GetNodeTextData(e)
3925
LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl)
3926
self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
3927
self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
3928
elif e.localName == "SharedConfig":
3929
self.SharedConfigUrl = GetNodeTextData(e)
3930
LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
3931
self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
3932
self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
3933
elif e.localName == "ExtensionsConfig":
3934
self.ExtensionsConfigUrl = GetNodeTextData(e)
3935
LogIfVerbose("ExtensionsConfigUrl:" + self.ExtensionsConfigUrl)
3936
self.ExtensionsConfigXml = self.HttpGetWithHeaders(self.ExtensionsConfigUrl)
3937
elif e.localName == "Certificates":
3938
self.CertificatesUrl = GetNodeTextData(e)
3939
LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl)
3940
self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
3941
self.Certificates = Certificates().Parse(self.CertificatesXml)
3942
if self.Incarnation == None:
3943
Error("GoalState.Parse: Incarnation missing")
3945
if self.ExpectedState == None:
3946
Error("GoalState.Parse: ExpectedState missing")
3948
if self.RoleInstanceId == None:
3949
Error("GoalState.Parse: RoleInstanceId missing")
3951
if self.ContainerId == None:
3952
Error("GoalState.Parse: ContainerId missing")
3954
SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
3959
Calls HostingEnvironmentConfig.Process()
3961
self.HostingEnvironmentConfig.Process()
3963
class OvfEnv(object):
3965
Read, and process provisioning info from provisioning file OvfEnv.xml
3968
# <?xml version="1.0" encoding="utf-8"?>
3969
# <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">
3970
# <wa:ProvisioningSection>
3971
# <wa:Version>1.0</wa:Version>
3972
# <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
3973
# <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
3974
# <HostName>HostName</HostName>
3975
# <UserName>UserName</UserName>
3976
# <UserPassword>UserPassword</UserPassword>
3977
# <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
3981
# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
3982
# <Path>$HOME/UserName/.ssh/authorized_keys</Path>
3987
# <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
3988
# <Path>$HOME/UserName/.ssh/id_rsa</Path>
3992
# </LinuxProvisioningConfigurationSet>
3993
# </wa:ProvisioningSection>
3999
def reinitialize(self):
4003
self.WaNs = "http://schemas.microsoft.com/windowsazure"
4004
self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
4005
self.MajorVersion = 1
4006
self.MinorVersion = 0
4007
self.ComputerName = None
4008
self.AdminPassword = None
4009
self.UserName = None
4010
self.UserPassword = None
4011
self.CustomData = None
4012
self.DisableSshPasswordAuthentication = True
4013
self.SshPublicKeys = []
4014
self.SshKeyPairs = []
4016
def Parse(self, xmlText):
4018
Parse xml tree, retreiving user and ssh key information.
4022
LogIfVerbose(xmlText)
4023
dom = xml.dom.minidom.parseString(xmlText)
4024
if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
4025
Error("Unable to parse OVF XML.")
4028
for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
4029
for n in p.childNodes:
4030
if n.localName == "Version":
4031
verparts = GetNodeTextData(n).split('.')
4032
major = int(verparts[0])
4033
minor = int(verparts[1])
4034
if major > self.MajorVersion:
4036
if major != self.MajorVersion:
4038
if minor > self.MinorVersion:
4042
Warn("Newer provisioning configuration detected. Please consider updating waagent.")
4044
Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
4046
self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
4047
self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
4049
self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
4054
CDSection=section.getElementsByTagNameNS(self.WaNs, "CustomData")
4055
if len(CDSection) > 0 :
4056
self.CustomData=GetNodeTextData(CDSection[0])
4057
if len(self.CustomData)>0:
4058
SetFileContents(LibDir + '/CustomData', MyDistro.translateCustomData(self.CustomData))
4059
Log('Wrote ' + LibDir + '/CustomData')
4061
Error('<CustomData> contains no data!')
4062
except Exception, e:
4063
Error( str(e)+' occured creating ' + LibDir + '/CustomData')
4064
disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
4065
if len(disableSshPass) != 0:
4066
self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true")
4067
for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"):
4068
LogIfVerbose(repr(pkey))
4071
for c in pkey.childNodes:
4072
if c.localName == "Fingerprint":
4073
fp = GetNodeTextData(c).upper()
4075
if c.localName == "Path":
4076
path = GetNodeTextData(c)
4078
self.SshPublicKeys += [[fp, path]]
4079
for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
4082
LogIfVerbose(repr(keyp))
4083
for c in keyp.childNodes:
4084
if c.localName == "Fingerprint":
4085
fp = GetNodeTextData(c).upper()
4087
if c.localName == "Path":
4088
path = GetNodeTextData(c)
4090
self.SshKeyPairs += [[fp, path]]
4093
def PrepareDir(self, filepath):
4095
Create home dir for self.UserName
4096
Change owner and return path.
4098
home = MyDistro.GetHome()
4099
# Expand HOME variable if present in path
4100
path = os.path.normpath(filepath.replace("$HOME", home))
4101
if (path.startswith("/") == False) or (path.endswith("/") == True):
4103
dir = path.rsplit('/', 1)[0]
4105
CreateDir(dir, "root", 0700)
4106
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
4107
ChangeOwner(dir, self.UserName)
4110
def NumberToBytes(self, i):
4112
Pack number into bytes. Retun as string.
4116
result.append(chr(i & 0xFF))
4119
return ''.join(result)
4121
def BitsToString(self, a):
4123
Return string representation of bits in a.
4129
c = c | (bit << index)
4132
s = s + struct.pack('>B', c)
4137
def OpensslToSsh(self, file):
4139
Return base-64 encoded key appropriate for ssh.
4141
from pyasn1.codec.der import decoder as der_decoder
4143
f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
4144
k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
4148
keydata += struct.pack('>I',len("ssh-rsa"))
4149
keydata += "ssh-rsa"
4150
keydata += struct.pack('>I',len(self.NumberToBytes(e)))
4151
keydata += self.NumberToBytes(e)
4152
keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
4154
keydata += self.NumberToBytes(n)
4155
except Exception, e:
4156
print("OpensslToSsh: Exception " + str(e))
4158
return "ssh-rsa " + base64.b64encode(keydata) + "\n"
4162
Process all certificate and key info.
4163
DisableSshPasswordAuthentication if configured.
4165
Wait for WaAgent.EnvMonitor.IsHostnamePublished().
4166
Restart ssh service.
4169
if self.ComputerName == None :
4170
return "Error: Hostname missing"
4171
error=WaAgent.EnvMonitor.SetHostName(self.ComputerName)
4172
if error: return error
4173
if self.DisableSshPasswordAuthentication:
4174
filepath = "/etc/ssh/sshd_config"
4175
# Disable RFC 4252 and RFC 4256 authentication schemes.
4176
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
4177
(a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
4178
GetFileContents(filepath).split('\n'))) + "\nPasswordAuthentication no\nChallengeResponseAuthentication no\n")
4179
Log("Disabled SSH password-based authentication methods.")
4180
if self.AdminPassword != None:
4181
MyDistro.changePass('root',self.AdminPassword)
4182
if self.UserName != None:
4183
error = MyDistro.CreateAccount(self.UserName, self.UserPassword, None, None)
4184
sel = MyDistro.isSelinuxRunning()
4186
MyDistro.setSelinuxEnforce(0)
4187
home = MyDistro.GetHome()
4188
for pkey in self.SshPublicKeys:
4189
Log("Deploy public key:{0}".format(pkey[0]))
4190
if not os.path.isfile(pkey[0] + ".crt"):
4191
Error("PublicKey not found: " + pkey[0])
4192
error = "Failed to deploy public key (0x09)."
4194
path = self.PrepareDir(pkey[1])
4196
Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
4197
error = "Invalid path for public key (0x03)."
4199
Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
4200
MyDistro.setSelinuxContext(pkey[0] + '.pub','unconfined_u:object_r:ssh_home_t:s0')
4201
MyDistro.sshDeployPublicKey(pkey[0] + '.pub',path)
4202
MyDistro.setSelinuxContext(path,'unconfined_u:object_r:ssh_home_t:s0')
4203
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
4204
ChangeOwner(path, self.UserName)
4205
for keyp in self.SshKeyPairs:
4206
Log("Deploy key pair:{0}".format(keyp[0]))
4207
if not os.path.isfile(keyp[0] + ".prv"):
4208
Error("KeyPair not found: " + keyp[0])
4209
error = "Failed to deploy key pair (0x0A)."
4211
path = self.PrepareDir(keyp[1])
4213
Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
4214
error = "Invalid path for key pair (0x05)."
4216
SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
4217
os.chmod(path, 0600)
4218
Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
4219
MyDistro.setSelinuxContext(path,'unconfined_u:object_r:ssh_home_t:s0')
4220
MyDistro.setSelinuxContext(path + '.pub','unconfined_u:object_r:ssh_home_t:s0')
4221
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
4222
ChangeOwner(path, self.UserName)
4223
ChangeOwner(path + ".pub", self.UserName)
4225
MyDistro.setSelinuxEnforce(1)
4226
while not WaAgent.EnvMonitor.IsHostnamePublished():
4228
MyDistro.restartSshService()
4232
class WALAEvent(object):
4243
self.RoleInstanceName=""
4245
self.ExecutionMode="IAAS"
4253
strEventid=u'<Event id="{0}"/>'.format(self.eventId)
4254
strProviderid=u'<Provider id="{0}"/>'.format(self.providerId)
4255
strRecordFormat = u'<Param Name="{0}" Value="{1}" T="{2}" />'
4256
strRecordNoQuoteFormat = u'<Param Name="{0}" Value={1} T="{2}" />'
4258
strMtUInt64=u'mt:uint64'
4259
strMtBool=u'mt:bool'
4260
strMtFloat=u'mt:float64'
4263
for attName in self.__dict__:
4264
if attName in ["eventId","filedCount","providerId"]:
4267
attValue = self.__dict__[attName]
4268
if type(attValue) is int:
4269
strEventsData+=strRecordFormat.format(attName,attValue,strMtUInt64)
4271
if type(attValue) is str:
4272
attValue = xml.sax.saxutils.quoteattr(attValue)
4273
strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
4275
if str(type(attValue)).count("'unicode'") >0 :
4276
attValue = xml.sax.saxutils.quoteattr(attValue)
4277
strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
4279
if type(attValue) is bool:
4280
strEventsData+=strRecordFormat.format(attName,attValue,strMtBool)
4282
if type(attValue) is float:
4283
strEventsData+=strRecordFormat.format(attName,attValue,strMtFloat)
4286
Log("Warning: property "+attName+":"+str(type(attValue))+":type"+str(type(attValue))+"Can't convert to events data:"+":type not supported")
4288
return u"<Data>{0}{1}{2}</Data>".format(strProviderid,strEventid,strEventsData)
4291
eventfolder = LibDir+"/events"
4292
if not os.path.exists(eventfolder):
4293
os.mkdir(eventfolder)
4294
os.chmod(eventfolder,0700)
4295
if len(os.listdir(eventfolder)) > 1000:
4296
raise Exception("WriteToFolder:Too many file under "+datafolder+" exit")
4298
filename = os.path.join(eventfolder,str(int(time.time()*1000000)))
4299
with open(filename+".tmp",'wb+') as hfile:
4300
hfile.write(self.ToXml().encode("utf-8"))
4301
os.rename(filename+".tmp",filename+".tld")
4304
class WALAEventOperation:
4305
HeartBeat="HeartBeat"
4306
Provision = "Provision"
4308
UnIsntall = "UnInstall"
4311
Download = "Download"
4315
def AddExtensionEvent(name,op,isSuccess,duration=0,version="1.0",message="",type="",isInternal=False):
4316
event = ExtensionEvent()
4318
event.Version=version
4319
event.IsInternal=isInternal
4321
event.OperationSuccess=isSuccess
4322
event.Message=message
4323
event.Duration=duration
4324
event.ExtensionType=type
4328
Error("Error "+traceback.format_exc())
4331
class ExtensionEvent(WALAEvent):
4334
WALAEvent.__init__(self)
4336
self.providerId="69B669B9-4AF8-4C50-BDC4-6006FA76E975"
4339
self.IsInternal=False
4341
self.OperationSuccess=True
4342
self.ExtensionType=""
4347
class WALAEventMonitor(WALAEvent):
4348
def __init__(self,postMethod):
4349
WALAEvent.__init__(self)
4350
self.post = postMethod
4352
self.eventdir = LibDir+"/events"
4353
self.issysteminfoinitilized = False
4355
def StartEventsLoop(self):
4356
eventThread = threading.Thread(target = self.EventsLoop)
4357
eventThread.setDaemon(True)
4360
def EventsLoop(self):
4361
LastReportHeartBeatTime = datetime.datetime.min
4364
if (datetime.datetime.now()-LastReportHeartBeatTime) > datetime.timedelta(hours=12):
4365
LastReportHeartBeatTime = datetime.datetime.now()
4366
AddExtensionEvent(op=WALAEventOperation.HeartBeat,name="WALA",isSuccess=True)
4367
self.postNumbersInOneLoop=0
4368
self.CollectAndSendWALAEvents()
4371
Error("Exception in events loop:"+traceback.format_exc())
4373
def SendEvent(self,providerid,events):
4374
dataFormat = u'<?xml version="1.0"?><TelemetryData version="1.0"><Provider id="{0}">{1}'\
4375
'</Provider></TelemetryData>'
4376
data = dataFormat.format(providerid,events)
4377
self.post("/machine/?comp=telemetrydata",data)
4379
def CollectAndSendWALAEvents(self):
4380
if not os.path.exists(self.eventdir):
4382
#Throtting, can't send more than 3 events in 15 seconds
4384
eventFiles = os.listdir(self.eventdir)
4386
for file in eventFiles:
4387
if not file.endswith(".tld"):
4389
with open(os.path.join(self.eventdir,file),"rb") as hfile:
4390
#if fail to open or delete the file, throw exception
4391
xmlStr = hfile.read().decode("utf-8",'ignore')
4392
os.remove(os.path.join(self.eventdir,file))
4396
#if exception happen during process an event, catch it and continue
4398
xmlStr = self.AddSystemInfo(xmlStr)
4399
for node in xml.dom.minidom.parseString(xmlStr.encode("utf-8")).childNodes[0].childNodes:
4400
if node.tagName == "Param":
4401
params+=node.toxml()
4402
if node.tagName == "Event":
4403
eventid=node.getAttribute("id")
4404
if node.tagName == "Provider":
4405
providerid = node.getAttribute("id")
4407
Error(traceback.format_exc())
4409
if len(params)==0 or len(eventid)==0 or len(providerid)==0:
4410
Error("Empty filed in params:"+params+" event id:"+eventid+" provider id:"+providerid)
4413
eventstr = u'<Event id="{0}"><![CDATA[{1}]]></Event>'.format(eventid,params)
4414
if not events.get(providerid):
4415
events[providerid]=""
4416
if len(events[providerid]) >0 and len(events.get(providerid)+eventstr)>= 63*1024:
4418
self.SendEvent(providerid,events.get(providerid))
4419
if eventSendNumber %3 ==0:
4421
events[providerid]=""
4422
if len(eventstr) >= 63*1024:
4423
Error("Signle event too large abort "+eventstr[:300])
4426
events[providerid]=events.get(providerid)+eventstr
4428
for key in events.keys():
4429
if len(events[key]) > 0:
4431
self.SendEvent(key,events[key])
4432
if eventSendNumber%3 == 0:
4436
def AddSystemInfo(self,eventData):
4437
if not self.issysteminfoinitilized:
4438
self.issysteminfoinitilized=True
4440
self.sysInfo["OSVersion"]=platform.system()+":"+"-".join(DistInfo())+":"+platform.release()
4441
self.sysInfo["GAVersion"]=GuestAgentVersion
4442
self.sysInfo["RAM"]=int(RunGetOutput("grep MemTotal /proc/meminfo |awk '{print $2}'")[1])/1024
4443
self.sysInfo["Processors"]=int(RunGetOutput("grep 'processor.*:' /proc/cpuinfo |wc -l")[1])
4444
sharedConfig = xml.dom.minidom.parse("/var/lib/waagent/SharedConfig.xml").childNodes[0]
4445
hostEnvConfig= xml.dom.minidom.parse("/var/lib/waagent/HostingEnvironmentConfig.xml").childNodes[0]
4446
gfiles = RunGetOutput("ls -t /var/lib/waagent/GoalState.*.xml")[1]
4447
goalStateConfi = xml.dom.minidom.parse(gfiles.split("\n")[0]).childNodes[0]
4448
self.sysInfo["TenantName"]=hostEnvConfig.getElementsByTagName("Deployment")[0].getAttribute("name")
4449
self.sysInfo["RoleName"]=hostEnvConfig.getElementsByTagName("Role")[0].getAttribute("name")
4450
self.sysInfo["RoleInstanceName"]=sharedConfig.getElementsByTagName("Instance")[0].getAttribute("id")
4451
self.sysInfo["ContainerId"]=goalStateConfi.getElementsByTagName("ContainerId")[0].childNodes[0].nodeValue
4453
Error(traceback.format_exc())
4455
eventObject = xml.dom.minidom.parseString(eventData.encode("utf-8")).childNodes[0]
4456
for node in eventObject.childNodes:
4457
if node.tagName == "Param":
4458
name = node.getAttribute("Name")
4459
if self.sysInfo.get(name):
4460
node.setAttribute("Value",xml.sax.saxutils.escape(str(self.sysInfo[name])))
4462
return eventObject.toxml()
4467
Primary object container for the provisioning process.
4471
self.GoalState = None
4472
self.Endpoint = None
4473
self.LoadBalancerProbeServer = None
4474
self.HealthReportCounter = 0
4475
self.TransportCert = ""
4476
self.EnvMonitor = None
4477
self.SendData = None
4478
self.DhcpResponse = None
4480
def CheckVersions(self):
4482
Query endpoint server for wire protocol version.
4483
Fail if our desired protocol version is not seen.
4485
#<?xml version="1.0" encoding="utf-8"?>
4488
# <Version>2010-12-15</Version>
4491
# <Version>2010-12-15</Version>
4492
# <Version>2010-28-10</Version>
4495
global ProtocolVersion
4496
protocolVersionSeen = False
4497
node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
4498
if node.localName != "Versions":
4499
Error("CheckVersions: root not Versions")
4501
for a in node.childNodes:
4502
if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
4503
for b in a.childNodes:
4504
if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
4505
v = GetNodeTextData(b)
4506
LogIfVerbose("Fabric supported wire protocol version: " + v)
4507
if v == ProtocolVersion:
4508
protocolVersionSeen = True
4509
if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
4510
v = GetNodeTextData(a.getElementsByTagName("Version")[0])
4511
Log("Fabric preferred wire protocol version: " + v)
4512
if not protocolVersionSeen:
4513
Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
4515
Log("Negotiated wire protocol version: " + ProtocolVersion)
4518
def Unpack(self, buffer, offset, range):
4520
Unpack bytes into python values.
4524
result = (result << 8) | Ord(buffer[offset + i])
4527
def UnpackLittleEndian(self, buffer, offset, length):
4529
Unpack little endian bytes into python values.
4531
return self.Unpack(buffer, offset, list(range(length - 1, -1, -1)))
4533
def UnpackBigEndian(self, buffer, offset, length):
4535
Unpack big endian bytes into python values.
4537
return self.Unpack(buffer, offset, list(range(0, length)))
4539
def HexDump3(self, buffer, offset, length):
4541
Dump range of buffer in formatted hex.
4543
return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
4545
def HexDump2(self, buffer):
4547
Dump buffer in formatted hex.
4549
return self.HexDump3(buffer, 0, len(buffer))
4551
def BuildDhcpRequest(self):
4553
Build DHCP request string.
4556
# typedef struct _DHCP {
4557
# UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
4558
# UINT8 HardwareAddressType; /* htype: ethernet */
4559
# UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
4560
# UINT8 Hops; /* hops: 0 */
4561
# UINT8 TransactionID[4]; /* xid: random */
4562
# UINT8 Seconds[2]; /* secs: 0 */
4563
# UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
4564
# UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
4565
# UINT8 YourIpAddress[4]; /* yiaddr: 0 */
4566
# UINT8 ServerIpAddress[4]; /* siaddr: 0 */
4567
# UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
4568
# UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */
4569
# UINT8 ServerName[64]; /* sname: 0 */
4570
# UINT8 BootFileName[128]; /* file: 0 */
4571
# UINT8 MagicCookie[4]; /* 99 130 83 99 */
4572
# /* 0x63 0x82 0x53 0x63 */
4573
# /* options -- hard code ours */
4575
# UINT8 MessageTypeCode; /* 53 */
4576
# UINT8 MessageTypeLength; /* 1 */
4577
# UINT8 MessageType; /* 1 for DISCOVER */
4578
# UINT8 End; /* 255 */
4582
# tuple of 244 zeros
4583
# (struct.pack_into would be good here, but requires Python 2.5)
4584
sendData = [0] * 244
4586
transactionID = os.urandom(4)
4587
macAddress = MyDistro.GetMacAddress()
4590
# HardwareAddressType = 1 (ethernet/MAC)
4591
# HardwareAddressLength = 6 (ethernet/MAC/48 bits)
4592
for a in range(0, 3):
4593
sendData[a] = [1, 1, 6][a]
4595
# fill in transaction id (random number to ensure response matches request)
4596
for a in range(0, 4):
4597
sendData[4 + a] = Ord(transactionID[a])
4599
LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
4601
# fill in ClientHardwareAddress
4602
for a in range(0, 6):
4603
sendData[0x1C + a] = Ord(macAddress[a])
4605
# DHCP Magic Cookie: 99, 130, 83, 99
4606
# MessageTypeCode = 53 DHCP Message Type
4607
# MessageTypeLength = 1
4608
# MessageType = DHCPDISCOVER
4609
# End = 255 DHCP_END
4610
for a in range(0, 8):
4611
sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
4612
return array.array("B", sendData)
4614
def IntegerToIpAddressV4String(self, a):
4616
Build DHCP request string.
4618
return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
4620
def RouteAdd(self, net, mask, gateway):
4622
Add specified route using /sbin/route add -net.
4624
net = self.IntegerToIpAddressV4String(net)
4625
mask = self.IntegerToIpAddressV4String(mask)
4626
gateway = self.IntegerToIpAddressV4String(gateway)
4627
Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway,chk_err=False)
4629
def HandleDhcpResponse(self, sendData, receiveBuffer):
4631
Parse DHCP response:
4632
Set default gateway.
4634
Retrieve endpoint server.
4635
Returns endpoint server or None on error.
4637
LogIfVerbose("HandleDhcpResponse")
4638
bytesReceived = len(receiveBuffer)
4639
if bytesReceived < 0xF6:
4640
Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
4643
LogIfVerbose("BytesReceived: " + hex(bytesReceived))
4644
LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
4646
# check transactionId, cookie, MAC address
4647
# cookie should never mismatch
4648
# transactionId and MAC address may mismatch if we see a response meant from another machine
4650
for offsets in [list(range(4, 4 + 4)), list(range(0x1C, 0x1C + 6)), list(range(0xEC, 0xEC + 4))]:
4651
for offset in offsets:
4652
sentByte = Ord(sendData[offset])
4653
receivedByte = Ord(receiveBuffer[offset])
4654
if sentByte != receivedByte:
4655
LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
4656
LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
4657
LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
4658
LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
4659
LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
4660
LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
4661
LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
4666
# Walk all the returned options, parsing out what we need, ignoring the others.
4667
# We need the custom option 245 to find the the endpoint we talk to,
4668
# as well as, to handle some Linux DHCP client incompatibilities,
4669
# options 3 for default gateway and 249 for routes. And 255 is end.
4672
i = 0xF0 # offset to first option
4673
while i < bytesReceived:
4674
option = Ord(receiveBuffer[i])
4676
if (i + 1) < bytesReceived:
4677
length = Ord(receiveBuffer[i + 1])
4678
LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
4680
LogIfVerbose("DHCP packet ended at offset " + hex(i))
4683
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
4684
LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
4686
Error("Data too small for option " + str(option))
4688
while j < (i + length + 2):
4689
maskLengthBits = Ord(receiveBuffer[j])
4690
maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
4691
mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
4693
net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
4694
net <<= (32 - maskLengthBytes * 8)
4696
j += maskLengthBytes
4697
gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
4699
self.RouteAdd(net, mask, gateway)
4700
if j != (i + length + 2):
4701
Error("HandleDhcpResponse: Unable to parse routes")
4702
elif option == 3 or option == 245:
4703
if i + 5 < bytesReceived:
4705
Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
4707
gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
4708
IpAddress = self.IntegerToIpAddressV4String(gateway)
4710
self.RouteAdd(0, 0, gateway)
4711
name = "DefaultGateway"
4713
endpoint = IpAddress
4714
name = "Windows Azure wire protocol endpoint"
4715
LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
4717
Error("HandleDhcpResponse: Data too small for option " + str(option))
4719
LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
4723
def DoDhcpWork(self):
4725
Discover the wire server via DHCP option 245.
4726
And workaround incompatibility with Windows Azure DHCP servers.
4728
ShortSleep = False # Sleep 1 second before retrying DHCP queries.
4731
sleepDurations = [0, 10, 30, 60, 60]
4732
maxRetry = len(sleepDurations)
4733
lastTry = (maxRetry - 1)
4734
for retry in range(0, maxRetry):
4736
#Open DHCP port if iptables is enabled.
4737
Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT",chk_err=False) # We supress error logging on error.
4738
Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT",chk_err=False) # We supress error logging on error.
4739
strRetry = str(retry)
4740
prefix = "DoDhcpWork: try=" + strRetry
4741
LogIfVerbose(prefix)
4742
sendData = self.BuildDhcpRequest()
4743
LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
4744
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
4745
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
4746
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
4747
missingDefaultRoute = True
4749
if DistInfo()[0] == 'FreeBSD':
4750
missingDefaultRoute = True
4752
routes = RunGetOutput("route -n")[1]
4753
for line in routes.split('\n'):
4754
if line.startswith("0.0.0.0 ") or line.startswith("default "):
4755
missingDefaultRoute = False
4758
if missingDefaultRoute:
4759
# This is required because sending after binding to 0.0.0.0 fails with
4760
# network unreachable when the default gateway is not set up.
4761
ifname=MyDistro.GetInterfaceName()
4762
Log("DoDhcpWork: Missing default route - adding broadcast route for DHCP.")
4763
if DistInfo()[0] == 'FreeBSD':
4764
Run("route add -net 255.255.255.255 -iface " + ifname,chk_err=False)
4766
Run("route add 255.255.255.255 dev " + ifname,chk_err=False)
4767
if MyDistro.isDHCPEnabled():
4769
sock.bind(("0.0.0.0", 68))
4770
sock.sendto(sendData, ("<broadcast>", 67))
4772
Log("DoDhcpWork: Setting socket.timeout=10, entering recv")
4773
receiveBuffer = sock.recv(1024)
4774
endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
4775
if endpoint == None:
4776
LogIfVerbose("DoDhcpWork: No endpoint found")
4777
if endpoint != None or retry == lastTry:
4778
if endpoint != None:
4779
self.SendData = sendData
4780
self.DhcpResponse = receiveBuffer
4781
if retry == lastTry:
4782
LogIfVerbose("DoDhcpWork: try=" + strRetry)
4784
sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
4785
LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
4786
time.sleep(sleepDuration)
4787
except Exception, e:
4788
ErrorWithPrefix(prefix, str(e))
4789
ErrorWithPrefix(prefix, traceback.format_exc())
4792
if missingDefaultRoute:
4793
#We added this route - delete it
4794
Log("DoDhcpWork: Removing broadcast route for DHCP.")
4795
if DistInfo()[0] == 'FreeBSD':
4796
Run("route del -net 255.255.255.255 -iface " + ifname,chk_err=False)
4798
Run("route del 255.255.255.255 dev " + ifname,chk_err=False) # We supress error logging on error.
4799
if MyDistro.isDHCPEnabled():
4800
MyDistro.startDHCP()
4803
def UpdateAndPublishHostName(self, name):
4805
Set hostname locally and publish to iDNS
4807
Log("Setting host name: " + name)
4808
MyDistro.publishHostname(name)
4809
ethernetInterface = MyDistro.GetInterfaceName()
4810
MyDistro.RestartInterface(ethernetInterface)
4811
self.RestoreRoutes()
4813
def RestoreRoutes(self):
4815
If there is a DHCP response, then call HandleDhcpResponse.
4817
if self.SendData != None and self.DhcpResponse != None:
4818
self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
4820
def UpdateGoalState(self):
4822
Retreive goal state information from endpoint server.
4823
Parse xml and initialize Agent.GoalState object.
4824
Return object or None on error.
4829
for retry in range(1, maxRetry + 1):
4830
strRetry = str(retry)
4831
log("retry UpdateGoalState,retry=" + strRetry)
4832
goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
4833
if goalStateXml != None:
4837
if not goalStateXml:
4838
Error("UpdateGoalState failed.")
4840
Log("Retrieved GoalState from Windows Azure Fabric.")
4841
self.GoalState = GoalState(self).Parse(goalStateXml)
4842
return self.GoalState
4844
def ReportReady(self):
4846
Send health report 'Ready' to server.
4847
This signals the fabric that our provosion is completed,
4848
and the host is ready for operation.
4850
counter = (self.HealthReportCounter + 1) % 1000000
4851
self.HealthReportCounter = counter
4852
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>"
4853
+ self.GoalState.Incarnation
4854
+ "</GoalStateIncarnation><Container><ContainerId>"
4855
+ self.GoalState.ContainerId
4856
+ "</ContainerId><RoleInstanceList><Role><InstanceId>"
4857
+ self.GoalState.RoleInstanceId
4858
+ "</InstanceId><Health><State>Ready</State></Health></Role></RoleInstanceList></Container></Health>")
4859
a = self.HttpPost("/machine?comp=health", healthReport)
4861
return a.getheader("x-ms-latest-goal-state-incarnation-number")
4864
def ReportNotReady(self, status, desc):
4866
Send health report 'Provisioning' to server.
4867
This signals the fabric that our provosion is starting.
4869
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>"
4870
+ self.GoalState.Incarnation
4871
+ "</GoalStateIncarnation><Container><ContainerId>"
4872
+ self.GoalState.ContainerId
4873
+ "</ContainerId><RoleInstanceList><Role><InstanceId>"
4874
+ self.GoalState.RoleInstanceId
4875
+ "</InstanceId><Health><State>NotReady</State>"
4876
+ "<Details><SubStatus>" + status + "</SubStatus><Description>" + desc + "</Description></Details>"
4877
+ "</Health></Role></RoleInstanceList></Container></Health>")
4878
a = self.HttpPost("/machine?comp=health", healthReport)
4880
return a.getheader("x-ms-latest-goal-state-incarnation-number")
4883
def ReportRoleProperties(self, thumbprint):
4885
Send roleProperties and thumbprint to server.
4887
roleProperties = ("<?xml version=\"1.0\" encoding=\"utf-8\"?><RoleProperties><Container>"
4888
+ "<ContainerId>" + self.GoalState.ContainerId + "</ContainerId>"
4889
+ "<RoleInstances><RoleInstance>"
4890
+ "<Id>" + self.GoalState.RoleInstanceId + "</Id>"
4891
+ "<Properties><Property name=\"CertificateThumbprint\" value=\"" + thumbprint + "\" /></Properties>"
4892
+ "</RoleInstance></RoleInstances></Container></RoleProperties>")
4893
a = self.HttpPost("/machine?comp=roleProperties", roleProperties)
4894
Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
4897
def LoadBalancerProbeServer_Shutdown(self):
4899
Shutdown the LoadBalancerProbeServer.
4901
if self.LoadBalancerProbeServer != None:
4902
self.LoadBalancerProbeServer.shutdown()
4903
self.LoadBalancerProbeServer = None
4905
def GenerateTransportCert(self):
4907
Create ssl certificate for https communication with endpoint server.
4909
Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
4911
for line in GetFileContents("TransportCert.pem").split('\n'):
4912
if not "CERTIFICATE" in line:
4913
cert += line.rstrip()
4916
def DoVmmStartup(self):
4918
Spawn the VMM startup script.
4920
Log("Starting Microsoft System Center VMM Initialization Process")
4921
pid = subprocess.Popen(["/bin/bash","/mnt/cdrom/secure/"+VMM_STARTUP_SCRIPT_NAME,"-p /mnt/cdrom/secure/ "]).pid
4925
def TryUnloadAtapiix(self):
4927
If global modloaded is True, then we loaded the ata_piix kernel module, unload it.
4930
Run("rmmod ata_piix.ko",chk_err=False)
4931
Log("Unloaded ata_piix.ko driver for ATAPI CD-ROM")
4933
def TryLoadAtapiix(self):
4935
Load the ata_piix kernel module if it exists.
4936
If successful, set global modloaded to True.
4937
If unable to load module leave modloaded False.
4941
retcode,krn=RunGetOutput('uname -r')
4942
krn_pth='/lib/modules/'+krn.strip('\n')+'/kernel/drivers/ata/ata_piix.ko'
4943
if Run("lsmod | grep ata_piix",chk_err=False) == 0 :
4944
Log("Module " + krn_pth + " driver for ATAPI CD-ROM is already present.")
4947
Error("Unable to provision: Failed to call uname -r")
4948
return "Unable to provision: Failed to call uname"
4949
if os.path.isfile(krn_pth):
4950
retcode,output=RunGetOutput("insmod " + krn_pth,chk_err=False)
4952
Log("Module " + krn_pth + " driver for ATAPI CD-ROM does not exist.")
4955
Error('Error calling insmod for '+ krn_pth + ' driver for ATAPI CD-ROM')
4958
# check 3 times if the mod is loaded
4960
if Run('lsmod | grep ata_piix'):
4966
Error('Unable to load '+ krn_pth + ' driver for ATAPI CD-ROM')
4969
Log("Loaded " + krn_pth + " driver for ATAPI CD-ROM")
4971
# we have succeeded loading the ata_piix mod if it can be done.
4973
def SearchForVMMStartup(self):
4975
Search for a DVD/CDROM containing VMM's VMM_CONFIG_FILE_NAME.
4976
Call TryLoadAtapiix in case we must load the ata_piix module first.
4978
If VMM_CONFIG_FILE_NAME is found, call DoVmmStartup.
4979
Else, return to Azure Provisioning process.
4981
self.TryLoadAtapiix()
4982
if os.path.exists('/mnt/cdrom/secure') == False:
4983
CreateDir("/mnt/cdrom/secure", "root", 0700)
4985
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/')]:
4988
dvd = '/dev/'+dvds.group(0)
4989
if Run("LC_ALL=C fdisk -l " + dvd + " | grep Disk",chk_err=False):
4990
continue # Not mountable
4992
for retry in range(1,6):
4993
retcode,output=RunGetOutput("mount -v " + dvd + " /mnt/cdrom/secure")
4996
Log("mount succeeded on attempt #" + str(retry) )
4999
if 'is already mounted on /mnt/cdrom/secure' in output:
5000
Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
5003
Log("mount failed on attempt #" + str(retry) )
5004
Log("mount loop sleeping 5...")
5009
if not os.path.isfile("/mnt/cdrom/secure/"+VMM_CONFIG_FILE_NAME):
5010
#nope - mount the next drive
5012
Run("umount "+dvd,chk_err=False)
5015
else : # it is the vmm startup
5018
Log("VMM Init script not found. Provisioning for Azure")
5021
def Provision(self):
5024
Regenerate ssh keys,
5025
Mount, read, and parse ovfenv.xml from provisioning dvd rom
5026
Process the ovfenv.xml info
5027
Call ReportRoleProperties
5028
If configured, delete root password.
5029
Return None on success, error string on error.
5031
enabled = Config.get("Provisioning.Enabled")
5032
if enabled != None and enabled.lower().startswith("n"):
5034
Log("Provisioning image started.")
5035
type = Config.get("Provisioning.SshHostKeyPairType")
5038
regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
5039
if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
5040
Run("rm -f /etc/ssh/ssh_host_*key*")
5041
Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key")
5042
MyDistro.restartSshService()
5043
#SetFileContents(LibDir + "/provisioned", "")
5045
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/')]:
5048
dvd = '/dev/'+dvds.group(0)
5050
# No DVD device detected
5051
Error("No DVD device detected, unable to provision.")
5052
return "No DVD device detected, unable to provision."
5053
if MyDistro.mediaHasFilesystem(dvd) is False :
5054
out=MyDistro.load_ata_piix()
5057
for i in range(10): # we may have to wait
5058
if os.path.exists(dvd):
5060
Log("Waiting for DVD - sleeping 1 - "+str(i+1)+" try...")
5062
if os.path.exists('/mnt/cdrom/secure') == False:
5063
CreateDir("/mnt/cdrom/secure", "root", 0700)
5064
#begin mount loop - 5 tries - 5 sec wait between
5065
for retry in range(1,6):
5066
location='/mnt/cdrom/secure'
5067
retcode,output=MyDistro.mountDVD(dvd,location)
5070
Log("mount succeeded on attempt #" + str(retry) )
5072
if 'is already mounted on /mnt/cdrom/secure' in output:
5073
Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
5075
Log("mount failed on attempt #" + str(retry) )
5076
Log("mount loop sleeping 5...")
5078
if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
5079
Error("Unable to provision: Missing ovf-env.xml on DVD.")
5080
return "Failed to retrieve provisioning data (0x02)."
5081
ovfxml = (GetFileContents(u"/mnt/cdrom/secure/ovf-env.xml",asbin=False)) # use unicode here to ensure correct codec gets used.
5082
if ord(ovfxml[0]) > 128 and ord(ovfxml[1]) > 128 and ord(ovfxml[2]) > 128 :
5083
ovfxml = ovfxml[3:] # BOM is not stripped. First three bytes are > 128 and not unicode chars so we ignore them.
5084
ovfxml=ovfxml.strip(chr(0x00)) # we may have NULLs.
5085
ovfxml=ovfxml[ovfxml.find('<?'):] # chop leading text if present
5086
SetFileContents("ovf-env.xml", re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml))
5087
Run("umount " + dvd,chk_err=False)
5088
MyDistro.unload_ata_piix()
5091
Log("Provisioning image using OVF settings in the DVD.")
5092
ovfobj = OvfEnv().Parse(ovfxml)
5094
error = ovfobj.Process()
5096
Error ("Provisioning image FAILED " + error)
5097
return ("Provisioning image FAILED " + error)
5098
Log("Ovf XML process finished")
5099
# This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml
5100
fingerprint = RunGetOutput("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub")[1].rstrip().split()[1].replace(':','')
5101
self.ReportRoleProperties(fingerprint)
5102
delRootPass = Config.get("Provisioning.DeleteRootPassword")
5103
if delRootPass != None and delRootPass.lower().startswith("y"):
5104
MyDistro.deleteRootPassword()
5105
Log("Provisioning image completed.")
5110
Called by 'waagent -daemon.'
5111
Main loop to process the goal state. State is posted every 25 seconds
5112
when provisioning has been completed.
5114
Search for VMM enviroment, start VMM script if found.
5115
Perform DHCP and endpoint server discovery by calling DoDhcpWork().
5116
Check wire protocol versions.
5117
Set SCSI timeout on root device.
5118
Call GenerateTransportCert() to create ssl certs for server communication.
5119
Call UpdateGoalState().
5120
If not provisioned, call ReportNotReady("Provisioning", "Starting")
5121
Call Provision(), set global provisioned = True if successful.
5122
Call goalState.Process()
5123
Start LBProbeServer if indicated in waagent.conf.
5124
Start the StateConsumer if indicated in waagent.conf.
5125
ReportReady if provisioning is complete.
5126
If provisioning failed, call ReportNotReady("ProvisioningFailed", provisionError)
5128
SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
5130
# Determine if we are in VMM. Spawn VMM_STARTUP_SCRIPT_NAME if found.
5131
self.SearchForVMMStartup()
5133
while ipv4 == '' or ipv4 == '0.0.0.0' :
5134
ipv4=MyDistro.GetIpv4Address()
5135
if ipv4 == '' or ipv4 == '0.0.0.0' :
5136
Log("Waiting for network.")
5139
Log("IPv4 address: " + ipv4)
5141
mac=MyDistro.GetMacAddress()
5143
Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in mac]))
5145
# Consume Entropy in ACPI table provided by Hyper-V
5147
SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
5151
Log("Probing for Windows Azure environment.")
5152
self.Endpoint = self.DoDhcpWork()
5154
if self.Endpoint == None:
5155
Log("Windows Azure environment not detected.")
5159
Log("Discovered Windows Azure endpoint: " + self.Endpoint)
5160
if not self.CheckVersions():
5161
Error("Agent.CheckVersions failed")
5164
# Set SCSI timeout on SCSI disks
5165
MyDistro.initScsiDiskTimeout()
5167
global provisionError
5170
Openssl = Config.get("OS.OpensslPath")
5174
self.TransportCert = self.GenerateTransportCert()
5177
incarnation = None # goalStateIncarnationFromHealthReport
5178
currentPort = None # loadBalancerProbePort
5179
goalState = None # self.GoalState, instance of GoalState
5180
provisioned = os.path.exists(LibDir + "/provisioned")
5181
program = Config.get("Role.StateConsumer")
5182
provisionError = None
5183
lbProbeResponder = True
5184
setting = Config.get("LBProbeResponder")
5185
if setting != None and setting.lower().startswith("n"):
5186
lbProbeResponder = False
5188
if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
5189
goalState = self.UpdateGoalState()
5190
if goalState == None :
5192
if provisioned == False:
5193
self.ReportNotReady("Provisioning", "Starting")
5197
if provisioned == False:
5198
provisionError = self.Provision()
5199
if provisionError == None :
5201
SetFileContents(LibDir + "/provisioned", "")
5202
lastCtime = os.path.getctime("/etc/waagent.conf")
5203
#Get Ctime of wala config, can help identify the base image of this VM
5204
AddExtensionEvent(name="WALA",op=WALAEventOperation.Provision,isSuccess=True,
5205
message="WALA Config Ctime:"+time.ctime(lastCtime))
5208
# only one port supported
5209
# restart server if new port is different than old port
5210
# stop server if no longer a port
5212
goalPort = goalState.LoadBalancerProbePort
5213
if currentPort != goalPort:
5214
self.LoadBalancerProbeServer_Shutdown()
5215
currentPort = goalPort
5216
if currentPort != None and lbProbeResponder == True:
5217
self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)
5218
if self.LoadBalancerProbeServer == None :
5219
lbProbeResponder = False
5220
Log("Unable to create LBProbeResponder.")
5222
# Report SSH key fingerprint
5223
type = Config.get("Provisioning.SshHostKeyPairType")
5227
host_key_path = "/etc/ssh/ssh_host_" + type + "_key.pub"
5228
if(MyDistro.waitForSshHostKey(host_key_path)):
5229
fingerprint = RunGetOutput("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub")[1].rstrip().split()[1].replace(':','')
5230
self.ReportRoleProperties(fingerprint)
5232
if program != None and DiskActivated == True:
5234
Children.append(subprocess.Popen([program, "Ready"]))
5236
ErrorWithPrefix('SharedConfig.Parse','Exception: '+ str(e) +' occured launching ' + program )
5239
sleepToReduceAccessDenied = 3
5240
time.sleep(sleepToReduceAccessDenied)
5241
if provisionError != None:
5242
incarnation = self.ReportNotReady("ProvisioningFailed", provisionError)
5244
incarnation = self.ReportReady()
5245
# Process our extensions.
5246
if goalState.ExtensionsConfig == None and goalState.ExtensionsConfigXml != None :
5247
goalState.ExtensionsConfig = ExtensionsConfig().Parse(goalState.ExtensionsConfigXml)
5249
# report the status/heartbeat results of extension processing
5250
if goalState.ExtensionsConfig != None :
5251
goalState.ExtensionsConfig.ReportHandlerStatus()
5253
if not eventMonitor:
5254
eventMonitor = WALAEventMonitor(self.HttpPost)
5255
eventMonitor.StartEventsLoop()
5257
time.sleep(25 - sleepToReduceAccessDenied)
5260
WaagentLogrotate = """\
5261
/var/log/waagent.log {
5269
def GetMountPoint(mountlist, device):
5271
Example of mountlist:
5272
/dev/sda1 on / type ext4 (rw)
5273
proc on /proc type proc (rw)
5274
sysfs on /sys type sysfs (rw)
5275
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
5276
tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
5277
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
5278
/dev/sdb1 on /mnt/resource type ext4 (rw)
5280
if (mountlist and device):
5281
for entry in mountlist.split('\n'):
5282
if(re.search(device, entry)):
5283
tokens = entry.split()
5284
#Return the 3rd column of this line
5285
return tokens[2] if len(tokens) > 2 else None
5288
def FindInLinuxKernelCmdline(option):
5290
Return match object if 'option' is present in the kernel boot options
5291
of the grub configuration.
5294
matchs=r'^.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?'+option+r'.*$'
5296
m=FindStringInFile(MyDistro.grubKernelBootOptionsFile,matchs)
5298
Error('FindInLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
5302
def AppendToLinuxKernelCmdline(option):
5304
Add 'option' to the kernel boot options of the grub configuration.
5306
if not FindInLinuxKernelCmdline(option):
5307
src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r')(.*?)("?)$'
5308
rep=r'\1\2 '+ option + r'\3'
5310
ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
5312
Error('AppendToLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
5314
Run("update-grub",chk_err=False)
5317
def RemoveFromLinuxKernelCmdline(option):
5319
Remove 'option' to the kernel boot options of the grub configuration.
5321
if FindInLinuxKernelCmdline(option):
5322
src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?)('+option+r')(.*?)("?)$'
5325
ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
5327
Error('RemoveFromLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
5329
Run("update-grub",chk_err=False)
5332
def FindStringInFile(fname,matchs):
5334
Return match object if found in file.
5337
ms=re.compile(matchs)
5338
for l in (open(fname,'r')).readlines():
5347
def ReplaceStringInFile(fname,src,repl):
5349
Replace 'src' with 'repl' in file.
5354
if FindStringInFile(fname,src):
5355
for l in (open(fname,'r')).readlines():
5358
ReplaceFileContentsAtomic(fname,updated)
5363
def ApplyVNUMAWorkaround():
5365
If kernel version has NUMA bug, add 'numa=off' to
5366
kernel boot options.
5368
VersionParts = platform.release().replace('-', '.').split('.')
5369
if int(VersionParts[0]) > 2:
5371
if int(VersionParts[1]) > 6:
5373
if int(VersionParts[2]) > 37:
5375
if AppendToLinuxKernelCmdline("numa=off") == 0 :
5376
Log("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
5378
"Error adding 'numa=off'. NUMA has not been disabled."
5380
def RevertVNUMAWorkaround():
5382
Remove 'numa=off' from kernel boot options.
5384
if RemoveFromLinuxKernelCmdline("numa=off") == 0 :
5385
Log('NUMA has been re-enabled')
5387
Log('NUMA has not been re-enabled')
5391
Install the agent service.
5393
Create /etc/waagent.conf and move old version to
5394
/etc/waagent.conf.old
5395
Copy RulesFiles to /var/lib/waagent
5396
Create /etc/logrotate.d/waagent
5397
Set /etc/ssh/sshd_config ClientAliveInterval to 180
5398
Call ApplyVNUMAWorkaround()
5400
if MyDistro.checkDependencies():
5402
os.chmod(sys.argv[0], 0755)
5404
for a in RulesFiles:
5405
if os.path.isfile(a):
5406
if os.path.isfile(GetLastPathElement(a)):
5407
os.remove(GetLastPathElement(a))
5409
Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
5410
MyDistro.registerAgentService()
5411
if os.path.isfile("/etc/waagent.conf"):
5413
os.remove("/etc/waagent.conf.old")
5417
os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
5418
Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
5421
SetFileContents("/etc/waagent.conf", MyDistro.waagent_conf_file)
5422
SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
5423
filepath = "/etc/ssh/sshd_config"
5424
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
5425
a.startswith("ClientAliveInterval"),
5426
GetFileContents(filepath).split('\n'))) + "\nClientAliveInterval 180\n")
5427
Log("Configured SSH client probing to keep connections alive.")
5428
ApplyVNUMAWorkaround()
5431
def GetMyDistro(dist_class_name=''):
5433
Return MyDistro object.
5434
NOTE: Logging is not initialized at this point.
5436
if dist_class_name == '':
5437
if 'Linux' in platform.system():
5438
Distro=DistInfo()[0]
5439
else : # I know this is not Linux!
5440
if 'FreeBSD' in platform.system():
5441
Distro=platform.system()
5442
Distro=Distro.strip('"')
5443
Distro=Distro.strip(' ')
5444
dist_class_name=Distro+'Distro'
5446
Distro=dist_class_name
5447
if not globals().has_key(dist_class_name):
5448
print Distro+' is not a supported distribution.'
5450
return globals()[dist_class_name]() # the distro class inside this module.
5452
def DistInfo(fullname=0):
5453
if 'FreeBSD' in platform.system():
5454
release = re.sub('\-.*\Z', '', str(platform.release()))
5455
distinfo = ['FreeBSD', release]
5457
if 'linux_distribution' in dir(platform):
5458
distinfo = list(platform.linux_distribution(full_distribution_name=fullname))
5459
distinfo[0] = distinfo[0].strip() # remove trailing whitespace in distro name
5462
return platform.dist()
5464
def PackagedInstall(buildroot):
5466
Called from setup.py for use by RPM.
5467
Generic implementation Creates directories and
5468
files /etc/waagent.conf, /etc/init.d/waagent, /usr/sbin/waagent,
5469
/etc/logrotate.d/waagent, /etc/sudoers.d/waagent under buildroot.
5470
Copies generated files waagent.conf, into place and exits.
5472
MyDistro=GetMyDistro()
5473
if MyDistro == None :
5475
MyDistro.packagedInstall(buildroot)
5477
def LibraryInstall(buildroot):
5482
Uninstall the agent service.
5483
Copy RulesFiles back to original locations.
5484
Delete agent-related files.
5485
Call RevertVNUMAWorkaround().
5488
for a in RulesFiles:
5489
if os.path.isfile(GetLastPathElement(a)):
5491
shutil.move(GetLastPathElement(a), a)
5492
Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
5495
MyDistro.unregisterAgentService()
5496
MyDistro.uninstallDeleteFiles()
5497
RevertVNUMAWorkaround()
5500
def Deprovision(force, deluser):
5502
Remove user accounts created by provisioning.
5503
Disables root password if Provisioning.DeleteRootPassword = 'y'
5505
Remove SSH host keys if they were generated by the provision.
5506
Set hostname to 'localhost.localdomain'.
5507
Delete cached system configuration files in /var/lib and /var/lib/waagent.
5510
#Append blank line at the end of file, so the ctime of this file is changed every time
5511
Run("echo ''>>"+"/etc/waagent.conf")
5514
ovfxml = GetFileContents(LibDir+"/ovf-env.xml")
5517
ovfobj = OvfEnv().Parse(ovfxml)
5519
print("WARNING! The waagent service will be stopped.")
5520
print("WARNING! All SSH host key pairs will be deleted.")
5521
print("WARNING! Cached DHCP leases will be deleted.")
5522
MyDistro.deprovisionWarnUser()
5523
delRootPass = Config.get("Provisioning.DeleteRootPassword")
5524
if delRootPass != None and delRootPass.lower().startswith("y"):
5525
print("WARNING! root password will be disabled. You will not be able to login as root.")
5527
if ovfobj != None and deluser == True:
5528
print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
5530
if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
5533
MyDistro.stopAgentService()
5535
MyDistro.DeleteAccount(ovfobj.UserName)
5537
# Remove SSH host keys
5538
regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
5539
if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
5540
Run("rm -f /etc/ssh/ssh_host_*key*")
5542
# Remove root password
5543
if delRootPass != None and delRootPass.lower().startswith("y"):
5544
MyDistro.deleteRootPassword()
5545
# Remove distribution specific networking configuration
5547
MyDistro.publishHostname('localhost.localdomain')
5548
MyDistro.deprovisionDeleteFiles()
5553
Switch to cwd to /var/lib/waagent.
5554
Create if not present.
5556
CreateDir(LibDir, "root", 0700)
5561
Print the arguments to waagent.
5563
print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
5570
Instantiate MyDistro, exit if distro class is not defined.
5571
Parse command-line arguments, exit with usage() on error.
5572
Instantiate ConfigurationProvider.
5573
Call appropriate non-daemon methods and exit.
5574
If daemon mode, enter Agent.Run() loop.
5576
if GuestAgentVersion == "":
5577
print("WARNING! This is a non-standard agent that does not include a valid version string.")
5579
if len(sys.argv) == 1:
5582
LoggerInit('/var/log/waagent.log','/dev/console')
5584
LinuxDistro=DistInfo()[0]
5586
MyDistro=GetMyDistro()
5587
if MyDistro == None :
5592
for a in sys.argv[1:]:
5593
if re.match("^([-/]*)(help|usage|\?)", a):
5595
elif re.match("^([-/]*)verbose", a):
5596
myLogger.verbose = True
5597
elif re.match("^([-/]*)force", a):
5599
elif re.match("^([-/]*)(setup|install)", a):
5600
sys.exit(MyDistro.Install())
5601
elif re.match("^([-/]*)(uninstall)", a):
5602
sys.exit(Uninstall())
5606
Config = ConfigurationProvider()
5608
verbose = Config.get("Logs.Verbose")
5609
if verbose != None and verbose.lower().startswith("y"):
5610
myLogger.verbose=True
5614
if re.match("^([-/]*)deprovision\+user", a):
5615
sys.exit(Deprovision(force, True))
5616
elif re.match("^([-/]*)deprovision", a):
5617
sys.exit(Deprovision(force, False))
5618
elif re.match("^([-/]*)daemon", a):
5620
elif re.match("^([-/]*)version", a):
5621
print(GuestAgentVersion + " running on " + LinuxDistro)
5623
elif re.match("^([-/]*)serialconsole", a):
5624
AppendToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
5625
Log("Configured kernel to use ttyS0 as the boot console.")
5628
print("Invalid command line parameter:" + a)
5637
Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
5639
Log("Linux Distribution Detected : " + LinuxDistro)
5643
except Exception, e:
5644
Error(traceback.format_exc())
5645
Error("Exception: " + str(e))