~ubuntu-branches/debian/sid/waagent/sid

« back to all changes in this revision

Viewing changes to bin/waagent

  • Committer: Package Import Robot
  • Author(s): Bastian Blank
  • Date: 2015-07-30 13:27:01 UTC
  • mfrom: (1.2.1) (3.1.1 experimental)
  • Revision ID: package-import@ubuntu.com-20150730132701-dvdym85n5twpucfk
Tags: 2.1.0-1
* New upstream version.
* Hijack package.
* Not longer try to install systemd stuff by package setup.
* Use own systemd service, use dh-systemd.
* Use own init script.
* Remove unused python3 support.
* Remove old postinst, postrm script.
* Remove upstart support, "It's dead, Jim".
* Make package arch-all.
* Disable environment monitor.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Windows Azure Linux Agent
 
4
#
 
5
# Copyright 2014 Microsoft Corporation
 
6
#
 
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
 
10
#
 
11
#     http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
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.
 
18
#
 
19
# Requires Python 2.4+ and Openssl 1.0+
 
20
#
 
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
 
24
#
 
25
 
 
26
if __name__ == '__main__' :
 
27
    import sys
 
28
    import azurelinuxagent.agent as agent
 
29
    agent.Main()
 
30
    sys.exit()
 
31
 
 
32
import array
 
33
import base64
 
34
import httplib
 
35
import os
 
36
import os.path
 
37
import platform
 
38
import pwd
 
39
import re
 
40
import shutil
 
41
import socket
 
42
import SocketServer
 
43
import struct
 
44
import string
 
45
import subprocess
 
46
import sys
 
47
import tempfile
 
48
import textwrap
 
49
import threading
 
50
import time
 
51
import traceback
 
52
import xml.dom.minidom
 
53
import fcntl
 
54
import inspect
 
55
import zipfile
 
56
import json
 
57
import datetime
 
58
import xml.sax.saxutils
 
59
 
 
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()
 
68
        if retcode:
 
69
            cmd = kwargs.get("args")
 
70
            if cmd is None:
 
71
                cmd = popenargs[0]
 
72
            raise subprocess.CalledProcessError(retcode, cmd, output=output)
 
73
        return output
 
74
 
 
75
    # Exception classes used by this module.
 
76
    class CalledProcessError(Exception):
 
77
        def __init__(self, returncode, cmd, output=None):
 
78
            self.returncode = returncode
 
79
            self.cmd = cmd
 
80
            self.output = output
 
81
        def __str__(self):
 
82
            return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
 
83
 
 
84
    subprocess.check_output=check_output
 
85
    subprocess.CalledProcessError=CalledProcessError
 
86
    
 
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.
 
91
 
 
92
Config = None
 
93
WaAgent = None
 
94
DiskActivated = False
 
95
Openssl = "openssl"
 
96
Children = []
 
97
ExtensionChildren = []
 
98
VMM_STARTUP_SCRIPT_NAME='install'
 
99
VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml'
 
100
global RulesFiles
 
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"]
 
105
global LibDir
 
106
LibDir = "/var/lib/waagent"
 
107
global provisioned
 
108
provisioned=False
 
109
global provisionError
 
110
provisionError=None
 
111
HandlerStatusToAggStatus = {"installed":"Installing", "enabled":"Ready", "unintalled":"NotReady", "disabled":"NotReady"}
 
112
 
 
113
WaagentConf = """\
 
114
#
 
115
# Windows Azure Linux Agent Configuration
 
116
#
 
117
 
 
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.
 
122
 
 
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.
 
128
 
 
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.
 
134
 
 
135
LBProbeResponder=y                      # Respond to load balancer probes if requested by Windows Azure.
 
136
 
 
137
Logs.Verbose=n                          # Enable verbose logs
 
138
 
 
139
OS.RootDeviceScsiTimeout=300            # Root device timeout in seconds.
 
140
OS.OpensslPath=None                     # If "None", the system default version is used.
 
141
"""
 
142
README_FILENAME="DATALOSS_WARNING_README.txt"
 
143
README_FILECONTENT="""\
 
144
WARNING: THIS IS A TEMPORARY DISK. 
 
145
 
 
146
Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT.
 
147
 
 
148
Please do not use this disk for storing any personal or application data.
 
149
 
 
150
For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx
 
151
"""
 
152
 
 
153
############################################################
 
154
# BEGIN DISTRO CLASS DEFS
 
155
############################################################
 
156
############################################################    
 
157
#       AbstractDistro
 
158
############################################################    
 
159
class AbstractDistro(object):
 
160
    """
 
161
    AbstractDistro defines a skeleton neccesary for a concrete Distro class.
 
162
 
 
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'.
 
167
    """
 
168
 
 
169
    def __init__(self):
 
170
        """
 
171
        Generic Attributes go here.  These are based on 'majority rules'.
 
172
        This __init__() may be called or overriden by the child.
 
173
        """
 
174
        self.agent_service_name = os.path.basename(sys.argv[0]) 
 
175
        self.selinux=None
 
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
 
196
        
 
197
    def isSelinuxSystem(self):
 
198
        """
 
199
        Checks and sets self.selinux = True if SELinux is available on system.
 
200
        """
 
201
        if self.selinux == None:
 
202
            if Run("which getenforce",chk_err=False):
 
203
                self.selinux = False
 
204
            else:
 
205
                self.selinux = True
 
206
        return self.selinux
 
207
    
 
208
    def isSelinuxRunning(self):
 
209
        """
 
210
        Calls shell command 'getenforce' and returns True if 'Enforcing'.
 
211
        """
 
212
        if self.isSelinuxSystem():
 
213
            return RunGetOutput("getenforce")[1].startswith("Enforcing")
 
214
        else:
 
215
            return False
 
216
        
 
217
    def setSelinuxEnforce(self,state):
 
218
        """
 
219
        Calls shell command 'setenforce' with 'state' and returns resulting exit code.
 
220
        """
 
221
        if self.isSelinuxSystem():
 
222
            if state: s = '1'
 
223
            else: s='0'
 
224
            return Run("setenforce "+s)
 
225
 
 
226
    def setSelinuxContext(self,path,cn):
 
227
        """
 
228
        Calls shell 'chcon' with 'path' and 'cn' context.
 
229
        Returns exit result.
 
230
        """
 
231
        if self.isSelinuxSystem():
 
232
            return Run('chcon ' + cn + ' ' + path)
 
233
        
 
234
    def setHostname(self,name):
 
235
        """
 
236
        Shell call to hostname.
 
237
        Returns resulting exit code.
 
238
        """
 
239
        return Run('hostname ' + name)
 
240
        
 
241
    def publishHostname(self,name):
 
242
        """
 
243
        Set the contents of the hostname file to 'name'.
 
244
        Return 1 on failure.
 
245
        """
 
246
        try:
 
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'))))
 
252
        except:
 
253
            return 1
 
254
        return r
 
255
        
 
256
    def installAgentServiceScriptFiles(self):
 
257
        """
 
258
        Create the waagent support files for service installation.
 
259
        Called by registerAgentService()
 
260
        Abstract Virtual Function.  Over-ridden in concrete Distro classes.
 
261
        """
 
262
        pass
 
263
 
 
264
    def registerAgentService(self):
 
265
        """
 
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.
 
269
        """
 
270
        pass
 
271
    
 
272
    def uninstallAgentService(self):
 
273
        """
 
274
        Call service subsystem to remove waagent script.
 
275
        Abstract Virtual Function.  Over-ridden in concrete Distro classes.
 
276
        """
 
277
        pass
 
278
 
 
279
    def unregisterAgentService(self):
 
280
        """
 
281
        Calls self.stopAgentService and call self.uninstallAgentService()
 
282
        """
 
283
        self.stopAgentService()
 
284
        self.uninstallAgentService()
 
285
    
 
286
    def startAgentService(self):
 
287
        """
 
288
        Service call to start the Agent service
 
289
        """
 
290
        return Run(self.service_cmd + ' ' + self.agent_service_name + ' start')
 
291
        
 
292
    def stopAgentService(self):
 
293
        """
 
294
        Service call to stop the Agent service
 
295
        """
 
296
        return Run(self.service_cmd + ' '  + self.agent_service_name + ' stop',False)
 
297
    
 
298
    def restartSshService(self):
 
299
        """
 
300
        Service call to re(start) the SSH service
 
301
        """
 
302
        sshRestartCmd = self.service_cmd + " " + self.ssh_service_name + " " + self.ssh_service_restart_option
 
303
        retcode = Run(sshRestartCmd)
 
304
        if retcode > 0:
 
305
            Error("Failed to restart SSH service with return code:" + str(retcode))
 
306
        return retcode
 
307
 
 
308
    def sshDeployPublicKey(self,fprint,path):
 
309
        """
 
310
        Generic sshDeployPublicKey - over-ridden in some concrete Distro classes due to minor differences in openssl packages deployed
 
311
        """
 
312
        error=0
 
313
        SshPubKey = OvfEnv().OpensslToSsh(fprint)
 
314
        if SshPubKey != None:
 
315
            AppendFileContents(path, SshPubKey)
 
316
        else:
 
317
            Error("Failed: " + fprint + ".crt -> " + path)
 
318
            error = 1
 
319
        return error
 
320
    
 
321
    def checkPackageInstalled(self,p):
 
322
        """
 
323
        Query package database for prescence of an installed package.
 
324
        Abstract Virtual Function.  Over-ridden in concrete Distro classes.
 
325
        """
 
326
        pass
 
327
 
 
328
    def checkPackageUpdateable(self,p):
 
329
        """
 
330
        Online check if updated package of walinuxagent is available.
 
331
        Abstract Virtual Function.  Over-ridden in concrete Distro classes.
 
332
        """
 
333
        pass
 
334
 
 
335
    def deleteRootPassword(self):
 
336
        """
 
337
        Generic root password removal.
 
338
        """
 
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.")
 
346
        return 0
 
347
    
 
348
    def changePass(self,user,password):
 
349
        return RunSendStdin("chpasswd",(user + ":" + password + "\n"))
 
350
    
 
351
    def load_ata_piix(self):
 
352
        return WaAgent.TryLoadAtapiix()
 
353
 
 
354
    def unload_ata_piix(self):
 
355
        """
 
356
        Generic function to remove ata_piix.ko.
 
357
        """
 
358
        return WaAgent.TryUnloadAtapiix()
 
359
        
 
360
    def deprovisionWarnUser(self):
 
361
        """
 
362
        Generic user warnings used at deprovision.
 
363
        """
 
364
        print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
 
365
 
 
366
    def deprovisionDeleteFiles(self):
 
367
        """
 
368
        Files to delete when VM is deprovisioned
 
369
        """
 
370
        for a in VarLibDhcpDirectories:
 
371
            Run("rm -f " + a + "/*")
 
372
 
 
373
        # Clear LibDir, remove nameserver and root bash history
 
374
        
 
375
        for f in os.listdir(LibDir) + self.fileBlackList:
 
376
            try:
 
377
                os.remove(f)
 
378
            except:
 
379
                pass
 
380
        return 0
 
381
    
 
382
    def uninstallDeleteFiles(self):
 
383
        """
 
384
        Files to delete when agent is uninstalled.
 
385
        """
 
386
        for f in self.agent_files_to_uninstall:
 
387
            try:
 
388
                os.remove(f)
 
389
            except:
 
390
                pass
 
391
        return 0
 
392
    
 
393
    def checkDependencies(self):
 
394
        """
 
395
        Generic dependency check.
 
396
        Return 1 unless all dependencies are satisfied.
 
397
        """
 
398
        if self.checkPackageInstalled('NetworkManager'):
 
399
            Error(GuestAgentLongName + " is not compatible with network-manager.")
 
400
            return 1
 
401
        try:
 
402
            m= __import__('pyasn1')
 
403
        except ImportError:
 
404
            Error(GuestAgentLongName + " requires python-pyasn1 for your Linux distribution.")
 
405
            return 1
 
406
        for a in self.requiredDeps:
 
407
            if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
 
408
                Error("Missing required dependency: " + a)
 
409
                return 1
 
410
        return 0
 
411
 
 
412
    def packagedInstall(self,buildroot):
 
413
        """
 
414
        Called from setup.py for use by RPM.
 
415
        Copies generated files waagent.conf, under the buildroot.
 
416
        """
 
417
        if not os.path.exists(buildroot+'/etc'):
 
418
            os.mkdir(buildroot+'/etc')
 
419
        SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
 
420
        
 
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)
 
424
    
 
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()
 
430
 
 
431
    def GetIpv4Address(self):
 
432
        """
 
433
        Return the ip of the 
 
434
        first active non-loopback interface.
 
435
        """
 
436
        addr=''
 
437
        iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
 
438
        return addr
 
439
 
 
440
    def GetMacAddress(self):
 
441
        return GetMacAddress()
 
442
 
 
443
    def GetInterfaceName(self):
 
444
        return GetFirstActiveNetworkInterfaceNonLoopback()[0]
 
445
 
 
446
    def RestartInterface(self, iface):
 
447
        Run("ifdown " + iface + " && ifup " + iface)
 
448
 
 
449
    def CreateAccount(self,user, password, expiration, thumbprint):
 
450
        return CreateAccount(user, password, expiration, thumbprint)
 
451
    
 
452
    def DeleteAccount(self,user):
 
453
        return DeleteAccount(user)
 
454
 
 
455
    def ActivateResourceDisk(self):
 
456
        """
 
457
        Format, mount, and if specified in the configuration
 
458
        set resource disk as swap.
 
459
        """
 
460
        global DiskActivated
 
461
        format = Config.get("ResourceDisk.Format")
 
462
        if format == None or format.lower().startswith("n"):
 
463
            DiskActivated = True
 
464
            return
 
465
        device = DeviceForIdePort(1)
 
466
        if device == None:
 
467
            Error("ActivateResourceDisk: Unable to detect disk topology.")
 
468
            return
 
469
        device = "/dev/" + device
 
470
 
 
471
        mountlist = RunGetOutput("mount")[1]
 
472
        mountpoint = GetMountPoint(mountlist, device)
 
473
 
 
474
        if(mountpoint):
 
475
            Log("ActivateResourceDisk: " + device + "1 is already mounted.")
 
476
        else:
 
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")
 
482
            if fs == None:
 
483
                fs = "ext3"
 
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).")
 
489
                return
 
490
            Log("Resource disk (" + device + "1) is mounted at " + mountpoint + " with fstype " + fs)
 
491
 
 
492
        #Create README file under the root of resource disk
 
493
        SetFileContents(os.path.join(mountpoint,README_FILENAME), README_FILECONTENT)
 
494
        DiskActivated = True
 
495
 
 
496
        #Create swap space
 
497
        swap = Config.get("ResourceDisk.EnableSwap")
 
498
        if swap == None or swap.lower().startswith("n"):
 
499
            return
 
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")
 
508
        else:
 
509
            Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
 
510
 
 
511
    def Install(self):
 
512
        return Install()
 
513
 
 
514
    def mediaHasFilesystem(self,dsk):
 
515
        if len(dsk) == 0 :
 
516
            return False
 
517
        if Run("LC_ALL=C fdisk -l " + dsk + " | grep Disk"):
 
518
            return False
 
519
        return True
 
520
    
 
521
    def mountDVD(self,dvd,location):
 
522
        return RunGetOutput(self.mount_dvd_cmd + ' ' + dvd + ' ' + location)
 
523
 
 
524
    def GetHome(self):
 
525
        return GetHome()
 
526
 
 
527
    def getDhcpClientName(self):
 
528
        return self.dhcp_client_name
 
529
 
 
530
    def initScsiDiskTimeout(self):
 
531
        """
 
532
        Set the SCSI disk timeout when the agent starts running
 
533
        """
 
534
        self.setScsiDiskTimeout()
 
535
 
 
536
    def setScsiDiskTimeout(self):
 
537
        """
 
538
        Iterate all SCSI disks(include hot-add) and set their timeout if their value are different from the OS.RootDeviceScsiTimeout
 
539
        """
 
540
        try:
 
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)
 
544
        except:
 
545
            pass
 
546
 
 
547
    def setBlockDeviceTimeout(self, device, timeout):
 
548
        """
 
549
        Set SCSI disk timeout by set /sys/block/sd*/device/timeout
 
550
        """
 
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)
 
556
 
 
557
    def waitForSshHostKey(self, path):
 
558
        """
 
559
        Provide a dummy waiting, since by default, ssh host key is created by waagent and the key
 
560
        should already been created.
 
561
        """
 
562
        if(os.path.isfile(path)):
 
563
            return True
 
564
        else:
 
565
            Error("Can't find host key: {0}".format(path))
 
566
            return False
 
567
 
 
568
    def isDHCPEnabled(self):
 
569
        return self.dhcp_enabled
 
570
 
 
571
    def stopDHCP(self):
 
572
        """
 
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.
 
576
        """
 
577
        raise NotImplementedError('stopDHCP method missing')
 
578
 
 
579
    def startDHCP(self):
 
580
        """
 
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.
 
583
        """
 
584
        raise NotImplementedError('startDHCP method missing')
 
585
 
 
586
    def translateCustomData(self, data):
 
587
        """
 
588
        Translate the custom data from a Base64 encoding. Default to no-op.
 
589
        """
 
590
        return data
 
591
 
 
592
    def getConfigurationPath(self):
 
593
        return "/etc/waagent.conf"
 
594
 
 
595
############################################################
 
596
#       GentooDistro
 
597
############################################################
 
598
gentoo_init_file = """\
 
599
#!/sbin/runscript
 
600
 
 
601
command=/usr/sbin/waagent
 
602
pidfile=/var/run/waagent.pid
 
603
command_args=-daemon
 
604
command_background=true
 
605
name="Windows Azure Linux Agent"
 
606
 
 
607
depend()
 
608
{
 
609
        need localmount
 
610
        use logger network
 
611
        after bootmisc modules
 
612
}
 
613
 
 
614
"""
 
615
class gentooDistro(AbstractDistro):
 
616
    """
 
617
    Gentoo distro concrete class
 
618
    """
 
619
 
 
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
 
628
        
 
629
    def publishHostname(self,name):
 
630
        try:
 
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"))))
 
634
        except:
 
635
            return 1
 
636
        return r
 
637
        
 
638
    def installAgentServiceScriptFiles(self):
 
639
        SetFileContents(self.init_script_file, self.init_file)
 
640
        os.chmod(self.init_script_file, 0755)
 
641
 
 
642
    def registerAgentService(self):
 
643
        self.installAgentServiceScriptFiles()
 
644
        return Run('rc-update add ' + self.agent_service_name + ' default')
 
645
    
 
646
    def uninstallAgentService(self):
 
647
        return Run('rc-update del ' + self.agent_service_name + ' default')
 
648
 
 
649
    def unregisterAgentService(self):
 
650
        self.stopAgentService()
 
651
        return self.uninstallAgentService()
 
652
 
 
653
    def checkPackageInstalled(self,p):
 
654
        if Run('eix -I ^' + p + '$',chk_err=False):
 
655
            return 0
 
656
        else:
 
657
            return 1
 
658
 
 
659
    def checkPackageUpdateable(self,p):
 
660
        if Run('eix -u ^' + p + '$',chk_err=False):
 
661
            return 0
 
662
        else:
 
663
            return 1
 
664
 
 
665
    def RestartInterface(self, iface):
 
666
        Run("/etc/init.d/net." + iface + " restart")
 
667
 
 
668
############################################################    
 
669
#       SuSEDistro
 
670
############################################################    
 
671
suse_init_file = """\
 
672
#! /bin/sh
 
673
#
 
674
# Windows Azure Linux Agent sysV init script
 
675
#
 
676
# Copyright 2013 Microsoft Corporation
 
677
# Copyright SUSE LLC
 
678
#
 
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
 
682
#
 
683
#     http://www.apache.org/licenses/LICENSE-2.0
 
684
#
 
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.
 
690
#
 
691
# /etc/init.d/waagent
 
692
#
 
693
#  and symbolic link
 
694
#
 
695
# /usr/sbin/rcwaagent
 
696
#
 
697
# System startup script for the waagent
 
698
#
 
699
### BEGIN INIT INFO
 
700
# Provides: WindowsAzureLinuxAgent
 
701
# Required-Start: $network sshd
 
702
# Required-Stop: $network sshd
 
703
# Default-Start: 3 5
 
704
# Default-Stop: 0 1 2 6
 
705
# Description: Start the WindowsAzureLinuxAgent
 
706
### END INIT INFO
 
707
 
 
708
PYTHON=/usr/bin/python
 
709
WAZD_BIN=/usr/sbin/waagent
 
710
WAZD_CONF=/etc/waagent.conf
 
711
WAZD_PIDFILE=/var/run/waagent.pid
 
712
 
 
713
test -x "$WAZD_BIN" || { echo "$WAZD_BIN not installed"; exit 5; }
 
714
test -e "$WAZD_CONF" || { echo "$WAZD_CONF not found"; exit 6; }
 
715
 
 
716
. /etc/rc.status
 
717
 
 
718
# First reset status of this service
 
719
rc_reset
 
720
 
 
721
# Return values acc. to LSB for all commands but status:
 
722
# 0 - success
 
723
# 1 - misc error
 
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
 
729
#
 
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.
 
734
 
 
735
 
 
736
case "$1" in
 
737
    start)
 
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
 
742
        rc_status -v
 
743
        ;;
 
744
    stop)
 
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}
 
749
        rc_status -v
 
750
        ;;
 
751
    try-restart)
 
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
 
755
        rc_status
 
756
        ;;
 
757
    restart)
 
758
        ## Stop the service and regardless of whether it was
 
759
        ## running or not, start it again.
 
760
        $0 stop
 
761
        sleep 1
 
762
        $0 start
 
763
        rc_status
 
764
        ;;
 
765
    force-reload|reload)
 
766
        rc_status
 
767
        ;;
 
768
    status)
 
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.
 
772
 
 
773
        checkproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
 
774
        rc_status -v
 
775
        ;;
 
776
    probe)
 
777
        ;;
 
778
    *)
 
779
        echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
 
780
        exit 1
 
781
        ;;
 
782
esac
 
783
rc_exit
 
784
"""
 
785
class SuSEDistro(AbstractDistro):
 
786
    """
 
787
    SuSE Distro concrete class
 
788
    Put SuSE specific behavior here...
 
789
    """
 
790
    def __init__(self):
 
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
 
805
        
 
806
    def checkPackageInstalled(self,p):
 
807
        if Run("rpm -q " + p,chk_err=False):
 
808
            return 0
 
809
        else:
 
810
            return 1
 
811
 
 
812
    def checkPackageUpdateable(self,p):
 
813
        if Run("zypper list-updates | grep " + p,chk_err=False):
 
814
            return 1
 
815
        else:
 
816
            return 0
 
817
        
 
818
 
 
819
    def installAgentServiceScriptFiles(self):
 
820
        try:
 
821
            SetFileContents(self.init_script_file, self.init_file)
 
822
            os.chmod(self.init_script_file, 0744)
 
823
        except:
 
824
            pass
 
825
        
 
826
    def registerAgentService(self):
 
827
        self.installAgentServiceScriptFiles()
 
828
        return Run('insserv ' + self.agent_service_name)
 
829
 
 
830
    def uninstallAgentService(self):
 
831
        return Run('insserv -r ' + self.agent_service_name)
 
832
 
 
833
    def unregisterAgentService(self):
 
834
        self.stopAgentService()
 
835
        return self.uninstallAgentService()
 
836
 
 
837
    def startDHCP(self):
 
838
        Run("service " + self.dhcp_client_name + " start", chk_err=False)
 
839
 
 
840
    def stopDHCP(self):
 
841
        Run("service " + self.dhcp_client_name + " stop", chk_err=False)
 
842
    
 
843
############################################################    
 
844
#       redhatDistro
 
845
############################################################    
 
846
 
 
847
redhat_init_file= """\
 
848
#!/bin/bash
 
849
#
 
850
# Init file for WindowsAzureLinuxAgent.
 
851
#
 
852
# chkconfig: 2345 60 80
 
853
# description: WindowsAzureLinuxAgent
 
854
#
 
855
 
 
856
# source function library
 
857
. /etc/rc.d/init.d/functions
 
858
 
 
859
RETVAL=0
 
860
FriendlyName="WindowsAzureLinuxAgent"
 
861
WAZD_BIN=/usr/sbin/waagent
 
862
 
 
863
start()
 
864
{
 
865
    echo -n $"Starting $FriendlyName: "
 
866
    $WAZD_BIN -daemon &
 
867
}
 
868
 
 
869
stop()
 
870
{
 
871
    echo -n $"Stopping $FriendlyName: "
 
872
    killproc -p /var/run/waagent.pid $WAZD_BIN
 
873
    RETVAL=$?
 
874
    echo
 
875
    return $RETVAL
 
876
}
 
877
 
 
878
case "$1" in
 
879
    start)
 
880
        start
 
881
        ;;
 
882
    stop)
 
883
        stop
 
884
        ;;
 
885
    restart)
 
886
        stop
 
887
        start
 
888
        ;;
 
889
    reload)
 
890
        ;;
 
891
    report)
 
892
        ;;
 
893
    status)
 
894
        status $WAZD_BIN
 
895
        RETVAL=$?
 
896
        ;;
 
897
    *)
 
898
        echo $"Usage: $0 {start|stop|restart|status}"
 
899
        RETVAL=1
 
900
esac
 
901
exit $RETVAL
 
902
"""
 
903
 
 
904
class redhatDistro(AbstractDistro):
 
905
    """
 
906
    Redhat Distro concrete class
 
907
    Put Redhat specific behavior here...
 
908
    """
 
909
    def __init__(self):
 
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'
 
918
 
 
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'))))
 
926
 
 
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'))))
 
932
        return 0
 
933
 
 
934
    def installAgentServiceScriptFiles(self):
 
935
        SetFileContents(self.init_script_file, self.init_file)
 
936
        os.chmod(self.init_script_file, 0744)
 
937
        return 0
 
938
 
 
939
    def registerAgentService(self):
 
940
        self.installAgentServiceScriptFiles()
 
941
        return Run('chkconfig --add waagent')
 
942
   
 
943
    def uninstallAgentService(self):
 
944
        return Run('chkconfig --del ' + self.agent_service_name)
 
945
 
 
946
    def unregisterAgentService(self):
 
947
        self.stopAgentService()
 
948
        return self.uninstallAgentService()
 
949
    
 
950
    def checkPackageInstalled(self,p):
 
951
        if Run("yum list installed " + p,chk_err=False):
 
952
            return 0
 
953
        else:
 
954
            return 1
 
955
 
 
956
    def checkPackageUpdateable(self,p):
 
957
        if Run("yum check-update | grep "+ p,chk_err=False):
 
958
            return 1
 
959
        else:
 
960
            return 0
 
961
 
 
962
 
 
963
 
 
964
############################################################    
 
965
#       centosDistro
 
966
############################################################    
 
967
 
 
968
class centosDistro(redhatDistro):
 
969
    """
 
970
    CentOS Distro concrete class
 
971
    Put CentOS specific behavior here...
 
972
    """
 
973
    def __init__(self):
 
974
        super(centosDistro,self).__init__()
 
975
 
 
976
 
 
977
############################################################
 
978
#   CoreOSDistro
 
979
############################################################
 
980
 
 
981
class CoreOSDistro(AbstractDistro):
 
982
    """
 
983
    CoreOS Distro concrete class
 
984
    Put CoreOS specific behavior here...
 
985
    """
 
986
    CORE_UID = 500
 
987
 
 
988
    def __init__(self):
 
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)
 
1002
        else:
 
1003
            os.environ['PATH'] = self.python_path
 
1004
 
 
1005
        if 'PYTHONPATH' in os.environ:
 
1006
            os.environ['PYTHONPATH'] = "{0}:{1}".format(os.environ['PYTHONPATH'], self.waagent_path)
 
1007
        else:
 
1008
            os.environ['PYTHONPATH'] = self.waagent_path
 
1009
 
 
1010
    def checkPackageInstalled(self,p):
 
1011
        """
 
1012
        There is no package manager in CoreOS.  Return 1 since it must be preinstalled.
 
1013
        """
 
1014
        return 1
 
1015
 
 
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)
 
1020
                return 1
 
1021
        return 0
 
1022
 
 
1023
 
 
1024
    def checkPackageUpdateable(self,p):
 
1025
        """
 
1026
        There is no package manager in CoreOS.  Return 0 since it can't be updated via package.
 
1027
        """
 
1028
        return 0
 
1029
 
 
1030
    def startAgentService(self):
 
1031
        return Run('systemctl start ' + self.agent_service_name)
 
1032
 
 
1033
    def stopAgentService(self):
 
1034
        return Run('systemctl stop ' + self.agent_service_name)
 
1035
 
 
1036
    def restartSshService(self):
 
1037
        """
 
1038
        Service call to re(start) the SSH service
 
1039
        """
 
1040
        retcode = Run("systemctl restart sshd")
 
1041
        if retcode > 0:
 
1042
            Error("Failed to restart SSH service with return code:" + str(retcode))
 
1043
        return retcode
 
1044
 
 
1045
    def sshDeployPublicKey(self,fprint,path):
 
1046
        """
 
1047
        We support PKCS8.
 
1048
        """
 
1049
        if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
 
1050
            return 1
 
1051
        else :
 
1052
            return 0
 
1053
 
 
1054
    def RestartInterface(self, iface):
 
1055
        Run("systemctl restart systemd-networkd")
 
1056
 
 
1057
    def CreateAccount(self, user, password, expiration, thumbprint):
 
1058
        """
 
1059
        Create a user account, with 'user', 'password', 'expiration', ssh keys
 
1060
        and sudo permissions.
 
1061
        Returns None if successful, error string on failure.
 
1062
        """
 
1063
        userentry = None
 
1064
        try:
 
1065
            userentry = pwd.getpwnam(user)
 
1066
        except:
 
1067
            pass
 
1068
        uidmin = None
 
1069
        try:
 
1070
            uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
 
1071
        except:
 
1072
            pass
 
1073
        if uidmin == None:
 
1074
            uidmin = 100
 
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]
 
1082
            if Run(command):
 
1083
                Error("Failed to create user account: " + user)
 
1084
                return "Failed to create user account: " + user + " (0x07)."
 
1085
        else:
 
1086
            Log("CreateAccount: " + user + " already exists. Will update password.")
 
1087
        if password != None:
 
1088
            RunSendStdin("chpasswd", user + ":" + password + "\n")
 
1089
        try:
 
1090
            if password == None:
 
1091
                SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
 
1092
            else:
 
1093
                SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
 
1094
            os.chmod("/etc/sudoers.d/waagent", 0440)
 
1095
        except:
 
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]:
 
1107
                os.chmod(f, 0600)
 
1108
                ChangeOwner(f, user)
 
1109
            SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
 
1110
            ChangeOwner(dir + "/authorized_keys", user)
 
1111
        Log("Created user account: " + user)
 
1112
        return None
 
1113
 
 
1114
    def startDHCP(self):
 
1115
        Run("systemctl start " + self.dhcp_client_name, chk_err=False)
 
1116
 
 
1117
    def stopDHCP(self):
 
1118
        Run("systemctl stop " + self.dhcp_client_name, chk_err=False)
 
1119
 
 
1120
    def translateCustomData(self, data):
 
1121
        return base64.b64decode(data)
 
1122
 
 
1123
    def getConfigurationPath(self):
 
1124
        return "/usr/share/oem/waagent.conf"
 
1125
 
 
1126
############################################################    
 
1127
#       debianDistro
 
1128
############################################################    
 
1129
debian_init_file = """\
 
1130
#!/bin/sh
 
1131
### BEGIN INIT INFO
 
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
 
1141
### END INIT INFO
 
1142
 
 
1143
. /lib/lsb/init-functions
 
1144
 
 
1145
OPTIONS="-daemon"
 
1146
WAZD_BIN=/usr/sbin/waagent
 
1147
WAZD_PID=/var/run/waagent.pid
 
1148
 
 
1149
case "$1" in
 
1150
    start)
 
1151
        log_begin_msg "Starting WindowsAzureLinuxAgent..."
 
1152
        pid=$( pidofproc $WAZD_BIN )
 
1153
        if [ -n "$pid" ] ; then
 
1154
              log_begin_msg "Already running."
 
1155
              log_end_msg 0
 
1156
              exit 0
 
1157
        fi
 
1158
        start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
 
1159
        log_end_msg $?
 
1160
        ;;
 
1161
 
 
1162
    stop)
 
1163
        log_begin_msg "Stopping WindowsAzureLinuxAgent..."
 
1164
        start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
 
1165
        ret=$?
 
1166
        rm -f $WAZD_PID
 
1167
        log_end_msg $ret
 
1168
        ;;
 
1169
    force-reload)
 
1170
        $0 restart
 
1171
        ;;
 
1172
    restart)
 
1173
        $0 stop
 
1174
        $0 start
 
1175
        ;;
 
1176
    status)
 
1177
        status_of_proc $WAZD_BIN && exit 0 || exit $?
 
1178
        ;;
 
1179
    *)
 
1180
        log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
 
1181
        exit 1
 
1182
        ;;
 
1183
esac
 
1184
 
 
1185
exit 0
 
1186
"""
 
1187
 
 
1188
class debianDistro(AbstractDistro):
 
1189
    """
 
1190
    debian Distro concrete class
 
1191
    Put debian specific behavior here...
 
1192
    """
 
1193
    def __init__(self):
 
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
 
1201
 
 
1202
    def checkPackageInstalled(self,p):
 
1203
        """
 
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
 
1208
        package name.
 
1209
        """
 
1210
        if not Run("dpkg-query -W -f='${Status}\n' '" + p + "' | grep ' installed' 2>&1",chk_err=False):
 
1211
            return 1
 
1212
        else:
 
1213
            return 0
 
1214
        
 
1215
    def checkDependencies(self):
 
1216
        """
 
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.
 
1220
        """
 
1221
        if self.checkPackageInstalled('network*manager'):
 
1222
            Error(GuestAgentLongName + " is not compatible with network-manager.")
 
1223
            return 1
 
1224
        for a in self.requiredDeps:
 
1225
            if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
 
1226
                Error("Missing required dependency: " + a)
 
1227
                return 1
 
1228
        return 0
 
1229
 
 
1230
    def checkPackageUpdateable(self,p):
 
1231
        if Run("apt-get update ; apt-get upgrade -us | grep " + p,chk_err=False):
 
1232
            return 1
 
1233
        else:
 
1234
            return 0
 
1235
            
 
1236
    def installAgentServiceScriptFiles(self):
 
1237
        """
 
1238
        If we are packaged - the service name is walinuxagent, do nothing.
 
1239
        """
 
1240
        if self.agent_service_name == 'walinuxagent':
 
1241
            return 0
 
1242
        try:
 
1243
            SetFileContents(self.init_script_file, self.init_file)
 
1244
            os.chmod(self.init_script_file, 0744)
 
1245
        except OSError, e:
 
1246
            ErrorWithPrefix('installAgentServiceScriptFiles','Exception: '+str(e)+' occured creating ' + self.init_script_file)
 
1247
            return 1
 
1248
        return 0
 
1249
    
 
1250
    def registerAgentService(self):
 
1251
        if self.installAgentServiceScriptFiles() == 0:
 
1252
            return Run('update-rc.d waagent defaults')
 
1253
        else :
 
1254
            return 1
 
1255
    
 
1256
    def uninstallAgentService(self):
 
1257
        return Run('update-rc.d -f ' + self.agent_service_name + ' remove')
 
1258
 
 
1259
    def unregisterAgentService(self):
 
1260
        self.stopAgentService()
 
1261
        return self.uninstallAgentService()
 
1262
    
 
1263
    def sshDeployPublicKey(self,fprint,path):
 
1264
        """
 
1265
        We support PKCS8.
 
1266
        """
 
1267
        if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
 
1268
            return 1
 
1269
        else :
 
1270
            return 0
 
1271
        
 
1272
############################################################    
 
1273
#       UbuntuDistro
 
1274
############################################################    
 
1275
ubuntu_upstart_file = """\
 
1276
#walinuxagent - start Windows Azure agent
 
1277
 
 
1278
description "walinuxagent"
 
1279
author "Ben Howard <ben.howard@canonical.com>"
 
1280
 
 
1281
start on (filesystem and started rsyslog)
 
1282
 
 
1283
pre-start script
 
1284
 
 
1285
        WALINUXAGENT_ENABLED=1
 
1286
    [ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent
 
1287
 
 
1288
    if [ "$WALINUXAGENT_ENABLED" != "1" ]; then
 
1289
        exit 1
 
1290
    fi
 
1291
 
 
1292
    if [ ! -x /usr/sbin/waagent ]; then
 
1293
        exit 1
 
1294
    fi
 
1295
 
 
1296
    #Load the udf module
 
1297
    modprobe -b udf
 
1298
end script
 
1299
 
 
1300
exec /usr/sbin/waagent -daemon
 
1301
"""
 
1302
 
 
1303
class UbuntuDistro(debianDistro):
 
1304
    """
 
1305
    Ubuntu Distro concrete class
 
1306
    Put Ubuntu specific behavior here...
 
1307
    """
 
1308
    def __init__(self):
 
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 '
 
1315
 
 
1316
    def registerAgentService(self):
 
1317
        return self.installAgentServiceScriptFiles()
 
1318
 
 
1319
    def startAgentService(self):
 
1320
        """
 
1321
        Use upstart syntax.
 
1322
        """
 
1323
        return Run('start ' + self.agent_service_name)
 
1324
        
 
1325
    def stopAgentService(self):
 
1326
        """
 
1327
        Use upstart syntax.
 
1328
        """
 
1329
        return Run('stop ' + self.agent_service_name)
 
1330
    
 
1331
    def uninstallAgentService(self):
 
1332
        """
 
1333
        If we are packaged - the service name is walinuxagent, do nothing.
 
1334
        """
 
1335
        if self.agent_service_name == 'walinuxagent':
 
1336
            return 0
 
1337
        os.remove('/etc/init/' + self.agent_service_name + '.conf')
 
1338
 
 
1339
    def unregisterAgentService(self):
 
1340
        """
 
1341
        If we are packaged - the service name is walinuxagent, do nothing.
 
1342
        """
 
1343
        if self.agent_service_name == 'walinuxagent':
 
1344
            return
 
1345
        self.stopAgentService()
 
1346
        return self.uninstallAgentService()
 
1347
    
 
1348
    def deprovisionWarnUser(self):
 
1349
        """
 
1350
        Ubuntu specific warning string from Deprovision.
 
1351
        """
 
1352
        print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,originial} will be deleted.")
 
1353
 
 
1354
    def deprovisionDeleteFiles(self):
 
1355
        """
 
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.
 
1359
        """
 
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')
 
1363
        else:
 
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:
 
1368
            try:
 
1369
                os.remove(f)
 
1370
            except:
 
1371
                pass
 
1372
        return 0
 
1373
 
 
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'
 
1379
        else :
 
1380
            self.dhcp_client_name='dhclient'
 
1381
        return self.dhcp_client_name
 
1382
 
 
1383
    def waitForSshHostKey(self, path):
 
1384
        """
 
1385
        Wait until the ssh host key is generated by cloud init.
 
1386
        """
 
1387
        for retry in range(0, 10):
 
1388
            if(os.path.isfile(path)):
 
1389
                return True
 
1390
            time.sleep(1)
 
1391
        Error("Can't find host key: {0}".format(path))
 
1392
        return False
 
1393
 
 
1394
 
 
1395
############################################################    
 
1396
#       LinuxMintDistro
 
1397
############################################################    
 
1398
 
 
1399
class LinuxMintDistro(UbuntuDistro):
 
1400
    """
 
1401
    LinuxMint Distro concrete class
 
1402
    Put LinuxMint specific behavior here...
 
1403
    """
 
1404
    def __init__(self):
 
1405
        super(LinuxMintDistro,self).__init__()
 
1406
 
 
1407
############################################################    
 
1408
#       fedoraDistro
 
1409
############################################################    
 
1410
fedora_systemd_service = """\
 
1411
[Unit]
 
1412
Description=Windows Azure Linux Agent
 
1413
After=network.target
 
1414
After=sshd.service
 
1415
ConditionFileIsExecutable=/usr/sbin/waagent
 
1416
ConditionPathExists=/etc/waagent.conf
 
1417
 
 
1418
[Service]
 
1419
Type=simple
 
1420
ExecStart=/usr/sbin/waagent -daemon
 
1421
 
 
1422
[Install]
 
1423
WantedBy=multi-user.target
 
1424
"""
 
1425
 
 
1426
class fedoraDistro(redhatDistro):
 
1427
    """
 
1428
    FedoraDistro concrete class
 
1429
    Put Fedora specific behavior here...
 
1430
    """
 
1431
    def __init__(self):
 
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='
 
1439
 
 
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'))))
 
1447
        return 0
 
1448
 
 
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')
 
1453
 
 
1454
    def registerAgentService(self):
 
1455
        self.installAgentServiceScriptFiles()
 
1456
        return Run(self.service_cmd + ' enable ' + self.agent_service_name)
 
1457
 
 
1458
    def uninstallAgentService(self):
 
1459
        """
 
1460
        Call service subsystem to remove waagent script.
 
1461
        """
 
1462
        return Run(self.service_cmd + ' disable ' + self.agent_service_name)
 
1463
 
 
1464
    def unregisterAgentService(self):
 
1465
        """
 
1466
        Calls self.stopAgentService and call self.uninstallAgentService()
 
1467
        """
 
1468
        self.stopAgentService()
 
1469
        self.uninstallAgentService()
 
1470
 
 
1471
    def startAgentService(self):
 
1472
        """
 
1473
        Service call to start the Agent service
 
1474
        """
 
1475
        return Run(self.service_cmd + ' start ' + self.agent_service_name)
 
1476
 
 
1477
    def stopAgentService(self):
 
1478
        """
 
1479
        Service call to stop the Agent service
 
1480
        """
 
1481
        return Run(self.service_cmd + ' stop '  + self.agent_service_name, False)
 
1482
 
 
1483
    def restartSshService(self):
 
1484
        """
 
1485
        Service call to re(start) the SSH service
 
1486
        """
 
1487
        sshRestartCmd = self.service_cmd + " " +  self.ssh_service_restart_option + " " + self.ssh_service_name
 
1488
        retcode = Run(sshRestartCmd)
 
1489
        if retcode > 0:
 
1490
            Error("Failed to restart SSH service with return code:" + str(retcode))
 
1491
        return retcode
 
1492
 
 
1493
    def checkPackageInstalled(self, p):
 
1494
        """
 
1495
        Query package database for prescence of an installed package.
 
1496
        """
 
1497
        import rpm
 
1498
        ts = rpm.TransactionSet()
 
1499
        rpms = ts.dbMatch(rpm.RPMTAG_PROVIDES, p)
 
1500
        return bool(len(rpms) > 0)
 
1501
 
 
1502
    def deleteRootPassword(self):
 
1503
        return Run("/sbin/usermod root -p '!!'")
 
1504
 
 
1505
    def packagedInstall(self,buildroot):
 
1506
        """
 
1507
        Called from setup.py for use by RPM.
 
1508
        Copies generated files waagent.conf, under the buildroot.
 
1509
        """
 
1510
        if not os.path.exists(buildroot+'/etc'):
 
1511
            os.mkdir(buildroot+'/etc')
 
1512
        SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
 
1513
 
 
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)
 
1517
 
 
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()
 
1523
 
 
1524
    def CreateAccount(self, user, password, expiration, thumbprint):
 
1525
        super(fedoraDistro, self).CreateAccount(user, password, expiration, thumbprint)
 
1526
        Run('/sbin/usermod ' + user + ' -G wheel')
 
1527
 
 
1528
    def DeleteAccount(self, user):
 
1529
        Run('/sbin/usermod ' + user + ' -G ""')
 
1530
        super(fedoraDistro, self).DeleteAccount(user)
 
1531
 
 
1532
############################################################    
 
1533
#       FreeBSD
 
1534
############################################################    
 
1535
FreeBSDWaagentConf = """\
 
1536
#
 
1537
# Windows Azure Linux Agent Configuration
 
1538
#
 
1539
 
 
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.
 
1544
 
 
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.
 
1550
 
 
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.
 
1556
 
 
1557
LBProbeResponder=y                      # Respond to load balancer probes if requested by Windows Azure.
 
1558
 
 
1559
Logs.Verbose=n                          # Enable verbose logs
 
1560
 
 
1561
OS.RootDeviceScsiTimeout=300            # Root device timeout in seconds.
 
1562
OS.OpensslPath=None                     # If "None", the system default version is used.
 
1563
"""
 
1564
 
 
1565
bsd_init_file="""\
 
1566
#! /bin/sh
 
1567
 
 
1568
# PROVIDE: waagent
 
1569
# REQUIRE: DAEMON cleanvar sshd
 
1570
# BEFORE: LOGIN
 
1571
# KEYWORD: nojail
 
1572
 
 
1573
. /etc/rc.subr
 
1574
export PATH=$PATH:/usr/local/bin
 
1575
name="waagent"
 
1576
rcvar="waagent_enable"
 
1577
command="/usr/sbin/${name}"
 
1578
command_interpreter="/usr/local/bin/python"
 
1579
waagent_flags=" daemon &"
 
1580
 
 
1581
pidfile="/var/run/waagent.pid"
 
1582
 
 
1583
load_rc_config $name
 
1584
run_rc_command "$1"
 
1585
 
 
1586
"""
 
1587
bsd_activate_resource_disk_txt="""\
 
1588
#!/usr/bin/env python
 
1589
 
 
1590
import os
 
1591
import sys
 
1592
import imp
 
1593
 
 
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"):
 
1602
    sys.exit(0)
 
1603
device_base = 'da1'
 
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.")
 
1608
        sys.exit(0)
 
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).")
 
1618
    sys.exit(0)
 
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"):
 
1622
    sys.exit(0)
 
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")
 
1632
else:
 
1633
    waagent.Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
 
1634
"""
 
1635
 
 
1636
class FreeBSDDistro(AbstractDistro):
 
1637
    """
 
1638
    """
 
1639
    def __init__(self):
 
1640
        """
 
1641
        Generic Attributes go here.  These are based on 'majority rules'.
 
1642
        This __init__() may be called or overriden by the child.
 
1643
        """
 
1644
        super(FreeBSDDistro,self).__init__()
 
1645
        self.agent_service_name = os.path.basename(sys.argv[0]) 
 
1646
        self.selinux=False
 
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
 
1664
        
 
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")
 
1669
        return 0
 
1670
 
 
1671
    def registerAgentService(self):
 
1672
        self.installAgentServiceScriptFiles()
 
1673
        return Run("services_mkdb " + self.init_script_file)
 
1674
 
 
1675
        
 
1676
    def sshDeployPublicKey(self,fprint,path):
 
1677
        """
 
1678
        We support PKCS8.
 
1679
        """
 
1680
        if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
 
1681
            return 1
 
1682
        else :
 
1683
            return 0
 
1684
 
 
1685
    def deleteRootPassword(self):
 
1686
        """
 
1687
        BSD root password removal.
 
1688
        """
 
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.")
 
1698
        return 0
 
1699
 
 
1700
    def changePass(self,user,password):
 
1701
        return RunSendStdin("pw usermod " + user + " -h 0 ",password)
 
1702
    
 
1703
    def load_ata_piix(self):
 
1704
        return 0
 
1705
 
 
1706
    def unload_ata_piix(self):
 
1707
        return 0
 
1708
 
 
1709
    def checkDependencies(self):
 
1710
        """
 
1711
        FreeBSD dependency check.
 
1712
        Return 1 unless all dependencies are satisfied.
 
1713
        """
 
1714
        for a in self.requiredDeps:
 
1715
            if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
 
1716
                Error("Missing required dependency: " + a)
 
1717
                return 1
 
1718
        return 0
 
1719
 
 
1720
    def packagedInstall(self,buildroot):
 
1721
        pass
 
1722
 
 
1723
    def GetInterfaceName(self):
 
1724
        """
 
1725
        Return the ip of the 
 
1726
        active ethernet interface.
 
1727
        """
 
1728
        iface,inet,mac=self.GetFreeBSDEthernetInfo()
 
1729
        return iface
 
1730
 
 
1731
    def RestartInterface(self, iface):
 
1732
        Run("service netif restart")
 
1733
 
 
1734
    def GetIpv4Address(self):
 
1735
        """
 
1736
        Return the ip of the 
 
1737
        active ethernet interface.
 
1738
        """
 
1739
        iface,inet,mac=self.GetFreeBSDEthernetInfo()
 
1740
        return inet
 
1741
 
 
1742
    def GetMacAddress(self):
 
1743
        """
 
1744
        Return the ip of the 
 
1745
        active ethernet interface.
 
1746
        """
 
1747
        iface,inet,mac=self.GetFreeBSDEthernetInfo()
 
1748
        l=mac.split(':')
 
1749
        r=[]
 
1750
        for i in l:
 
1751
            r.append(string.atoi(i,16))
 
1752
        return r
 
1753
 
 
1754
    def GetFreeBSDEthernetInfo(self):
 
1755
        """
 
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.
 
1761
        """
 
1762
        code,output=RunGetOutput("ifconfig",chk_err=False)
 
1763
        Log(output)
 
1764
        retries=10
 
1765
        cmd='ifconfig | grep -A1 -B2 ether | grep -B3 inet | grep -A3 UP '
 
1766
        code=1
 
1767
 
 
1768
        while code > 0 :
 
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)
 
1773
            retries-=1
 
1774
            if code > 0 and retries > 0 :
 
1775
                Log("GetFreeBSDEthernetInfo - Error: retry ethernet detection " + str(retries))
 
1776
                if retries == 9 :
 
1777
                    c,o=RunGetOutput("ifconfig | grep -A1 -B2 ether",chk_err=False)
 
1778
                    if c == 0:
 
1779
                        t=o.replace('\n',' ')
 
1780
                        t=t.split()
 
1781
                        i=t[0][:-1]
 
1782
                        Log(RunGetOutput('id')[1])
 
1783
                        Run('dhclient '+i)
 
1784
                time.sleep(10)
 
1785
 
 
1786
        j=output.replace('\n',' ')
 
1787
        j=j.split()
 
1788
        iface=j[0][:-1]
 
1789
 
 
1790
        for i in range(len(j)):
 
1791
            if j[i] == 'inet' :
 
1792
                inet=j[i+1]
 
1793
            elif j[i] == 'ether' :
 
1794
                mac=j[i+1]
 
1795
 
 
1796
        return iface, inet, mac
 
1797
 
 
1798
    def CreateAccount(self,user, password, expiration, thumbprint):
 
1799
        """
 
1800
        Create a user account, with 'user', 'password', 'expiration', ssh keys
 
1801
        and sudo permissions.
 
1802
        Returns None if successful, error string on failure.
 
1803
        """
 
1804
        userentry = None
 
1805
        try:
 
1806
            userentry = pwd.getpwnam(user)
 
1807
        except:
 
1808
            pass
 
1809
        uidmin = None
 
1810
        try:
 
1811
            uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
 
1812
        except:
 
1813
            pass
 
1814
        if uidmin == None:
 
1815
            uidmin = 100
 
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]
 
1823
            if Run(command):
 
1824
                Error("Failed to create user account: " + user)
 
1825
                return "Failed to create user account: " + user + " (0x07)."
 
1826
            else:
 
1827
                Log("CreateAccount: " + user + " already exists. Will update password.")
 
1828
        
 
1829
        if password != None:
 
1830
            self.changePass(user,password)
 
1831
        try:
 
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")
 
1840
            else:
 
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)
 
1843
        except:
 
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]:
 
1855
                os.chmod(f, 0600)
 
1856
                ChangeOwner(f, user)
 
1857
            SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
 
1858
            ChangeOwner(dir + "/authorized_keys", user)
 
1859
        Log("Created user account: " + user)
 
1860
        return None
 
1861
    
 
1862
    def DeleteAccount(self,user):
 
1863
        """
 
1864
        Delete the 'user'.
 
1865
        Clear utmp first, to avoid error.
 
1866
        Removes the /etc/sudoers.d/waagent file.
 
1867
        """
 
1868
        userentry = None
 
1869
        try:
 
1870
            userentry = pwd.getpwnam(user)
 
1871
        except:
 
1872
            pass
 
1873
        if userentry == None:
 
1874
            Error("DeleteAccount: " + user + " not found.")
 
1875
            return
 
1876
        uidmin = None
 
1877
        try:
 
1878
            uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
 
1879
        except:
 
1880
            pass
 
1881
        if uidmin == None:
 
1882
            uidmin = 100
 
1883
        if userentry[2] < uidmin:
 
1884
            Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
 
1885
            return
 
1886
        Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
 
1887
        Run("rmuser -y " + user)
 
1888
        try:
 
1889
            os.remove(MyDistro.sudoers_dir_base+"/sudoers.d/waagent")
 
1890
        except:
 
1891
            pass
 
1892
        return
 
1893
 
 
1894
    def ActivateResourceDiskNoThread(self):
 
1895
        """
 
1896
        Format, mount, and if specified in the configuration
 
1897
        set resource disk as swap.
 
1898
        """
 
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
 
1906
        return
 
1907
 
 
1908
    def Install(self):
 
1909
        """
 
1910
        Install the agent service.
 
1911
        Check dependencies.
 
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()
 
1918
        """
 
1919
        if MyDistro.checkDependencies():
 
1920
            return 1
 
1921
        os.chmod(sys.argv[0], 0755)
 
1922
        SwitchCwd()
 
1923
        for a in RulesFiles:
 
1924
            if os.path.isfile(a):
 
1925
                if os.path.isfile(GetLastPathElement(a)):
 
1926
                    os.remove(GetLastPathElement(a))
 
1927
                shutil.move(a, ".")
 
1928
                Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
 
1929
        MyDistro.registerAgentService()
 
1930
        if os.path.isfile("/etc/waagent.conf"):
 
1931
            try:
 
1932
                os.remove("/etc/waagent.conf.old")
 
1933
            except:
 
1934
                pass
 
1935
            try:
 
1936
                os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
 
1937
                Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
 
1938
            except:
 
1939
                pass
 
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()
 
1949
        return 0
 
1950
    
 
1951
    def mediaHasFilesystem(self,dsk):
 
1952
        if Run('LC_ALL=C fdisk -p ' + dsk + ' | grep "invalid fdisk partition table found" ',False):
 
1953
            return False
 
1954
        return True
 
1955
    
 
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')
 
1959
        if retcode != 0:
 
1960
            return retcode,out
 
1961
 
 
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)
 
1970
        return retcode,out
 
1971
 
 
1972
    def GetHome(self):
 
1973
        return '/home'
 
1974
 
 
1975
    def initScsiDiskTimeout(self):
 
1976
        """
 
1977
        Set the SCSI disk timeout by updating the kernal config
 
1978
        """
 
1979
        timeout = Config.get("OS.RootDeviceScsiTimeout") 
 
1980
        if timeout:
 
1981
            Run("sysctl kern.cam.da.default_timeout=" + timeout)
 
1982
 
 
1983
    def setScsiDiskTimeout(self):
 
1984
        return
 
1985
 
 
1986
    def setBlockDeviceTimeout(self, device, timeout):
 
1987
        return
 
1988
 
 
1989
############################################################
 
1990
# END DISTRO CLASS DEFS
 
1991
############################################################  
 
1992
 
 
1993
# This lets us index into a string or an array of integers transparently.
 
1994
def Ord(a):
 
1995
    """
 
1996
    Allows indexing into a string or an array of integers transparently.
 
1997
    Generic utility function.
 
1998
    """
 
1999
    if type(a) == type("a"):
 
2000
        a = ord(a)
 
2001
    return a
 
2002
 
 
2003
def IsLinux():
 
2004
    """
 
2005
    Returns True if platform is Linux.
 
2006
    Generic utility function.
 
2007
    """
 
2008
    return (platform.uname()[0] == "Linux")
 
2009
 
 
2010
def GetLastPathElement(path):
 
2011
    """
 
2012
    Similar to basename.
 
2013
    Generic utility function.
 
2014
    """
 
2015
    return path.rsplit('/', 1)[1]
 
2016
 
 
2017
def GetFileContents(filepath,asbin=False):
 
2018
    """
 
2019
    Read and return contents of 'filepath'.
 
2020
    """
 
2021
    mode='r'
 
2022
    if asbin:
 
2023
        mode+='b'
 
2024
    c=None
 
2025
    try:
 
2026
        with open(filepath, mode) as F :
 
2027
            c=F.read()
 
2028
    except IOError, e:
 
2029
        ErrorWithPrefix('GetFileContents','Reading from file ' + filepath + ' Exception is ' + str(e))
 
2030
        return None        
 
2031
    return c
 
2032
 
 
2033
def SetFileContents(filepath, contents):
 
2034
    """
 
2035
    Write 'contents' to 'filepath'.
 
2036
    """
 
2037
    if type(contents) == str :
 
2038
        contents=contents.encode('latin-1', 'ignore')
 
2039
    try:
 
2040
        with open(filepath, "wb+") as F :
 
2041
            F.write(contents)
 
2042
    except IOError, e:
 
2043
        ErrorWithPrefix('SetFileContents','Writing to file ' + filepath + ' Exception is ' + str(e))
 
2044
        return None
 
2045
    return 0
 
2046
 
 
2047
def AppendFileContents(filepath, contents):
 
2048
    """
 
2049
    Append 'contents' to 'filepath'.
 
2050
    """
 
2051
    if type(contents) == str :
 
2052
        contents=contents.encode('latin-1')
 
2053
    try: 
 
2054
        with open(filepath, "a+") as F :
 
2055
            F.write(contents)
 
2056
    except IOError, e:
 
2057
        ErrorWithPrefix('AppendFileContents','Appending to file ' + filepath + ' Exception is ' + str(e))
 
2058
        return None
 
2059
    return 0
 
2060
 
 
2061
def ReplaceFileContentsAtomic(filepath, contents):
 
2062
    """
 
2063
    Write 'contents' to 'filepath' by creating a temp file, and replacing original.
 
2064
    """
 
2065
    handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
 
2066
    if type(contents) == str :
 
2067
        contents=contents.encode('latin-1')
 
2068
    try:
 
2069
        os.write(handle, contents)
 
2070
    except IOError, e:
 
2071
        ErrorWithPrefix('ReplaceFileContentsAtomic','Writing to file ' + filepath + ' Exception is ' + str(e))
 
2072
        return None
 
2073
    finally:
 
2074
        os.close(handle)
 
2075
    try:
 
2076
        os.rename(temp, filepath)
 
2077
        return None
 
2078
    except IOError, e:
 
2079
        ErrorWithPrefix('ReplaceFileContentsAtomic','Renaming ' + temp+ ' to ' + filepath + ' Exception is ' + str(e))
 
2080
    try:
 
2081
        os.remove(filepath)
 
2082
    except IOError, e:
 
2083
        ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
 
2084
    try:
 
2085
        os.rename(temp,filepath)
 
2086
    except IOError, e:
 
2087
        ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
 
2088
        return 1
 
2089
    return 0
 
2090
 
 
2091
def GetLineStartingWith(prefix, filepath):
 
2092
    """
 
2093
    Return line from 'filepath' if the line startswith 'prefix'
 
2094
    """
 
2095
    for line in GetFileContents(filepath).split('\n'):
 
2096
        if line.startswith(prefix):
 
2097
            return line
 
2098
    return None
 
2099
 
 
2100
def Run(cmd,chk_err=True):
 
2101
    """
 
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.
 
2105
    """
 
2106
    retcode,out=RunGetOutput(cmd,chk_err)
 
2107
    return retcode
 
2108
 
 
2109
def RunGetOutput(cmd,chk_err=True):
 
2110
    """
 
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
 
2114
    """
 
2115
    LogIfVerbose(cmd)
 
2116
    try:                                     
 
2117
        output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
 
2118
    except subprocess.CalledProcessError,e :
 
2119
        if chk_err :
 
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')
 
2125
 
 
2126
def RunSendStdin(cmd,input,chk_err=True):
 
2127
    """
 
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
 
2132
    """
 
2133
    LogIfVerbose(cmd+input)
 
2134
    try:                                     
 
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 :
 
2138
        if chk_err :
 
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')
 
2148
 
 
2149
def GetNodeTextData(a):
 
2150
    """
 
2151
    Filter non-text nodes from DOM tree
 
2152
    """
 
2153
    for b in a.childNodes:
 
2154
        if b.nodeType == b.TEXT_NODE:
 
2155
            return b.data
 
2156
 
 
2157
def GetHome():
 
2158
    """
 
2159
    Attempt to guess the $HOME location.
 
2160
    Return the path string.
 
2161
    """
 
2162
    home = None
 
2163
    try:
 
2164
        home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
 
2165
    except:
 
2166
        pass
 
2167
    if (home == None) or (home.startswith("/") == False):
 
2168
        home = "/home"
 
2169
    return home
 
2170
 
 
2171
def ChangeOwner(filepath, user):
 
2172
    """
 
2173
    Lookup user.  Attempt chown 'filepath' to 'user'.
 
2174
    """
 
2175
    p = None
 
2176
    try:
 
2177
        p = pwd.getpwnam(user)
 
2178
    except:
 
2179
        pass
 
2180
    if p != None:
 
2181
        os.chown(filepath, p[2], p[3])
 
2182
 
 
2183
def CreateDir(dirpath, user, mode):
 
2184
    """
 
2185
    Attempt os.makedirs, catch all exceptions.
 
2186
    Call ChangeOwner afterwards.
 
2187
    """
 
2188
    try:
 
2189
        os.makedirs(dirpath, mode)
 
2190
    except:
 
2191
        pass
 
2192
    ChangeOwner(dirpath, user)
 
2193
 
 
2194
def CreateAccount(user, password, expiration, thumbprint):
 
2195
    """
 
2196
    Create a user account, with 'user', 'password', 'expiration', ssh keys
 
2197
    and sudo permissions.
 
2198
    Returns None if successful, error string on failure.
 
2199
    """
 
2200
    userentry = None
 
2201
    try:
 
2202
        userentry = pwd.getpwnam(user)
 
2203
    except:
 
2204
        pass
 
2205
    uidmin = None
 
2206
    try:
 
2207
        uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
 
2208
    except:
 
2209
        pass
 
2210
    if uidmin == None:
 
2211
        uidmin = 100
 
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]
 
2219
        if Run(command):
 
2220
            Error("Failed to create user account: " + user)
 
2221
            return "Failed to create user account: " + user + " (0x07)."
 
2222
    else:
 
2223
        Log("CreateAccount: " + user + " already exists. Will update password.")
 
2224
    if password != None:
 
2225
        RunSendStdin("chpasswd",(user + ":" + password + "\n"))
 
2226
    try:
 
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")
 
2235
        else:
 
2236
            SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
 
2237
        os.chmod("/etc/sudoers.d/waagent", 0440)
 
2238
    except:
 
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]:
 
2250
            os.chmod(f, 0600)
 
2251
            ChangeOwner(f, user)
 
2252
        SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
 
2253
        ChangeOwner(dir + "/authorized_keys", user)
 
2254
    Log("Created user account: " + user)
 
2255
    return None
 
2256
 
 
2257
def DeleteAccount(user):
 
2258
    """
 
2259
    Delete the 'user'.
 
2260
    Clear utmp first, to avoid error.
 
2261
    Removes the /etc/sudoers.d/waagent file.
 
2262
    """
 
2263
    userentry = None
 
2264
    try:
 
2265
        userentry = pwd.getpwnam(user)
 
2266
    except:
 
2267
        pass
 
2268
    if userentry == None:
 
2269
        Error("DeleteAccount: " + user + " not found.")
 
2270
        return
 
2271
    uidmin = None
 
2272
    try:
 
2273
        uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
 
2274
    except:
 
2275
        pass
 
2276
    if uidmin == None:
 
2277
        uidmin = 100
 
2278
    if userentry[2] < uidmin:
 
2279
        Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
 
2280
        return
 
2281
    Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
 
2282
    Run("userdel -f -r " + user)
 
2283
    try:
 
2284
        os.remove("/etc/sudoers.d/waagent")
 
2285
    except:
 
2286
        pass
 
2287
    return
 
2288
 
 
2289
def IsInRangeInclusive(a, low, high):
 
2290
    """
 
2291
    Return True if 'a' in 'low' <= a >= 'high'
 
2292
    """
 
2293
    return (a >= low and a <= high)
 
2294
 
 
2295
def IsPrintable(ch):
 
2296
    """
 
2297
    Return True if character is displayable.
 
2298
    """
 
2299
    return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
 
2300
 
 
2301
def HexDump(buffer, size):
 
2302
    """
 
2303
    Return Hex formated dump of a 'buffer' of 'size'.
 
2304
    """
 
2305
    if size < 0:
 
2306
        size = len(buffer)
 
2307
    result = ""
 
2308
    for i in range(0, size):
 
2309
        if (i % 16) == 0:
 
2310
            result += "%06X: " % i
 
2311
        byte = buffer[i]
 
2312
        if type(byte) == str:
 
2313
            byte = ord(byte.decode('latin1'))
 
2314
        result += "%02X " % byte
 
2315
        if (i & 15) == 7:
 
2316
            result += " "
 
2317
        if ((i + 1) % 16) == 0 or (i + 1) == size:
 
2318
            j = i
 
2319
            while ((j + 1) % 16) != 0:
 
2320
                result += "   "
 
2321
                if (j & 7) == 7:
 
2322
                    result += " "
 
2323
                j += 1
 
2324
            result += " "
 
2325
            for j in range(i - (i % 16), i + 1):
 
2326
                byte=buffer[j]
 
2327
                if type(byte) == str:
 
2328
                    byte = ord(byte.decode('latin1'))
 
2329
                k = '.'
 
2330
                if IsPrintable(byte):
 
2331
                    k = chr(byte)
 
2332
                result += k
 
2333
            if (i + 1) != size:
 
2334
                result += "\n"
 
2335
    return result
 
2336
 
 
2337
def SimpleLog(file_path,message):
 
2338
    if not file_path or len(message) < 1:
 
2339
        return
 
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")
 
2346
 
 
2347
class Logger(object):
 
2348
    """
 
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.
 
2356
    """
 
2357
 
 
2358
    def __init__(self,filepath,conpath,verbose=False):
 
2359
        """
 
2360
        Construct an instance of Logger.
 
2361
        """
 
2362
        self.file_path=filepath
 
2363
        self.con_path=conpath
 
2364
        self.verbose=verbose
 
2365
        
 
2366
    def ThrottleLog(self,counter):
 
2367
        """
 
2368
        Log everything up to 10, every 10 up to 100, then every 100.
 
2369
        """
 
2370
        return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
 
2371
    
 
2372
    def LogToFile(self,message):
 
2373
        """
 
2374
        Write 'message' to logfile.
 
2375
        """
 
2376
        if self.file_path:
 
2377
            try:
 
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")
 
2381
            except IOError, e:
 
2382
                print e
 
2383
                pass
 
2384
            
 
2385
    def LogToCon(self,message):
 
2386
        """
 
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.
 
2390
        """
 
2391
        if self.con_path:
 
2392
            try:
 
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")
 
2396
            except IOError, e:
 
2397
                print e
 
2398
                pass
 
2399
                
 
2400
    def Log(self,message):
 
2401
        """
 
2402
        Standard Log function.
 
2403
        Logs to self.file_path, and con_path
 
2404
        """
 
2405
        self.LogWithPrefix("", message)
 
2406
    
 
2407
    def LogWithPrefix(self,prefix, message):
 
2408
        """
 
2409
        Prefix each line of 'message' with current time+'prefix'.
 
2410
        """
 
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)
 
2413
        t += prefix
 
2414
        for line in message.split('\n'):
 
2415
            line = t + line
 
2416
            self.LogToFile(line)
 
2417
            self.LogToCon(line)
 
2418
            
 
2419
    def NoLog(self,message):
 
2420
        """
 
2421
        Don't Log.
 
2422
        """
 
2423
        pass
 
2424
    
 
2425
    def LogIfVerbose(self,message):
 
2426
        """
 
2427
        Only log 'message' if global Verbose is True.
 
2428
        """
 
2429
        self.LogWithPrefixIfVerbose('',message)
 
2430
    
 
2431
    def LogWithPrefixIfVerbose(self,prefix, message):
 
2432
        """
 
2433
        Only log 'message' if global Verbose is True.
 
2434
        Prefix each line of 'message' with current time+'prefix'.
 
2435
        """
 
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)
 
2439
            t += prefix
 
2440
            for line in message.split('\n'):
 
2441
                line = t + line
 
2442
                self.LogToFile(line)
 
2443
                self.LogToCon(line)
 
2444
                
 
2445
    def Warn(self,message):
 
2446
        """
 
2447
        Prepend the text "WARNING:" to the prefix for each line in 'message'.
 
2448
        """
 
2449
        self.LogWithPrefix("WARNING:", message)
 
2450
    
 
2451
    def Error(self,message):
 
2452
        """
 
2453
        Call ErrorWithPrefix(message).
 
2454
        """
 
2455
        ErrorWithPrefix("", message)
 
2456
    
 
2457
    def ErrorWithPrefix(self,prefix, message):
 
2458
        """
 
2459
        Prepend the text "ERROR:" to the prefix for each line in 'message'.
 
2460
        Errors written to logfile, and /dev/console
 
2461
        """
 
2462
        self.LogWithPrefix("ERROR:", message)
 
2463
    
 
2464
def LoggerInit(log_file_path,log_con_path,verbose=False):
 
2465
    """
 
2466
    Create log object and export its methods to global scope.
 
2467
    """
 
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
 
2471
 
 
2472
def Linux_ioctl_GetInterfaceMac(ifname):
 
2473
    """
 
2474
    Return the mac-address bound to the socket.
 
2475
    """
 
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]])
 
2479
 
 
2480
def GetFirstActiveNetworkInterfaceNonLoopback():
 
2481
    """
 
2482
    Return the interface name, and ip addr of the
 
2483
    first active non-loopback interface.
 
2484
    """
 
2485
    iface=''
 
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.')
 
2493
    s=buff.tostring()
 
2494
    for i in range(0,struct_size*expected,struct_size):
 
2495
        iface=s[i:i+16].split(b'\0', 1)[0]
 
2496
        if iface == b'lo':
 
2497
            continue
 
2498
        else :
 
2499
            break
 
2500
    return iface.decode('latin-1'), socket.inet_ntoa(s[i+20:i+24])
 
2501
 
 
2502
def GetIpv4Address():
 
2503
    """
 
2504
    Return the ip of the 
 
2505
    first active non-loopback interface.
 
2506
    """
 
2507
    iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
 
2508
    return addr
 
2509
 
 
2510
def HexStringToByteArray(a):
 
2511
    """
 
2512
    Return hex string packed into a binary struct.
 
2513
    """
 
2514
    b = b""
 
2515
    for c in range(0, len(a) // 2):
 
2516
        b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
 
2517
    return b
 
2518
 
 
2519
def GetMacAddress():
 
2520
    """
 
2521
    Convienience function, returns mac addr bound to
 
2522
    first non-loobback interface.
 
2523
    """
 
2524
    ifname=''
 
2525
    while len(ifname) < 2 :
 
2526
        ifname=GetFirstActiveNetworkInterfaceNonLoopback()[0]
 
2527
    a = Linux_ioctl_GetInterfaceMac(ifname)        
 
2528
    return HexStringToByteArray(a)
 
2529
 
 
2530
def DeviceForIdePort(n):
 
2531
    """
 
2532
    Return device name attached to ide port 'n'.
 
2533
    """
 
2534
    if n > 3:
 
2535
        return None
 
2536
    g0 = "00000000"
 
2537
    if n > 1:
 
2538
        g0 = "00000001"
 
2539
        n = n - 2
 
2540
    device = None
 
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"):
 
2547
                    device = dirs[0]
 
2548
                    break
 
2549
                else : #older distros
 
2550
                    for d in dirs:
 
2551
                        if ':' in d and "block" == d.split(':')[0]:
 
2552
                            device = d.split(':')[1]
 
2553
                            break
 
2554
            break
 
2555
    return device
 
2556
 
 
2557
class Util(object):
 
2558
    """
 
2559
    Http communication class.
 
2560
    Base of GoalState, and Agent classes.
 
2561
    """
 
2562
    def _HttpGet(self, url, headers):
 
2563
        """
 
2564
        Do HTTP get on 'url' with 'headers'.
 
2565
        On error, sleep 10 and maxRetry times.
 
2566
        Return the output buffer or None.
 
2567
        """
 
2568
        LogIfVerbose("HttpGet(" + url + ")")
 
2569
        maxRetry = 2
 
2570
        if url.startswith("http://"):
 
2571
            url = url[7:]
 
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)
 
2577
            response = None
 
2578
            strStatus = "None"
 
2579
            try:
 
2580
                httpConnection = httplib.HTTPConnection(self.Endpoint)
 
2581
                if headers == None:
 
2582
                    request = httpConnection.request("GET", url)
 
2583
                else:
 
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))
 
2589
            except IOError, e:
 
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)
 
2596
                    return None
 
2597
                else:
 
2598
                    Error("sleep 10 seconds HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
 
2599
                    time.sleep(10)
 
2600
            else:
 
2601
                log("return HttpGet(" + url + "),retry=" + strRetry + ",status=" + strStatus)
 
2602
                return response.read()
 
2603
 
 
2604
    def HttpGetWithoutHeaders(self, url):
 
2605
        """
 
2606
        Return data from an HTTP get on 'url'.
 
2607
        """
 
2608
        return self._HttpGet(url, None)
 
2609
 
 
2610
    def HttpGetWithHeaders(self, url):
 
2611
        """
 
2612
        Return data from an HTTP get on 'url' with
 
2613
        x-ms-agent-name and x-ms-version
 
2614
        headers.
 
2615
        """
 
2616
        return self._HttpGet(url, {"x-ms-agent-name": GuestAgentName, "x-ms-version": ProtocolVersion})
 
2617
 
 
2618
    def HttpSecureGetWithHeaders(self, url, transportCert):
 
2619
        """
 
2620
        Return output of get using ssl cert.
 
2621
        """
 
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})
 
2626
 
 
2627
    def HttpPost(self, url, data):
 
2628
        """
 
2629
        Send http POST to server, sleeping 10 retrying maxRetry times upon error.
 
2630
        """
 
2631
        LogIfVerbose("HttpPost(" + url + ")")
 
2632
        maxRetry = 2
 
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)
 
2637
            response = None
 
2638
            strStatus = "None"
 
2639
            try:
 
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))
 
2648
            except IOError, e:
 
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)
 
2655
                    return None
 
2656
                else:
 
2657
                    Error("sleep 10 seconds HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
 
2658
                    time.sleep(10)
 
2659
            else:
 
2660
                log("return HttpPost(" + url + "),retry=" + strRetry + ",status=" + strStatus)
 
2661
                return response
 
2662
 
 
2663
    def HttpPutBlockBlob(self, url, data):
 
2664
        """
 
2665
        Send http PUT to server, sleeping 10 retrying maxRetry times upon error.
 
2666
        """
 
2667
        LogIfVerbose("HttpPutBlockBlob(" + url + ")")
 
2668
        maxRetry = 2
 
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)
 
2673
            response = None
 
2674
            strStatus = "None"
 
2675
            try:
 
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))
 
2682
            except IOError, e:
 
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)
 
2689
                    return None
 
2690
                else:
 
2691
                    Error("sleep 10 seconds HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus)
 
2692
                    time.sleep(10)
 
2693
            else:
 
2694
                log("return HttpPutBlockBlob(" + url + "),retry=" + strRetry + ",status=" + strStatus)
 
2695
                return response
 
2696
 
 
2697
class TCPHandler(SocketServer.BaseRequestHandler):
 
2698
    """
 
2699
    Callback object for LoadBalancerProbeServer.
 
2700
    Recv and send LB probe messages.
 
2701
    """
 
2702
    def __init__(self,lb_probe):
 
2703
        super(TCPHandler,self).__init__()
 
2704
        self.lb_probe=lb_probe
 
2705
        
 
2706
    def GetHttpDateTimeNow(self):
 
2707
        """
 
2708
        Return formatted gmtime "Date: Fri, 25 Mar 2011 04:53:10 GMT"
 
2709
        """
 
2710
        return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
 
2711
 
 
2712
    def handle(self):
 
2713
        """
 
2714
        Log LB probe messages, read the socket buffer,
 
2715
        send LB probe response back to server.
 
2716
        """
 
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")
 
2725
            
 
2726
class LoadBalancerProbeServer(object):
 
2727
    """
 
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.
 
2731
    """
 
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()
 
2738
        
 
2739
    def shutdown(self):
 
2740
        self.server.shutdown()
 
2741
 
 
2742
    def get_ip(self):
 
2743
        for retry in range(1,6):
 
2744
            ip = MyDistro.GetIpv4Address()
 
2745
            if ip == None :
 
2746
                Log("LoadBalancerProbeServer: GetIpv4Address() returned None, sleeping 10 before retry " + str(retry+1) )
 
2747
                time.sleep(10)
 
2748
            else:
 
2749
                return ip
 
2750
 
 
2751
class ConfigurationProvider(object):
 
2752
    """
 
2753
    Parse amd store key:values in waagent.conf
 
2754
    """
 
2755
    def __init__(self):
 
2756
        self.values = dict()
 
2757
        walaConfigFile = MyDistro.getConfigurationPath()
 
2758
        if os.path.isfile(walaConfigFile) == False:
 
2759
            raise Exception("Missing configuration in {0}".format(walaConfigFile))
 
2760
        try:
 
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("\" ")
 
2765
                    if value != "None":
 
2766
                        self.values[parts[0]] = value
 
2767
                    else:
 
2768
                        self.values[parts[0]] = None
 
2769
        except:
 
2770
            Error("Unable to parse {0}".format(walaConfigFile))
 
2771
            raise
 
2772
        return
 
2773
 
 
2774
    def get(self, key):
 
2775
        return self.values.get(key)
 
2776
 
 
2777
class EnvMonitor(object):
 
2778
    """
 
2779
    Montor changes to dhcp and hostname.
 
2780
    If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
 
2781
    """
 
2782
    def __init__(self):
 
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
 
2789
 
 
2790
    def monitor(self):
 
2791
        """
 
2792
        Monitor dhcp client pid and hostname.
 
2793
        If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
 
2794
        """
 
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))
 
2803
                    shutil.move(a, ".")
 
2804
                    Log("EnvMonitor: Moved " + a + " -> " + LibDir)
 
2805
            MyDistro.setScsiDiskTimeout()
 
2806
            if publish != None and publish.lower().startswith("y"):
 
2807
                try:
 
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
 
2814
                except:
 
2815
                    pass
 
2816
            else:
 
2817
                self.published = True
 
2818
            pid = ""
 
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()
 
2824
                dhcppid = pid
 
2825
            for child in Children:
 
2826
                if child.poll() != None:
 
2827
                    Children.remove(child)
 
2828
            time.sleep(5)
 
2829
 
 
2830
    def SetHostName(self, name):
 
2831
        """
 
2832
        Generic call to MyDistro.setHostname(name).
 
2833
        Complian to Log on error.
 
2834
        """
 
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)
 
2840
 
 
2841
    def IsHostnamePublished(self):
 
2842
        """
 
2843
        Return self.published  
 
2844
        """
 
2845
        return self.published
 
2846
 
 
2847
    def ShutdownService(self):
 
2848
        """
 
2849
        Stop server comminucation and join the thread to main thread.
 
2850
        """
 
2851
        self.shutdown = True
 
2852
        self.server_thread.join()
 
2853
 
 
2854
class Certificates(object):
 
2855
    """
 
2856
    Object containing certificates of host and provisioned user.
 
2857
    Parses and splits certificates into files.
 
2858
    """
 
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>
 
2863
    #     <Data>MIILTAY...
 
2864
    #     </Data>
 
2865
    #     </CertificateFile>
 
2866
 
 
2867
    def __init__(self):
 
2868
        self.reinitialize()
 
2869
 
 
2870
    def reinitialize(self):
 
2871
        """
 
2872
        Reset the Role, Incarnation
 
2873
        """
 
2874
        self.Incarnation = None
 
2875
        self.Role = None
 
2876
 
 
2877
    def Parse(self, xmlText):
 
2878
        """
 
2879
        Parse multiple certificates into seperate files.
 
2880
        """
 
2881
        self.reinitialize()
 
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)
 
2888
                return None
 
2889
        node = dom.childNodes[0]
 
2890
        if node.localName != "CertificateFile":
 
2891
            Error("Certificates.Parse: root not CertificateFile")
 
2892
            return None
 
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.")
 
2901
            return self
 
2902
        # There may be multiple certificates in this package. Split them.
 
2903
        file = open("Certificates.pem")
 
2904
        pindex = 1
 
2905
        cindex = 1
 
2906
        output = open("temp.pem", "w")
 
2907
        for line in file.readlines():
 
2908
            output.write(line)
 
2909
            if re.match(r'[-]+END .*?(KEY|CERTIFICATE)[-]+$',line):
 
2910
                output.close()
 
2911
                if re.match(r'[-]+END .*?KEY[-]+$',line):
 
2912
                    os.rename("temp.pem", str(pindex) + ".prv")
 
2913
                    pindex += 1
 
2914
                else:
 
2915
                    os.rename("temp.pem", str(cindex) + ".crt")
 
2916
                    cindex += 1
 
2917
                output = open("temp.pem", "w")
 
2918
        output.close()
 
2919
        os.remove("temp.pem")
 
2920
        keys = dict()
 
2921
        index = 1
 
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')
 
2930
            index += 1
 
2931
            filename = str(index) + ".crt"
 
2932
        index = 1
 
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')
 
2939
            index += 1
 
2940
            filename = str(index) + ".prv"
 
2941
        return self
 
2942
 
 
2943
class SharedConfig(object):
 
2944
    """
 
2945
    Parse role endpoint server and goal state config.
 
2946
    """
 
2947
    #
 
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}" />
 
2952
    #   </Deployment>
 
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">
 
2956
    #     <Probes>
 
2957
    #       <Probe name="MachineRole" />
 
2958
    #       <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
 
2959
    #       <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
 
2960
    #     </Probes>
 
2961
    #   </LoadBalancerSettings>
 
2962
    #   <OutputEndpoints>
 
2963
    #     <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
 
2964
    #       <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
 
2965
    #     </Endpoint>
 
2966
    #   </OutputEndpoints>
 
2967
    #   <Instances>
 
2968
    #     <Instance id="MachineRole_IN_0" address="10.115.153.75">
 
2969
    #       <FaultDomains randomId="0" updateId="0" updateCount="0" />
 
2970
    #       <InputEndpoints>
 
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">
 
2972
    #           <LocalPorts>
 
2973
    #             <LocalPortRange from="80" to="80" />
 
2974
    #           </LocalPorts>
 
2975
    #         </Endpoint>
 
2976
    #         <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
 
2977
    #           <LocalPorts>
 
2978
    #             <LocalPortRange from="3389" to="3389" />
 
2979
    #           </LocalPorts>
 
2980
    #         </Endpoint>
 
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">
 
2982
    #           <LocalPorts>
 
2983
    #             <LocalPortRange from="20000" to="20000" />
 
2984
    #           </LocalPorts>
 
2985
    #         </Endpoint>
 
2986
    #       </InputEndpoints>
 
2987
    #     </Instance>
 
2988
    #   </Instances>
 
2989
    # </SharedConfig>
 
2990
    #
 
2991
    def __init__(self):
 
2992
        self.reinitialize()
 
2993
 
 
2994
    def reinitialize(self):
 
2995
        """
 
2996
        Reset members.
 
2997
        """
 
2998
        self.Deployment = None
 
2999
        self.Incarnation = None
 
3000
        self.Role = None
 
3001
        self.LoadBalancerSettings = None
 
3002
        self.OutputEndpoints = None
 
3003
        self.Instances = None
 
3004
 
 
3005
    def Parse(self, xmlText):
 
3006
        """
 
3007
        Parse and write configuration to file SharedConfig.xml.
 
3008
        """
 
3009
        self.reinitialize()
 
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)
 
3016
                return None
 
3017
        node = dom.childNodes[0]
 
3018
        if node.localName != "SharedConfig":
 
3019
            Error("SharedConfig.Parse: root not SharedConfig")
 
3020
            return None
 
3021
        program = Config.get("Role.TopologyConsumer")
 
3022
        if program != None:
 
3023
            try:
 
3024
                Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"]))
 
3025
            except OSError, e :
 
3026
                ErrorWithPrefix('Agent.Run','Exception: '+ str(e) +' occured launching ' + program )
 
3027
        return self
 
3028
 
 
3029
class ExtensionsConfig(object):
 
3030
    """
 
3031
    Parse ExtensionsConfig, downloading and unpacking them to /var/lib/waagent.
 
3032
    Install if <enabled>true</enabled>, remove if it is set to false.
 
3033
    """
 
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" />
 
3039
    #</Plugins>
 
3040
    #<PluginSettings>
 
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>
 
3048
    #  </Plugin>
 
3049
    #</PluginSettings>
 
3050
    #<StatusUploadBlob>https://ostcextensions.blob.core.test-cint.azure-test.net/vhds/eg-plugin7-vm.eg-plugin7-vm.eg-plugin7-vm.status?sr=b&amp;sp=rw&amp;
 
3051
    #se=9999-01-01&amp;sk=key1&amp;sv=2012-02-12&amp;sig=wRUIDN1x2GC06FWaetBP9sjjifOWvRzS2y2XBB4qoBU%3D</StatusUploadBlob></Extensions>
 
3052
 
 
3053
    def __init__(self):
 
3054
        self.reinitialize()
 
3055
 
 
3056
    def reinitialize(self):
 
3057
        """
 
3058
        Reset members.
 
3059
        """
 
3060
        self.Extensions = None
 
3061
        self.Plugins = None
 
3062
        self.Util = None
 
3063
        
 
3064
    def Parse(self, xmlText):
 
3065
        """
 
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:
 
3082
            call disableCommand
 
3083
        if state is uninstall:
 
3084
            call uninstallCommand
 
3085
            remove old plugin directory.
 
3086
        """
 
3087
        self.reinitialize()
 
3088
        self.Util=Util()
 
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)
 
3094
        try:
 
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)
 
3100
        except:
 
3101
            LogIfVerbose('ERROR:  Error parsing ExtensionsConfig.')
 
3102
            return None
 
3103
        for p in self.Plugins:
 
3104
            if len(p.getAttribute("location"))<1:  # this plugin is inside the PluginSettings
 
3105
                continue
 
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.")
 
3117
                continue
 
3118
            Log("Found Plugin: " + name + ' version: ' + version)
 
3119
            if p.getAttribute("state") == 'disabled' or p.getAttribute("state") == 'uninstall': 
 
3120
                #disable 
 
3121
                zip_dir=LibDir+"/" + name + '-' + version
 
3122
                mfile=None
 
3123
                for root, dirs, files in os.walk(zip_dir):
 
3124
                    for f in files:
 
3125
                        if f in ('HandlerManifest.json'):
 
3126
                            mfile=os.path.join(root,f)
 
3127
                    if mfile != None:
 
3128
                        break
 
3129
                if mfile == None :
 
3130
                    Error('HandlerManifest.json not found.')
 
3131
                    continue
 
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)
 
3138
                else :
 
3139
                    self.SetHandlerState(handler, 'Disabled')
 
3140
                    Log(name+' is disabled')
 
3141
                    SimpleLog(p.plugin_log,name+' is disabled')
 
3142
 
 
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)
 
3149
                    else :
 
3150
                        self.SetHandlerState(handler, 'NotInstalled')
 
3151
                        Log(name+' uninstallCommand completed .')
 
3152
                    # remove the plugin
 
3153
                    Run('rm -rf ' + LibDir + '/' + name +'-'+ version + '*')
 
3154
                    Log(name +'-'+ version + ' extension files deleted.')
 
3155
                    SimpleLog(p.plugin_log,name +'-'+ version + ' extension files deleted.')
 
3156
 
 
3157
                continue    
 
3158
            # state is enabled
 
3159
            # if the same plugin exists and the version is newer or
 
3160
            # does not exist then download and unzip the new plugin
 
3161
            plg_dir=None
 
3162
            for root, dirs, files in os.walk(LibDir):
 
3163
                for d in dirs:
 
3164
                    if name in d:
 
3165
                        plg_dir=os.path.join(root,d)
 
3166
                    if plg_dir != None:
 
3167
                        break
 
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)
 
3174
 
 
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)
 
3178
 
 
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)
 
3187
 
 
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)))
 
3194
 
 
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)
 
3203
                bundle_uri = ""
 
3204
                for mp in man_dom.getElementsByTagName("Plugin"):
 
3205
                    if GetNodeTextData(mp.getElementsByTagName("Version")[0]) == version:
 
3206
                        bundle_uri = GetNodeTextData(mp.getElementsByTagName("Uri")[0])
 
3207
                        break
 
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')
 
3213
                        continue
 
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)
 
3217
                    continue
 
3218
                Log("Bundle URI = " + bundle_uri)
 
3219
                SimpleLog(p.plugin_log,"Bundle URI = " + bundle_uri)
 
3220
 
 
3221
                # Download the zipfile archive and save as '.zip'
 
3222
                bundle=self.Util.HttpGetWithoutHeaders(bundle_uri)
 
3223
                if bundle == None:
 
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 )
 
3227
                    continue
 
3228
                AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download Success")
 
3229
                b=bytearray(bundle)
 
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)))
 
3234
 
 
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) 
 
3241
 
 
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.
 
3245
                mfile=None
 
3246
                for root, dirs, files in os.walk(zip_dir):
 
3247
                    for f in files:
 
3248
                        if f in ('HandlerManifest.json'):
 
3249
                            mfile=os.path.join(root,f)
 
3250
                    if mfile != None:
 
3251
                        break
 
3252
                if mfile == None :
 
3253
                    Error('HandlerManifest.json not found.')
 
3254
                    SimpleLog(p.plugin_log,'HandlerManifest.json not found.')
 
3255
                    continue
 
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.
 
3262
                config=''
 
3263
                seqNo='0'
 
3264
                if len(dom.getElementsByTagName("PluginSettings")) != 0 :
 
3265
                    pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin")
 
3266
                    for ps in pslist:
 
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)
 
3270
 
 
3271
                            config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0])
 
3272
                            seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo") 
 
3273
                            break
 
3274
                if config == '':
 
3275
                    Log("No RuntimeSettings for " + name + " V " + version)
 
3276
                    SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version)
 
3277
 
 
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')
 
3283
 
 
3284
                cmd = ''
 
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)
 
3295
                        else :
 
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')
 
3299
 
 
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
 
3306
                    else :
 
3307
                        Log('Update complete'+name+'-'+version)
 
3308
                        SimpleLog(p.plugin_log,'Update complete'+name+'-'+version)
 
3309
 
 
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
 
3316
                    else :
 
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)
 
3326
                    else :
 
3327
                        self.SetHandlerState(handler, 'Installed')
 
3328
                        Log('Installation completed for '+name+'-'+version)
 
3329
                        SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version)
 
3330
 
 
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
 
3334
            mfile=None
 
3335
            for root, dirs, files in os.walk(zip_dir):
 
3336
                for f in files:
 
3337
                    if f in ('HandlerManifest.json'):
 
3338
                        mfile=os.path.join(root,f)
 
3339
                if mfile != None:
 
3340
                    break
 
3341
            if mfile == None :
 
3342
                Error('HandlerManifest.json not found.')
 
3343
                SimpleLog(p.plugin_log,'HandlerManifest.json not found.')
 
3344
 
 
3345
                continue
 
3346
            manifest = GetFileContents(mfile)
 
3347
            p.setAttribute('manifestdata',manifest)
 
3348
            config=''
 
3349
            seqNo='0'
 
3350
            if len(dom.getElementsByTagName("PluginSettings")) != 0 :
 
3351
                try:
 
3352
                    pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin")
 
3353
                except:
 
3354
                    Error('Error parsing ExtensionsConfig.')
 
3355
                    SimpleLog(p.plugin_log,'Error parsing ExtensionsConfig.')
 
3356
 
 
3357
                    continue
 
3358
                for ps in pslist:
 
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)
 
3362
 
 
3363
                        config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0])
 
3364
                        seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo") 
 
3365
                        break
 
3366
            if config == '':
 
3367
                Error("No RuntimeSettings for " + name + " V " + version)
 
3368
                SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version)
 
3369
 
 
3370
            SetFileContents(root +"/config/" + seqNo +".settings",  config )
 
3371
 
 
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)
 
3378
 
 
3379
                else :
 
3380
                    self.SetHandlerState(handler, 'Installed')
 
3381
                    Log('Installation completed for '+name+'-'+version)
 
3382
                    SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version)
 
3383
 
 
3384
 
 
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)
 
3390
 
 
3391
                else :
 
3392
                    self.SetHandlerState(handler, 'Enabled')
 
3393
                    Log('Enable completed for '+name+'-'+version)
 
3394
                    SimpleLog(p.plugin_log,'Enable completed for '+name+'-'+version)
 
3395
 
 
3396
            # this plugin processing is complete
 
3397
            Log('Processing completed for '+name+'-'+version)
 
3398
            SimpleLog(p.plugin_log,'Processing completed for '+name+'-'+version)
 
3399
 
 
3400
        #end plugin processing loop
 
3401
        Log('Finished processing ExtensionsConfig.xml')
 
3402
        try:
 
3403
            SimpleLog(p.plugin_log,'Finished processing ExtensionsConfig.xml')
 
3404
        except:
 
3405
            pass
 
3406
        
 
3407
        return self
 
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,
 
3415
        }
 
3416
        isSuccess=True
 
3417
        start = datetime.datetime.now()
 
3418
        r=self.__launchCommandWithoutEventLog(plugin_log,name,version,command,prev_version)
 
3419
        if r==None:
 
3420
            isSuccess=False
 
3421
        Duration = int((datetime.datetime.now() - start).seconds)
 
3422
        if commandToEventOperation.get(command):
 
3423
            AddExtensionEvent(name,commandToEventOperation[command],isSuccess,Duration,version)
 
3424
        return r
 
3425
 
 
3426
    def __launchCommandWithoutEventLog(self,plugin_log,name,version,command,prev_version=None):
 
3427
        # get the manifest and read the command
 
3428
        mfile=None
 
3429
        zip_dir=LibDir+"/" + name + '-' + version
 
3430
        for root, dirs, files in os.walk(zip_dir):
 
3431
            for f in files:
 
3432
                if f in ('HandlerManifest.json'):
 
3433
                    mfile=os.path.join(root,f)
 
3434
            if mfile != None:
 
3435
                break
 
3436
        if mfile == None :
 
3437
            Error('HandlerManifest.json not found.')
 
3438
            SimpleLog(plugin_log,'HandlerManifest.json not found.')
 
3439
            
 
3440
            return None
 
3441
        manifest = GetFileContents(mfile)
 
3442
        try:
 
3443
            jsn = json.loads(manifest)
 
3444
        except:
 
3445
            Error('Error parsing HandlerManifest.json.')
 
3446
            SimpleLog(plugin_log,'Error parsing HandlerManifest.json.')
 
3447
 
 
3448
            return None
 
3449
        if type(jsn)==list:
 
3450
            jsn=jsn[0]
 
3451
        if jsn.has_key('handlerManifest') :
 
3452
            cmd = jsn['handlerManifest'][command]
 
3453
        else :
 
3454
            Error('Key handlerManifest not found.  Handler cannot be installed.')
 
3455
            SimpleLog(plugin_log,'Key handlerManifest not found.  Handler cannot be installed.')
 
3456
 
 
3457
        if len(cmd) == 0 :
 
3458
            Error('Unable to read ' + command )
 
3459
            SimpleLog(plugin_log,'Unable to read ' + command )
 
3460
 
 
3461
            return None
 
3462
 
 
3463
        # for update we send the path of the old installation
 
3464
        arg=''
 
3465
        if prev_version != None :
 
3466
            arg=' ' + LibDir+'/' + name + '-' + prev_version
 
3467
        dirpath=os.path.dirname(mfile)
 
3468
        LogIfVerbose('Command is '+ dirpath+'/'+ cmd)
 
3469
        # launch
 
3470
        pid=None
 
3471
        try:
 
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))
 
3476
 
 
3477
        pid = child.pid
 
3478
        if pid == None or pid < 1 :
 
3479
            ExtensionChildren.append((-1,root))
 
3480
            Error('Error launching ' + cmd + '.')
 
3481
            SimpleLog(plugin_log,'Error launching ' + cmd + '.')
 
3482
 
 
3483
        else :
 
3484
            ExtensionChildren.append((pid,root))
 
3485
            Log("Spawned "+ cmd + " PID " + str(pid))
 
3486
            SimpleLog(plugin_log,"Spawned "+ cmd + " PID " + str(pid))
 
3487
 
 
3488
 
 
3489
        # wait until install/upgrade is finished
 
3490
        timeout = 300 # 5 minutes
 
3491
        retry = timeout/5
 
3492
        while retry > 0 and child.poll() == None:
 
3493
            LogIfVerbose(cmd + ' still running with PID ' + str(pid))
 
3494
            time.sleep(5)
 
3495
            retry-=1
 
3496
        if retry==0:
 
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))
 
3499
 
 
3500
            os.kill(pid,9)
 
3501
            return None
 
3502
        code = child.wait()
 
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) + ')')
 
3506
 
 
3507
            return None
 
3508
        Log(command + ' completed.')
 
3509
        SimpleLog(plugin_log,command + ' completed.')
 
3510
 
 
3511
        return 0
 
3512
 
 
3513
    def ReportHandlerStatus(self):
 
3514
        """
 
3515
        Collect all status reports.
 
3516
        """
 
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" }, 
 
3523
        # "substatus": [
 
3524
        # { "name": "StdOut", "status": "success", "formattedMessage": { "lang": "en-US", "message": "Goodbye world!" }  }, 
 
3525
        # { "name": "StdErr", "status": "success", "formattedMessage": { "lang": "en-US", "message": "" } }
 
3526
        # ] 
 
3527
        # } } } }
 
3528
        # ]
 
3529
        #  }}
 
3530
 
 
3531
        try:
 
3532
            incarnation=self.Extensions[0].getAttribute("goalStateIncarnation")
 
3533
        except:
 
3534
            Error('Error parsing ExtensionsConfig.  Unable to send status reports')
 
3535
            return None
 
3536
        status=''
 
3537
        statuses=''
 
3538
        for p in self.Plugins:
 
3539
            if p.getAttribute("state") == 'uninstall' or p.getAttribute("restricted") == 'true' :
 
3540
                continue
 
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.")
 
3545
                continue
 
3546
            reportHeartbeat = False
 
3547
            if len(p.getAttribute("manifestdata"))<1:
 
3548
                Error("Failed to get manifestdata.")
 
3549
            else:
 
3550
                reportHeartbeat = json.loads(p.getAttribute("manifestdata"))[0]['handlerManifest']['reportHeartbeat']
 
3551
            if len(statuses)>0:
 
3552
                statuses+=','
 
3553
            statuses+=self.GenerateAggStatus(name, version, reportHeartbeat)
 
3554
        tstamp=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
 
3555
        #header
 
3556
        #agent state
 
3557
        if provisioned == False:
 
3558
            if provisionError == None :
 
3559
                agent_state='Provisioning'
 
3560
                agent_msg='Guest Agent is starting.'
 
3561
            else:
 
3562
                agent_state='Provisioning Error.'
 
3563
                agent_msg=provisionError
 
3564
        else:
 
3565
            agent_state='Ready'
 
3566
            agent_msg='GuestAgent is running and accepting new configurations.'
 
3567
            
 
3568
        status='{"version":"1.0","timestampUTC":"'+tstamp+'","aggregateStatus":{"guestAgentStatus":{"version":"'+GuestAgentVersion+'","status":"'+agent_state+'","formattedMessage":{"lang":"en-US","message":"'+agent_msg+'"}},"handlerAggregateStatus":['+statuses+']}}'
 
3569
        try:
 
3570
            uri=GetNodeTextData(self.Extensions[0].getElementsByTagName("StatusUploadBlob")[0]).replace('&amp;','&')
 
3571
        except:
 
3572
            Error('Error parsing ExtensionsConfig.  Unable to send status reports')
 
3573
            return None
 
3574
        self.Util.Endpoint=uri.split('/')[2]
 
3575
        self.Util.HttpPutBlockBlob(uri, status)
 
3576
        LogIfVerbose('Status report '+status+' sent to ' + uri)
 
3577
        return True
 
3578
 
 
3579
    def GetCurrentSequenceNumber(self, plugin_base_dir):
 
3580
        """
 
3581
        Get the settings file with biggest file number in config folder
 
3582
        """
 
3583
        config_dir = os.path.join(plugin_base_dir, 'config')
 
3584
        seq_no = 0
 
3585
        for subdir, dirs, files in os.walk(config_dir):
 
3586
            for file in files:
 
3587
                try:
 
3588
                    cur_seq_no = int(os.path.basename(file).split('.')[0])
 
3589
                    if cur_seq_no > seq_no:
 
3590
                        seq_no = cur_seq_no
 
3591
                except ValueError:
 
3592
                    continue
 
3593
        return str(seq_no)
 
3594
 
 
3595
 
 
3596
    def GenerateAggStatus(self, name, version, reportHeartbeat = False):
 
3597
        """
 
3598
        Generate the status which Azure can understand by the status and heartbeat reported by extension
 
3599
        """
 
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')
 
3604
 
 
3605
        handler_state_file = os.path.join(plugin_base_dir,  'config', 'HandlerState')
 
3606
        agg_state = 'NotReady'
 
3607
        handler_state = None
 
3608
        status_obj = None
 
3609
        status_code = None
 
3610
        formatted_message = None
 
3611
        localized_message = None
 
3612
 
 
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]
 
3617
        if reportHeartbeat:
 
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'
 
3622
                else:
 
3623
                    try:
 
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")
 
3629
                    except:
 
3630
                        Error("Incorrect heartbeat file. Ignore it. ")
 
3631
            else:
 
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
 
3636
            try:
 
3637
                status_obj = json.loads(GetFileContents(status_file))[0]
 
3638
                del status_obj["version"]
 
3639
            except:
 
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}}
 
3643
        if status_obj:
 
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
 
3654
    
 
3655
 
 
3656
    def SetHandlerState(self, handler, state=''):
 
3657
        zip_dir=LibDir+"/" + handler
 
3658
        mfile=None
 
3659
        for root, dirs, files in os.walk(zip_dir):
 
3660
            for f in files:
 
3661
                if f in ('HandlerManifest.json'):
 
3662
                    mfile=os.path.join(root,f)
 
3663
            if mfile != None:
 
3664
                break
 
3665
        if mfile == None :
 
3666
            Error('SetHandlerState(): HandlerManifest.json not found, cannot set HandlerState.')
 
3667
            return None
 
3668
        Log("SetHandlerState: "+handler+", "+state)
 
3669
        return SetFileContents(os.path.dirname(mfile)+'/config/HandlerState', state)
 
3670
 
 
3671
    def GetHandlerState(self, handler):
 
3672
        handlerState = GetFileContents(handler+'/config/HandlerState')
 
3673
        if (handlerState):
 
3674
            return handlerState.rstrip('\r\n')
 
3675
        else:
 
3676
            return 'NotInstalled'
 
3677
 
 
3678
 
 
3679
class HostingEnvironmentConfig(object):
 
3680
    """
 
3681
    Parse Hosting enviromnet config and store in
 
3682
    HostingEnvironmentConfig.xml
 
3683
    """
 
3684
    #
 
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}" />
 
3692
    #   </Deployment>
 
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="&lt;m role=&quot;MachineRole&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;MachineRole&quot;>&lt;e name=&quot;a&quot; />&lt;e name=&quot;b&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;/r>&lt;/m>" />
 
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>
 
3709
    #
 
3710
    def __init__(self):
 
3711
        self.reinitialize()
 
3712
 
 
3713
    def reinitialize(self):
 
3714
        """
 
3715
        Reset Members.
 
3716
        """
 
3717
        self.StoredCertificates = None
 
3718
        self.Deployment = None
 
3719
        self.Incarnation = None
 
3720
        self.Role = None
 
3721
        self.HostingEnvironmentSettings = None
 
3722
        self.ApplicationSettings = None
 
3723
        self.Certificates = None
 
3724
        self.ResourceReferences = None
 
3725
 
 
3726
    def Parse(self, xmlText):
 
3727
        """
 
3728
        Parse and create HostingEnvironmentConfig.xml.
 
3729
        """
 
3730
        self.reinitialize()
 
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)
 
3737
                return None
 
3738
        node = dom.childNodes[0]
 
3739
        if node.localName != "HostingEnvironmentConfig":
 
3740
            Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
 
3741
            return None
 
3742
        self.ApplicationSettings = dom.getElementsByTagName("Setting")
 
3743
        self.Certificates = dom.getElementsByTagName("StoredCertificate")
 
3744
        return self
 
3745
 
 
3746
    def DecryptPassword(self, e):
 
3747
        """
 
3748
        Return decrypted password.
 
3749
        """
 
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]
 
3757
 
 
3758
    def ActivateResourceDisk(self):
 
3759
        return MyDistro.ActivateResourceDisk()
 
3760
 
 
3761
    def Process(self):
 
3762
        """
 
3763
        Execute ActivateResourceDisk in separate thread.
 
3764
        Create the user account.
 
3765
        Launch ConfigurationConsumer if specified in the config.
 
3766
        """
 
3767
        no_thread = False
 
3768
        if DiskActivated == False:
 
3769
            for m in inspect.getmembers(MyDistro):
 
3770
                if 'ActivateResourceDiskNoThread' in m:
 
3771
                    no_thread = True
 
3772
                    break
 
3773
            if no_thread == True :   
 
3774
                MyDistro.ActivateResourceDiskNoThread()
 
3775
            else :
 
3776
                diskThread = threading.Thread(target = self.ActivateResourceDisk)
 
3777
                diskThread.start()
 
3778
        User = None
 
3779
        Pass = None
 
3780
        Expiration = None
 
3781
        Thumbprint = None
 
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)
 
3788
            else:
 
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")
 
3797
        if program != None:
 
3798
            try:
 
3799
                Children.append(subprocess.Popen([program, LibDir + "/HostingEnvironmentConfig.xml"]))
 
3800
            except OSError, e :
 
3801
                ErrorWithPrefix('HostingEnvironmentConfig.Process','Exception: '+ str(e) +' occured launching ' + program )
 
3802
 
 
3803
class GoalState(Util):
 
3804
    """
 
3805
    Primary container for all configuration except OvfXml.
 
3806
    Encapsulates http communication with endpoint server.
 
3807
    Initializes and populates:
 
3808
    self.HostingEnvironmentConfig
 
3809
    self.SharedConfig
 
3810
    self.ExtensionsConfig
 
3811
    self.Certificates
 
3812
    """
 
3813
    #
 
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>
 
3817
    #   <Machine>
 
3818
    #     <ExpectedState>Started</ExpectedState>
 
3819
    #     <LBProbePorts>
 
3820
    #       <Port>16001</Port>
 
3821
    #     </LBProbePorts>
 
3822
    #   </Machine>
 
3823
    #   <Container>
 
3824
    #     <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
 
3825
    #     <RoleInstanceList>
 
3826
    #       <RoleInstance>
 
3827
    #         <InstanceId>MachineRole_IN_0</InstanceId>
 
3828
    #         <State>Started</State>
 
3829
    #         <Configuration>
 
3830
    #           <HostingEnvironmentConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=hostingEnvironmentConfig&amp;incarnation=1</HostingEnvironmentConfig>
 
3831
    #           <SharedConfig>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&amp;type=sharedConfig&amp;incarnation=1</SharedConfig>
 
3832
    #           <Certificates>http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&amp;incarnation=1</Certificates>
 
3833
    #          <ExtensionsConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&amp;type=extensionsConfig&amp;incarnation=2</ExtensionsConfig>
 
3834
    #         <FullConfig>http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&amp;type=fullConfig&amp;incarnation=2</FullConfig>
 
3835
 
 
3836
    #         </Configuration>
 
3837
    #       </RoleInstance>
 
3838
    #     </RoleInstanceList>
 
3839
    #   </Container>
 
3840
    # </GoalState>
 
3841
    #
 
3842
    # There is only one Role for VM images.
 
3843
    #
 
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
 
3848
    #
 
3849
    def __init__(self, Agent):
 
3850
        self.Agent = Agent
 
3851
        self.Endpoint = Agent.Endpoint
 
3852
        self.TransportCert = Agent.TransportCert
 
3853
        self.reinitialize()
 
3854
 
 
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
 
3873
 
 
3874
    def Parse(self, xmlText):
 
3875
        """
 
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
 
3882
        """
 
3883
        self.reinitialize()
 
3884
        LogIfVerbose(xmlText)
 
3885
        node = xml.dom.minidom.parseString(xmlText).childNodes[0]
 
3886
        if node.localName != "GoalState":
 
3887
            Error("GoalState.Parse: root not GoalState")
 
3888
            return None
 
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":
 
3918
                                                    pass
 
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")
 
3944
            return None
 
3945
        if self.ExpectedState == None:
 
3946
            Error("GoalState.Parse: ExpectedState missing")
 
3947
            return None
 
3948
        if self.RoleInstanceId == None:
 
3949
            Error("GoalState.Parse: RoleInstanceId missing")
 
3950
            return None
 
3951
        if self.ContainerId == None:
 
3952
            Error("GoalState.Parse: ContainerId missing")
 
3953
            return None
 
3954
        SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
 
3955
        return self
 
3956
 
 
3957
    def Process(self):
 
3958
        """
 
3959
        Calls HostingEnvironmentConfig.Process()
 
3960
        """
 
3961
        self.HostingEnvironmentConfig.Process()
 
3962
        
 
3963
class OvfEnv(object):
 
3964
    """
 
3965
    Read, and process provisioning info from provisioning file OvfEnv.xml
 
3966
    """
 
3967
    #
 
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>
 
3978
    #        <SSH>
 
3979
    #          <PublicKeys>
 
3980
    #            <PublicKey>
 
3981
    #              <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
 
3982
    #              <Path>$HOME/UserName/.ssh/authorized_keys</Path>
 
3983
    #            </PublicKey>
 
3984
    #          </PublicKeys>
 
3985
    #          <KeyPairs>
 
3986
    #            <KeyPair>
 
3987
    #              <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
 
3988
    #              <Path>$HOME/UserName/.ssh/id_rsa</Path>
 
3989
    #            </KeyPair>
 
3990
    #          </KeyPairs>
 
3991
    #        </SSH>
 
3992
    #      </LinuxProvisioningConfigurationSet>
 
3993
    #    </wa:ProvisioningSection>
 
3994
    # </Environment>
 
3995
    #
 
3996
    def __init__(self):
 
3997
        self.reinitialize()
 
3998
 
 
3999
    def reinitialize(self):
 
4000
        """
 
4001
        Reset members.
 
4002
        """
 
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 = []
 
4015
 
 
4016
    def Parse(self, xmlText):
 
4017
        """
 
4018
        Parse xml tree, retreiving user and ssh key information.
 
4019
        Return self.
 
4020
        """
 
4021
        self.reinitialize()
 
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.")
 
4026
        section = None
 
4027
        newer = False
 
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:
 
4035
                        newer = True
 
4036
                    if major != self.MajorVersion:
 
4037
                        break
 
4038
                    if minor > self.MinorVersion:
 
4039
                        newer = True
 
4040
                    section = p
 
4041
        if newer == True:
 
4042
            Warn("Newer provisioning configuration detected. Please consider updating waagent.")
 
4043
        if section == None:
 
4044
            Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
 
4045
            return None
 
4046
        self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
 
4047
        self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
 
4048
        try:
 
4049
            self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
 
4050
        except:
 
4051
            pass
 
4052
        CDSection=None
 
4053
        try:
 
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')
 
4060
                else :
 
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))
 
4069
            fp = None
 
4070
            path = None
 
4071
            for c in pkey.childNodes:
 
4072
                if c.localName == "Fingerprint":
 
4073
                    fp = GetNodeTextData(c).upper()
 
4074
                    LogIfVerbose(fp)
 
4075
                if c.localName == "Path":
 
4076
                    path = GetNodeTextData(c)
 
4077
                    LogIfVerbose(path)
 
4078
            self.SshPublicKeys += [[fp, path]]
 
4079
        for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
 
4080
            fp = None
 
4081
            path = None
 
4082
            LogIfVerbose(repr(keyp))
 
4083
            for c in keyp.childNodes:
 
4084
                if c.localName == "Fingerprint":
 
4085
                    fp = GetNodeTextData(c).upper()
 
4086
                    LogIfVerbose(fp)
 
4087
                if c.localName == "Path":
 
4088
                    path = GetNodeTextData(c)
 
4089
                    LogIfVerbose(path)
 
4090
            self.SshKeyPairs += [[fp, path]]
 
4091
        return self
 
4092
 
 
4093
    def PrepareDir(self, filepath):
 
4094
        """
 
4095
        Create home dir for self.UserName
 
4096
        Change owner and return path.
 
4097
        """
 
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):
 
4102
            return None
 
4103
        dir = path.rsplit('/', 1)[0]
 
4104
        if dir != "":
 
4105
            CreateDir(dir, "root", 0700)
 
4106
            if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
 
4107
                ChangeOwner(dir, self.UserName)
 
4108
        return path
 
4109
 
 
4110
    def NumberToBytes(self, i):
 
4111
        """
 
4112
        Pack number into bytes.  Retun as string.
 
4113
        """
 
4114
        result = []
 
4115
        while i:
 
4116
            result.append(chr(i & 0xFF))
 
4117
            i >>= 8
 
4118
        result.reverse()
 
4119
        return ''.join(result)
 
4120
 
 
4121
    def BitsToString(self, a):
 
4122
        """
 
4123
        Return string representation of bits in a.
 
4124
        """
 
4125
        index=7
 
4126
        s = ""
 
4127
        c = 0
 
4128
        for bit in a:
 
4129
            c = c | (bit << index)
 
4130
            index = index - 1
 
4131
            if index == -1:
 
4132
                s = s + struct.pack('>B', c)
 
4133
                c = 0
 
4134
                index = 7
 
4135
        return s
 
4136
 
 
4137
    def OpensslToSsh(self, file):
 
4138
        """
 
4139
        Return base-64 encoded key appropriate for ssh.
 
4140
        """
 
4141
        from pyasn1.codec.der import decoder as der_decoder
 
4142
        try:
 
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]
 
4145
            n=k[0]
 
4146
            e=k[1]
 
4147
            keydata=""
 
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)
 
4153
            keydata += "\0"
 
4154
            keydata += self.NumberToBytes(n)
 
4155
        except Exception, e:
 
4156
            print("OpensslToSsh: Exception " + str(e))
 
4157
            return None
 
4158
        return "ssh-rsa " + base64.b64encode(keydata) + "\n"
 
4159
 
 
4160
    def Process(self):
 
4161
        """
 
4162
        Process all certificate and key info.
 
4163
        DisableSshPasswordAuthentication if configured.
 
4164
        CreateAccount(user)
 
4165
        Wait for WaAgent.EnvMonitor.IsHostnamePublished().
 
4166
        Restart ssh service.
 
4167
        """
 
4168
        error = None
 
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()
 
4185
        if sel :
 
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)."
 
4193
                continue
 
4194
            path = self.PrepareDir(pkey[1])
 
4195
            if path == None:
 
4196
                Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
 
4197
                error = "Invalid path for public key (0x03)."
 
4198
                continue
 
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)."
 
4210
                continue
 
4211
            path = self.PrepareDir(keyp[1])
 
4212
            if path == None:
 
4213
                Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
 
4214
                error = "Invalid path for key pair (0x05)."
 
4215
                continue
 
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)
 
4224
        if sel :
 
4225
            MyDistro.setSelinuxEnforce(1)
 
4226
        while not WaAgent.EnvMonitor.IsHostnamePublished():
 
4227
            time.sleep(1)
 
4228
        MyDistro.restartSshService()
 
4229
        return error
 
4230
 
 
4231
 
 
4232
class WALAEvent(object):   
 
4233
    def __init__(self):
 
4234
            
 
4235
        self.providerId=""
 
4236
        self.eventId=1
 
4237
        
 
4238
        self.OpcodeName=""
 
4239
        self.KeywordName=""
 
4240
        self.TaskName=""
 
4241
        self.TenantName=""
 
4242
        self.RoleName=""
 
4243
        self.RoleInstanceName=""
 
4244
        self.ContainerId=""
 
4245
        self.ExecutionMode="IAAS"
 
4246
        self.OSVersion=""
 
4247
        self.GAVersion=""
 
4248
        self.RAM=0
 
4249
        self.Processors=0
 
4250
 
 
4251
 
 
4252
    def ToXml(self):
 
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}" />'
 
4257
        strMtStr=u'mt:wstr'
 
4258
        strMtUInt64=u'mt:uint64'
 
4259
        strMtBool=u'mt:bool'
 
4260
        strMtFloat=u'mt:float64'
 
4261
        strEventsData=u""
 
4262
 
 
4263
        for attName in  self.__dict__:
 
4264
            if attName in ["eventId","filedCount","providerId"]:
 
4265
                continue
 
4266
            
 
4267
            attValue = self.__dict__[attName]
 
4268
            if type(attValue) is int:
 
4269
                strEventsData+=strRecordFormat.format(attName,attValue,strMtUInt64)
 
4270
                continue
 
4271
            if type(attValue) is str:
 
4272
                attValue = xml.sax.saxutils.quoteattr(attValue)                                 
 
4273
                strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
 
4274
                continue
 
4275
            if str(type(attValue)).count("'unicode'") >0 :
 
4276
                attValue = xml.sax.saxutils.quoteattr(attValue)                  
 
4277
                strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
 
4278
                continue
 
4279
            if type(attValue) is bool:
 
4280
                strEventsData+=strRecordFormat.format(attName,attValue,strMtBool)
 
4281
                continue
 
4282
            if type(attValue) is float:
 
4283
                strEventsData+=strRecordFormat.format(attName,attValue,strMtFloat)
 
4284
                continue
 
4285
            
 
4286
            Log("Warning: property "+attName+":"+str(type(attValue))+":type"+str(type(attValue))+"Can't convert to events data:"+":type not supported")
 
4287
 
 
4288
        return u"<Data>{0}{1}{2}</Data>".format(strProviderid,strEventid,strEventsData)
 
4289
 
 
4290
    def Save(self):
 
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")
 
4297
    
 
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")
 
4302
 
 
4303
 
 
4304
class WALAEventOperation:
 
4305
    HeartBeat="HeartBeat"
 
4306
    Provision = "Provision"
 
4307
    Install = "Install"
 
4308
    UnIsntall = "UnInstall"
 
4309
    Disable = "Disable"
 
4310
    Enable = "Enable"
 
4311
    Download = "Download"
 
4312
    Upgrade = "Upgrade"
 
4313
    Update = "Update"           
 
4314
 
 
4315
def AddExtensionEvent(name,op,isSuccess,duration=0,version="1.0",message="",type="",isInternal=False):
 
4316
    event = ExtensionEvent()
 
4317
    event.Name=name 
 
4318
    event.Version=version 
 
4319
    event.IsInternal=isInternal
 
4320
    event.Operation=op
 
4321
    event.OperationSuccess=isSuccess
 
4322
    event.Message=message 
 
4323
    event.Duration=duration
 
4324
    event.ExtensionType=type
 
4325
    try:
 
4326
        event.Save()
 
4327
    except:
 
4328
        Error("Error "+traceback.format_exc())
 
4329
        
 
4330
    
 
4331
class ExtensionEvent(WALAEvent):
 
4332
    def __init__(self):
 
4333
                
 
4334
        WALAEvent.__init__(self)
 
4335
        self.eventId=1
 
4336
        self.providerId="69B669B9-4AF8-4C50-BDC4-6006FA76E975"
 
4337
        self.Name=""
 
4338
        self.Version=""
 
4339
        self.IsInternal=False
 
4340
        self.Operation=""
 
4341
        self.OperationSuccess=True
 
4342
        self.ExtensionType=""
 
4343
        self.Message=""
 
4344
        self.Duration=0
 
4345
    
 
4346
                                   
 
4347
class WALAEventMonitor(WALAEvent):
 
4348
    def __init__(self,postMethod):
 
4349
        WALAEvent.__init__(self)
 
4350
        self.post = postMethod
 
4351
        self.sysInfo={}
 
4352
        self.eventdir = LibDir+"/events"
 
4353
        self.issysteminfoinitilized = False
 
4354
 
 
4355
    def StartEventsLoop(self):
 
4356
        eventThread = threading.Thread(target = self.EventsLoop)
 
4357
        eventThread.setDaemon(True)
 
4358
        eventThread.start()
 
4359
        
 
4360
    def EventsLoop(self):
 
4361
        LastReportHeartBeatTime = datetime.datetime.min
 
4362
        try:
 
4363
            while(True):
 
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()
 
4369
                time.sleep(60)
 
4370
        except:
 
4371
            Error("Exception in events loop:"+traceback.format_exc())
 
4372
                                                        
 
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)
 
4378
 
 
4379
    def CollectAndSendWALAEvents(self):        
 
4380
        if not os.path.exists(self.eventdir):
 
4381
            return
 
4382
        #Throtting, can't send more than 3 events in 15 seconds  
 
4383
        eventSendNumber=0
 
4384
        eventFiles = os.listdir(self.eventdir)
 
4385
        events = {}
 
4386
        for file in eventFiles:
 
4387
            if not file.endswith(".tld"):
 
4388
                continue      
 
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))
 
4393
            params=""
 
4394
            eventid=""
 
4395
            providerid=""
 
4396
            #if exception happen during process an event, catch it and continue
 
4397
            try:
 
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")
 
4406
            except:
 
4407
                Error(traceback.format_exc())
 
4408
                continue
 
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)
 
4411
                continue
 
4412
 
 
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:
 
4417
                eventSendNumber+=1
 
4418
                self.SendEvent(providerid,events.get(providerid))
 
4419
                if eventSendNumber %3 ==0:
 
4420
                    time.sleep(15)
 
4421
                events[providerid]=""
 
4422
            if len(eventstr) >= 63*1024:
 
4423
                Error("Signle event too large abort "+eventstr[:300])
 
4424
                continue
 
4425
 
 
4426
            events[providerid]=events.get(providerid)+eventstr
 
4427
 
 
4428
        for key in events.keys():
 
4429
            if len(events[key]) > 0:
 
4430
                eventSendNumber+=1
 
4431
                self.SendEvent(key,events[key])
 
4432
                if eventSendNumber%3 == 0:
 
4433
                    time.sleep(15)
 
4434
                
 
4435
 
 
4436
    def AddSystemInfo(self,eventData):
 
4437
        if not self.issysteminfoinitilized:
 
4438
            self.issysteminfoinitilized=True
 
4439
            try:
 
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
 
4452
            except:
 
4453
                Error(traceback.format_exc())
 
4454
 
 
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])))
 
4461
 
 
4462
        return  eventObject.toxml()            
 
4463
 
 
4464
 
 
4465
class Agent(Util):
 
4466
    """
 
4467
    Primary object container for the provisioning process.
 
4468
    
 
4469
    """
 
4470
    def __init__(self):
 
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
 
4479
 
 
4480
    def CheckVersions(self):
 
4481
        """
 
4482
        Query endpoint server for wire protocol version.
 
4483
        Fail if our desired protocol version is not seen.
 
4484
        """
 
4485
        #<?xml version="1.0" encoding="utf-8"?>
 
4486
        #<Versions>
 
4487
        #  <Preferred>
 
4488
        #    <Version>2010-12-15</Version>
 
4489
        #  </Preferred>
 
4490
        #  <Supported>
 
4491
        #    <Version>2010-12-15</Version>
 
4492
        #    <Version>2010-28-10</Version>
 
4493
        #  </Supported>
 
4494
        #</Versions>
 
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")
 
4500
            return False
 
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.")
 
4514
        else:
 
4515
            Log("Negotiated wire protocol version: " + ProtocolVersion)
 
4516
        return True
 
4517
 
 
4518
    def Unpack(self, buffer, offset, range):
 
4519
        """
 
4520
        Unpack bytes into python values.
 
4521
        """
 
4522
        result = 0
 
4523
        for i in range:
 
4524
            result = (result << 8) | Ord(buffer[offset + i])
 
4525
        return result
 
4526
 
 
4527
    def UnpackLittleEndian(self, buffer, offset, length):
 
4528
        """
 
4529
        Unpack little endian bytes into python values.
 
4530
        """
 
4531
        return self.Unpack(buffer, offset, list(range(length - 1, -1, -1)))
 
4532
 
 
4533
    def UnpackBigEndian(self, buffer, offset, length):
 
4534
        """
 
4535
        Unpack big endian bytes into python values.
 
4536
        """
 
4537
        return self.Unpack(buffer, offset, list(range(0, length)))
 
4538
 
 
4539
    def HexDump3(self, buffer, offset, length):
 
4540
        """
 
4541
        Dump range of buffer in formatted hex.
 
4542
        """
 
4543
        return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
 
4544
 
 
4545
    def HexDump2(self, buffer):
 
4546
        """
 
4547
        Dump buffer in formatted hex.
 
4548
        """
 
4549
        return self.HexDump3(buffer, 0, len(buffer))
 
4550
 
 
4551
    def BuildDhcpRequest(self):
 
4552
        """
 
4553
        Build DHCP request string.
 
4554
        """
 
4555
        #
 
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 */
 
4574
        #
 
4575
        #     UINT8 MessageTypeCode;              /* 53 */
 
4576
        #     UINT8 MessageTypeLength;            /* 1 */
 
4577
        #     UINT8 MessageType;                  /* 1 for DISCOVER */
 
4578
        #     UINT8 End;                          /* 255 */
 
4579
        # } DHCP;
 
4580
        #
 
4581
 
 
4582
        # tuple of 244 zeros
 
4583
        # (struct.pack_into would be good here, but requires Python 2.5)
 
4584
        sendData = [0] * 244
 
4585
 
 
4586
        transactionID = os.urandom(4)
 
4587
        macAddress = MyDistro.GetMacAddress()
 
4588
 
 
4589
        # Opcode = 1
 
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]
 
4594
 
 
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])
 
4598
 
 
4599
        LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
 
4600
 
 
4601
        # fill in ClientHardwareAddress
 
4602
        for a in range(0, 6):
 
4603
            sendData[0x1C + a] = Ord(macAddress[a])
 
4604
 
 
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)
 
4613
 
 
4614
    def IntegerToIpAddressV4String(self, a):
 
4615
        """
 
4616
        Build DHCP request string.
 
4617
        """
 
4618
        return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
 
4619
 
 
4620
    def RouteAdd(self, net, mask, gateway):
 
4621
        """
 
4622
        Add specified route using /sbin/route add -net.
 
4623
        """
 
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)
 
4628
 
 
4629
    def HandleDhcpResponse(self, sendData, receiveBuffer):
 
4630
        """
 
4631
        Parse DHCP response:
 
4632
        Set default gateway.
 
4633
        Set default routes.
 
4634
        Retrieve endpoint server.
 
4635
        Returns endpoint server or None on error.
 
4636
        """
 
4637
        LogIfVerbose("HandleDhcpResponse")
 
4638
        bytesReceived = len(receiveBuffer)
 
4639
        if bytesReceived < 0xF6:
 
4640
            Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
 
4641
            return None
 
4642
 
 
4643
        LogIfVerbose("BytesReceived: " + hex(bytesReceived))
 
4644
        LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
 
4645
 
 
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
 
4649
 
 
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")
 
4662
                    return None
 
4663
        endpoint = None
 
4664
 
 
4665
        #
 
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.
 
4670
        #
 
4671
 
 
4672
        i = 0xF0 # offset to first option
 
4673
        while i < bytesReceived:
 
4674
            option = Ord(receiveBuffer[i])
 
4675
            length = 0
 
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))
 
4679
            if option == 255:
 
4680
                LogIfVerbose("DHCP packet ended at offset " + hex(i))
 
4681
                break
 
4682
            elif option == 249:
 
4683
                # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
 
4684
                LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
 
4685
                if length < 5:
 
4686
                    Error("Data too small for option " + str(option))
 
4687
                j = i + 2
 
4688
                while j < (i + length + 2):
 
4689
                    maskLengthBits = Ord(receiveBuffer[j])
 
4690
                    maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
 
4691
                    mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
 
4692
                    j += 1
 
4693
                    net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
 
4694
                    net <<= (32 - maskLengthBytes * 8)
 
4695
                    net &= mask
 
4696
                    j += maskLengthBytes
 
4697
                    gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
 
4698
                    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:
 
4704
                    if length != 4:
 
4705
                        Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
 
4706
                        return None
 
4707
                    gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
 
4708
                    IpAddress = self.IntegerToIpAddressV4String(gateway)
 
4709
                    if option == 3:
 
4710
                        self.RouteAdd(0, 0, gateway)
 
4711
                        name = "DefaultGateway"
 
4712
                    else:
 
4713
                        endpoint = IpAddress
 
4714
                        name = "Windows Azure wire protocol endpoint"
 
4715
                    LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
 
4716
                else:
 
4717
                    Error("HandleDhcpResponse: Data too small for option " + str(option))
 
4718
            else:
 
4719
                LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
 
4720
            i += length + 2
 
4721
        return endpoint
 
4722
 
 
4723
    def DoDhcpWork(self):
 
4724
        """
 
4725
        Discover the wire server via DHCP option 245.
 
4726
        And workaround incompatibility with Windows Azure DHCP servers.
 
4727
        """
 
4728
        ShortSleep = False # Sleep 1 second before retrying DHCP queries.
 
4729
        ifname=None
 
4730
 
 
4731
        sleepDurations = [0, 10, 30, 60, 60]
 
4732
        maxRetry = len(sleepDurations)
 
4733
        lastTry = (maxRetry - 1)
 
4734
        for retry in range(0, maxRetry):
 
4735
            try:
 
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
 
4748
                try:
 
4749
                    if DistInfo()[0] == 'FreeBSD':
 
4750
                        missingDefaultRoute = True
 
4751
                    else:
 
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
 
4756
                except:
 
4757
                    pass
 
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)
 
4765
                    else:
 
4766
                        Run("route add 255.255.255.255 dev " + ifname,chk_err=False)
 
4767
                if MyDistro.isDHCPEnabled():
 
4768
                    MyDistro.stopDHCP()
 
4769
                sock.bind(("0.0.0.0", 68)) 
 
4770
                sock.sendto(sendData, ("<broadcast>", 67))
 
4771
                sock.settimeout(10)
 
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)
 
4783
                    return endpoint
 
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())
 
4790
            finally:
 
4791
                sock.close()
 
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)
 
4797
                    else:
 
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()
 
4801
        return None
 
4802
 
 
4803
    def UpdateAndPublishHostName(self, name):
 
4804
        """
 
4805
        Set hostname locally and publish to iDNS
 
4806
        """
 
4807
        Log("Setting host name: " + name)
 
4808
        MyDistro.publishHostname(name)
 
4809
        ethernetInterface = MyDistro.GetInterfaceName()
 
4810
        MyDistro.RestartInterface(ethernetInterface)
 
4811
        self.RestoreRoutes()
 
4812
 
 
4813
    def RestoreRoutes(self):
 
4814
        """
 
4815
        If there is a DHCP response, then call HandleDhcpResponse.
 
4816
        """
 
4817
        if self.SendData != None and self.DhcpResponse != None:
 
4818
            self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
 
4819
 
 
4820
    def UpdateGoalState(self):
 
4821
        """
 
4822
        Retreive goal state information from endpoint server.
 
4823
        Parse xml and initialize Agent.GoalState object.
 
4824
        Return object or None on error.
 
4825
        """
 
4826
        goalStateXml = None
 
4827
        maxRetry = 9
 
4828
        log = NoLog
 
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:
 
4834
                break
 
4835
            log = Log
 
4836
            time.sleep(retry)
 
4837
        if not goalStateXml:
 
4838
            Error("UpdateGoalState failed.")
 
4839
            return
 
4840
        Log("Retrieved GoalState from Windows Azure Fabric.")
 
4841
        self.GoalState = GoalState(self).Parse(goalStateXml)
 
4842
        return self.GoalState
 
4843
 
 
4844
    def ReportReady(self):
 
4845
        """
 
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.
 
4849
        """
 
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)
 
4860
        if a != None:
 
4861
            return a.getheader("x-ms-latest-goal-state-incarnation-number")
 
4862
        return None
 
4863
 
 
4864
    def ReportNotReady(self, status, desc):
 
4865
        """
 
4866
        Send health report 'Provisioning' to server.
 
4867
        This signals the fabric that our provosion is starting.
 
4868
        """
 
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)
 
4879
        if a != None:
 
4880
            return a.getheader("x-ms-latest-goal-state-incarnation-number")
 
4881
        return None
 
4882
 
 
4883
    def ReportRoleProperties(self, thumbprint):
 
4884
        """
 
4885
        Send roleProperties and thumbprint to server. 
 
4886
        """
 
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)
 
4895
        return a
 
4896
 
 
4897
    def LoadBalancerProbeServer_Shutdown(self):
 
4898
        """
 
4899
        Shutdown the LoadBalancerProbeServer.
 
4900
        """
 
4901
        if self.LoadBalancerProbeServer != None:
 
4902
            self.LoadBalancerProbeServer.shutdown()
 
4903
            self.LoadBalancerProbeServer = None
 
4904
 
 
4905
    def GenerateTransportCert(self):
 
4906
        """
 
4907
        Create ssl certificate for https communication with endpoint server.
 
4908
        """
 
4909
        Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
 
4910
        cert = ""
 
4911
        for line in GetFileContents("TransportCert.pem").split('\n'):
 
4912
            if not "CERTIFICATE" in line:
 
4913
                cert += line.rstrip()
 
4914
        return cert
 
4915
 
 
4916
    def DoVmmStartup(self):
 
4917
        """
 
4918
        Spawn the VMM startup script.
 
4919
        """
 
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
 
4922
        time.sleep(5)
 
4923
        sys.exit(0)
 
4924
        
 
4925
    def TryUnloadAtapiix(self):
 
4926
        """
 
4927
        If global modloaded is True, then we loaded the ata_piix kernel module, unload it.
 
4928
        """
 
4929
        if modloaded:
 
4930
            Run("rmmod ata_piix.ko",chk_err=False)
 
4931
            Log("Unloaded ata_piix.ko driver for ATAPI CD-ROM")
 
4932
 
 
4933
    def TryLoadAtapiix(self):
 
4934
        """
 
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.
 
4938
        """
 
4939
        global modloaded
 
4940
        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.")
 
4945
            return 0
 
4946
        if retcode:
 
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)
 
4951
        else:
 
4952
            Log("Module " + krn_pth + " driver for ATAPI CD-ROM does not exist.")
 
4953
            return 1
 
4954
        if retcode != 0:
 
4955
            Error('Error calling insmod for '+ krn_pth + ' driver for ATAPI CD-ROM')
 
4956
            return retcode
 
4957
        time.sleep(1)
 
4958
        # check 3 times if the mod is loaded
 
4959
        for i in range(3):
 
4960
            if Run('lsmod | grep ata_piix'):
 
4961
                continue
 
4962
            else :
 
4963
                modloaded=True
 
4964
                break
 
4965
        if not modloaded:
 
4966
            Error('Unable to load '+ krn_pth + ' driver for ATAPI CD-ROM')
 
4967
            return 1
 
4968
        
 
4969
        Log("Loaded " + krn_pth + " driver for ATAPI CD-ROM")
 
4970
        
 
4971
        # we have succeeded loading the ata_piix mod if it can be done.
 
4972
 
 
4973
    def SearchForVMMStartup(self):
 
4974
        """
 
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.
 
4977
 
 
4978
        If VMM_CONFIG_FILE_NAME is found, call DoVmmStartup.
 
4979
        Else, return to Azure Provisioning process.
 
4980
        """
 
4981
        self.TryLoadAtapiix()
 
4982
        if os.path.exists('/mnt/cdrom/secure') == False:
 
4983
            CreateDir("/mnt/cdrom/secure", "root", 0700)
 
4984
        mounted=False
 
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/')]:
 
4986
            if dvds == None:
 
4987
                continue
 
4988
            dvd = '/dev/'+dvds.group(0)
 
4989
            if Run("LC_ALL=C fdisk -l " + dvd + " | grep Disk",chk_err=False):
 
4990
                continue  # Not mountable
 
4991
            else:
 
4992
                for retry in range(1,6):
 
4993
                    retcode,output=RunGetOutput("mount -v " + dvd + " /mnt/cdrom/secure")
 
4994
                    Log(output[:-1])
 
4995
                    if retcode == 0:
 
4996
                        Log("mount succeeded on attempt #" + str(retry) )
 
4997
                        mounted=True
 
4998
                        break
 
4999
                    if 'is already mounted on /mnt/cdrom/secure' in output:
 
5000
                        Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
 
5001
                        mounted=True
 
5002
                        break
 
5003
                    Log("mount failed on attempt #" + str(retry) )
 
5004
                    Log("mount loop sleeping 5...")
 
5005
                    time.sleep(5)
 
5006
                if not mounted:
 
5007
                    # unable to mount
 
5008
                    continue
 
5009
                if not os.path.isfile("/mnt/cdrom/secure/"+VMM_CONFIG_FILE_NAME):
 
5010
                    #nope - mount the next drive
 
5011
                    if mounted:
 
5012
                        Run("umount "+dvd,chk_err=False)
 
5013
                        mounted=False
 
5014
                        continue
 
5015
                else : # it is the vmm startup
 
5016
                    self.DoVmmStartup()
 
5017
 
 
5018
        Log("VMM Init script not found.  Provisioning for Azure")
 
5019
        return 
 
5020
        
 
5021
    def Provision(self):
 
5022
        """
 
5023
        Responible for:
 
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.
 
5030
        """
 
5031
        enabled = Config.get("Provisioning.Enabled")
 
5032
        if enabled != None and enabled.lower().startswith("n"):
 
5033
            return
 
5034
        Log("Provisioning image started.")
 
5035
        type = Config.get("Provisioning.SshHostKeyPairType")
 
5036
        if type == None:
 
5037
            type = "rsa"
 
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", "")
 
5044
        dvd = None
 
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/')]:
 
5046
            if dvds == None :
 
5047
                continue
 
5048
            dvd = '/dev/'+dvds.group(0)
 
5049
        if dvd == None:
 
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()
 
5055
            if out:
 
5056
                return out
 
5057
            for i in range(10): # we may have to wait 
 
5058
                if os.path.exists(dvd):
 
5059
                    break
 
5060
                Log("Waiting for DVD - sleeping 1 - "+str(i+1)+" try...")
 
5061
                time.sleep(1)
 
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)
 
5068
            Log(output[:-1])
 
5069
            if retcode == 0:
 
5070
                Log("mount succeeded on attempt #" + str(retry) )
 
5071
                break
 
5072
            if 'is already mounted on /mnt/cdrom/secure' in output:
 
5073
                Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
 
5074
                break
 
5075
            Log("mount failed on attempt #" + str(retry) )
 
5076
            Log("mount loop sleeping 5...")
 
5077
            time.sleep(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()
 
5089
        error = None
 
5090
        if ovfxml != None:
 
5091
            Log("Provisioning image using OVF settings in the DVD.")
 
5092
            ovfobj = OvfEnv().Parse(ovfxml)
 
5093
            if ovfobj != None:
 
5094
                error = ovfobj.Process()
 
5095
                if error :
 
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.")
 
5106
        return error
 
5107
 
 
5108
    def Run(self):
 
5109
        """
 
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.
 
5113
        
 
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)
 
5127
        """
 
5128
        SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
 
5129
 
 
5130
        # Determine if we are in VMM.  Spawn VMM_STARTUP_SCRIPT_NAME if found.
 
5131
        self.SearchForVMMStartup()
 
5132
        ipv4=''
 
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.")
 
5137
                time.sleep(10)
 
5138
 
 
5139
        Log("IPv4 address: " + ipv4)
 
5140
        mac=''
 
5141
        mac=MyDistro.GetMacAddress()
 
5142
        if len(mac)>0 :
 
5143
            Log("MAC  address: " + ":".join(["%02X" % Ord(a) for a in mac]))
 
5144
        
 
5145
        # Consume Entropy in ACPI table provided by Hyper-V
 
5146
        try:
 
5147
            SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
 
5148
        except:
 
5149
            pass
 
5150
 
 
5151
        Log("Probing for Windows Azure environment.")
 
5152
        self.Endpoint = self.DoDhcpWork()
 
5153
 
 
5154
        if self.Endpoint == None:
 
5155
            Log("Windows Azure environment not detected.")
 
5156
            while True:
 
5157
                time.sleep(60)
 
5158
 
 
5159
        Log("Discovered Windows Azure endpoint: " + self.Endpoint)
 
5160
        if not self.CheckVersions():
 
5161
            Error("Agent.CheckVersions failed")
 
5162
            sys.exit(1)
 
5163
 
 
5164
        # Set SCSI timeout on SCSI disks
 
5165
        MyDistro.initScsiDiskTimeout()
 
5166
        global provisioned
 
5167
        global provisionError
 
5168
        
 
5169
        global Openssl
 
5170
        Openssl = Config.get("OS.OpensslPath")
 
5171
        if Openssl == None:
 
5172
            Openssl = "openssl"
 
5173
 
 
5174
        self.TransportCert = self.GenerateTransportCert()
 
5175
        
 
5176
        eventMonitor = None
 
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
 
5187
        while True:
 
5188
            if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
 
5189
                goalState = self.UpdateGoalState()
 
5190
                if goalState == None :
 
5191
                    continue
 
5192
                if provisioned == False:
 
5193
                    self.ReportNotReady("Provisioning", "Starting")
 
5194
 
 
5195
                goalState.Process()
 
5196
 
 
5197
                if provisioned == False:
 
5198
                    provisionError = self.Provision()
 
5199
                    if provisionError == None :
 
5200
                        provisioned = True
 
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))
 
5206
                        
 
5207
                #
 
5208
                # only one port supported
 
5209
                # restart server if new port is different than old port
 
5210
                # stop server if no longer a port
 
5211
                #
 
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.")
 
5221
 
 
5222
                # Report SSH key fingerprint
 
5223
                type = Config.get("Provisioning.SshHostKeyPairType")
 
5224
                if type == None:
 
5225
                    type = "rsa"
 
5226
 
 
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)
 
5231
 
 
5232
            if program != None and DiskActivated == True:
 
5233
                try:
 
5234
                    Children.append(subprocess.Popen([program, "Ready"]))
 
5235
                except OSError, e :
 
5236
                    ErrorWithPrefix('SharedConfig.Parse','Exception: '+ str(e) +' occured launching ' + program )
 
5237
                program = None
 
5238
 
 
5239
            sleepToReduceAccessDenied = 3
 
5240
            time.sleep(sleepToReduceAccessDenied)
 
5241
            if provisionError != None:
 
5242
                incarnation = self.ReportNotReady("ProvisioningFailed", provisionError)
 
5243
            else:
 
5244
                incarnation = self.ReportReady()
 
5245
            # Process our extensions.
 
5246
            if goalState.ExtensionsConfig == None and goalState.ExtensionsConfigXml != None :
 
5247
                goalState.ExtensionsConfig = ExtensionsConfig().Parse(goalState.ExtensionsConfigXml)
 
5248
 
 
5249
            # report the status/heartbeat results of extension processing
 
5250
            if goalState.ExtensionsConfig != None :
 
5251
                goalState.ExtensionsConfig.ReportHandlerStatus()
 
5252
            
 
5253
            if not eventMonitor:
 
5254
                eventMonitor = WALAEventMonitor(self.HttpPost)
 
5255
                eventMonitor.StartEventsLoop()
 
5256
 
 
5257
            time.sleep(25 - sleepToReduceAccessDenied)  
 
5258
 
 
5259
            
 
5260
WaagentLogrotate = """\
 
5261
/var/log/waagent.log {
 
5262
    monthly
 
5263
    rotate 6
 
5264
    notifempty
 
5265
    missingok
 
5266
}
 
5267
"""
 
5268
 
 
5269
def GetMountPoint(mountlist, device):
 
5270
    """
 
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)
 
5279
    """
 
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
 
5286
    return None
 
5287
 
 
5288
def FindInLinuxKernelCmdline(option):
 
5289
    """
 
5290
    Return match object if 'option' is present in the kernel boot options
 
5291
    of the grub configuration.
 
5292
    """
 
5293
    m=None
 
5294
    matchs=r'^.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?'+option+r'.*$'
 
5295
    try:
 
5296
        m=FindStringInFile(MyDistro.grubKernelBootOptionsFile,matchs)
 
5297
    except IOError, e:
 
5298
        Error('FindInLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
 
5299
        
 
5300
    return m
 
5301
 
 
5302
def AppendToLinuxKernelCmdline(option):
 
5303
    """
 
5304
    Add 'option' to the kernel boot options of the grub configuration.
 
5305
    """
 
5306
    if not FindInLinuxKernelCmdline(option):
 
5307
        src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r')(.*?)("?)$'
 
5308
        rep=r'\1\2 '+ option + r'\3'
 
5309
        try:
 
5310
            ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
 
5311
        except IOError, e :
 
5312
            Error('AppendToLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
 
5313
            return 1
 
5314
        Run("update-grub",chk_err=False)    
 
5315
    return 0
 
5316
 
 
5317
def RemoveFromLinuxKernelCmdline(option):
 
5318
    """
 
5319
    Remove 'option' to the kernel boot options of the grub configuration.
 
5320
    """
 
5321
    if FindInLinuxKernelCmdline(option):
 
5322
        src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?)('+option+r')(.*?)("?)$'
 
5323
        rep=r'\1\3\4'
 
5324
        try:
 
5325
            ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
 
5326
        except IOError, e :
 
5327
            Error('RemoveFromLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
 
5328
            return 1
 
5329
        Run("update-grub",chk_err=False)    
 
5330
    return 0
 
5331
 
 
5332
def FindStringInFile(fname,matchs):
 
5333
    """
 
5334
    Return match object if found in file.
 
5335
    """
 
5336
    try:
 
5337
        ms=re.compile(matchs)
 
5338
        for l in (open(fname,'r')).readlines():
 
5339
            m=re.search(ms,l)
 
5340
            if m:
 
5341
                return m
 
5342
    except:
 
5343
        raise
 
5344
    
 
5345
    return None
 
5346
 
 
5347
def ReplaceStringInFile(fname,src,repl):
 
5348
    """
 
5349
    Replace 'src' with 'repl' in file.
 
5350
    """
 
5351
    updated=''
 
5352
    try:
 
5353
        sr=re.compile(src)
 
5354
        if FindStringInFile(fname,src):
 
5355
            for l in (open(fname,'r')).readlines():
 
5356
                n=re.sub(sr,repl,l)
 
5357
                updated+=n
 
5358
        ReplaceFileContentsAtomic(fname,updated)
 
5359
    except :
 
5360
        raise
 
5361
    return
 
5362
 
 
5363
def ApplyVNUMAWorkaround():
 
5364
    """
 
5365
    If kernel version has NUMA bug, add 'numa=off' to
 
5366
    kernel boot options.
 
5367
    """
 
5368
    VersionParts = platform.release().replace('-', '.').split('.')
 
5369
    if int(VersionParts[0]) > 2:
 
5370
        return
 
5371
    if int(VersionParts[1]) > 6:
 
5372
        return
 
5373
    if int(VersionParts[2]) > 37:
 
5374
        return
 
5375
    if AppendToLinuxKernelCmdline("numa=off") == 0 :
 
5376
        Log("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
 
5377
    else :
 
5378
        "Error adding 'numa=off'.  NUMA has not been disabled."
 
5379
        
 
5380
def RevertVNUMAWorkaround():
 
5381
    """
 
5382
    Remove 'numa=off' from kernel boot options.
 
5383
    """
 
5384
    if RemoveFromLinuxKernelCmdline("numa=off") == 0 :
 
5385
        Log('NUMA has been re-enabled')
 
5386
    else :
 
5387
        Log('NUMA has not been re-enabled')
 
5388
 
 
5389
def Install():
 
5390
    """
 
5391
    Install the agent service.
 
5392
    Check dependencies.
 
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()
 
5399
    """
 
5400
    if MyDistro.checkDependencies():
 
5401
        return 1
 
5402
    os.chmod(sys.argv[0], 0755)
 
5403
    SwitchCwd()
 
5404
    for a in RulesFiles:
 
5405
        if os.path.isfile(a):
 
5406
            if os.path.isfile(GetLastPathElement(a)):
 
5407
                os.remove(GetLastPathElement(a))
 
5408
            shutil.move(a, ".")
 
5409
            Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
 
5410
    MyDistro.registerAgentService()
 
5411
    if os.path.isfile("/etc/waagent.conf"):
 
5412
        try:
 
5413
            os.remove("/etc/waagent.conf.old")
 
5414
        except:
 
5415
            pass
 
5416
        try:
 
5417
            os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
 
5418
            Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
 
5419
        except:
 
5420
            pass
 
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()
 
5429
    return 0
 
5430
 
 
5431
def GetMyDistro(dist_class_name=''):
 
5432
    """
 
5433
    Return MyDistro object.
 
5434
    NOTE: Logging is not initialized at this point.
 
5435
    """
 
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'
 
5445
    else:
 
5446
        Distro=dist_class_name
 
5447
    if not globals().has_key(dist_class_name):
 
5448
        print Distro+' is not a supported distribution.'
 
5449
        return None
 
5450
    return globals()[dist_class_name]() # the distro class inside this module.
 
5451
 
 
5452
def DistInfo(fullname=0):
 
5453
    if 'FreeBSD' in platform.system():
 
5454
        release = re.sub('\-.*\Z', '', str(platform.release()))
 
5455
        distinfo = ['FreeBSD', release]
 
5456
        return distinfo
 
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
 
5460
        return distinfo
 
5461
    else:
 
5462
        return platform.dist()
 
5463
 
 
5464
def PackagedInstall(buildroot):
 
5465
    """
 
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.
 
5471
    """
 
5472
    MyDistro=GetMyDistro()
 
5473
    if MyDistro == None :
 
5474
        sys.exit(1)
 
5475
    MyDistro.packagedInstall(buildroot)
 
5476
 
 
5477
def LibraryInstall(buildroot):
 
5478
    pass
 
5479
 
 
5480
def Uninstall():
 
5481
    """
 
5482
    Uninstall the agent service.
 
5483
    Copy RulesFiles back to original locations.
 
5484
    Delete agent-related files.
 
5485
    Call RevertVNUMAWorkaround().
 
5486
    """
 
5487
    SwitchCwd()
 
5488
    for a in RulesFiles:
 
5489
        if os.path.isfile(GetLastPathElement(a)):
 
5490
            try:
 
5491
                shutil.move(GetLastPathElement(a), a)
 
5492
                Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
 
5493
            except:
 
5494
                pass
 
5495
    MyDistro.unregisterAgentService()
 
5496
    MyDistro.uninstallDeleteFiles()
 
5497
    RevertVNUMAWorkaround()
 
5498
    return 0
 
5499
 
 
5500
def Deprovision(force, deluser):
 
5501
    """
 
5502
    Remove user accounts created by provisioning.
 
5503
    Disables root password if Provisioning.DeleteRootPassword = 'y'
 
5504
    Stop agent service.
 
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.
 
5508
    """
 
5509
    
 
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")
 
5512
 
 
5513
    SwitchCwd()
 
5514
    ovfxml = GetFileContents(LibDir+"/ovf-env.xml")
 
5515
    ovfobj = None
 
5516
    if ovfxml != None:
 
5517
        ovfobj = OvfEnv().Parse(ovfxml)
 
5518
 
 
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.")
 
5526
 
 
5527
    if ovfobj != None and deluser == True:
 
5528
        print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
 
5529
 
 
5530
    if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
 
5531
        return 1
 
5532
 
 
5533
    MyDistro.stopAgentService()
 
5534
    if deluser == True:
 
5535
        MyDistro.DeleteAccount(ovfobj.UserName)
 
5536
 
 
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*")
 
5541
 
 
5542
    # Remove root password
 
5543
    if delRootPass != None and delRootPass.lower().startswith("y"):
 
5544
        MyDistro.deleteRootPassword()
 
5545
    # Remove distribution specific networking configuration
 
5546
 
 
5547
    MyDistro.publishHostname('localhost.localdomain')
 
5548
    MyDistro.deprovisionDeleteFiles()
 
5549
    return 0
 
5550
 
 
5551
def SwitchCwd():
 
5552
    """
 
5553
    Switch to cwd to /var/lib/waagent.
 
5554
    Create if not present.
 
5555
    """
 
5556
    CreateDir(LibDir, "root", 0700)
 
5557
    os.chdir(LibDir)
 
5558
 
 
5559
def Usage():
 
5560
    """
 
5561
    Print the arguments to waagent.
 
5562
    """
 
5563
    print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
 
5564
    return 0
 
5565
 
 
5566
 
 
5567
 
 
5568
def main():
 
5569
    """
 
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.
 
5575
    """
 
5576
    if GuestAgentVersion == "":
 
5577
        print("WARNING! This is a non-standard agent that does not include a valid version string.")
 
5578
    
 
5579
    if len(sys.argv) == 1:
 
5580
        sys.exit(Usage())
 
5581
 
 
5582
    LoggerInit('/var/log/waagent.log','/dev/console')
 
5583
    global LinuxDistro
 
5584
    LinuxDistro=DistInfo()[0]
 
5585
    global MyDistro
 
5586
    MyDistro=GetMyDistro()
 
5587
    if MyDistro == None :
 
5588
        sys.exit(1)
 
5589
    args = []
 
5590
    global force
 
5591
    force = False
 
5592
    for a in sys.argv[1:]:
 
5593
        if re.match("^([-/]*)(help|usage|\?)", a):
 
5594
            sys.exit(Usage())
 
5595
        elif re.match("^([-/]*)verbose", a):
 
5596
            myLogger.verbose = True
 
5597
        elif re.match("^([-/]*)force", a):
 
5598
            force = True
 
5599
        elif re.match("^([-/]*)(setup|install)", a):
 
5600
            sys.exit(MyDistro.Install())
 
5601
        elif re.match("^([-/]*)(uninstall)", a):
 
5602
            sys.exit(Uninstall())
 
5603
        else:
 
5604
            args.append(a)
 
5605
    global Config
 
5606
    Config = ConfigurationProvider()
 
5607
    
 
5608
    verbose = Config.get("Logs.Verbose")
 
5609
    if verbose != None and verbose.lower().startswith("y"):
 
5610
        myLogger.verbose=True
 
5611
    global daemon
 
5612
    daemon = False
 
5613
    for a in args:
 
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):
 
5619
            daemon = True
 
5620
        elif re.match("^([-/]*)version", a):
 
5621
            print(GuestAgentVersion + " running on " + LinuxDistro)
 
5622
            sys.exit(0)
 
5623
        elif re.match("^([-/]*)serialconsole", a):
 
5624
            AppendToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
 
5625
            Log("Configured kernel to use ttyS0 as the boot console.")
 
5626
            sys.exit(0)
 
5627
        else:
 
5628
            print("Invalid command line parameter:" + a)
 
5629
            sys.exit(1)
 
5630
    
 
5631
    if daemon == False:
 
5632
        sys.exit(Usage())
 
5633
    global modloaded
 
5634
    modloaded = False
 
5635
    try:
 
5636
        SwitchCwd()
 
5637
        Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
 
5638
        if IsLinux():
 
5639
            Log("Linux Distribution Detected      : " + LinuxDistro)
 
5640
        global WaAgent
 
5641
        WaAgent = Agent()
 
5642
        WaAgent.Run()
 
5643
    except Exception, e:
 
5644
        Error(traceback.format_exc())
 
5645
        Error("Exception: " + str(e))
 
5646
        sys.exit(1)
 
5647