summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes63
-rw-r--r--.gitignore62
-rw-r--r--.travis.yml14
-rw-r--r--Changelog154
-rw-r--r--Dockerfile10
-rw-r--r--MANIFEST17
-rw-r--r--MANIFEST.in3
-rw-r--r--README40
-rw-r--r--__main__.py20
-rw-r--r--azurelinuxagent/__init__.py17
-rw-r--r--azurelinuxagent/agent.py141
-rw-r--r--azurelinuxagent/conf.py109
-rw-r--r--azurelinuxagent/distro/__init__.py19
-rw-r--r--azurelinuxagent/distro/centos/__init__.py19
-rw-r--r--azurelinuxagent/distro/centos/loader.py25
-rw-r--r--azurelinuxagent/distro/coreos/__init__.py18
-rw-r--r--azurelinuxagent/distro/coreos/deprovision.py30
-rw-r--r--azurelinuxagent/distro/coreos/handlerFactory.py27
-rw-r--r--azurelinuxagent/distro/coreos/loader.py28
-rw-r--r--azurelinuxagent/distro/coreos/osutil.py90
-rw-r--r--azurelinuxagent/distro/debian/__init__.py19
-rw-r--r--azurelinuxagent/distro/debian/loader.py24
-rw-r--r--azurelinuxagent/distro/debian/osutil.py47
-rw-r--r--azurelinuxagent/distro/default/__init__.py19
-rw-r--r--azurelinuxagent/distro/default/deprovision.py117
-rw-r--r--azurelinuxagent/distro/default/dhcp.py330
-rw-r--r--azurelinuxagent/distro/default/env.py115
-rw-r--r--azurelinuxagent/distro/default/extension.py647
-rw-r--r--azurelinuxagent/distro/default/handlerFactory.py40
-rw-r--r--azurelinuxagent/distro/default/init.py49
-rw-r--r--azurelinuxagent/distro/default/loader.py28
-rw-r--r--azurelinuxagent/distro/default/osutil.py657
-rw-r--r--azurelinuxagent/distro/default/provision.py165
-rw-r--r--azurelinuxagent/distro/default/resourceDisk.py166
-rw-r--r--azurelinuxagent/distro/default/run.py86
-rw-r--r--azurelinuxagent/distro/default/scvmm.py47
-rw-r--r--azurelinuxagent/distro/loader.py46
-rw-r--r--azurelinuxagent/distro/oracle/__init__.py19
-rw-r--r--azurelinuxagent/distro/oracle/loader.py25
-rw-r--r--azurelinuxagent/distro/redhat/__init__.py19
-rw-r--r--azurelinuxagent/distro/redhat/loader.py28
-rw-r--r--azurelinuxagent/distro/redhat/osutil.py148
-rw-r--r--azurelinuxagent/distro/suse/__init__.py19
-rw-r--r--azurelinuxagent/distro/suse/loader.py29
-rw-r--r--azurelinuxagent/distro/suse/osutil.py88
-rw-r--r--azurelinuxagent/distro/ubuntu/__init__.py19
-rw-r--r--azurelinuxagent/distro/ubuntu/deprovision.py43
-rw-r--r--azurelinuxagent/distro/ubuntu/handlerFactory.py29
-rw-r--r--azurelinuxagent/distro/ubuntu/loader.py36
-rw-r--r--azurelinuxagent/distro/ubuntu/osutil.py65
-rw-r--r--azurelinuxagent/distro/ubuntu/provision.py72
-rw-r--r--azurelinuxagent/event.py188
-rw-r--r--azurelinuxagent/exception.py65
-rw-r--r--azurelinuxagent/future.py19
-rw-r--r--azurelinuxagent/handler.py28
-rw-r--r--azurelinuxagent/logger.py158
-rw-r--r--azurelinuxagent/metadata.py93
-rw-r--r--azurelinuxagent/protocol/__init__.py23
-rw-r--r--azurelinuxagent/protocol/common.py245
-rw-r--r--azurelinuxagent/protocol/ovfenv.py146
-rw-r--r--azurelinuxagent/protocol/protocolFactory.py114
-rw-r--r--azurelinuxagent/protocol/v1.py964
-rw-r--r--azurelinuxagent/protocol/v2.py122
-rw-r--r--azurelinuxagent/utils/__init__.py19
-rw-r--r--azurelinuxagent/utils/fileutil.py186
-rw-r--r--azurelinuxagent/utils/osutil.py27
-rw-r--r--azurelinuxagent/utils/restutil.py154
-rw-r--r--azurelinuxagent/utils/shellutil.py85
-rw-r--r--azurelinuxagent/utils/textutil.py228
-rwxr-xr-xbin/waagent49
-rwxr-xr-x[-rw-r--r--]bin/waagent2.0 (renamed from waagent)15
-rw-r--r--config/99-azure-product-uuid.rules9
-rw-r--r--config/suse/waagent.conf (renamed from config/docker-waagent.conf)23
-rw-r--r--config/ubuntu/waagent.conf70
-rw-r--r--config/waagent.conf17
-rw-r--r--debian/changelog8
-rw-r--r--debian/control4
-rw-r--r--debian/install1
-rw-r--r--debian/patches/build_info.txt4
-rw-r--r--debian/patches/disable-provisioning.patch11
-rw-r--r--debian/patches/disable-udev-rules-removal.patch12
-rw-r--r--debian/patches/disable-udev-rules.patch12
-rw-r--r--debian/patches/disable_provisioning.patch55
-rw-r--r--debian/patches/fixup_setup_file.patch11
-rw-r--r--debian/patches/series8
-rw-r--r--debian/patches/start-after-cloudinit.patch12
-rwxr-xr-xdebian/rules5
l---------debian/walinuxagent.service2
-rw-r--r--distro/suse/waagent.sysV112
-rwxr-xr-xget-agent.py69
-rw-r--r--init/coreos/cloud-config.yml52
-rw-r--r--init/ubuntu/walinuxagent2
-rw-r--r--init/ubuntu/walinuxagent.conf26
-rwxr-xr-xinit/ubuntu/walinuxagent.service15
-rwxr-xr-x[-rw-r--r--]init/waagent (renamed from distro/redhat/waagent.sysV)11
-rwxr-xr-x[-rw-r--r--]init/waagent.service (renamed from distro/systemd/waagent.service)3
-rw-r--r--rpm/README45
-rw-r--r--rpm/post-inst1
-rw-r--r--rpm/walinuxagent.spec118
-rw-r--r--script/buildrpm.sh31
-rwxr-xr-xsetup.py310
-rwxr-xr-xtests/azure_test.py687
-rw-r--r--tests/dhcpbin0 -> 328 bytes
-rw-r--r--tests/env.py13
-rwxr-xr-xtests/run_all.sh35
-rw-r--r--tests/sshd_config90
-rw-r--r--tests/test.crt17
-rw-r--r--tests/test.prv15
-rw-r--r--tests/test_agent.py45
-rw-r--r--tests/test_certificates.py198
-rw-r--r--tests/test_conf.py69
-rw-r--r--tests/test_deprovision.py54
-rw-r--r--tests/test_dhcp.py68
-rw-r--r--tests/test_distroLoader.py42
-rw-r--r--tests/test_envmon.py52
-rw-r--r--tests/test_event.py53
-rw-r--r--tests/test_ext.py178
-rw-r--r--tests/test_extensionsconfig.py159
-rw-r--r--tests/test_file_util.py75
-rw-r--r--tests/test_future.py (renamed from tests/part-gpt.py)28
-rw-r--r--tests/test_goalstate.py71
-rw-r--r--tests/test_hostingenv.py66
-rw-r--r--tests/test_http.py147
-rw-r--r--tests/test_import_waagent.py (renamed from tests/upload_status_blob.py)39
-rw-r--r--tests/test_logger.py87
-rw-r--r--tests/test_metadata.py (renamed from fix-gpt-ubuntu.py)35
-rw-r--r--tests/test_osutil.py174
-rw-r--r--tests/test_ovfxml.py81
-rw-r--r--tests/test_protocol.py88
-rw-r--r--tests/test_protocolFactory.py37
-rw-r--r--tests/test_redhat.py49
-rw-r--r--tests/test_resourcedisk.py63
-rw-r--r--tests/test_rest_util.py63
-rw-r--r--tests/test_shared_config.py148
-rw-r--r--tests/test_sharedconfig.py79
-rw-r--r--tests/test_shell_util.py39
-rw-r--r--tests/test_text_util.py47
-rw-r--r--tests/test_util.py61
-rw-r--r--tests/test_utils.py83
-rw-r--r--tests/test_v1.py173
-rw-r--r--tests/test_version.py53
-rwxr-xr-xtests/test_waagent.py386
-rw-r--r--tests/tools.py20
143 files changed, 9640 insertions, 2461 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
index 7ff8793..db1491b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,62 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
waagentc
-*.py[cod]*
+*.pyproj
+*.sln
+*.suo
-tests/status_blob_url.py
+waagentc
+bin/waagent2.0c
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..853d545
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: python
+python:
+ - "2.6"
+ - "2.7"
+ #- "3.2"
+ #- "3.3"
+ - "3.4"
+# command to install dependencies
+install:
+ #- pip install .
+ #- pip install -r requirements.txt
+ - pip install pyasn1
+# command to run tests
+script: nosetests tests
diff --git a/Changelog b/Changelog
index 6706892..bc79449 100644
--- a/Changelog
+++ b/Changelog
@@ -1,150 +1,12 @@
WALinuxAgent Changelog
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
-1 Jul 2015, WALinuxAgent 2.0.14
- . Fix rdma config
- . Fix page blob uploading for python 2.6
- . Fix http request error handling
+07 Aug 2015, WALinuxAgent 2.1.1
+ . Support python3
+ . Fixed bugs for metadata protocol
+ . Fixed a few pylint warnings
+ . Enabled travis-ci
-1 Jun 2015, WALinuxAgent 2.0.13
- . Handle http 410 returned by host
- . Add support for http proxy
- . Add support to execute CustomData after provisioning
- . Add a udev rule for product-uuid
- . Fix agent path for CoreOS
- . Update service start/stop command for Ubuntu
-
-15 Jan 2015, WALinuxAgent 2.0.12
- . Add support for page blob status report
-
-11 Dec 2014, WALinuxAgent 2.0.11
- . Add support for GPT(Guid Partition Table)
-
-27 Nov 2014, WALinuxAgent 2.0.10
- . Multiple fixes for openSUSE-13.2+
- . Multiple fixes for FreeBSD
-
-06 Nov 2014, WALinuxAgent 2.0.9
- . Add support for CoreOS
- . Add support for Fedora
- . Add diagnostic service
- . Fix FreeBSD deprovisioning issue
- . Fix encoding issue for logger
-
-25 Aug 2014, WALinuxAgent 2.0.8
- . Fix for extension timeout handling
-
-08 Aug 2014, WALinuxAgent 2.0.7
- . Add warning for resource disk data loss
- . Multiple fixes for provisioning and extension handler
-
-13 Jun 2014, WALinuxAgent 2.0.6
- . Add support for Gentoo
- . Multiple fixes for extension handler in status report and heartbeat
-
-19 May 2014, WALinuxAgent 2.0.5
- . Multiple fixes for FreeBSD provisioning
- . Add support for SLES 12
- . Multiple fixes for extension handler framework
- . New logging support
- . Add state handling for each extension
- . Properly handle non-JSON extensions
- . Several other bugfixes
- . Replace platform.* calls with DistInfo() function
- . Inherit from redhatDistro in centosDistro class
- . Fix hostname configuration for RHEL7-based systems
- . EnvMonitor - Set SCSI I/O timeout for all attached disks
-
-02 Apr 2014, WALinuxAgent 2.0.4
- . Fix encoding issue in LogToFile() & LogToCon()
- . Add support for parsing ExtensionsConfiguration from GoalState document
- . Add support for the Fedora distribution
- . Several fixes to FreeBSDDistro class
-
-16 Jan 2014, WALinuxAgent 2.0.3
- . Add exception awareness to GetFileContents, SetFileContents, and
- AppendFileContents
- . Fix publishHostname() - leave dhclient.conf alone if it is already
- configured to send the system's current hostname to the DHCP server.
-
-18 Dec 2013, WALinuxAgent 2.0.2
- . Fix UpdateAndPublishHostName() to use correct interface name
- . Specialize file mode of /etc/shadow when clearing the root password
- . Fix publishHostname() to use self.hostname_file_path
- . Remove reference to VM shutdown on "stopped" state. This behavior was an
- artifact from pre-GA IaaS VMs on Windows Azure.
- . Revert to logging non-verbose by default
- . Revert to no swap setup by default (same as 1.x behavior)
-
-05 Nov 2013, WALinuxAgent 2.0.1
- . Add support for CustomData
- . Add exception handling for external consumer scripts
- . Ensure correct unicode encoding for ovf-env.xml
- . Add self.service_cmd for distro compatibility
- . Fix DeleteAccount() with -deprovision+user
- . Save/Restore SELinux state during provision
-
-23 Sep 2013, WALinuxAgent 2.0.0
- . Support for wire protocol 2012-11-30
- . Added support for Python3 via 2to3
- . Added support for new Linux distributions and FreeBSD (see README)
- . Source is now importable as a python module
- . Code refactor to ease task of adding/testing new Linux distributions and
- improve code readability
-
-23 Aug 2013, WALinuxAgent 1.4.0
- . Add support for bootstrapping VMM agent for Linux when running in SCVMM
- 2012R2 environments (see README)
- . Add setup.py to assist in package creation
- . Fix DVD detection for non-en locales
-
-30 May 2013, WALinuxAgent 1.3.3
- . Improve pyasn1 python module import
- . Improve wire protocol version checking, remove superfluous warning
- . Include support for walking /sys/bus/vmbus/devices for older distributions
- . Include support for creation of sudoers.d for older distributions
- . Remove old references to IsWindows() (used for testing)
- . Fix agent exit if GoalState=None
- . Fix unhandled socket exception (Util.HttpPost/Util._HttpGet)
- . Agent verbose log lines are missing in log when 'verbose' option is used
-
-26 Feb 2013, WALinuxAgent 1.3.2
- . Fix name error in _HttpGet/HttpPost exception handlers.
-
-15 Feb 2013, WALinuxAgent 1.3.1
- . Merge RPM packaging information.
- . Capture all system command output if an error has occurred. Normalization
- of shell commands on python subprocess module.
- . Duplicate non-verbose log output to /dev/console. This to support serial
- logging from boot when console=/dev/ttyS0 is set in the kernel boot options.
- . Merge Ubuntu packaging.
- . Fixed typo in DVD mounting procedure, thanks Ante.
-
-18 Jan 2013, WALinuxAgent 1.3
- . Add some error checking and robustness to DVD mounting operation during
- provisioning
- . Remove redundant check for IP and Port in LoadBalancerProbe
- . Add check to self.computername to detect empty hostname in configuration
- . Fix manual uninstall on Ubuntu
-
-07 Dec 2012, WALinuxAgent 1.2
- . Add Feature - ata_piix.ko module loaded if needed for CDROM device support
- . Added Init_Ubuntu upstart support and improved resolvconf support on Ubuntu
- . Additional logging for DoDhcpWork()
- . Update sock.recv timeout from 30 to 10 seconds
- . Fix 572301 - Linux waagent: deprovision, user is not deleted properly
- . Fix 578109 - Make LBProbeResponder construction more robust
- . Fix 575725 - Agent fails to provision user with public/private key pairs
- . Fix 573304 - DHCP broadcast response not received
- . Fix 576901 - Linux agent fails to delete root user password
- . Fix 577000 - Linux agent should report error messages to Fabric when
- passed an invalid hostname.
+|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+01 Jul 2015, WALinuxAgent 2.1.0
+ . Divide waagent into different modules
-09 Nov 2012, WALinuxAgent 1.1
- . Added sock.settimeout in DoDhcpWork() to properly timeout sock.recv
- . Added missingDefaultRoute to handle routing issues when DHCP responses not
- handled properly
- . Added Children.append to avoid zombies
- . Fixed ifrenew for compatibility
- . Fixed shadow password file for correct SELinux context
- . Minor cleanup work
- . Added Changelog :)
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index bc917e7..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM debian:jessie
-RUN apt-get update && apt-get upgrade --no-install-recommends -y
-RUN apt-get install -y --no-install-recommends \
- openssl ca-certificates ssh parted sudo net-tools ifupdown python python-pyasn1 python-rpm
-COPY waagent /usr/sbin/
-COPY config/docker-waagent.conf /etc/waagent.conf
-RUN chmod +x /usr/sbin/waagent && \
- rm -rf /etc/skel && \
- ln -sf /dev/stdout /var/log/waagent.log
-ENTRYPOINT ["/usr/sbin/waagent"]
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..00cc635
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,17 @@
+# file GENERATED by distutils, do NOT edit
+README
+setup.py
+bin/waagent
+config/waagent.conf
+config/waagent.logrotate
+test/test_logger.py
+walinuxagent/__init__.py
+walinuxagent/agent.py
+walinuxagent/conf.py
+walinuxagent/envmonitor.py
+walinuxagent/extension.py
+walinuxagent/install.py
+walinuxagent/logger.py
+walinuxagent/protocol.py
+walinuxagent/provision.py
+walinuxagent/util.py
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..c7e32c0
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+recursive-include bin *
+recursive-include init *
+recursive-include config *
diff --git a/README b/README
index b1c97e5..d5d24b5 100644
--- a/README
+++ b/README
@@ -87,23 +87,24 @@ Installation via your distribution's package repository is preferred.
You can also customize your own RPM or DEB packages using the configuration
files provided (see debian/README and rpm/README).
-If installing manually, waagent should be copied to /usr/sbin/waagent and
-installed by running:
+For more advanced installation options, such as installing to custom locations
+or prefixes, you can use setuptools to install from source by running:
+
+ #sudo python setup.py install --register-service
- # sudo chmod 755 /usr/sbin/waagent
- # sudo /usr/sbin/waagent -install -verbose
+You can view more installation options by running:
-The agent's log file is kept at /var/log/waagent.log.
+ #sudo python setup.py install --help
+The agent's log file is kept at /var/log/waagent.log.
UPGRADE
Upgrading via your distribution's package repository is preferred.
-If upgrading manually, same with installation above, waagent should be copied
-to /usr/sbin/waagent to override original file and installed by running:
+If upgrading manually, same with installation above by running:
- # sudo chmod 755 /usr/sbin/waagent
+ #sudo python setup.py install -force
Restart waagent service,for most of linux distributions:
@@ -128,10 +129,6 @@ Flags:
-force: Skip interactive confirmation for some commands
-Options:
-
- -conf=/path/to/file.conf: Specify configuration file to use instead of default one.
-
Commands:
-help: Lists the supported commands and flags.
@@ -181,6 +178,7 @@ Commands:
-daemon: Run waagent as a daemon to manage interaction with the platform.
This argument is specified to waagent in the waagent init script.
+ -start: Run waagent as a background process
CONFIGURATION
@@ -207,8 +205,6 @@ ResourceDisk.MountPoint=/mnt/resource
ResourceDisk.EnableSwap=n
ResourceDisk.SwapSizeMB=0
LBProbeResponder=y
-Logs.File=/var/log/waagent.log
-Logs.Console=y
Logs.Verbose=n
OS.RootDeviceScsiTimeout=300
OS.OpensslPath=None
@@ -343,23 +339,11 @@ Type: Boolean Default: y
If set, waagent will respond to load balancer probes from the platform (if
present).
-Logs.File:
-Type: String Default: /dev/stdout
-
-If set, logs are appended to this file (instead of stdout). Bundled
-config (config/waagent.conf) sets this to '/var/log/waagent.log'.
-
-Logs.Console:
-Type: Boolean Default: n
-
-If set, waagent also logs to serial console. Bundled config (config/waagent.conf)
-sets this to 'y'.
-
Logs.Verbose:
Type: Boolean Default: n
-If set, log verbosity is boosted. When waagent logs to file it can
-leverage the system logrotate functionality to rotate logs.
+If set, log verbosity is boosted. Waagent logs to /var/log/waagent.log and
+leverages the system logrotate functionality to rotate logs.
OS.RootDeviceScsiTimeout:
Type: Integer Default: 300
diff --git a/__main__.py b/__main__.py
new file mode 100644
index 0000000..fe61ae7
--- /dev/null
+++ b/__main__.py
@@ -0,0 +1,20 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.agent as agent
+
+agent.main()
diff --git a/azurelinuxagent/__init__.py b/azurelinuxagent/__init__.py
new file mode 100644
index 0000000..1ea2f38
--- /dev/null
+++ b/azurelinuxagent/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/agent.py b/azurelinuxagent/agent.py
new file mode 100644
index 0000000..5e61a6c
--- /dev/null
+++ b/azurelinuxagent/agent.py
@@ -0,0 +1,141 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+"""
+Module agent
+"""
+
+import os
+import sys
+import re
+import subprocess
+from azurelinuxagent.metadata import AGENT_NAME, AGENT_LONG_VERSION, \
+ DISTRO_NAME, DISTRO_VERSION, \
+ PY_VERSION_MAJOR, PY_VERSION_MINOR, \
+ PY_VERSION_MICRO
+from azurelinuxagent.utils.osutil import OSUTIL
+from azurelinuxagent.handler import HANDLERS
+
+
+def init(verbose):
+ """
+ Initialize agent running environment.
+ """
+ HANDLERS.init_handler.init(verbose)
+
+def run():
+ """
+ Run agent daemon
+ """
+ HANDLERS.main_handler.run()
+
+def deprovision(force=False, deluser=False):
+ """
+ Run deprovision command
+ """
+ HANDLERS.deprovision_handler.deprovision(force=force, deluser=deluser)
+
+def parse_args(sys_args):
+ """
+ Parse command line arguments
+ """
+ cmd = "help"
+ force = False
+ verbose = False
+ for a in sys_args:
+ if re.match("^([-/]*)deprovision\\+user", a):
+ cmd = "deprovision+user"
+ elif re.match("^([-/]*)deprovision", a):
+ cmd = "deprovision"
+ elif re.match("^([-/]*)daemon", a):
+ cmd = "daemon"
+ elif re.match("^([-/]*)start", a):
+ cmd = "start"
+ elif re.match("^([-/]*)register-service", a):
+ cmd = "register-service"
+ elif re.match("^([-/]*)version", a):
+ cmd = "version"
+ elif re.match("^([-/]*)verbose", a):
+ verbose = True
+ elif re.match("^([-/]*)force", a):
+ force = True
+ elif re.match("^([-/]*)(help|usage|\\?)", a):
+ cmd = "help"
+ else:
+ cmd = "help"
+ break
+ return cmd, force, verbose
+
+def version():
+ """
+ Show agent version
+ """
+ print(("{0} running on {1} {2}".format(AGENT_LONG_VERSION, DISTRO_NAME,
+ DISTRO_VERSION)))
+ print("Python: {0}.{1}.{2}".format(PY_VERSION_MAJOR, PY_VERSION_MINOR,
+ PY_VERSION_MICRO))
+def usage():
+ """
+ Show agent usage
+ """
+ print("")
+ print((("usage: {0} [-verbose] [-force] [-help]"
+ "-deprovision[+user]|-register-service|-version|-daemon|-start]"
+ "").format(sys.argv[0])))
+ print("")
+
+def start():
+ """
+ Start agent daemon in a background process and set stdout/stderr to
+ /dev/null
+ """
+ devnull = open(os.devnull, 'w')
+ subprocess.Popen([sys.argv[0], '-daemon'], stdout=devnull, stderr=devnull)
+
+def register_service():
+ """
+ Register agent as a service
+ """
+ print("Register {0} service".format(AGENT_NAME))
+ OSUTIL.register_agent_service()
+ print("Start {0} service".format(AGENT_NAME))
+ OSUTIL.start_agent_service()
+
+def main():
+ """
+ Parse command line arguments, exit with usage() on error.
+ Invoke different methods according to different command
+ """
+ command, force, verbose = parse_args(sys.argv[1:])
+ if command == "version":
+ version()
+ elif command == "help":
+ usage()
+ else:
+ init(verbose)
+ if command == "deprovision+user":
+ deprovision(force, deluser=True)
+ elif command == "deprovision":
+ deprovision(force, deluser=False)
+ elif command == "start":
+ start()
+ elif command == "register-service":
+ register_service()
+ elif command == "daemon":
+ run()
diff --git a/azurelinuxagent/conf.py b/azurelinuxagent/conf.py
new file mode 100644
index 0000000..3185d99
--- /dev/null
+++ b/azurelinuxagent/conf.py
@@ -0,0 +1,109 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+"""
+Module conf loads and parses configuration file
+"""
+import os
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.exception import AgentConfigError
+
+class ConfigurationProvider(object):
+ """
+ Parse amd store key:values in /etc/waagent.conf.
+ """
+ def __init__(self):
+ self.values = dict()
+
+ def load(self, content):
+ if not content:
+ raise AgentConfigError("Can't not parse empty configuration")
+ for line in content.split('\n'):
+ if not line.startswith("#") and "=" in line:
+ parts = line.split()[0].split('=')
+ value = parts[1].strip("\" ")
+ if value != "None":
+ self.values[parts[0]] = value
+ else:
+ self.values[parts[0]] = None
+
+ def get(self, key, default_val=None):
+ val = self.values.get(key)
+ return val if val is not None else default_val
+
+ def get_switch(self, key, default_val=False):
+ val = self.values.get(key)
+ if val is not None and val.lower() == 'y':
+ return True
+ elif val is not None and val.lower() == 'n':
+ return False
+ return default_val
+
+ def get_int(self, key, default_val=-1):
+ try:
+ return int(self.values.get(key))
+ except TypeError:
+ return default_val
+ except ValueError:
+ return default_val
+
+
+__config__ = ConfigurationProvider()
+
+def load_conf(conf_file_path, conf=__config__):
+ """
+ Load conf file from: conf_file_path
+ """
+ if os.path.isfile(conf_file_path) == False:
+ raise AgentConfigError(("Missing configuration in {0}"
+ "").format(conf_file_path))
+ try:
+ content = fileutil.read_file(conf_file_path)
+ conf.load(content)
+ except IOError as err:
+ raise AgentConfigError(("Failed to load conf file:{0}, {1}"
+ "").format(conf_file_path, err))
+
+def get(key, default_val=None, conf=__config__):
+ """
+ Get option value by key, return default_val if not found
+ """
+ if conf is not None:
+ return conf.get(key, default_val)
+ else:
+ return default_val
+
+def get_switch(key, default_val=None, conf=__config__):
+ """
+ Get bool option value by key, return default_val if not found
+ """
+ if conf is not None:
+ return conf.get_switch(key, default_val)
+ else:
+ return default_val
+
+def get_int(key, default_val=None, conf=__config__):
+ """
+ Get int option value by key, return default_val if not found
+ """
+ if conf is not None:
+ return conf.get_int(key, default_val)
+ else:
+ return default_val
+
diff --git a/azurelinuxagent/distro/__init__.py b/azurelinuxagent/distro/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/centos/__init__.py b/azurelinuxagent/distro/centos/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/centos/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/centos/loader.py b/azurelinuxagent/distro/centos/loader.py
new file mode 100644
index 0000000..379f027
--- /dev/null
+++ b/azurelinuxagent/distro/centos/loader.py
@@ -0,0 +1,25 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+import azurelinuxagent.distro.redhat.loader as redhat
+
+def get_osutil():
+ return redhat.get_osutil()
+
diff --git a/azurelinuxagent/distro/coreos/__init__.py b/azurelinuxagent/distro/coreos/__init__.py
new file mode 100644
index 0000000..7a4980e
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/__init__.py
@@ -0,0 +1,18 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
diff --git a/azurelinuxagent/distro/coreos/deprovision.py b/azurelinuxagent/distro/coreos/deprovision.py
new file mode 100644
index 0000000..f0ff604
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/deprovision.py
@@ -0,0 +1,30 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.distro.default.deprovision import DeprovisionHandler, DeprovisionAction
+
+class CoreOSDeprovisionHandler(DeprovisionHandler):
+ def setup(self, deluser):
+ warnings, actions = super(CoreOSDeprovisionHandler, self).setup(deluser)
+ warnings.append("WARNING! /etc/machine-id will be removed.")
+ files_to_del = ['/etc/machine-id']
+ actions.append(DeprovisionAction(fileutil.rm_files, files_to_del))
+ return warnings, actions
+
diff --git a/azurelinuxagent/distro/coreos/handlerFactory.py b/azurelinuxagent/distro/coreos/handlerFactory.py
new file mode 100644
index 0000000..f0490e8
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/handlerFactory.py
@@ -0,0 +1,27 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from .deprovision import CoreOSDeprovisionHandler
+from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory
+
+class CoreOSHandlerFactory(DefaultHandlerFactory):
+ def __init__(self):
+ super(CoreOSHandlerFactory, self).__init__()
+ self.deprovision_handler = CoreOSDeprovisionHandler()
+
diff --git a/azurelinuxagent/distro/coreos/loader.py b/azurelinuxagent/distro/coreos/loader.py
new file mode 100644
index 0000000..ec009ef
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/loader.py
@@ -0,0 +1,28 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+
+def get_osutil():
+ from azurelinuxagent.distro.coreos.osutil import CoreOSUtil
+ return CoreOSUtil()
+
+def get_handlers():
+ from azurelinuxagent.distro.coreos.handlerFactory import CoreOSHandlerFactory
+ return CoreOSHandlerFactory()
+
diff --git a/azurelinuxagent/distro/coreos/osutil.py b/azurelinuxagent/distro/coreos/osutil.py
new file mode 100644
index 0000000..6dfba64
--- /dev/null
+++ b/azurelinuxagent/distro/coreos/osutil.py
@@ -0,0 +1,90 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import base64
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class CoreOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(CoreOSUtil, self).__init__()
+ self.waagent_path='/usr/share/oem/bin/waagent'
+ self.python_path='/usr/share/oem/python/bin'
+ self.conf_path = '/usr/share/oem/waagent.conf'
+ if 'PATH' in os.environ:
+ path = "{0}:{1}".format(os.environ['PATH'], self.python_path)
+ else:
+ path = self.python_path
+ os.environ['PATH'] = path
+
+ if 'PYTHONPATH' in os.environ:
+ py_path = os.environ['PYTHONPATH']
+ py_path = "{0}:{1}".format(py_path, self.waagent_path)
+ else:
+ py_path = self.waagent_path
+ os.environ['PYTHONPATH'] = py_path
+
+ def is_sys_user(self, username):
+ #User 'core' is not a sysuser
+ if username == 'core':
+ return False
+ return super(CoreOSUtil, self).IsSysUser(username)
+
+ def is_dhcp_enabled(self):
+ return True
+
+ def start_network(self) :
+ return shellutil.run("systemctl start systemd-networkd", chk_err=False)
+
+ def restart_if(self, iface):
+ shellutil.run("systemctl restart systemd-networkd")
+
+ def restart_ssh_service(self):
+ return shellutil.run("systemctl restart sshd", chk_err=False)
+
+ def stop_dhcp_service(self):
+ return shellutil.run("systemctl stop systemd-networkd", chk_err=False)
+
+ def start_dhcp_service(self):
+ return shellutil.run("systemctl start systemd-networkd", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("systemctl start wagent", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("systemctl stop wagent", chk_err=False)
+
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof systemd-networkd")
+ return ret[1] if ret[0] == 0 else None
+
+ def decode_customdata(self, data):
+ return base64.b64decode(data)
+
diff --git a/azurelinuxagent/distro/debian/__init__.py b/azurelinuxagent/distro/debian/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/debian/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/debian/loader.py b/azurelinuxagent/distro/debian/loader.py
new file mode 100644
index 0000000..0787758
--- /dev/null
+++ b/azurelinuxagent/distro/debian/loader.py
@@ -0,0 +1,24 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+
+def get_osutil():
+ from azurelinuxagent.distro.debian.osutil import DebianOSUtil
+ return DebianOSUtil()
+
diff --git a/azurelinuxagent/distro/debian/osutil.py b/azurelinuxagent/distro/debian/osutil.py
new file mode 100644
index 0000000..a40c1de
--- /dev/null
+++ b/azurelinuxagent/distro/debian/osutil.py
@@ -0,0 +1,47 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import base64
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class DebianOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(DebianOSUtil, self).__init__()
+
+ def restart_ssh_service(self):
+ return shellutil.run("service sshd restart", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("service azurelinuxagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("service azurelinuxagent start", chk_err=False)
+
diff --git a/azurelinuxagent/distro/default/__init__.py b/azurelinuxagent/distro/default/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/default/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/default/deprovision.py b/azurelinuxagent/distro/default/deprovision.py
new file mode 100644
index 0000000..231f4eb
--- /dev/null
+++ b/azurelinuxagent/distro/default/deprovision.py
@@ -0,0 +1,117 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.conf as conf
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.protocol as prot
+import azurelinuxagent.protocol.ovfenv as ovf
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+
+class DeprovisionAction(object):
+ def __init__(self, func, args=[], kwargs={}):
+ self.func = func
+ self.args = args
+ self.kwargs = kwargs
+
+ def invoke(self):
+ self.func(*self.args, **self.kwargs)
+
+class DeprovisionHandler(object):
+
+ def del_root_password(self, warnings, actions):
+ warnings.append("WARNING! root password will be disabled. "
+ "You will not be able to login as root.")
+
+ actions.append(DeprovisionAction(OSUTIL.del_root_password))
+
+ def del_user(self, warnings, actions):
+
+ try:
+ ovfenv = ovf.get_ovf_env()
+ except prot.ProtocolError:
+ warnings.append("WARNING! ovf-env.xml is not found.")
+ warnings.append("WARNING! Skip delete user.")
+ return
+
+ username = ovfenv.username
+ warnings.append(("WARNING! {0} account and entire home directory "
+ "will be deleted.").format(username))
+ actions.append(DeprovisionAction(OSUTIL.del_account, [username]))
+
+
+ def regen_ssh_host_key(self, warnings, actions):
+ warnings.append("WARNING! All SSH host key pairs will be deleted.")
+ actions.append(DeprovisionAction(OSUTIL.set_hostname,
+ ['localhost.localdomain']))
+ actions.append(DeprovisionAction(shellutil.run,
+ ['rm -f /etc/ssh/ssh_host_*key*']))
+
+ def stop_agent_service(self, warnings, actions):
+ warnings.append("WARNING! The waagent service will be stopped.")
+ actions.append(DeprovisionAction(OSUTIL.stop_agent_service))
+
+ def del_files(self, warnings, actions):
+ files_to_del = ['/root/.bash_history', '/var/log/waagent.log']
+ actions.append(DeprovisionAction(fileutil.rm_files, files_to_del))
+
+ def del_dhcp_lease(self, warnings, actions):
+ warnings.append("WARNING! Cached DHCP leases will be deleted.")
+ dirs_to_del = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
+ actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del))
+
+ def del_lib_dir(self, warnings, actions):
+ dirs_to_del = [OSUTIL.get_lib_dir()]
+ actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del))
+
+ def setup(self, deluser):
+ warnings = []
+ actions = []
+
+ self.stop_agent_service(warnings, actions)
+ if conf.get_switch("Provisioning.RegenerateSshHostkey", False):
+ self.regen_ssh_host_key(warnings, actions)
+
+ self.del_dhcp_lease(warnings, actions)
+
+ if conf.get_switch("Provisioning.DeleteRootPassword", False):
+ self.del_root_password(warnings, actions)
+
+ self.del_lib_dir(warnings, actions)
+ self.del_files(warnings, actions)
+
+ if deluser:
+ self.del_user(warnings, actions)
+
+ return warnings, actions
+
+ def deprovision(self, force=False, deluser=False):
+ warnings, actions = self.setup(deluser)
+ for warning in warnings:
+ print(warning)
+
+ if not force:
+ confirm = input("Do you want to proceed (y/n)")
+ if not confirm.lower().startswith('y'):
+ return
+
+ for action in actions:
+ action.invoke()
+
+
diff --git a/azurelinuxagent/distro/default/dhcp.py b/azurelinuxagent/distro/default/dhcp.py
new file mode 100644
index 0000000..574ebd4
--- /dev/null
+++ b/azurelinuxagent/distro/default/dhcp.py
@@ -0,0 +1,330 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+import os
+import socket
+import array
+import time
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+from azurelinuxagent.exception import AgentNetworkError
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+from azurelinuxagent.utils.textutil import *
+
+WIRE_SERVER_ADDR_FILE_NAME="WireServer"
+
+class DhcpHandler(object):
+ def __init__(self):
+ self.endpoint = None
+ self.gateway = None
+ self.routes = None
+
+ def wait_for_network(self):
+ ipv4 = OSUTIL.get_ip4_addr()
+ while ipv4 == '' or ipv4 == '0.0.0.0':
+ logger.info("Waiting for network.")
+ time.sleep(10)
+ OSUTIL.start_network()
+ ipv4 = OSUTIL.get_ip4_addr()
+
+ def probe(self):
+ logger.info("Send dhcp request")
+ self.wait_for_network()
+ mac_addr = OSUTIL.get_mac_addr()
+ req = build_dhcp_request(mac_addr)
+ resp = send_dhcp_request(req)
+ if resp is None:
+ logger.warn("Failed to detect wire server.")
+ return
+ endpoint, gateway, routes = parse_dhcp_resp(resp)
+ self.endpoint = endpoint
+ logger.info("Wire server endpoint:{0}", endpoint)
+ logger.info("Gateway:{0}", gateway)
+ logger.info("Routes:{0}", routes)
+ if endpoint is not None:
+ path = os.path.join(OSUTIL.get_lib_dir(), WIRE_SERVER_ADDR_FILE_NAME)
+ fileutil.write_file(path, endpoint)
+ self.gateway = gateway
+ self.routes = routes
+ self.conf_routes()
+
+ def get_endpoint(self):
+ return self.endpoint
+
+ def conf_routes(self):
+ logger.info("Configure routes")
+ #Add default gateway
+ if self.gateway is not None:
+ OSUTIL.route_add(0 , 0, self.gateway)
+ if self.routes is not None:
+ for route in self.routes:
+ OSUTIL.route_add(route[0], route[1], route[2])
+
+def validate_dhcp_resp(request, response):
+ bytes_recv = len(response)
+ if bytes_recv < 0xF6:
+ logger.error("HandleDhcpResponse: Too few bytes received:{0}",
+ bytes_recv)
+ return False
+
+ logger.verb("BytesReceived:{0}", hex(bytes_recv))
+ logger.verb("DHCP response:{0}", hex_dump(response, bytes_recv))
+
+ # check transactionId, cookie, MAC address cookie should never mismatch
+ # transactionId and MAC address may mismatch if we see a response
+ # meant from another machine
+ if not compare_bytes(request, response, 0xEC, 4):
+ logger.verb("Cookie not match:\nsend={0},\nreceive={1}",
+ hex_dump3(request, 0xEC, 4),
+ hex_dump3(response, 0xEC, 4))
+ raise AgentNetworkError("Cookie in dhcp respones "
+ "doesn't match the request")
+
+ if not compare_bytes(request, response, 4, 4):
+ logger.verb("TransactionID not match:\nsend={0},\nreceive={1}",
+ hex_dump3(request, 4, 4),
+ hex_dump3(response, 4, 4))
+ raise AgentNetworkError("TransactionID in dhcp respones "
+ "doesn't match the request")
+
+ if not compare_bytes(request, response, 0x1C, 6):
+ logger.verb("Mac Address not match:\nsend={0},\nreceive={1}",
+ hex_dump3(request, 0x1C, 6),
+ hex_dump3(response, 0x1C, 6))
+ raise AgentNetworkError("Mac Addr in dhcp respones "
+ "doesn't match the request")
+
+def parse_route(response, option, i, length, bytes_recv):
+ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+ logger.verb("Routes at offset: {0} with length:{1}",
+ hex(i),
+ hex(length))
+ routes = []
+ if length < 5:
+ logger.error("Data too small for option:{0}", option)
+ j = i + 2
+ while j < (i + length + 2):
+ mask_len_bits = str_to_ord(response[j])
+ mask_len_bytes = (((mask_len_bits + 7) & ~7) >> 3)
+ mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - mask_len_bits))
+ j += 1
+ net = unpack_big_endian(response, j, mask_len_bytes)
+ net <<= (32 - mask_len_bytes * 8)
+ net &= mask
+ j += mask_len_bytes
+ gateway = unpack_big_endian(response, j, 4)
+ j += 4
+ routes.append((net, mask, gateway))
+ if j != (i + length + 2):
+ logger.error("Unable to parse routes")
+ return routes
+
+def parse_ip_addr(response, option, i, length, bytes_recv):
+ if i + 5 < bytes_recv:
+ if length != 4:
+ logger.error("Endpoint or Default Gateway not 4 bytes")
+ return None
+ addr = unpack_big_endian(response, i + 2, 4)
+ ip_addr = int_to_ip4_addr(addr)
+ return ip_addr
+ else:
+ logger.error("Data too small for option:{0}", option)
+ return None
+
+def parse_dhcp_resp(response):
+ """
+ Parse DHCP response:
+ Returns endpoint server or None on error.
+ """
+ logger.verb("parse Dhcp Response")
+ bytes_recv = len(response)
+ endpoint = None
+ gateway = None
+ routes = None
+
+ # Walk all the returned options, parsing out what we need, ignoring the
+ # others. We need the custom option 245 to find the the endpoint we talk to,
+ # as well as, to handle some Linux DHCP client incompatibilities,
+ # options 3 for default gateway and 249 for routes. And 255 is end.
+
+ i = 0xF0 # offset to first option
+ while i < bytes_recv:
+ option = str_to_ord(response[i])
+ length = 0
+ if (i + 1) < bytes_recv:
+ length = str_to_ord(response[i + 1])
+ logger.verb("DHCP option {0} at offset:{1} with length:{2}",
+ hex(option),
+ hex(i),
+ hex(length))
+ if option == 255:
+ logger.verb("DHCP packet ended at offset:{0}", hex(i))
+ break
+ elif option == 249:
+ routes = parse_route(response, option, i, length, bytes_recv)
+ elif option == 3:
+ gateway = parse_ip_addr(response, option, i, length, bytes_recv)
+ logger.verb("Default gateway:{0}, at {1}",
+ gateway,
+ hex(i))
+ elif option == 245:
+ endpoint = parse_ip_addr(response, option, i, length, bytes_recv)
+ logger.verb("Azure wire protocol endpoint:{0}, at {1}",
+ gateway,
+ hex(i))
+ else:
+ logger.verb("Skipping DHCP option:{0} at {1} with length {2}",
+ hex(option),
+ hex(i),
+ hex(length))
+ i += length + 2
+ return endpoint, gateway, routes
+
+
+def allow_dhcp_broadcast(func):
+ """
+ Temporary allow broadcase for dhcp. Remove the route when done.
+ """
+ def wrapper(*args, **kwargs):
+ missing_default_route = OSUTIL.is_missing_default_route()
+ ifname = OSUTIL.get_if_name()
+ if missing_default_route:
+ OSUTIL.set_route_for_dhcp_broadcast(ifname)
+ result = func(*args, **kwargs)
+ if missing_default_route:
+ OSUTIL.remove_route_for_dhcp_broadcast(ifname)
+ return result
+ return wrapper
+
+def disable_dhcp_service(func):
+ """
+ In some distros, dhcp service needs to be shutdown before agent probe
+ endpoint through dhcp.
+ """
+ def wrapper(*args, **kwargs):
+ if OSUTIL.is_dhcp_enabled():
+ OSUTIL.stop_dhcp_service()
+ result = func(*args, **kwargs)
+ OSUTIL.start_dhcp_service()
+ return result
+ else:
+ return func(*args, **kwargs)
+ return wrapper
+
+
+@allow_dhcp_broadcast
+@disable_dhcp_service
+def send_dhcp_request(request):
+ __waiting_duration__ = [0, 10, 30, 60, 60]
+ for duration in __waiting_duration__:
+ try:
+ OSUTIL.allow_dhcp_broadcast()
+ response = socket_send(request)
+ validate_dhcp_resp(request, response)
+ return response
+ except AgentNetworkError as e:
+ logger.warn("Failed to send DHCP request: {0}", e)
+ time.sleep(duration)
+ return None
+
+def socket_send(request):
+ sock = None
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind(("0.0.0.0", 68))
+ sock.sendto(request, ("<broadcast>", 67))
+ sock.settimeout(10)
+ logger.verb("Send DHCP request: Setting socket.timeout=10, "
+ "entering recv")
+ response = sock.recv(1024)
+ return response
+ except IOError as e:
+ raise AgentNetworkError("{0}".format(e))
+ finally:
+ if sock is not None:
+ sock.close()
+
+def build_dhcp_request(mac_addr):
+ """
+ Build DHCP request string.
+ """
+ #
+ # typedef struct _DHCP {
+ # UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
+ # UINT8 HardwareAddressType; /* htype: ethernet */
+ # UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
+ # UINT8 Hops; /* hops: 0 */
+ # UINT8 TransactionID[4]; /* xid: random */
+ # UINT8 Seconds[2]; /* secs: 0 */
+ # UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
+ # UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
+ # UINT8 YourIpAddress[4]; /* yiaddr: 0 */
+ # UINT8 ServerIpAddress[4]; /* siaddr: 0 */
+ # UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
+ # UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte eth MAC address */
+ # UINT8 ServerName[64]; /* sname: 0 */
+ # UINT8 BootFileName[128]; /* file: 0 */
+ # UINT8 MagicCookie[4]; /* 99 130 83 99 */
+ # /* 0x63 0x82 0x53 0x63 */
+ # /* options -- hard code ours */
+ #
+ # UINT8 MessageTypeCode; /* 53 */
+ # UINT8 MessageTypeLength; /* 1 */
+ # UINT8 MessageType; /* 1 for DISCOVER */
+ # UINT8 End; /* 255 */
+ # } DHCP;
+ #
+
+ # tuple of 244 zeros
+ # (struct.pack_into would be good here, but requires Python 2.5)
+ request = [0] * 244
+
+ trans_id = gen_trans_id()
+
+ # Opcode = 1
+ # HardwareAddressType = 1 (ethernet/MAC)
+ # HardwareAddressLength = 6 (ethernet/MAC/48 bits)
+ for a in range(0, 3):
+ request[a] = [1, 1, 6][a]
+
+ # fill in transaction id (random number to ensure response matches request)
+ for a in range(0, 4):
+ request[4 + a] = str_to_ord(trans_id[a])
+
+ logger.verb("BuildDhcpRequest: transactionId:%s,%04X" % (
+ hex_dump2(trans_id),
+ unpack_big_endian(request, 4, 4)))
+
+ # fill in ClientHardwareAddress
+ for a in range(0, 6):
+ request[0x1C + a] = str_to_ord(mac_addr[a])
+
+ # DHCP Magic Cookie: 99, 130, 83, 99
+ # MessageTypeCode = 53 DHCP Message Type
+ # MessageTypeLength = 1
+ # MessageType = DHCPDISCOVER
+ # End = 255 DHCP_END
+ for a in range(0, 8):
+ request[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
+ return array.array("B", request)
+
+def gen_trans_id():
+ return os.urandom(4)
diff --git a/azurelinuxagent/distro/default/env.py b/azurelinuxagent/distro/default/env.py
new file mode 100644
index 0000000..6a67113
--- /dev/null
+++ b/azurelinuxagent/distro/default/env.py
@@ -0,0 +1,115 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import socket
+import threading
+import time
+import azurelinuxagent.logger as logger
+import azurelinuxagent.conf as conf
+from azurelinuxagent.utils.osutil import OSUTIL
+
+class EnvHandler(object):
+ """
+ Monitor changes to dhcp and hostname.
+ If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
+
+ Monitor scsi disk.
+ If new scsi disk found, set
+ """
+ def __init__(self, handlers):
+ self.monitor = EnvMonitor(handlers.dhcp_handler)
+
+ def start(self):
+ self.monitor.start()
+
+ def stop(self):
+ self.monitor.stop()
+
+class EnvMonitor(object):
+
+ def __init__(self, dhcp_handler):
+ self.dhcp_handler = dhcp_handler
+ self.stopped = True
+ self.hostname = None
+ self.dhcpid = None
+ self.server_thread=None
+
+ def start(self):
+ if not self.stopped:
+ logger.info("Stop existing env monitor service.")
+ self.stop()
+
+ self.stopped = False
+ logger.info("Start env monitor service.")
+ self.hostname = socket.gethostname()
+ self.dhcpid = OSUTIL.get_dhcp_pid()
+ self.server_thread = threading.Thread(target = self.monitor)
+ self.server_thread.setDaemon(True)
+ self.server_thread.start()
+
+ def monitor(self):
+ """
+ Monitor dhcp client pid and hostname.
+ If dhcp clinet process re-start has occurred, reset routes.
+ """
+ while not self.stopped:
+ OSUTIL.remove_rules_files()
+ timeout = conf.get("OS.RootDeviceScsiTimeout", None)
+ if timeout is not None:
+ OSUTIL.set_scsi_disks_timeout(timeout)
+ if conf.get_switch("Provisioning.MonitorHostName", False):
+ self.handle_hostname_update()
+ self.handle_dhclient_restart()
+ time.sleep(5)
+
+ def handle_hostname_update(self):
+ curr_hostname = socket.gethostname()
+ if curr_hostname != self.hostname:
+ logger.info("EnvMonitor: Detected host name change: {0} -> {1}",
+ self.hostname, curr_hostname)
+ OSUTIL.set_hostname(curr_hostname)
+ OSUTIL.publish_hostname(curr_hostname)
+ self.hostname = curr_hostname
+
+ def handle_dhclient_restart(self):
+ if self.dhcpid is None:
+ logger.warn("Dhcp client is not running. ")
+ self.dhcpid = OSUTIL.get_dhcp_pid()
+ return
+
+ #The dhcp process hasn't changed since last check
+ if os.path.isdir(os.path.join('/proc', self.dhcpid.strip())):
+ return
+
+ newpid = OSUTIL.get_dhcp_pid()
+ if newpid is not None and newpid != self.dhcpid:
+ logger.info("EnvMonitor: Detected dhcp client restart. "
+ "Restoring routing table.")
+ self.dhcp_handler.conf_routes()
+ self.dhcpid = newpid
+
+ def stop(self):
+ """
+ Stop server comminucation and join the thread to main thread.
+ """
+ self.stopped = True
+ if self.server_thread is not None:
+ self.server_thread.join()
+
diff --git a/azurelinuxagent/distro/default/extension.py b/azurelinuxagent/distro/default/extension.py
new file mode 100644
index 0000000..58ba84e
--- /dev/null
+++ b/azurelinuxagent/distro/default/extension.py
@@ -0,0 +1,647 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+import os
+import zipfile
+import time
+import json
+import subprocess
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.protocol as prot
+from azurelinuxagent.event import add_event, WALAEventOperation
+from azurelinuxagent.exception import ExtensionError
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.restutil as restutil
+import azurelinuxagent.utils.shellutil as shellutil
+
+VALID_EXTENSION_STATUS = ['transitioning', 'error', 'success', 'warning']
+
+def validate_has_key(obj, key, fullname):
+ if key not in obj:
+ raise ExtensionError("Missing: {0}".format(fullname))
+
+def validate_in_range(val, valid_range, name):
+ if val not in valid_range:
+ raise ExtensionError("Invalid {0}: {1}".format(name, val))
+
+def try_get(dictionary, key, default=None):
+ try:
+ return dictionary[key]
+ except KeyError:
+ return default
+
+def extension_sub_status_to_v2(substatus):
+ #Check extension sub status format
+ validate_has_key(substatus, 'name', 'substatus/name')
+ validate_has_key(substatus, 'status', 'substatus/status')
+ validate_has_key(substatus, 'code', 'substatus/code')
+ validate_has_key(substatus, 'formattedMessage', 'substatus/formattedMessage')
+ validate_has_key(substatus['formattedMessage'], 'lang',
+ 'substatus/formattedMessage/lang')
+ validate_has_key(substatus['formattedMessage'], 'message',
+ 'substatus/formattedMessage/message')
+
+ validate_in_range(substatus['status'], VALID_EXTENSION_STATUS,
+ 'substatus/status')
+ status = prot.ExtensionSubStatus()
+ status.name = try_get(substatus, 'name')
+ status.status = try_get(substatus, 'status')
+ status.code = try_get(substatus, 'code')
+ status.message = try_get(substatus['formattedMessage'], 'message')
+ return status
+
+def ext_status_to_v2(ext_status, seq_no):
+ #Check extension status format
+ validate_has_key(ext_status, 'status', 'status')
+ validate_has_key(ext_status['status'], 'status', 'status/status')
+ validate_has_key(ext_status['status'], 'operation', 'status/operation')
+ validate_has_key(ext_status['status'], 'code', 'status/code')
+ validate_has_key(ext_status['status'], 'name', 'status/name')
+ validate_has_key(ext_status['status'], 'formattedMessage',
+ 'status/formattedMessage')
+ validate_has_key(ext_status['status']['formattedMessage'], 'lang',
+ 'status/formattedMessage/lang')
+ validate_has_key(ext_status['status']['formattedMessage'], 'message',
+ 'status/formattedMessage/message')
+
+ validate_in_range(ext_status['status']['status'], VALID_EXTENSION_STATUS,
+ 'status/status')
+
+ status = prot.ExtensionStatus()
+ status.name = try_get(ext_status['status'], 'name')
+ status.configurationAppliedTime = try_get(ext_status['status'],
+ 'configurationAppliedTime')
+ status.operation = try_get(ext_status['status'], 'operation')
+ status.status = try_get(ext_status['status'], 'status')
+ status.code = try_get(ext_status['status'], 'code')
+ status.message = try_get(ext_status['status']['formattedMessage'], 'message')
+ status.sequenceNumber = seq_no
+
+ substatus_list = try_get(ext_status['status'], 'substatus', [])
+ for substatus in substatus_list:
+ status.substatusList.extend(extension_sub_status_to_v2(substatus))
+ return status
+
+class ExtensionsHandler(object):
+
+ def process(self):
+ protocol = prot.FACTORY.get_default_protocol()
+ ext_list = protocol.get_extensions()
+
+ h_status_list = []
+ for extension in ext_list.extensions:
+ #TODO handle extension in parallel
+ pkg_list = protocol.get_extension_pkgs(extension)
+ h_status = self.process_extension(extension, pkg_list)
+ h_status_list.append(h_status)
+
+ return h_status_list
+
+ def process_extension(self, extension, pkg_list):
+ installed_version = get_installed_version(extension.name)
+ if installed_version is not None:
+ ext = ExtensionInstance(extension, pkg_list,
+ installed_version, installed=True)
+ else:
+ ext = ExtensionInstance(extension, pkg_list,
+ extension.properties.version)
+ try:
+ ext.init_logger()
+ ext.handle()
+ status = ext.collect_handler_status()
+ except ExtensionError as e:
+ logger.error("Failed to handle extension: {0}-{1}\n {2}",
+ ext.get_name(), ext.get_version(), e)
+ add_event(name=ext.get_name(), is_success=False,
+ op=ext.get_curr_op(), message = text(e))
+ ext_status = prot.ExtensionStatus(status='error', code='-1',
+ operation = ext.get_curr_op(),
+ message = text(e),
+ seq_no = ext.get_seq_no())
+ status = ext.create_handler_status(ext_status)
+ status.status = "Ready"
+ return status
+
+def parse_extension_dirname(dirname):
+ """
+ Parse installed extension dir name. Sample: ExtensionName-Version/
+ """
+ seprator = dirname.rfind('-')
+ if seprator < 0:
+ raise ExtensionError("Invalid extenation dir name")
+ return dirname[0:seprator], dirname[seprator + 1:]
+
+def get_installed_version(target_name):
+ """
+ Return the highest version instance with the same name
+ """
+ installed_version = None
+ lib_dir = OSUTIL.get_lib_dir()
+ for dir_name in os.listdir(lib_dir):
+ path = os.path.join(lib_dir, dir_name)
+ if os.path.isdir(path) and dir_name.startswith(target_name):
+ name, version = parse_extension_dirname(dir_name)
+ #Here we need to ensure names are exactly the same.
+ if name == target_name:
+ if installed_version is None or installed_version < version:
+ installed_version = version
+ return installed_version
+
+class ExtensionInstance(object):
+ def __init__(self, extension, pkg_list, curr_version, installed=False):
+ self.extension = extension
+ self.pkg_list = pkg_list
+ self.curr_version = curr_version
+ self.lib_dir = OSUTIL.get_lib_dir()
+ self.installed = installed
+ self.settings = None
+
+ #Extension will have no more than 1 settings instance
+ if len(extension.properties.extensions) > 0:
+ self.settings = extension.properties.extensions[0]
+ self.enabled = False
+ self.curr_op = None
+
+ prefix = "[{0}]".format(self.get_full_name())
+ self.logger = logger.Logger(logger.DEFAULT_LOGGER, prefix)
+
+ def init_logger(self):
+ #Init logger appender for extension
+ fileutil.mkdir(self.get_log_dir(), mode=0o700)
+ log_file = os.path.join(self.get_log_dir(), "CommandExecution.log")
+ self.logger.add_appender(logger.AppenderType.FILE,
+ logger.LogLevel.INFO, log_file)
+
+ def handle(self):
+ self.logger.info("Process extension settings:")
+ self.logger.info(" Name: {0}", self.get_name())
+ self.logger.info(" Version: {0}", self.get_version())
+
+ if self.installed:
+ self.logger.info("Installed version:{0}", self.curr_version)
+ h_status = self.get_handler_status()
+ self.enabled = (h_status == "Ready")
+
+ state = self.get_state()
+ if state == 'enabled':
+ self.handle_enable()
+ elif state == 'disabled':
+ self.handle_disable()
+ elif state == 'uninstall':
+ self.handle_disable()
+ self.handle_uninstall()
+ else:
+ raise ExtensionError("Unknown extension state:{0}".format(state))
+
+ def handle_enable(self):
+ target_version = self.get_target_version()
+ if self.installed:
+ if target_version > self.curr_version:
+ self.upgrade(target_version)
+ elif target_version == self.curr_version:
+ self.enable()
+ else:
+ raise ExtensionError("A newer version has already been installed")
+ else:
+ if target_version > self.get_version():
+ #This will happen when auto upgrade policy is enabled
+ self.logger.info("Auto upgrade to new version:{0}",
+ target_version)
+ self.curr_version = target_version
+ self.download()
+ self.init_dir()
+ self.install()
+ self.enable()
+
+ def handle_disable(self):
+ if not self.installed or not self.enabled:
+ return
+ self.disable()
+
+ def handle_uninstall(self):
+ if not self.installed:
+ return
+ self.uninstall()
+
+ def upgrade(self, target_version):
+ self.logger.info("Upgrade from: {0} to {1}", self.curr_version,
+ target_version)
+ self.curr_op=WALAEventOperation.Upgrade
+ old = self
+ new = ExtensionInstance(self.extension, self.pkg_list, target_version)
+ self.logger.info("Download new extension package")
+ new.init_logger()
+ new.download()
+ self.logger.info("Initialize new extension directory")
+ new.init_dir()
+
+ old.disable()
+ self.logger.info("Update new extension")
+ new.update()
+ old.uninstall()
+ man = new.load_manifest()
+ if man.is_update_with_install():
+ self.logger.info("Install new extension")
+ new.install()
+ self.logger.info("Enable new extension")
+ new.enable()
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def download(self):
+ self.logger.info("Download extension package")
+ self.curr_op=WALAEventOperation.Download
+ uris = self.get_package_uris()
+ package = None
+ for uri in uris:
+ try:
+ resp = restutil.http_get(uri.uri, chk_proxy=True)
+ package = resp.read()
+ break
+ except restutil.HttpError as e:
+ self.logger.warn("Failed download extension from: {0}", uri.uri)
+
+ if package is None:
+ raise ExtensionError("Download extension failed")
+
+ self.logger.info("Unpack extension package")
+ pkg_file = os.path.join(self.lib_dir, os.path.basename(uri.uri) + ".zip")
+ fileutil.write_file(pkg_file, bytearray(package), asbin=True)
+ zipfile.ZipFile(pkg_file).extractall(self.get_base_dir())
+ chmod = "find {0} -type f | xargs chmod u+x".format(self.get_base_dir())
+ shellutil.run(chmod)
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def init_dir(self):
+ self.logger.info("Initialize extension directory")
+ #Save HandlerManifest.json
+ man_file = fileutil.search_file(self.get_base_dir(),
+ 'HandlerManifest.json')
+ man = fileutil.read_file(man_file, remove_bom=True)
+ fileutil.write_file(self.get_manifest_file(), man)
+
+ #Create status and config dir
+ status_dir = self.get_status_dir()
+ fileutil.mkdir(status_dir, mode=0o700)
+ conf_dir = self.get_conf_dir()
+ fileutil.mkdir(conf_dir, mode=0o700)
+
+ #Init handler state to uninstall
+ self.set_handler_status("NotReady")
+
+ #Save HandlerEnvironment.json
+ self.create_handler_env()
+
+ def enable(self):
+ self.logger.info("Enable extension.")
+ self.curr_op=WALAEventOperation.Enable
+ man = self.load_manifest()
+ self.launch_command(man.get_enable_command())
+ self.set_handler_status("Ready")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def disable(self):
+ self.logger.info("Disable extension.")
+ self.curr_op=WALAEventOperation.Disable
+ man = self.load_manifest()
+ self.launch_command(man.get_disable_command(), timeout=900)
+ self.set_handler_status("Ready")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def install(self):
+ self.logger.info("Install extension.")
+ self.curr_op=WALAEventOperation.Install
+ man = self.load_manifest()
+ self.set_handler_status("Installing")
+ self.launch_command(man.get_install_command(), timeout=900)
+ self.set_handler_status("Ready")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def uninstall(self):
+ self.logger.info("Uninstall extension.")
+ self.curr_op=WALAEventOperation.UnInstall
+ man = self.load_manifest()
+ self.launch_command(man.get_uninstall_command())
+ self.set_handler_status("NotReady")
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def update(self):
+ self.logger.info("Update extension.")
+ self.curr_op=WALAEventOperation.Update
+ man = self.load_manifest()
+ self.launch_command(man.get_update_command(), timeout=900)
+ add_event(name=self.get_name(), is_success=True,
+ op=self.curr_op, message="")
+
+ def create_handler_status(self, ext_status, heartbeat=None):
+ status = prot.ExtensionHandlerStatus()
+ status.handlerName = self.get_name()
+ status.handlerVersion = self.get_version()
+ status.status = self.get_handler_status()
+ status.extensionStatusList.append(ext_status)
+ return status
+
+ def collect_handler_status(self):
+ man = self.load_manifest()
+ heartbeat=None
+ if man.is_report_heartbeat():
+ heartbeat = self.collect_heartbeat()
+ ext_status = self.collect_extension_status()
+ status= self.create_handler_status(ext_status, heartbeat)
+ status.status = self.get_handler_status()
+ if heartbeat is not None:
+ status.status = heartbeat['status']
+ status.extensionStatusList.append(ext_status)
+ return status
+
+ def collect_extension_status(self):
+ ext_status_file = self.get_status_file()
+ try:
+ ext_status_str = fileutil.read_file(ext_status_file)
+ ext_status = json.loads(ext_status_str)
+ except IOError as e:
+ raise ExtensionError("Failed to get status file: {0}".format(e))
+ except ValueError as e:
+ raise ExtensionError("Malformed status file: {0}".format(e))
+ return ext_status_to_v2(ext_status[0],
+ self.settings.sequenceNumber)
+
+ def get_handler_status(self):
+ h_status = "uninstalled"
+ h_status_file = self.get_handler_state_file()
+ try:
+ h_status = fileutil.read_file(h_status_file)
+ return h_status
+ except IOError as e:
+ raise ExtensionError("Failed to get handler status: {0}".format(e))
+
+ def set_handler_status(self, status):
+ h_status_file = self.get_handler_state_file()
+ try:
+ fileutil.write_file(h_status_file, status)
+ except IOError as e:
+ raise ExtensionError("Failed to set handler status: {0}".format(e))
+
+ def collect_heartbeat(self):
+ self.logger.info("Collect heart beat")
+ heartbeat_file = os.path.join(OSUTIL.get_lib_dir(),
+ self.get_heartbeat_file())
+ if not os.path.isfile(heartbeat_file):
+ raise ExtensionError("Failed to get heart beat file")
+ if not self.is_responsive(heartbeat_file):
+ return {
+ "status": "Unresponsive",
+ "code": -1,
+ "message": "Extension heartbeat is not responsive"
+ }
+ try:
+ heartbeat_json = fileutil.read_file(heartbeat_file)
+ heartbeat = json.loads(heartbeat_json)[0]['heartbeat']
+ except IOError as e:
+ raise ExtensionError("Failed to get heartbeat file:{0}".format(e))
+ except ValueError as e:
+ raise ExtensionError("Malformed heartbeat file: {0}".format(e))
+ return heartbeat
+
+ def is_responsive(self, heartbeat_file):
+ last_update=int(time.time()-os.stat(heartbeat_file).st_mtime)
+ return last_update > 600 # not updated for more than 10 min
+
+ def launch_command(self, cmd, timeout=300):
+ self.logger.info("Launch command:{0}", cmd)
+ base_dir = self.get_base_dir()
+ self.update_settings()
+ try:
+ devnull = open(os.devnull, 'w')
+ child = subprocess.Popen(base_dir + "/" + cmd, shell=True,
+ cwd=base_dir, stdout=devnull)
+ except Exception as e:
+ #TODO do not catch all exception
+ raise ExtensionError("Failed to launch: {0}, {1}".format(cmd, e))
+
+ retry = timeout / 5
+ while retry > 0 and child.poll == None:
+ time.sleep(5)
+ retry -= 1
+ if retry == 0:
+ os.kill(child.pid, 9)
+ raise ExtensionError("Timeout({0}): {1}".format(timeout, cmd))
+
+ ret = child.wait()
+ if ret == None or ret != 0:
+ raise ExtensionError("Non-zero exit code: {0}, {1}".format(ret, cmd))
+
+ def load_manifest(self):
+ man_file = self.get_manifest_file()
+ try:
+ data = json.loads(fileutil.read_file(man_file))
+ except IOError as e:
+ raise ExtensionError('Failed to load manifest file.')
+ except ValueError as e:
+ raise ExtensionError('Malformed manifest file.')
+
+ return HandlerManifest(data[0])
+
+
+ def update_settings(self):
+ if self.settings is None:
+ self.logger.verbose("Extension has no settings")
+ return
+
+ settings = {
+ 'publicSettings': self.settings.publicSettings,
+ 'protectedSettings': self.settings.privateSettings,
+ 'protectedSettingsCertThumbprint': self.settings.certificateThumbprint
+ }
+ ext_settings = {
+ "runtimeSettings":[{
+ "handlerSettings": settings
+ }]
+ }
+ fileutil.write_file(self.get_settings_file(), json.dumps(ext_settings))
+
+ latest = os.path.join(self.get_conf_dir(), "latest")
+ fileutil.write_file(latest, self.settings.sequenceNumber)
+
+ def create_handler_env(self):
+ env = [{
+ "name": self.get_name(),
+ "version" : self.get_version(),
+ "handlerEnvironment" : {
+ "logFolder" : self.get_log_dir(),
+ "configFolder" : self.get_conf_dir(),
+ "statusFolder" : self.get_status_dir(),
+ "heartbeatFile" : self.get_heartbeat_file()
+ }
+ }]
+ fileutil.write_file(self.get_env_file(),
+ json.dumps(env))
+
+ def get_target_version(self):
+ version = self.get_version()
+ update_policy = self.get_upgrade_policy()
+ if update_policy is None or update_policy.lower() != 'auto':
+ return version
+
+ major = version.split('.')[0]
+ if major is None:
+ raise ExtensionError("Wrong version format: {0}".format(version))
+
+ packages = [x for x in self.pkg_list.versions if x.version.startswith(major + ".")]
+ packages = sorted(packages, key=lambda x: x.version, reverse=True)
+ if len(packages) <= 0:
+ raise ExtensionError("Can't find version: {0}.*".format(major))
+
+ return packages[0].version
+
+ def get_package_uris(self):
+ version = self.get_version()
+ packages = self.pkg_list.versions
+ if packages is None:
+ raise ExtensionError("Package uris is None.")
+
+ for package in packages:
+ if package.version == version:
+ return package.uris
+
+ raise ExtensionError("Can't get package uris for {0}.".format(version))
+
+ def get_curr_op(self):
+ return self.curr_op
+
+ def get_name(self):
+ return self.extension.name
+
+ def get_version(self):
+ return self.extension.properties.version
+
+ def get_state(self):
+ return self.extension.properties.state
+
+ def get_seq_no(self):
+ return self.settings.sequenceNumber
+
+ def get_upgrade_policy(self):
+ return self.extension.properties.upgradePolicy
+
+ def get_full_name(self):
+ return "{0}-{1}".format(self.get_name(), self.curr_version)
+
+ def get_base_dir(self):
+ return os.path.join(OSUTIL.get_lib_dir(), self.get_full_name())
+
+ def get_status_dir(self):
+ return os.path.join(self.get_base_dir(), "status")
+
+ def get_status_file(self):
+ return os.path.join(self.get_status_dir(),
+ "{0}.status".format(self.settings.sequenceNumber))
+
+ def get_conf_dir(self):
+ return os.path.join(self.get_base_dir(), 'config')
+
+ def get_settings_file(self):
+ return os.path.join(self.get_conf_dir(),
+ "{0}.settings".format(self.settings.sequenceNumber))
+
+ def get_handler_state_file(self):
+ return os.path.join(self.get_conf_dir(), 'HandlerState')
+
+ def get_heartbeat_file(self):
+ return os.path.join(self.get_base_dir(), 'heartbeat.log')
+
+ def get_manifest_file(self):
+ return os.path.join(self.get_base_dir(), 'HandlerManifest.json')
+
+ def get_env_file(self):
+ return os.path.join(self.get_base_dir(), 'HandlerEnvironment.json')
+
+ def get_log_dir(self):
+ return os.path.join(OSUTIL.get_ext_log_dir(), self.get_name(),
+ self.curr_version)
+
+class HandlerEnvironment(object):
+ def __init__(self, data):
+ self.data = data
+
+ def get_version(self):
+ return self.data["version"]
+
+ def get_log_dir(self):
+ return self.data["handlerEnvironment"]["logFolder"]
+
+ def get_conf_dir(self):
+ return self.data["handlerEnvironment"]["configFolder"]
+
+ def get_status_dir(self):
+ return self.data["handlerEnvironment"]["statusFolder"]
+
+ def get_heartbeat_file(self):
+ return self.data["handlerEnvironment"]["heartbeatFile"]
+
+class HandlerManifest(object):
+ def __init__(self, data):
+ if data is None or data['handlerManifest'] is None:
+ raise ExtensionError('Malformed manifest file.')
+ self.data = data
+
+ def get_name(self):
+ return self.data["name"]
+
+ def get_version(self):
+ return self.data["version"]
+
+ def get_install_command(self):
+ return self.data['handlerManifest']["installCommand"]
+
+ def get_uninstall_command(self):
+ return self.data['handlerManifest']["uninstallCommand"]
+
+ def get_update_command(self):
+ return self.data['handlerManifest']["updateCommand"]
+
+ def get_enable_command(self):
+ return self.data['handlerManifest']["enableCommand"]
+
+ def get_disable_command(self):
+ return self.data['handlerManifest']["disableCommand"]
+
+ def is_reboot_after_install(self):
+ #TODO handle reboot after install
+ if "rebootAfterInstall" not in self.data['handlerManifest']:
+ return False
+ return self.data['handlerManifest']["rebootAfterInstall"]
+
+ def is_report_heartbeat(self):
+ if "reportHeartbeat" not in self.data['handlerManifest']:
+ return False
+ return self.data['handlerManifest']["reportHeartbeat"]
+
+ def is_update_with_install(self):
+ if "updateMode" not in self.data['handlerManifest']:
+ return False
+ if "updateMode" in self.data:
+ return self.data['handlerManifest']["updateMode"].lower() == "updatewithinstall"
+ return False
diff --git a/azurelinuxagent/distro/default/handlerFactory.py b/azurelinuxagent/distro/default/handlerFactory.py
new file mode 100644
index 0000000..98b2380
--- /dev/null
+++ b/azurelinuxagent/distro/default/handlerFactory.py
@@ -0,0 +1,40 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+from .init import InitHandler
+from .run import MainHandler
+from .scvmm import ScvmmHandler
+from .dhcp import DhcpHandler
+from .env import EnvHandler
+from .provision import ProvisionHandler
+from .resourceDisk import ResourceDiskHandler
+from .extension import ExtensionsHandler
+from .deprovision import DeprovisionHandler
+
+class DefaultHandlerFactory(object):
+ def __init__(self):
+ self.init_handler = InitHandler()
+ self.main_handler = MainHandler(self)
+ self.scvmm_handler = ScvmmHandler()
+ self.dhcp_handler = DhcpHandler()
+ self.env_handler = EnvHandler(self)
+ self.provision_handler = ProvisionHandler()
+ self.resource_disk_handler = ResourceDiskHandler()
+ self.extension_handler = ExtensionsHandler()
+ self.deprovision_handler = DeprovisionHandler()
+
diff --git a/azurelinuxagent/distro/default/init.py b/azurelinuxagent/distro/default/init.py
new file mode 100644
index 0000000..337fdea
--- /dev/null
+++ b/azurelinuxagent/distro/default/init.py
@@ -0,0 +1,49 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import azurelinuxagent.conf as conf
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.fileutil as fileutil
+
+
+class InitHandler(object):
+ def init(self, verbose):
+ #Init stdout log
+ level = logger.LogLevel.VERBOSE if verbose else logger.LogLevel.INFO
+ logger.add_logger_appender(logger.AppenderType.STDOUT, level)
+
+ #Init config
+ conf_file_path = OSUTIL.get_conf_file_path()
+ conf.load_conf(conf_file_path)
+
+ #Init log
+ verbose = verbose or conf.get_switch("Logs.Verbose", False)
+ level = logger.LogLevel.VERBOSE if verbose else logger.LogLevel.INFO
+ logger.add_logger_appender(logger.AppenderType.FILE, level,
+ path="/var/log/waagent.log")
+ logger.add_logger_appender(logger.AppenderType.CONSOLE, level,
+ path="/dev/console")
+
+ #Create lib dir
+ fileutil.mkdir(OSUTIL.get_lib_dir(), mode=0o700)
+ os.chdir(OSUTIL.get_lib_dir())
+
+
diff --git a/azurelinuxagent/distro/default/loader.py b/azurelinuxagent/distro/default/loader.py
new file mode 100644
index 0000000..d7dbe87
--- /dev/null
+++ b/azurelinuxagent/distro/default/loader.py
@@ -0,0 +1,28 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+def get_osutil():
+ from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+ return DefaultOSUtil()
+
+def get_handlers():
+ from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory
+ return DefaultHandlerFactory()
+
+
diff --git a/azurelinuxagent/distro/default/osutil.py b/azurelinuxagent/distro/default/osutil.py
new file mode 100644
index 0000000..8e3fb77
--- /dev/null
+++ b/azurelinuxagent/distro/default/osutil.py
@@ -0,0 +1,657 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import shutil
+import socket
+import array
+import struct
+import time
+import pwd
+import fcntl
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+
+__RULES_FILES__ = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
+ "/etc/udev/rules.d/70-persistent-net.rules" ]
+
+"""
+Define distro specific behavior. OSUtil class defines default behavior
+for all distros. Each concrete distro classes could overwrite default behavior
+if needed.
+"""
+
+class OSUtilError(Exception):
+ pass
+
+class DefaultOSUtil(object):
+
+ def __init__(self):
+ self.lib_dir = "/var/lib/waagent"
+ self.ext_log_dir = "/var/log/azure"
+ self.dvd_mount_point = "/mnt/cdrom/secure"
+ self.ovf_env_file_path = "/mnt/cdrom/secure/ovf-env.xml"
+ self.agent_pid_file_path = "/var/run/waagent.pid"
+ self.passwd_file_path = "/etc/shadow"
+ self.home = '/home'
+ self.sshd_conf_file_path = '/etc/ssh/sshd_config'
+ self.openssl_cmd = '/usr/bin/openssl'
+ self.conf_file_path = '/etc/waagent.conf'
+ self.selinux=None
+
+ def get_lib_dir(self):
+ return self.lib_dir
+
+ def get_ext_log_dir(self):
+ return self.ext_log_dir
+
+ def get_dvd_mount_point(self):
+ return self.dvd_mount_point
+
+ def get_conf_file_path(self):
+ return self.conf_file_path
+
+ def get_ovf_env_file_path_on_dvd(self):
+ return self.ovf_env_file_path
+
+ def get_agent_pid_file_path(self):
+ return self.agent_pid_file_path
+
+ def get_openssl_cmd(self):
+ return self.openssl_cmd
+
+ def get_userentry(self, username):
+ try:
+ return pwd.getpwnam(username)
+ except KeyError:
+ return None
+
+ def is_sys_user(self, username):
+ userentry = self.get_userentry(username)
+ uidmin = None
+ try:
+ uidmin_def = fileutil.get_line_startingwith("UID_MIN",
+ "/etc/login.defs")
+ if uidmin_def is not None:
+ uidmin = int(uidmin_def.split()[1])
+ except IOError as e:
+ pass
+ if uidmin == None:
+ uidmin = 100
+ if userentry != None and userentry[2] < uidmin:
+ return True
+ else:
+ return False
+
+ def useradd(self, username, expiration=None):
+ """
+ Update password and ssh key for user account.
+ New account will be created if not exists.
+ """
+ if expiration is not None:
+ cmd = "useradd -m {0} -e {1}".format(username, expiration)
+ else:
+ cmd = "useradd -m {0}".format(username)
+ retcode, out = shellutil.run_get_output(cmd)
+ if retcode != 0:
+ raise OSUtilError(("Failed to create user account:{0}, "
+ "retcode:{1}, "
+ "output:{2}").format(username, retcode, out))
+
+ def chpasswd(self, username, password, use_salt=True, salt_type=6,
+ salt_len=10):
+ if self.is_sys_user(username):
+ raise OSUtilError(("User {0} is a system user. "
+ "Will not set passwd.").format(username))
+ passwd_hash = textutil.gen_password_hash(password, use_salt, salt_type,
+ salt_len)
+ try:
+ passwd_content = fileutil.read_file(self.passwd_file_path)
+ passwd = passwd_content.split("\n")
+ new_passwd = [x for x in passwd if not x.startswith(username)]
+ new_passwd.append("{0}:{1}:14600::::::".format(username, passwd_hash))
+ fileutil.write_file(self.passwd_file_path, "\n".join(new_passwd))
+ except IOError as e:
+ raise OSUtilError(("Failed to set password for {0}: {1}"
+ "").format(username, e))
+
+ def conf_sudoer(self, username, nopasswd):
+ # for older distros create sudoers.d
+ if not os.path.isdir('/etc/sudoers.d/'):
+ # create the /etc/sudoers.d/ directory
+ os.mkdir('/etc/sudoers.d/')
+ # add the include of sudoers.d to the /etc/sudoers
+ sudoers = '\n' + '#includedir /etc/sudoers.d/\n'
+ fileutil.append_file('/etc/sudoers', sudoers)
+ sudoer = None
+ if nopasswd:
+ sudoer = "{0} ALL = (ALL) NOPASSWD\n".format(username)
+ else:
+ sudoer = "{0} ALL = (ALL) ALL\n".format(username)
+ fileutil.append_file('/etc/sudoers.d/waagent', sudoer)
+ fileutil.chmod('/etc/sudoers.d/waagent', 0o440)
+
+ def del_root_password(self):
+ try:
+ passwd_content = fileutil.read_file(self.passwd_file_path)
+ passwd = passwd_content.split('\n')
+ new_passwd = [x for x in passwd if not x.startswith("root:")]
+ new_passwd.insert(0, "root:*LOCK*:14600::::::")
+ fileutil.write_file(self.passwd_file_path, "\n".join(new_passwd))
+ except IOError as e:
+ raise OSUtilError("Failed to delete root password:{0}".format(e))
+
+ def get_home(self):
+ return self.home
+
+ def get_pubkey_from_prv(self, file_name):
+ cmd = "{0} rsa -in {1} -pubout 2>/dev/null".format(self.openssl_cmd,
+ file_name)
+ pub = shellutil.run_get_output(cmd)[1]
+ return pub
+
+ def get_pubkey_from_crt(self, file_name):
+ cmd = "{0} x509 -in {1} -pubkey -noout".format(self.openssl_cmd,
+ file_name)
+ pub = shellutil.run_get_output(cmd)[1]
+ return pub
+
+ def _norm_path(self, filepath):
+ home = self.get_home()
+ # Expand HOME variable if present in path
+ path = os.path.normpath(filepath.replace("$HOME", home))
+ return path
+
+ def get_thumbprint_from_crt(self, file_name):
+ cmd="{0} x509 -in {1} -fingerprint -noout".format(self.openssl_cmd,
+ file_name)
+ thumbprint = shellutil.run_get_output(cmd)[1]
+ thumbprint = thumbprint.rstrip().split('=')[1].replace(':', '').upper()
+ return thumbprint
+
+ def deploy_ssh_keypair(self, username, keypair):
+ """
+ Deploy id_rsa and id_rsa.pub
+ """
+ path, thumbprint = keypair
+ path = self._norm_path(path)
+ dir_path = os.path.dirname(path)
+ fileutil.mkdir(dir_path, mode=0o700, owner=username)
+ lib_dir = self.get_lib_dir()
+ prv_path = os.path.join(lib_dir, thumbprint + '.prv')
+ if not os.path.isfile(prv_path):
+ raise OSUtilError("Can't find {0}.prv".format(thumbprint))
+ shutil.copyfile(prv_path, path)
+ pub_path = path + '.pub'
+ pub = self.get_pubkey_from_prv(prv_path)
+ fileutil.write_file(pub_path, pub)
+ self.set_selinux_context(pub_path, 'unconfined_u:object_r:ssh_home_t:s0')
+ self.set_selinux_context(path, 'unconfined_u:object_r:ssh_home_t:s0')
+ os.chmod(path, 0o644)
+ os.chmod(pub_path, 0o600)
+
+ def openssl_to_openssh(self, input_file, output_file):
+ shellutil.run("ssh-keygen -i -m PKCS8 -f {0} >> {1}".format(input_file,
+ output_file))
+
+ def deploy_ssh_pubkey(self, username, pubkey):
+ """
+ Deploy authorized_key
+ """
+ path, thumbprint, value = pubkey
+ if path is None:
+ raise OSUtilError("Publich key path is None")
+
+ path = self._norm_path(path)
+ dir_path = os.path.dirname(path)
+ fileutil.mkdir(dir_path, mode=0o700, owner=username)
+ if value is not None:
+ if not value.startswith("ssh-"):
+ raise OSUtilError("Bad public key: {0}".format(value))
+ fileutil.write_file(path, value)
+ elif thumbprint is not None:
+ lib_dir = self.get_lib_dir()
+ crt_path = os.path.join(lib_dir, thumbprint + '.crt')
+ if not os.path.isfile(crt_path):
+ raise OSUtilError("Can't find {0}.crt".format(thumbprint))
+ pub_path = os.path.join(lib_dir, thumbprint + '.pub')
+ pub = self.get_pubkey_from_crt(crt_path)
+ fileutil.write_file(pub_path, pub)
+ self.set_selinux_context(pub_path,
+ 'unconfined_u:object_r:ssh_home_t:s0')
+ self.openssl_to_openssh(pub_path, path)
+ fileutil.chmod(pub_path, 0o600)
+ else:
+ raise OSUtilError("SSH public key Fingerprint and Value are None")
+
+ self.set_selinux_context(path, 'unconfined_u:object_r:ssh_home_t:s0')
+ fileutil.chowner(path, username)
+ fileutil.chmod(path, 0o644)
+
+ def is_selinux_system(self):
+ """
+ Checks and sets self.selinux = True if SELinux is available on system.
+ """
+ if self.selinux == None:
+ if shellutil.run("which getenforce", chk_err=False) == 0:
+ self.selinux = True
+ else:
+ self.selinux = False
+ return self.selinux
+
+ def is_selinux_enforcing(self):
+ """
+ Calls shell command 'getenforce' and returns True if 'Enforcing'.
+ """
+ if self.is_selinux_system():
+ output = shellutil.run_get_output("getenforce")[1]
+ return output.startswith("Enforcing")
+ else:
+ return False
+
+ def set_selinux_enforce(self, state):
+ """
+ Calls shell command 'setenforce' with 'state'
+ and returns resulting exit code.
+ """
+ if self.is_selinux_system():
+ if state: s = '1'
+ else: s='0'
+ return shellutil.run("setenforce "+s)
+
+ def set_selinux_context(self, path, con):
+ """
+ Calls shell 'chcon' with 'path' and 'con' context.
+ Returns exit result.
+ """
+ if self.is_selinux_system():
+ return shellutil.run('chcon ' + con + ' ' + path)
+
+ def get_sshd_conf_file_path(self):
+ return self.sshd_conf_file_path
+
+ def set_ssh_client_alive_interval(self):
+ conf_file_path = self.get_sshd_conf_file_path()
+ conf = fileutil.read_file(conf_file_path).split("\n")
+ textutil.set_ssh_config(conf, "ClientAliveInterval", "180")
+ fileutil.write_file(conf_file_path, '\n'.join(conf))
+ logger.info("Configured SSH client probing to keep connections alive.")
+
+ def conf_sshd(self, disable_password):
+ option = "no" if disable_password else "yes"
+ conf_file_path = self.get_sshd_conf_file_path()
+ conf = fileutil.read_file(conf_file_path).split("\n")
+ textutil.set_ssh_config(conf, "PasswordAuthentication", option)
+ textutil.set_ssh_config(conf, "ChallengeResponseAuthentication", option)
+ fileutil.write_file(conf_file_path, "\n".join(conf))
+ logger.info("Disabled SSH password-based authentication methods.")
+
+
+ def get_dvd_device(self, dev_dir='/dev'):
+ patten=r'(sr[0-9]|hd[c-z]|cdrom[0-9])'
+ for dvd in [re.match(patten, dev) for dev in os.listdir(dev_dir)]:
+ if dvd is not None:
+ return "/dev/{0}".format(dvd.group(0))
+ raise OSUtilError("Failed to get dvd device")
+
+ def mount_dvd(self, max_retry=6, chk_err=True):
+ dvd = self.get_dvd_device()
+ mount_point = self.get_dvd_mount_point()
+ mountlist = shellutil.run_get_output("mount")[1]
+ existing = self.get_mount_point(mountlist, dvd)
+ if existing is not None: #Already mounted
+ logger.info("{0} is already mounted at {1}", dvd, existing)
+ return
+ if not os.path.isdir(mount_point):
+ os.makedirs(mount_point)
+
+ for retry in range(0, max_retry):
+ retcode = self.mount(dvd, mount_point, option="-o ro -t iso9660,udf",
+ chk_err=chk_err)
+ if retcode == 0:
+ logger.info("Successfully mounted dvd")
+ return
+ if retry < max_retry - 1:
+ logger.warn("Mount dvd failed: retry={0}, ret={1}", retry,
+ retcode)
+ time.sleep(5)
+ if chk_err:
+ raise OSUtilError("Failed to mount dvd.")
+
+ def umount_dvd(self, chk_err=True):
+ mount_point = self.get_dvd_mount_point()
+ retcode = self.umount(mount_point, chk_err=chk_err)
+ if chk_err and retcode != 0:
+ raise OSUtilError("Failed to umount dvd.")
+
+ def eject_dvd(self, chk_err=True):
+ retcode = shellutil.run("eject")
+ if chk_err and retcode != 0:
+ raise OSUtilError("Failed to eject dvd")
+
+ def load_atappix_mod(self):
+ if self.is_atapiix_mod_loaded():
+ return
+ ret, kern_version = shellutil.run_get_output("uname -r")
+ if ret != 0:
+ raise Exception("Failed to call uname -r")
+ mod_path = os.path.join('/lib/modules',
+ kern_version.strip('\n'),
+ 'kernel/drivers/ata/ata_piix.ko')
+ if not os.path.isfile(mod_path):
+ raise Exception("Can't find module file:{0}".format(mod_path))
+
+ ret, output = shellutil.run_get_output("insmod " + mod_path)
+ if ret != 0:
+ raise Exception("Error calling insmod for ATAPI CD-ROM driver")
+ if not self.is_atapiix_mod_loaded(max_retry=3):
+ raise Exception("Failed to load ATAPI CD-ROM driver")
+
+ def is_atapiix_mod_loaded(self, max_retry=1):
+ for retry in range(0, max_retry):
+ ret = shellutil.run("lsmod | grep ata_piix", chk_err=False)
+ if ret == 0:
+ logger.info("Module driver for ATAPI CD-ROM is already present.")
+ return True
+ if retry < max_retry - 1:
+ time.sleep(1)
+ return False
+
+ def mount(self, dvd, mount_point, option="", chk_err=True):
+ cmd = "mount {0} {1} {2}".format(dvd, option, mount_point)
+ return shellutil.run_get_output(cmd, chk_err)[0]
+
+ def umount(self, mount_point, chk_err=True):
+ return shellutil.run("umount {0}".format(mount_point), chk_err=chk_err)
+
+ def allow_dhcp_broadcast(self):
+ #Open DHCP port if iptables is enabled.
+ # We supress error logging on error.
+ shellutil.run("iptables -D INPUT -p udp --dport 68 -j ACCEPT",
+ chk_err=False)
+ shellutil.run("iptables -I INPUT -p udp --dport 68 -j ACCEPT",
+ chk_err=False)
+
+ def gen_transport_cert(self):
+ """
+ Create ssl certificate for https communication with endpoint server.
+ """
+ cmd = ("{0} req -x509 -nodes -subj /CN=LinuxTransport -days 32768 "
+ "-newkey rsa:2048 -keyout TransportPrivate.pem "
+ "-out TransportCert.pem").format(self.openssl_cmd)
+ shellutil.run(cmd)
+
+ def remove_rules_files(self, rules_files=__RULES_FILES__):
+ lib_dir = self.get_lib_dir()
+ for src in rules_files:
+ file_name = fileutil.base_name(src)
+ dest = os.path.join(lib_dir, file_name)
+ if os.path.isfile(dest):
+ os.remove(dest)
+ if os.path.isfile(src):
+ logger.warn("Move rules file {0} to {1}", file_name, dest)
+ shutil.move(src, dest)
+
+ def restore_rules_files(self, rules_files=__RULES_FILES__):
+ lib_dir = self.get_lib_dir()
+ for dest in rules_files:
+ filename = fileutil.base_name(dest)
+ src = os.path.join(lib_dir, filename)
+ if os.path.isfile(dest):
+ continue
+ if os.path.isfile(src):
+ logger.warn("Move rules file {0} to {1}", filename, dest)
+ shutil.move(src, dest)
+
+ def get_mac_addr(self):
+ """
+ Convienience function, returns mac addr bound to
+ first non-loobback interface.
+ """
+ ifname=''
+ while len(ifname) < 2 :
+ ifname=self.get_first_if()[0]
+ addr = self.get_if_mac(ifname)
+ return textutil.hexstr_to_bytearray(addr)
+
+ def get_if_mac(self, ifname):
+ """
+ Return the mac-address bound to the socket.
+ """
+ sock = socket.socket(socket.AF_INET,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP)
+ param = struct.pack('256s', (ifname[:15]+('\0'*241)).encode('latin-1'))
+ info = fcntl.ioctl(sock.fileno(), 0x8927, param)
+ return ''.join(['%02X' % textutil.str_to_ord(char) for char in info[18:24]])
+
+ def get_first_if(self):
+ """
+ Return the interface name, and ip addr of the
+ first active non-loopback interface.
+ """
+ iface=''
+ expected=16 # how many devices should I expect...
+ struct_size=40 # for 64bit the size is 40 bytes
+ sock = socket.socket(socket.AF_INET,
+ socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP)
+ buff=array.array('B', b'\0' * (expected * struct_size))
+ param = struct.pack('iL',
+ expected*struct_size,
+ buff.buffer_info()[0])
+ ret = fcntl.ioctl(sock.fileno(), 0x8912, param)
+ retsize=(struct.unpack('iL', ret)[0])
+ if retsize == (expected * struct_size):
+ logger.warn(('SIOCGIFCONF returned more than {0} up '
+ 'network interfaces.'), expected)
+ sock = buff.tostring()
+ for i in range(0, struct_size * expected, struct_size):
+ iface=sock[i:i+16].split(b'\0', 1)[0]
+ if iface == b'lo':
+ continue
+ else:
+ break
+ return iface.decode('latin-1'), socket.inet_ntoa(sock[i+20:i+24])
+
+ def is_missing_default_route(self):
+ routes = shellutil.run_get_output("route -n")[1]
+ for route in routes.split("\n"):
+ if route.startswith("0.0.0.0 ") or route.startswith("default "):
+ return False
+ return True
+
+ def get_if_name(self):
+ return self.get_first_if()[0]
+
+ def get_ip4_addr(self):
+ return self.get_first_if()[1]
+
+ def set_route_for_dhcp_broadcast(self, ifname):
+ return shellutil.run("route add 255.255.255.255 dev {0}".format(ifname),
+ chk_err=False)
+
+ def remove_route_for_dhcp_broadcast(self, ifname):
+ shellutil.run("route del 255.255.255.255 dev {0}".format(ifname),
+ chk_err=False)
+
+ def is_dhcp_enabled(self):
+ return False
+
+ def stop_dhcp_service(self):
+ pass
+
+ def start_dhcp_service(self):
+ pass
+
+ def start_network(self):
+ pass
+
+ def start_agent_service(self):
+ pass
+
+ def stop_agent_service(self):
+ pass
+
+ def register_agent_service(self):
+ pass
+
+ def unregister_agent_service(self):
+ pass
+
+ def restart_ssh_service(self):
+ pass
+
+ def route_add(self, net, mask, gateway):
+ """
+ Add specified route using /sbin/route add -net.
+ """
+ cmd = ("/sbin/route add -net "
+ "{0} netmask {1} gw {2}").format(net, mask, gateway)
+ return shellutil.run(cmd, chk_err=False)
+
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof dhclient")
+ return ret[1] if ret[0] == 0 else None
+
+ def set_hostname(self, hostname):
+ fileutil.write_file('/etc/hostname', hostname)
+ shellutil.run("hostname {0}".format(hostname), chk_err=False)
+
+ def set_dhcp_hostname(self, hostname):
+ autosend = r'^[^#]*?send\s*host-name.*?(<hostname>|gethostname[(,)])'
+ dhclient_files = ['/etc/dhcp/dhclient.conf', '/etc/dhcp3/dhclient.conf']
+ for conf_file in dhclient_files:
+ if not os.path.isfile(conf_file):
+ continue
+ if fileutil.findstr_in_file(conf_file, autosend):
+ #Return if auto send host-name is configured
+ return
+ fileutil.update_conf_file(conf_file,
+ 'send host-name',
+ 'send host-name {0}'.format(hostname))
+
+ def restart_if(self, ifname):
+ shellutil.run("ifdown {0} && ifup {1}".format(ifname, ifname))
+
+ def publish_hostname(self, hostname):
+ self.set_dhcp_hostname(hostname)
+ ifname = self.get_if_name()
+ self.restart_if(ifname)
+
+ def set_scsi_disks_timeout(self, timeout):
+ for dev in os.listdir("/sys/block"):
+ if dev.startswith('sd'):
+ self.set_block_device_timeout(dev, timeout)
+
+ def set_block_device_timeout(self, dev, timeout):
+ if dev is not None and timeout is not None:
+ file_path = "/sys/block/{0}/device/timeout".format(dev)
+ content = fileutil.read_file(file_path)
+ original = content.splitlines()[0].rstrip()
+ if original != timeout:
+ fileutil.write_file(file_path, timeout)
+ logger.info("Set block dev timeout: {0} with timeout: {1}",
+ dev, timeout)
+
+ def get_mount_point(self, mountlist, device):
+ """
+ Example of mountlist:
+ /dev/sda1 on / type ext4 (rw)
+ proc on /proc type proc (rw)
+ sysfs on /sys type sysfs (rw)
+ devpts on /dev/pts type devpts (rw,gid=5,mode=620)
+ tmpfs on /dev/shm type tmpfs
+ (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
+ none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
+ /dev/sdb1 on /mnt/resource type ext4 (rw)
+ """
+ if (mountlist and device):
+ for entry in mountlist.split('\n'):
+ if(re.search(device, entry)):
+ tokens = entry.split()
+ #Return the 3rd column of this line
+ return tokens[2] if len(tokens) > 2 else None
+ return None
+
+ def device_for_ide_port(self, port_id):
+ """
+ Return device name attached to ide port 'n'.
+ """
+ if port_id > 3:
+ return None
+ g0 = "00000000"
+ if port_id > 1:
+ g0 = "00000001"
+ port_id = port_id - 2
+ device = None
+ path = "/sys/bus/vmbus/devices/"
+ for vmbus in os.listdir(path):
+ deviceid = fileutil.read_file(os.path.join(path, vmbus, "device_id"))
+ guid = deviceid.lstrip('{').split('-')
+ if guid[0] == g0 and guid[1] == "000" + text(port_id):
+ for root, dirs, files in os.walk(path + vmbus):
+ if root.endswith("/block"):
+ device = dirs[0]
+ break
+ else : #older distros
+ for d in dirs:
+ if ':' in d and "block" == d.split(':')[0]:
+ device = d.split(':')[1]
+ break
+ break
+ return device
+
+ def del_account(self, username):
+ if self.is_sys_user(username):
+ logger.error("{0} is a system user. Will not delete it.", username)
+ shellutil.run("> /var/run/utmp")
+ shellutil.run("userdel -f -r " + username)
+ #Remove user from suders
+ if os.path.isfile("/etc/suders.d/waagent"):
+ try:
+ content = fileutil.read_file("/etc/sudoers.d/waagent")
+ sudoers = content.split("\n")
+ sudoers = [x for x in sudoers if username not in x]
+ fileutil.write_file("/etc/sudoers.d/waagent",
+ "\n".join(sudoers))
+ except IOError as e:
+ raise OSUtilError("Failed to remove sudoer: {0}".format(e))
+
+ def decode_customdata(self, data):
+ return data
+
+ def get_total_mem(self):
+ cmd = "grep MemTotal /proc/meminfo |awk '{print $2}'"
+ ret = shellutil.run_get_output(cmd)
+ if ret[0] == 0:
+ return int(ret[1])/1024
+ else:
+ raise OSUtilError("Failed to get total memory: {0}".format(ret[1]))
+
+ def get_processor_cores(self):
+ ret = shellutil.run_get_output("grep 'processor.*:' /proc/cpuinfo |wc -l")
+ if ret[0] == 0:
+ return int(ret[1])
+ else:
+ raise OSUtilError("Failed to get procerssor cores")
+
diff --git a/azurelinuxagent/distro/default/provision.py b/azurelinuxagent/distro/default/provision.py
new file mode 100644
index 0000000..1e9c459
--- /dev/null
+++ b/azurelinuxagent/distro/default/provision.py
@@ -0,0 +1,165 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+"""
+Provision handler
+"""
+
+import os
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+from azurelinuxagent.event import add_event, WALAEventOperation
+from azurelinuxagent.exception import *
+from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError
+import azurelinuxagent.protocol as prot
+import azurelinuxagent.protocol.ovfenv as ovf
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.fileutil as fileutil
+
+CUSTOM_DATA_FILE="CustomData"
+
+class ProvisionHandler(object):
+
+ def process(self):
+ #If provision is not enabled, return
+ if not conf.get_switch("Provisioning.Enabled", True):
+ logger.info("Provisioning is disabled. Skip.")
+ return
+
+ provisioned = os.path.join(OSUTIL.get_lib_dir(), "provisioned")
+ if os.path.isfile(provisioned):
+ return
+
+ logger.info("run provision handler.")
+ protocol = prot.FACTORY.get_default_protocol()
+ try:
+ status = prot.ProvisionStatus(status="NotReady",
+ subStatus="Provision started")
+ protocol.report_provision_status(status)
+
+ self.provision()
+ fileutil.write_file(provisioned, "")
+ thumbprint = self.reg_ssh_host_key()
+
+ logger.info("Finished provisioning")
+ status = prot.ProvisionStatus(status="Ready")
+ status.properties.certificateThumbprint = thumbprint
+ protocol.report_provision_status(status)
+
+ add_event(name="WALA", is_success=True, message="",
+ op=WALAEventOperation.Provision)
+ except ProvisionError as e:
+ logger.error("Provision failed: {0}", e)
+ status = prot.ProvisionStatus(status="NotReady",
+ subStatus= text(e))
+ protocol.report_provision_status(status)
+ add_event(name="WALA", is_success=False, message=text(e),
+ op=WALAEventOperation.Provision)
+
+ def reg_ssh_host_key(self):
+ keypair_type = conf.get("Provisioning.SshHostKeyPairType", "rsa")
+ if conf.get_switch("Provisioning.RegenerateSshHostKeyPair"):
+ shellutil.run("rm -f /etc/ssh/ssh_host_*key*")
+ shellutil.run(("ssh-keygen -N '' -t {0} -f /etc/ssh/ssh_host_{1}_key"
+ "").format(keypair_type, keypair_type))
+ thumbprint = self.get_ssh_host_key_thumbprint(keypair_type)
+ return thumbprint
+
+ def get_ssh_host_key_thumbprint(self, keypair_type):
+ cmd = "ssh-keygen -lf /etc/ssh/ssh_host_{0}_key.pub".format(keypair_type)
+ ret = shellutil.run_get_output(cmd)
+ if ret[0] == 0:
+ return ret[1].rstrip().split()[1].replace(':', '')
+ else:
+ raise ProvisionError(("Failed to generate ssh host key: "
+ "ret={0}, out= {1}").format(ret[0], ret[1]))
+
+
+ def provision(self):
+ logger.info("Copy ovf-env.xml.")
+ try:
+ ovfenv = ovf.copy_ovf_env()
+ except prot.ProtocolError as e:
+ raise ProvisionError("Failed to copy ovf-env.xml: {0}".format(e))
+
+ logger.info("Handle ovf-env.xml.")
+ try:
+ logger.info("Set host name.")
+ OSUTIL.set_hostname(ovfenv.hostname)
+
+ logger.info("Publish host name.")
+ OSUTIL.publish_hostname(ovfenv.hostname)
+
+ self.config_user_account(ovfenv)
+
+ self.save_customdata(ovfenv)
+
+ if conf.get_switch("Provisioning.DeleteRootPassword"):
+ OSUTIL.del_root_password()
+ except OSUtilError as e:
+ raise ProvisionError("Failed to handle ovf-env.xml: {0}".format(e))
+
+ def config_user_account(self, ovfenv):
+ logger.info("Create user account if not exists")
+ OSUTIL.useradd(ovfenv.username)
+
+ if ovfenv.user_password is not None:
+ logger.info("Set user password.")
+ use_salt = conf.get_switch("Provision.UseSalt", True)
+ salt_type = conf.get_switch("Provision.SaltType", 6)
+ OSUTIL.chpasswd(ovfenv.username, ovfenv.user_password,
+ use_salt,salt_type)
+
+ logger.info("Configure sudoer")
+ OSUTIL.conf_sudoer(ovfenv.username, ovfenv.user_password is None)
+
+ logger.info("Configure sshd")
+ OSUTIL.conf_sshd(ovfenv.disable_ssh_password_auth)
+
+ #Disable selinux temporary
+ sel = OSUTIL.is_selinux_enforcing()
+ if sel:
+ OSUTIL.set_selinux_enforce(0)
+
+ self.deploy_ssh_pubkeys(ovfenv)
+ self.deploy_ssh_keypairs(ovfenv)
+
+ if sel:
+ OSUTIL.set_selinux_enforce(1)
+
+ OSUTIL.restart_ssh_service()
+
+ def save_customdata(self, ovfenv):
+ logger.info("Save custom data")
+ customdata = ovfenv.customdata
+ if customdata is None:
+ return
+ lib_dir = OSUTIL.get_lib_dir()
+ fileutil.write_file(os.path.join(lib_dir, CUSTOM_DATA_FILE),
+ OSUTIL.decode_customdata(customdata))
+
+ def deploy_ssh_pubkeys(self, ovfenv):
+ for pubkey in ovfenv.ssh_pubkeys:
+ logger.info("Deploy ssh public key.")
+ OSUTIL.deploy_ssh_pubkey(ovfenv.username, pubkey)
+
+ def deploy_ssh_keypairs(self, ovfenv):
+ for keypair in ovfenv.ssh_keypairs:
+ logger.info("Deploy ssh key pairs.")
+ OSUTIL.deploy_ssh_keypair(ovfenv.username, keypair)
+
diff --git a/azurelinuxagent/distro/default/resourceDisk.py b/azurelinuxagent/distro/default/resourceDisk.py
new file mode 100644
index 0000000..d4ef1c9
--- /dev/null
+++ b/azurelinuxagent/distro/default/resourceDisk.py
@@ -0,0 +1,166 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import threading
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+from azurelinuxagent.utils.osutil import OSUTIL
+from azurelinuxagent.event import add_event, WALAEventOperation
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+from azurelinuxagent.exception import ResourceDiskError
+
+DATALOSS_WARNING_FILE_NAME="DATALOSS_WARNING_README.txt"
+DATA_LOSS_WARNING="""\
+WARNING: THIS IS A TEMPORARY DISK.
+
+Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT.
+
+Please do not use this disk for storing any personal or application data.
+
+For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx
+"""
+
+class ResourceDiskHandler(object):
+
+ def start_activate_resource_disk(self):
+ disk_thread = threading.Thread(target = self.run)
+ disk_thread.start()
+
+ def run(self):
+ mount_point = None
+ if conf.get_switch("ResourceDisk.Format", False):
+ mount_point = self.activate_resource_disk()
+ if mount_point is not None and \
+ conf.get_switch("ResourceDisk.EnableSwap", False):
+ self.enable_swap(mount_point)
+
+ def activate_resource_disk(self):
+ logger.info("Activate resource disk")
+ try:
+ mount_point = conf.get("ResourceDisk.MountPoint", "/mnt/resource")
+ fs = conf.get("ResourceDisk.Filesystem", "ext3")
+ mount_point = self.mount_resource_disk(mount_point, fs)
+ warning_file = os.path.join(mount_point, DATALOSS_WARNING_FILE_NAME)
+ try:
+ fileutil.write_file(warning_file, DATA_LOSS_WARNING)
+ except IOError as e:
+ logger.warn("Failed to write data loss warnning:{0}", e)
+ return mount_point
+ except ResourceDiskError as e:
+ logger.error("Failed to mount resource disk {0}", e)
+ add_event(name="WALA", is_success=False, message=text(e),
+ op=WALAEventOperation.ActivateResourceDisk)
+
+ def enable_swap(self, mount_point):
+ logger.info("Enable swap")
+ try:
+ size_mb = conf.get_int("ResourceDisk.SwapSizeMB", 0)
+ self.create_swap_space(mount_point, size_mb)
+ except ResourceDiskError as e:
+ logger.error("Failed to enable swap {0}", e)
+
+ def mount_resource_disk(self, mount_point, fs):
+ device = OSUTIL.device_for_ide_port(1)
+ if device is None:
+ raise ResourceDiskError("unable to detect disk topology")
+
+ device = "/dev/" + device
+ mountlist = shellutil.run_get_output("mount")[1]
+ existing = OSUTIL.get_mount_point(mountlist, device)
+
+ if(existing):
+ logger.info("Resource disk {0}1 is already mounted", device)
+ return existing
+
+ fileutil.mkdir(mount_point, mode=0o755)
+
+ logger.info("Detect GPT...")
+ partition = device + "1"
+ ret = shellutil.run_get_output("parted {0} print".format(device))
+ if ret[0]:
+ raise ResourceDiskError("({0}) {1}".format(device, ret[1]))
+
+ if "gpt" in ret[1]:
+ logger.info("GPT detected")
+ logger.info("Get GPT partitions")
+ parts = [x for x in ret[1].split("\n") if re.match("^\s*[0-9]+", x)]
+ logger.info("Found more than {0} GPT partitions.", len(parts))
+ if len(parts) > 1:
+ logger.info("Remove old GPT partitions")
+ for i in range(1, len(parts) + 1):
+ logger.info("Remove partition: {0}", i)
+ shellutil.run("parted {0} rm {1}".format(device, i))
+
+ logger.info("Create a new GPT partition using entire disk space")
+ shellutil.run("parted {0} mkpart primary 0% 100%".format(device))
+
+ logger.info("Format partition: {0} with fstype {1}",partition,fs)
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+ else:
+ logger.info("GPT not detected")
+ logger.info("Check fstype")
+ ret = shellutil.run_get_output("sfdisk -q -c {0} 1".format(device))
+ if ret[1].rstrip() == "7" and fs != "ntfs":
+ logger.info("The partition is formatted with ntfs")
+ logger.info("Format partition: {0} with fstype {1}",partition,fs)
+ shellutil.run("sfdisk -c {0} 1 83".format(device))
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+
+ logger.info("Mount resource disk")
+ ret = shellutil.run("mount {0} {1}".format(partition, mount_point),
+ chk_err=False)
+ if ret:
+ logger.warn("Failed to mount resource disk. Retry mounting")
+ shellutil.run("mkfs." + fs + " " + partition + " -F")
+ ret = shellutil.run("mount {0} {1}".format(partition, mount_point))
+ if ret:
+ raise ResourceDiskError("({0}) {1}".format(partition, ret))
+
+ logger.info("Resource disk ({0}) is mounted at {1} with fstype {2}",
+ device, mount_point, fs)
+ return mount_point
+
+ def create_swap_space(self, mount_point, size_mb):
+ size_kb = size_mb * 1024
+ size = size_kb * 1024
+ swapfile = os.path.join(mount_point, 'swapfile')
+ swaplist = shellutil.run_get_output("swapon -s")[1]
+
+ if swapfile in swaplist and os.path.getsize(swapfile) == size:
+ logger.info("Swap already enabled")
+ return
+
+ if os.path.isfile(swapfile) and os.path.getsize(swapfile) != size:
+ logger.info("Remove old swap file")
+ shellutil.run("swapoff -a", chk_err=False)
+ os.remove(swapfile)
+
+ if not os.path.isfile(swapfile):
+ logger.info("Create swap file")
+ shellutil.run(("dd if=/dev/zero of={0} bs=1024 "
+ "count={1}").format(swapfile, size_kb))
+ shellutil.run("mkswap {0}".format(swapfile))
+ if shellutil.run("swapon {0}".format(swapfile)):
+ raise ResourceDiskError("{0}".format(swapfile))
+ logger.info("Enabled {0}KB of swap at {1}".format(size_kb, swapfile))
+
diff --git a/azurelinuxagent/distro/default/run.py b/azurelinuxagent/distro/default/run.py
new file mode 100644
index 0000000..13880b4
--- /dev/null
+++ b/azurelinuxagent/distro/default/run.py
@@ -0,0 +1,86 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import time
+import sys
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+from azurelinuxagent.metadata import AGENT_LONG_NAME, AGENT_VERSION, \
+ DISTRO_NAME, DISTRO_VERSION, \
+ DISTRO_FULL_NAME, PY_VERSION_MAJOR, \
+ PY_VERSION_MINOR, PY_VERSION_MICRO
+import azurelinuxagent.protocol as prot
+import azurelinuxagent.event as event
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.fileutil as fileutil
+
+
+class MainHandler(object):
+ def __init__(self, handlers):
+ self.handlers = handlers
+
+ def run(self):
+ logger.info("{0} Version:{1}", AGENT_LONG_NAME, AGENT_VERSION)
+ logger.info("OS: {0} {1}", DISTRO_NAME, DISTRO_VERSION)
+ logger.info("Python: {0}.{1}.{2}", PY_VERSION_MAJOR, PY_VERSION_MINOR,
+ PY_VERSION_MICRO)
+
+ event.enable_unhandled_err_dump("Azure Linux Agent")
+ fileutil.write_file(OSUTIL.get_agent_pid_file_path(), text(os.getpid()))
+
+ if conf.get_switch("DetectScvmmEnv", False):
+ if self.handlers.scvmm_handler.detect_scvmm_env():
+ return
+
+ self.handlers.dhcp_handler.probe()
+
+ prot.detect_default_protocol()
+
+ event.EventMonitor().start()
+
+ self.handlers.provision_handler.process()
+
+ if conf.get_switch("ResourceDisk.Format", False):
+ self.handlers.resource_disk_handler.start_activate_resource_disk()
+
+ self.handlers.env_handler.start()
+
+ protocol = prot.FACTORY.get_default_protocol()
+ while True:
+
+ #Handle extensions
+ h_status_list = self.handlers.extension_handler.process()
+
+ #Report status
+ vm_status = prot.VMStatus()
+ vm_status.vmAgent.agentVersion = AGENT_LONG_NAME
+ vm_status.vmAgent.status = "Ready"
+ vm_status.vmAgent.message = "Guest Agent is running"
+ for h_status in h_status_list:
+ vm_status.extensionHandlers.append(h_status)
+ try:
+ logger.info("Report vm status")
+ protocol.report_status(vm_status)
+ except prot.ProtocolError as e:
+ logger.error("Failed to report vm status: {0}", e)
+
+ time.sleep(25)
+
diff --git a/azurelinuxagent/distro/default/scvmm.py b/azurelinuxagent/distro/default/scvmm.py
new file mode 100644
index 0000000..18fad4b
--- /dev/null
+++ b/azurelinuxagent/distro/default/scvmm.py
@@ -0,0 +1,47 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import subprocess
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+
+VMM_CONF_FILE_NAME = "linuxosconfiguration.xml"
+VMM_STARTUP_SCRIPT_NAME= "install"
+
+class ScvmmHandler(object):
+
+ def detect_scvmm_env(self):
+ logger.info("Detecting Microsoft System Center VMM Environment")
+ OSUTIL.mount_dvd(max_retry=1, chk_err=False)
+ mount_point = OSUTIL.get_dvd_mount_point()
+ found = os.path.isfile(os.path.join(mount_point, VMM_CONF_FILE_NAME))
+ if found:
+ self.start_scvmm_agent()
+ else:
+ OSUTIL.umount_dvd(chk_err=False)
+ return found
+
+ def start_scvmm_agent(self):
+ logger.info("Starting Microsoft System Center VMM Initialization "
+ "Process")
+ mount_point = OSUTIL.get_dvd_mount_point()
+ startup_script = os.path.join(mount_point, VMM_STARTUP_SCRIPT_NAME)
+ subprocess.Popen(["/bin/bash", startup_script, "-p " + mount_point])
+
diff --git a/azurelinuxagent/distro/loader.py b/azurelinuxagent/distro/loader.py
new file mode 100644
index 0000000..0060a7f
--- /dev/null
+++ b/azurelinuxagent/distro/loader.py
@@ -0,0 +1,46 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.logger as logger
+from azurelinuxagent.metadata import DISTRO_NAME
+import azurelinuxagent.distro.default.loader as default_loader
+
+
+def get_distro_loader():
+ try:
+ logger.verb("Loading distro implemetation from: {0}", DISTRO_NAME)
+ pkg_name = "azurelinuxagent.distro.{0}.loader".format(DISTRO_NAME)
+ return __import__(pkg_name, fromlist="loader")
+ except ImportError as e:
+ logger.warn("Unable to load distro implemetation for {0}.", DISTRO_NAME)
+ logger.warn("Use default distro implemetation instead.")
+ return default_loader
+
+DISTRO_LOADER = get_distro_loader()
+
+def get_osutil():
+ try:
+ return DISTRO_LOADER.get_osutil()
+ except AttributeError:
+ return default_loader.get_osutil()
+
+def get_handlers():
+ try:
+ return DISTRO_LOADER.get_handlers()
+ except AttributeError:
+ return default_loader.get_handlers()
+
diff --git a/azurelinuxagent/distro/oracle/__init__.py b/azurelinuxagent/distro/oracle/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/oracle/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/oracle/loader.py b/azurelinuxagent/distro/oracle/loader.py
new file mode 100644
index 0000000..379f027
--- /dev/null
+++ b/azurelinuxagent/distro/oracle/loader.py
@@ -0,0 +1,25 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+import azurelinuxagent.distro.redhat.loader as redhat
+
+def get_osutil():
+ return redhat.get_osutil()
+
diff --git a/azurelinuxagent/distro/redhat/__init__.py b/azurelinuxagent/distro/redhat/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/redhat/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/redhat/loader.py b/azurelinuxagent/distro/redhat/loader.py
new file mode 100644
index 0000000..911e74d
--- /dev/null
+++ b/azurelinuxagent/distro/redhat/loader.py
@@ -0,0 +1,28 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+
+def get_osutil():
+ from azurelinuxagent.distro.redhat.osutil import Redhat6xOSUtil, RedhatOSUtil
+ if DISTRO_VERSION < "7":
+ return Redhat6xOSUtil()
+ else:
+ return RedhatOSUtil()
+
diff --git a/azurelinuxagent/distro/redhat/osutil.py b/azurelinuxagent/distro/redhat/osutil.py
new file mode 100644
index 0000000..c6c3016
--- /dev/null
+++ b/azurelinuxagent/distro/redhat/osutil.py
@@ -0,0 +1,148 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import base64
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text, bytebuffer
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil, OSUtilError
+
+class Redhat6xOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(Redhat6xOSUtil, self).__init__()
+ self.sshd_conf_file_path = '/etc/ssh/sshd_config'
+ self.openssl_cmd = '/usr/bin/openssl'
+ self.conf_file_path = '/etc/waagent.conf'
+ self.selinux=None
+
+ def start_network(self):
+ return shellutil.run("/sbin/service networking start", chk_err=False)
+
+ def restart_ssh_service(self):
+ return shellutil.run("/sbin/service sshd condrestart", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("/sbin/service waagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("/sbin/service waagent start", chk_err=False)
+
+ def register_agent_service(self):
+ return shellutil.run("chkconfig --add waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("chkconfig --del waagent", chk_err=False)
+
+ def asn1_to_ssh_rsa(self, pubkey):
+ lines = pubkey.split("\n")
+ lines = [x for x in lines if not x.startswith("----")]
+ base64_encoded = "".join(lines)
+ try:
+ #TODO remove pyasn1 dependency
+ from pyasn1.codec.der import decoder as der_decoder
+ der_encoded = base64.b64decode(base64_encoded)
+ der_encoded = der_decoder.decode(der_encoded)[0][1]
+ key = der_decoder.decode(self.bits_to_bytes(der_encoded))[0]
+ n=key[0]
+ e=key[1]
+ keydata = bytearray()
+ keydata.extend(struct.pack('>I', len("ssh-rsa")))
+ keydata.extend(b"ssh-rsa")
+ keydata.extend(struct.pack('>I', len(self.num_to_bytes(e))))
+ keydata.extend(self.num_to_bytes(e))
+ keydata.extend(struct.pack('>I', len(self.num_to_bytes(n)) + 1))
+ keydata.extend(b"\0")
+ keydata.extend(self.num_to_bytes(n))
+ keydata_base64 = base64.b64encode(bytebuffer(keydata))
+ return text(b"ssh-rsa " + keydata_base64 + b"\n",
+ encoding='utf-8')
+ except ImportError as e:
+ raise OSUtilError("Failed to load pyasn1.codec.der")
+
+ def num_to_bytes(self, num):
+ """
+ Pack number into bytes. Retun as string.
+ """
+ result = bytearray()
+ while num:
+ result.append(num & 0xFF)
+ num >>= 8
+ result.reverse()
+ return result
+
+ def bits_to_bytes(self, bits):
+ """
+ Convert an array contains bits, [0,1] to a byte array
+ """
+ index = 7
+ byte_array = bytearray()
+ curr = 0
+ for bit in bits:
+ curr = curr | (bit << index)
+ index = index - 1
+ if index == -1:
+ byte_array.append(curr)
+ curr = 0
+ index = 7
+ return bytes(byte_array)
+
+ def openssl_to_openssh(self, input_file, output_file):
+ pubkey = fileutil.read_file(input_file)
+ ssh_rsa_pubkey = self.asn1_to_ssh_rsa(pubkey)
+ fileutil.write_file(output_file, ssh_rsa_pubkey)
+
+ #Override
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof dhclient")
+ return ret[1] if ret[0] == 0 else None
+
+class RedhatOSUtil(Redhat6xOSUtil):
+ def __init__(self):
+ super(RedhatOSUtil, self).__init__()
+
+ def set_hostname(self, hostname):
+ super(RedhatOSUtil, self).set_hostname(hostname)
+ fileutil.update_conf_file('/etc/sysconfig/network',
+ 'HOSTNAME',
+ 'HOSTNAME={0}'.format(hostname))
+
+ def set_dhcp_hostname(self, hostname):
+ ifname = self.get_if_name()
+ filepath = "/etc/sysconfig/network-scripts/ifcfg-{0}".format(ifname)
+ fileutil.update_conf_file(filepath,
+ 'DHCP_HOSTNAME',
+ 'DHCP_HOSTNAME={0}'.format(hostname))
+
+ def register_agent_service(self):
+ return shellutil.run("systemctl enable waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("systemctl disable waagent", chk_err=False)
+
+
diff --git a/azurelinuxagent/distro/suse/__init__.py b/azurelinuxagent/distro/suse/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/suse/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/suse/loader.py b/azurelinuxagent/distro/suse/loader.py
new file mode 100644
index 0000000..e38aa17
--- /dev/null
+++ b/azurelinuxagent/distro/suse/loader.py
@@ -0,0 +1,29 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME
+
+def get_osutil():
+ from azurelinuxagent.distro.suse.osutil import SUSE11OSUtil, SUSEOSUtil
+ if DISTRO_FULL_NAME=='SUSE Linux Enterprise Server' and DISTRO_VERSION < '12' \
+ or DISTRO_FULL_NAME == 'openSUSE' and DISTRO_VERSION < '13.2':
+ return SUSE11OSUtil()
+ else:
+ return SUSEOSUtil()
+
diff --git a/azurelinuxagent/distro/suse/osutil.py b/azurelinuxagent/distro/suse/osutil.py
new file mode 100644
index 0000000..870e0b7
--- /dev/null
+++ b/azurelinuxagent/distro/suse/osutil.py
@@ -0,0 +1,88 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class SUSE11OSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(SUSE11OSUtil, self).__init__()
+ self.dhclient_name='dhcpcd'
+
+ def set_hostname(self, hostname):
+ fileutil.write_file('/etc/HOSTNAME', hostname)
+ shellutil.run("hostname {0}".format(hostname), chk_err=False)
+
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof {0}".format(self.dhclient_name))
+ return ret[1] if ret[0] == 0 else None
+
+ def is_dhcp_enabled(self):
+ return True
+
+ def stop_dhcp_service(self):
+ cmd = "/sbin/service {0} stop".format(self.dhclient_name)
+ return shellutil.run(cmd, chk_err=False)
+
+ def start_dhcp_service(self):
+ cmd = "/sbin/service {0} start".format(self.dhclient_name)
+ return shellutil.run(cmd, chk_err=False)
+
+ def start_network(self) :
+ return shellutil.run("/sbin/service start network", chk_err=False)
+
+ def restart_ssh_service(self):
+ return shellutil.run("/sbin/service sshd restart", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("/sbin/service waagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("/sbin/service waagent start", chk_err=False)
+
+ def register_agent_service(self):
+ return shellutil.run("/sbin/insserv waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("/sbin/insserv -r waagent", chk_err=False)
+
+class SUSEOSUtil(SUSE11OSUtil):
+ def __init__(self):
+ super(SUSEOSUtil, self).__init__()
+ self.dhclient_name = 'wickedd-dhcp4'
+
+ def register_agent_service(self):
+ return shellutil.run("systemctl enable waagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("systemctl disable waagent", chk_err=False)
+
+
diff --git a/azurelinuxagent/distro/ubuntu/__init__.py b/azurelinuxagent/distro/ubuntu/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/distro/ubuntu/deprovision.py b/azurelinuxagent/distro/ubuntu/deprovision.py
new file mode 100644
index 0000000..10fa123
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/deprovision.py
@@ -0,0 +1,43 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.distro.default.deprovision import DeprovisionHandler, DeprovisionAction
+
+def del_resolv():
+ if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
+ logger.info("resolvconf is not configured. Removing /etc/resolv.conf")
+ fileutil.rm_files('/etc/resolv.conf')
+ else:
+ logger.info("resolvconf is enabled; leaving /etc/resolv.conf intact")
+ fileutil.rm_files('/etc/resolvconf/resolv.conf.d/tail',
+ '/etc/resolvconf/resolv.conf.d/originial')
+
+
+class UbuntuDeprovisionHandler(DeprovisionHandler):
+ def setup(self, deluser):
+ warnings, actions = super(UbuntuDeprovisionHandler, self).setup(deluser)
+ warnings.append("WARNING! Nameserver configuration in "
+ "/etc/resolvconf/resolv.conf.d/{tail,originial} "
+ "will be deleted.")
+ actions.append(DeprovisionAction(del_resolv))
+ return warnings, actions
+
diff --git a/azurelinuxagent/distro/ubuntu/handlerFactory.py b/azurelinuxagent/distro/ubuntu/handlerFactory.py
new file mode 100644
index 0000000..c8d0906
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/handlerFactory.py
@@ -0,0 +1,29 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.distro.ubuntu.provision import UbuntuProvisionHandler
+from azurelinuxagent.distro.ubuntu.deprovision import UbuntuDeprovisionHandler
+from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory
+
+class UbuntuHandlerFactory(DefaultHandlerFactory):
+ def __init__(self):
+ super(UbuntuHandlerFactory, self).__init__()
+ self.provision_handler = UbuntuProvisionHandler()
+ self.deprovision_handler = UbuntuDeprovisionHandler()
+
diff --git a/azurelinuxagent/distro/ubuntu/loader.py b/azurelinuxagent/distro/ubuntu/loader.py
new file mode 100644
index 0000000..26db4fa
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/loader.py
@@ -0,0 +1,36 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION
+
+def get_osutil():
+ from azurelinuxagent.distro.ubuntu.osutil import Ubuntu1204OSUtil, \
+ UbuntuOSUtil, \
+ Ubuntu14xOSUtil
+ if DISTRO_VERSION == "12.04":
+ return Ubuntu1204OSUtil()
+ elif DISTRO_VERSION == "14.04" or DISTRO_VERSION == "14.10":
+ return Ubuntu14xOSUtil()
+ else:
+ return UbuntuOSUtil()
+
+def get_handlers():
+ from azurelinuxagent.distro.ubuntu.handlerFactory import UbuntuHandlerFactory
+ return UbuntuHandlerFactory()
+
diff --git a/azurelinuxagent/distro/ubuntu/osutil.py b/azurelinuxagent/distro/ubuntu/osutil.py
new file mode 100644
index 0000000..1e51c2a
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/osutil.py
@@ -0,0 +1,65 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import pwd
+import shutil
+import socket
+import array
+import struct
+import fcntl
+import time
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.textutil as textutil
+from azurelinuxagent.distro.default.osutil import DefaultOSUtil
+
+class Ubuntu14xOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(Ubuntu14xOSUtil, self).__init__()
+
+ def start_network(self):
+ return shellutil.run("service networking start", chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run("service walinuxagent stop", chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run("service walinuxagent start", chk_err=False)
+
+class Ubuntu1204OSUtil(Ubuntu14xOSUtil):
+ def __init__(self):
+ super(Ubuntu1204OSUtil, self).__init__()
+
+ #Override
+ def get_dhcp_pid(self):
+ ret= shellutil.run_get_output("pidof dhclient3")
+ return ret[1] if ret[0] == 0 else None
+
+class UbuntuOSUtil(Ubuntu14xOSUtil):
+ def __init__(self):
+ super(UbuntuOSUtil, self).__init__()
+
+ def register_agent_service(self):
+ return shellutil.run("systemctl unmask walinuxagent", chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run("systemctl mask walinuxagent", chk_err=False)
+
diff --git a/azurelinuxagent/distro/ubuntu/provision.py b/azurelinuxagent/distro/ubuntu/provision.py
new file mode 100644
index 0000000..7551074
--- /dev/null
+++ b/azurelinuxagent/distro/ubuntu/provision.py
@@ -0,0 +1,72 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import time
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.conf as conf
+import azurelinuxagent.protocol as prot
+from azurelinuxagent.exception import *
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.distro.default.provision import ProvisionHandler
+
+"""
+On ubuntu image, provision could be disabled.
+"""
+class UbuntuProvisionHandler(ProvisionHandler):
+ def process(self):
+ #If provision is enabled, run default provision handler
+ if conf.get_switch("Provisioning.Enabled", False):
+ super(UbuntuProvisionHandler, self).process()
+ return
+
+ logger.info("run Ubuntu provision handler")
+ provisioned = os.path.join(OSUTIL.get_lib_dir(), "provisioned")
+ if os.path.isfile(provisioned):
+ return
+
+ logger.info("Waiting cloud-init to finish provisioning.")
+ protocol = prot.FACTORY.get_default_protocol()
+ try:
+ logger.info("Wait for ssh host key to be generated.")
+ thumbprint = self.wait_for_ssh_host_key()
+ fileutil.write_file(provisioned, "")
+
+ logger.info("Finished provisioning")
+ status = prot.ProvisionStatus(status="Ready")
+ status.properties.certificateThumbprint = thumbprint
+ protocol.report_provision_status(status)
+
+ except ProvisionError as e:
+ logger.error("Provision failed: {0}", e)
+ protocol.report_provision_status(status="NotReady", subStatus=text(e))
+
+ def wait_for_ssh_host_key(self, max_retry=60):
+ kepair_type = conf.get("Provisioning.SshHostKeyPairType", "rsa")
+ path = '/etc/ssh/ssh_host_{0}_key'.format(kepair_type)
+ for retry in range(0, max_retry):
+ if os.path.isfile(path):
+ return self.get_ssh_host_key_thumbprint(kepair_type)
+ if retry < max_retry - 1:
+ logger.info("Wait for ssh host key be generated: {0}", path)
+ time.sleep(5)
+ raise ProvisionError("Ssh hsot key is not generated.")
diff --git a/azurelinuxagent/event.py b/azurelinuxagent/event.py
new file mode 100644
index 0000000..f866a22
--- /dev/null
+++ b/azurelinuxagent/event.py
@@ -0,0 +1,188 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import sys
+import traceback
+import atexit
+import json
+import time
+import datetime
+import threading
+import platform
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.protocol as prot
+from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, \
+ DISTRO_CODE_NAME, AGENT_VERSION
+from azurelinuxagent.utils.osutil import OSUTIL
+
+class EventError(Exception):
+ pass
+
+class WALAEventOperation:
+ HeartBeat="HeartBeat"
+ Provision = "Provision"
+ Install = "Install"
+ UnInstall = "UnInstall"
+ Disable = "Disable"
+ Enable = "Enable"
+ Download = "Download"
+ Upgrade = "Upgrade"
+ Update = "Update"
+ ActivateResourceDisk="ActivateResourceDisk"
+ UnhandledError="UnhandledError"
+
+class EventMonitor(object):
+ def __init__(self):
+ self.sysinfo = []
+ self.event_dir = os.path.join(OSUTIL.get_lib_dir(), "events")
+
+ def init_sysinfo(self):
+ osversion = "{0}:{1}-{2}-{3}:{4}".format(platform.system(),
+ DISTRO_NAME,
+ DISTRO_VERSION,
+ DISTRO_CODE_NAME,
+ platform.release())
+
+ self.sysinfo.append(prot.TelemetryEventParam("OSVersion", osversion))
+ self.sysinfo.append(prot.TelemetryEventParam("GAVersion",
+ AGENT_VERSION))
+ self.sysinfo.append(prot.TelemetryEventParam("RAM",
+ OSUTIL.get_total_mem()))
+ self.sysinfo.append(prot.TelemetryEventParam("Processors",
+ OSUTIL.get_processor_cores()))
+ try:
+ protocol = prot.FACTORY.get_default_protocol()
+ vminfo = protocol.get_vminfo()
+ self.sysinfo.append(prot.TelemetryEventParam("VMName",
+ vminfo.vmName))
+ #TODO add other system info like, subscription id, etc.
+ except prot.ProtocolError as e:
+ logger.warn("Failed to get vm info: {0}", e)
+
+ def start(self):
+ event_thread = threading.Thread(target = self.run)
+ event_thread.setDaemon(True)
+ event_thread.start()
+
+ def collect_event(self, evt_file_name):
+ try:
+ with open(evt_file_name, "rb") as evt_file:
+ #if fail to open or delete the file, throw exception
+ json_str = evt_file.read().decode("utf-8",'ignore')
+ os.remove(evt_file_name)
+ return json_str
+ except IOError as e:
+ msg = "Failed to process {0}, {1}".format(evt_file_name, e)
+ raise EventError(msg)
+
+ def collect_and_send_events(self):
+ event_list = prot.TelemetryEventList()
+ event_files = os.listdir(self.event_dir)
+ for event_file in event_files:
+ if not event_file.endswith(".tld"):
+ continue
+ event_file_path = os.path.join(self.event_dir, event_file)
+ try:
+ data_str = self.collect_event(event_file_path)
+ except EventError as e:
+ logger.error("{0}", e)
+ continue
+ try:
+ data = json.loads(data_str)
+ except ValueError as e:
+ logger.verb(data_str)
+ logger.error("Failed to decode json event file{0}", e)
+ continue
+
+ event = prot.TelemetryEvent()
+ prot.set_properties(event, data)
+ event.parameters.extend(self.sysinfo)
+ event_list.events.append(event)
+ if len(event_list.events) == 0:
+ return
+
+ try:
+ protocol = prot.FACTORY.get_default_protocol()
+ protocol.report_event(event_list)
+ except prot.ProtocolError as e:
+ logger.error("{0}", e)
+
+ def run(self):
+ self.init_sysinfo()
+ last_heartbeat = datetime.datetime.min
+ period = datetime.timedelta(hours = 12)
+ while(True):
+ if (datetime.datetime.now()-last_heartbeat) > period:
+ last_heartbeat = datetime.datetime.now()
+ add_event(op=WALAEventOperation.HeartBeat,
+ name="WALA",is_success=True)
+ self.collect_and_send_events()
+ time.sleep(60)
+
+def save_event(data):
+ event_dir = os.path.join(OSUTIL.get_lib_dir(), 'events')
+ if not os.path.exists(event_dir):
+ os.mkdir(event_dir)
+ os.chmod(event_dir,0o700)
+ if len(os.listdir(event_dir)) > 1000:
+ raise EventError("Too many files under: {0}", event_dir)
+
+ filename = os.path.join(event_dir, text(int(time.time()*1000000)))
+ try:
+ with open(filename+".tmp",'wb+') as hfile:
+ hfile.write(data.encode("utf-8"))
+ os.rename(filename+".tmp", filename+".tld")
+ except IOError as e:
+ raise EventError("Failed to write events to file:{0}", e)
+
+def add_event(name, op, is_success, duration=0, version="1.0",
+ message="", evt_type="", is_internal=False):
+ event = prot.TelemetryEvent(1, "69B669B9-4AF8-4C50-BDC4-6006FA76E975")
+ event.parameters.append(prot.TelemetryEventParam('Name', name))
+ event.parameters.append(prot.TelemetryEventParam('Version', version))
+ event.parameters.append(prot.TelemetryEventParam('IsInternal', is_internal))
+ event.parameters.append(prot.TelemetryEventParam('Operation', op))
+ event.parameters.append(prot.TelemetryEventParam('OperationSuccess',
+ is_success))
+ event.parameters.append(prot.TelemetryEventParam('Message', message))
+ event.parameters.append(prot.TelemetryEventParam('Duration', duration))
+ event.parameters.append(prot.TelemetryEventParam('ExtensionType', evt_type))
+
+ data = prot.get_properties(event)
+ try:
+ save_event(json.dumps(data))
+ except EventError as e:
+ logger.error("{0}", e)
+
+def dump_unhandled_err(name):
+ if hasattr(sys, 'last_type') and hasattr(sys, 'last_value') and \
+ hasattr(sys, 'last_traceback'):
+ last_type = getattr(sys, 'last_type')
+ last_value = getattr(sys, 'last_value')
+ last_traceback = getattr(sys, 'last_traceback')
+ error = traceback.format_exception(last_type, last_value,
+ last_traceback)
+ message= "".join(error)
+ logger.error(message)
+ add_event(name, is_success=False, message=message,
+ op=WALAEventOperation.UnhandledError)
+
+def enable_unhandled_err_dump(name):
+ atexit.register(dump_unhandled_err, name)
+
diff --git a/azurelinuxagent/exception.py b/azurelinuxagent/exception.py
new file mode 100644
index 0000000..7c31394
--- /dev/null
+++ b/azurelinuxagent/exception.py
@@ -0,0 +1,65 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+"""
+Defines all exceptions
+"""
+
+class AgentError(Exception):
+ """
+ Base class of agent error.
+ """
+ def __init__(self, errno, msg):
+ msg = "({0}){1}".format(errno, msg)
+ super(AgentError, self).__init__(msg)
+
+class AgentConfigError(AgentError):
+ """
+ When configure file is not found or malformed.
+ """
+ def __init__(self, msg):
+ super(AgentConfigError, self).__init__('000001', msg)
+
+class AgentNetworkError(AgentError):
+ """
+ When network is not avaiable.
+ """
+ def __init__(self, msg):
+ super(AgentNetworkError, self).__init__('000002', msg)
+
+class ExtensionError(AgentError):
+ """
+ When failed to execute an extension
+ """
+ def __init__(self, msg):
+ super(ExtensionError, self).__init__('000003', msg)
+
+class ProvisionError(AgentError):
+ """
+ When provision failed
+ """
+ def __init__(self, msg):
+ super(ProvisionError, self).__init__('000004', msg)
+
+class ResourceDiskError(AgentError):
+ """
+ Mount resource disk failed
+ """
+ def __init__(self, msg):
+ super(ResourceDiskError, self).__init__('000005', msg)
+
diff --git a/azurelinuxagent/future.py b/azurelinuxagent/future.py
new file mode 100644
index 0000000..8186fcf
--- /dev/null
+++ b/azurelinuxagent/future.py
@@ -0,0 +1,19 @@
+import sys
+
+"""
+Add alies for python2 and python3 libs and fucntions.
+"""
+
+if sys.version_info[0]== 3:
+ import http.client as httpclient
+ from urllib.parse import urlparse
+ text = str
+ bytebuffer = memoryview
+elif sys.version_info[0] == 2:
+ import httplib as httpclient
+ from urlparse import urlparse
+ text = unicode
+ bytebuffer = buffer
+else:
+ raise ImportError("Unknown python version:{0}".format(sys.version_info))
+
diff --git a/azurelinuxagent/handler.py b/azurelinuxagent/handler.py
new file mode 100644
index 0000000..c180112
--- /dev/null
+++ b/azurelinuxagent/handler.py
@@ -0,0 +1,28 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+"""
+Handler handles different tasks like, provisioning, deprovisioning etc.
+The handlers could be extended for different distros. The default
+implementation is under azurelinuxagent.distros.default
+"""
+import azurelinuxagent.distro.loader as loader
+
+HANDLERS = loader.get_handlers()
+
diff --git a/azurelinuxagent/logger.py b/azurelinuxagent/logger.py
new file mode 100644
index 0000000..126d6bc
--- /dev/null
+++ b/azurelinuxagent/logger.py
@@ -0,0 +1,158 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and openssl_bin 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+"""
+Log utils
+"""
+
+import sys
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.textutil as textutil
+from datetime import datetime
+
+class Logger(object):
+ """
+ Logger class
+ """
+ def __init__(self, logger=None, prefix=None):
+ self.appenders = []
+ if logger is not None:
+ self.appenders.extend(logger.appenders)
+ self.prefix = prefix
+
+ def verbose(self, msg_format, *args):
+ self.log(LogLevel.VERBOSE, msg_format, *args)
+
+ def info(self, msg_format, *args):
+ self.log(LogLevel.INFO, msg_format, *args)
+
+ def warn(self, msg_format, *args):
+ self.log(LogLevel.WARNING, msg_format, *args)
+
+ def error(self, msg_format, *args):
+ self.log(LogLevel.ERROR, msg_format, *args)
+
+ def log(self, level, msg_format, *args):
+ if len(args) > 0:
+ msg = msg_format.format(*args)
+ else:
+ msg = msg_format
+ time = datetime.now().strftime(u'%Y/%m/%d %H:%M:%S.%f')
+ level_str = LogLevel.STRINGS[level]
+ if self.prefix is not None:
+ log_item = u"{0} {1} {2} {3}\n".format(time, level_str, self.prefix,
+ msg)
+ else:
+ log_item = u"{0} {1} {2}\n".format(time, level_str, msg)
+ log_item = text(log_item.encode("ascii", "backslashreplace"), encoding='ascii')
+ for appender in self.appenders:
+ appender.write(level, log_item)
+
+ def add_appender(self, appender_type, level, path):
+ appender = _create_logger_appender(appender_type, level, path)
+ self.appenders.append(appender)
+
+class ConsoleAppender(object):
+ def __init__(self, level, path):
+ self.level = LogLevel.INFO
+ if level >= LogLevel.INFO:
+ self.level = level
+ self.path = path
+
+ def write(self, level, msg):
+ if self.level <= level:
+ try:
+ with open(self.path, "w") as console:
+ console.write(msg)
+ except IOError:
+ pass
+
+class FileAppender(object):
+ def __init__(self, level, path):
+ self.level = level
+ self.path = path
+
+ def write(self, level, msg):
+ if self.level <= level:
+ try:
+ with open(self.path, "a+") as log_file:
+ log_file.write(msg)
+ except IOError:
+ pass
+
+class StdoutAppender(object):
+ def __init__(self, level):
+ self.level = level
+
+ def write(self, level, msg):
+ if self.level <= level:
+ try:
+ sys.stdout.write(msg)
+ except IOError:
+ pass
+
+
+#Initialize logger instance
+DEFAULT_LOGGER = Logger()
+
+class LogLevel(object):
+ VERBOSE = 0
+ INFO = 1
+ WARNING = 2
+ ERROR = 3
+ STRINGS = [
+ "VERBOSE",
+ "INFO",
+ "WARNING",
+ "ERROR"
+ ]
+
+class AppenderType(object):
+ FILE = 0
+ CONSOLE = 1
+ STDOUT = 2
+
+def add_logger_appender(appender_type, level=LogLevel.INFO, path=None):
+ DEFAULT_LOGGER.add_appender(appender_type, level, path)
+
+def verb(msg_format, *args):
+ DEFAULT_LOGGER.verbose(msg_format, *args)
+
+def info(msg_format, *args):
+ DEFAULT_LOGGER.info(msg_format, *args)
+
+def warn(msg_format, *args):
+ DEFAULT_LOGGER.warn(msg_format, *args)
+
+def error(msg_format, *args):
+ DEFAULT_LOGGER.error(msg_format, *args)
+
+def log(level, msg_format, *args):
+ DEFAULT_LOGGER.log(level, msg_format, args)
+
+def _create_logger_appender(appender_type, level=LogLevel.INFO, path=None):
+ if appender_type == AppenderType.CONSOLE:
+ return ConsoleAppender(level, path)
+ elif appender_type == AppenderType.FILE:
+ return FileAppender(level, path)
+ elif appender_type == AppenderType.STDOUT:
+ return StdoutAppender(level)
+ else:
+ raise ValueError("Unknown appender type")
+
diff --git a/azurelinuxagent/metadata.py b/azurelinuxagent/metadata.py
new file mode 100644
index 0000000..83d4676
--- /dev/null
+++ b/azurelinuxagent/metadata.py
@@ -0,0 +1,93 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import re
+import platform
+import sys
+from azurelinuxagent.future import text
+
+def get_distro():
+ if 'FreeBSD' in platform.system():
+ release = re.sub('\-.*\Z', '', text(platform.release()))
+ osinfo = ['freebsd', release, '', 'freebsd']
+ if 'linux_distribution' in dir(platform):
+ osinfo = list(platform.linux_distribution(full_distribution_name=0))
+ full_name = platform.linux_distribution()[0].strip()
+ osinfo.append(full_name)
+ else:
+ osinfo = platform.dist()
+
+ #The platform.py lib has issue with detecting oracle linux distribution.
+ #Merge the following patch provided by oracle as a temparory fix.
+ if os.path.exists("/etc/oracle-release"):
+ osinfo[2] = "oracle"
+ osinfo[3] = "Oracle Linux"
+
+ #Remove trailing whitespace and quote in distro name
+ osinfo[0] = osinfo[0].strip('"').strip(' ').lower()
+ return osinfo
+
+AGENT_NAME = "WALinuxAgent"
+AGENT_LONG_NAME = "Azure Linux Agent"
+AGENT_VERSION = '2.1.1'
+AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
+AGENT_DESCRIPTION = """\
+The Azure Linux Agent supports the provisioning and running of Linux
+VMs in the Azure cloud. This package should be installed on Linux disk
+images that are built to run in the Azure environment.
+"""
+
+__distro__ = get_distro()
+DISTRO_NAME = __distro__[0]
+DISTRO_VERSION = __distro__[1]
+DISTRO_CODE_NAME = __distro__[2]
+DISTRO_FULL_NAME = __distro__[3]
+
+PY_VERSION = sys.version_info
+PY_VERSION_MAJOR = sys.version_info[0]
+PY_VERSION_MINOR = sys.version_info[1]
+PY_VERSION_MICRO = sys.version_info[2]
+
+
+"""
+Add this walk arround for detecting Snappy Ubuntu Core temporarily, until ubuntu
+fixed this bug: https://bugs.launchpad.net/snappy/+bug/1481086
+"""
+def which(program):
+ # Return path of program for execution if found in path
+ def is_exe(fpath):
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+ _fpath, _ = os.path.split(program)
+ if _fpath:
+ if is_exe(program):
+ return program
+ else:
+ for path in os.environ.get("PATH", "").split(os.pathsep):
+ path = path.strip('"')
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+ return None
+
+def is_snappy():
+ return which("snappy")
+
+if is_snappy():
+ DISTRO_FULL_NAME = "Snappy Ubuntu Core"
diff --git a/azurelinuxagent/protocol/__init__.py b/azurelinuxagent/protocol/__init__.py
new file mode 100644
index 0000000..65d8a5d
--- /dev/null
+++ b/azurelinuxagent/protocol/__init__.py
@@ -0,0 +1,23 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from azurelinuxagent.protocol.common import *
+from azurelinuxagent.protocol.protocolFactory import FACTORY, \
+ detect_default_protocol
+
diff --git a/azurelinuxagent/protocol/common.py b/azurelinuxagent/protocol/common.py
new file mode 100644
index 0000000..77247ab
--- /dev/null
+++ b/azurelinuxagent/protocol/common.py
@@ -0,0 +1,245 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+import os
+import copy
+import re
+import json
+import xml.dom.minidom
+import azurelinuxagent.logger as logger
+import azurelinuxagent.utils.fileutil as fileutil
+
+class ProtocolError(Exception):
+ pass
+
+class ProtocolNotFound(Exception):
+ pass
+
+def validata_param(name, val, expected_type):
+ if val is None:
+ raise ProtocolError("Param {0} is None".format(name))
+ if not isinstance(val, expected_type):
+ raise ProtocolError("Param {0} type should be {1}".format(name,
+ expected_type))
+
+def set_properties(obj, data):
+ validata_param("obj", obj, DataContract)
+ validata_param("data", data, dict)
+
+ props = vars(obj)
+ for name, val in list(props.items()):
+ try:
+ new_val = data[name]
+ except KeyError:
+ continue
+
+ if isinstance(new_val, dict):
+ set_properties(val, new_val)
+ elif isinstance(new_val, list):
+ validata_param("list", val, DataContractList)
+ for data_item in new_val:
+ item = val.item_cls()
+ set_properties(item, data_item)
+ val.append(item)
+ else:
+ setattr(obj, name, new_val)
+
+def get_properties(obj):
+ validata_param("obj", obj, DataContract)
+
+ data = {}
+ props = vars(obj)
+ for name, val in list(props.items()):
+ if isinstance(val, DataContract):
+ data[name] = get_properties(val)
+ elif isinstance(val, DataContractList):
+ if len(val) == 0:
+ continue
+ data[name] = []
+ for item in val:
+ date_item = get_properties(item)
+ data[name].append(date_item)
+ elif val is not None:
+ data[name] = val
+ return data
+
+class DataContract(object):
+ pass
+
+class DataContractList(list):
+ def __init__(self, item_cls):
+ self.item_cls = item_cls
+
+class VMInfo(DataContract):
+ def __init__(self, subscriptionId=None, vmName=None):
+ self.subscriptionId = subscriptionId
+ self.vmName = vmName
+
+class Cert(DataContract):
+ def __init__(self, name=None, thumbprint=None, certificateDataUri=None):
+ self.name = name
+ self.thumbprint = thumbprint
+ self.certificateDataUri = certificateDataUri
+
+class CertList(DataContract):
+ def __init__(self):
+ self.certificates = DataContractList(Cert)
+
+class ExtensionSettings(DataContract):
+ def __init__(self, name=None, sequenceNumber=None, publicSettings=None,
+ privateSettings=None, certificateThumbprint=None):
+ self.name = name
+ self.sequenceNumber = sequenceNumber
+ self.publicSettings = publicSettings
+ self.privateSettings = privateSettings
+ self.certificateThumbprint = certificateThumbprint
+
+class ExtensionProperties(DataContract):
+ def __init__(self):
+ self.version = None
+ self.upgradePolicy = None
+ self.state = None
+ self.extensions = DataContractList(ExtensionSettings)
+
+class ExtensionVersionUri(DataContract):
+ def __init__(self):
+ self.uri = None
+
+class Extension(DataContract):
+ def __init__(self, name=None):
+ self.name = name
+ self.properties = ExtensionProperties()
+ self.version_uris = DataContractList(ExtensionVersionUri)
+
+class ExtensionList(DataContract):
+ def __init__(self):
+ self.extensions = DataContractList(Extension)
+
+class ExtensionPackageUri(DataContract):
+ def __init__(self, uri=None):
+ self.uri = uri
+
+class ExtensionPackage(DataContract):
+ def __init__(self, version = None):
+ self.version = version
+ self.uris = DataContractList(ExtensionPackageUri)
+
+class ExtensionPackageList(DataContract):
+ def __init__(self):
+ self.versions = DataContractList(ExtensionPackage)
+
+class InstanceMetadata(DataContract):
+ def __init__(self, deploymentName=None, roleName=None, roleInstanceId=None,
+ containerId=None):
+ self.deploymentName = deploymentName
+ self.roleName = roleName
+ self.roleInstanceId = roleInstanceId
+ self.containerId = containerId
+
+class VMProperties(DataContract):
+ def __init__(self, certificateThumbprint=None):
+ #TODO need to confirm the property name
+ self.certificateThumbprint = certificateThumbprint
+
+class ProvisionStatus(DataContract):
+ def __init__(self, status=None, subStatus=None, description=None):
+ self.status = status
+ self.subStatus = subStatus
+ self.description = description
+ self.properties = VMProperties()
+
+class VMAgentStatus(DataContract):
+ def __init__(self, agentVersion=None, status=None, message=None):
+ self.agentVersion = agentVersion
+ self.status = status
+ self.message = message
+
+class ExtensionSubStatus(DataContract):
+ def __init__(self, name=None, status=None, code=None, message=None):
+ self.name = name
+ self.status = status
+ self.code = code
+ self.message = message
+
+class ExtensionStatus(DataContract):
+ def __init__(self, name=None, configurationAppliedTime=None, operation=None,
+ status=None, code=None, message=None, seq_no=None):
+ self.name = name
+ self.configurationAppliedTime = configurationAppliedTime
+ self.operation = operation
+ self.status = status
+ self.code = code
+ self.message = message
+ self.sequenceNumber = seq_no
+ self.substatusList = DataContractList(ExtensionSubStatus)
+
+class ExtensionHandlerStatus(DataContract):
+ def __init__(self, handlerName=None, handlerVersion=None, status=None,
+ message=None):
+ self.handlerName = handlerName
+ self.handlerVersion = handlerVersion
+ self.status = status
+ self.message = message
+ self.extensionStatusList = DataContractList(ExtensionStatus)
+
+class VMStatus(DataContract):
+ def __init__(self):
+ self.vmAgent = VMAgentStatus()
+ self.extensionHandlers = DataContractList(ExtensionHandlerStatus)
+
+class TelemetryEventParam(DataContract):
+ def __init__(self, name=None, value=None):
+ self.name = name
+ self.value = value
+
+class TelemetryEvent(DataContract):
+ def __init__(self, eventId=None, providerId=None):
+ self.eventId = eventId
+ self.providerId = providerId
+ self.parameters = DataContractList(TelemetryEventParam)
+
+class TelemetryEventList(DataContract):
+ def __init__(self):
+ self.events = DataContractList(TelemetryEvent)
+
+class Protocol(DataContract):
+
+ def initialize(self):
+ raise NotImplementedError()
+
+ def get_vminfo(self):
+ raise NotImplementedError()
+
+ def get_certs(self):
+ raise NotImplementedError()
+
+ def get_extensions(self):
+ raise NotImplementedError()
+
+ def get_extension_pkgs(self, extension):
+ raise NotImplementedError()
+
+ def report_provision_status(self, status):
+ raise NotImplementedError()
+
+ def report_status(self, status):
+ raise NotImplementedError()
+
+ def report_event(self, event):
+ raise NotImplementedError()
+
diff --git a/azurelinuxagent/protocol/ovfenv.py b/azurelinuxagent/protocol/ovfenv.py
new file mode 100644
index 0000000..2e0411d
--- /dev/null
+++ b/azurelinuxagent/protocol/ovfenv.py
@@ -0,0 +1,146 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+"""
+Copy and parse ovf-env.xml from provisiong ISO and local cache
+"""
+import os
+import re
+import xml.dom.minidom as minidom
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.utils.textutil import parse_doc, findall, find, findtext
+from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError
+from azurelinuxagent.protocol import ProtocolError
+
+OVF_FILE_NAME = "ovf-env.xml"
+OVF_VERSION = "1.0"
+OVF_NAME_SPACE = "http://schemas.dmtf.org/ovf/environment/1"
+WA_NAME_SPACE = "http://schemas.microsoft.com/windowsazure"
+
+def get_ovf_env():
+ """
+ Load saved ovf-env.xml
+ """
+ ovf_file_path = os.path.join(OSUTIL.get_lib_dir(), OVF_FILE_NAME)
+ if os.path.isfile(ovf_file_path):
+ xml_text = fileutil.read_file(ovf_file_path)
+ return OvfEnv(xml_text)
+ else:
+ raise ProtocolError("ovf-env.xml is missing.")
+
+def copy_ovf_env():
+ """
+ Copy ovf env file from dvd to hard disk.
+ Remove password before save it to the disk
+ """
+ try:
+ OSUTIL.mount_dvd()
+ ovf_file_path_on_dvd = OSUTIL.get_ovf_env_file_path_on_dvd()
+ ovfxml = fileutil.read_file(ovf_file_path_on_dvd, remove_bom=True)
+ ovfenv = OvfEnv(ovfxml)
+ ovfxml = re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml)
+ ovf_file_path = os.path.join(OSUTIL.get_lib_dir(), OVF_FILE_NAME)
+ fileutil.write_file(ovf_file_path, ovfxml)
+ OSUTIL.umount_dvd()
+ OSUTIL.eject_dvd()
+ except IOError as e:
+ raise ProtocolError(text(e))
+ except OSUtilError as e:
+ raise ProtocolError(text(e))
+ return ovfenv
+
+def _validate_ovf(val, msg):
+ if val is None:
+ raise ProtocolError("Failed to parse OVF XML: {0}".format(msg))
+
+class OvfEnv(object):
+ """
+ Read, and process provisioning info from provisioning file OvfEnv.xml
+ """
+ def __init__(self, xml_text):
+ if xml_text is None:
+ raise ValueError("ovf-env is None")
+ logger.verb("Load ovf-env.xml")
+ self.hostname = None
+ self.username = None
+ self.user_password = None
+ self.customdata = None
+ self.disable_ssh_password_auth = True
+ self.ssh_pubkeys = []
+ self.ssh_keypairs = []
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ """
+ Parse xml tree, retreiving user and ssh key information.
+ Return self.
+ """
+ wans = WA_NAME_SPACE
+ ovfns = OVF_NAME_SPACE
+
+ xml_doc = parse_doc(xml_text)
+
+ environment = find(xml_doc, "Environment", namespace=ovfns)
+ _validate_ovf(environment, "Environment not found")
+
+ section = find(environment, "ProvisioningSection", namespace=wans)
+ _validate_ovf(section, "ProvisioningSection not found")
+
+ version = findtext(environment, "Version", namespace=wans)
+ _validate_ovf(version, "Version not found")
+
+ if version > OVF_VERSION:
+ logger.warn("Newer provisioning configuration detected. "
+ "Please consider updating waagent")
+
+ conf_set = find(section, "LinuxProvisioningConfigurationSet",
+ namespace=wans)
+ _validate_ovf(conf_set, "LinuxProvisioningConfigurationSet not found")
+
+ self.hostname = findtext(conf_set, "HostName", namespace=wans)
+ _validate_ovf(self.hostname, "HostName not found")
+
+ self.username = findtext(conf_set, "UserName", namespace=wans)
+ _validate_ovf(self.username, "UserName not found")
+
+ self.user_password = findtext(conf_set, "UserPassword", namespace=wans)
+
+ self.customdata = findtext(conf_set, "CustomData", namespace=wans)
+
+ auth_option = findtext(conf_set, "DisableSshPasswordAuthentication",
+ namespace=wans)
+ if auth_option is not None and auth_option.lower() == "true":
+ self.disable_ssh_password_auth = True
+ else:
+ self.disable_ssh_password_auth = False
+
+ public_keys = findall(conf_set, "PublicKey", namespace=wans)
+ for public_key in public_keys:
+ path = findtext(public_key, "Path", namespace=wans)
+ fingerprint = findtext(public_key, "Fingerprint", namespace=wans)
+ value = findtext(public_key, "Value", namespace=wans)
+ self.ssh_pubkeys.append((path, fingerprint, value))
+
+ keypairs = findall(conf_set, "KeyPair", namespace=wans)
+ for keypair in keypairs:
+ path = findtext(keypair, "Path", namespace=wans)
+ fingerprint = findtext(keypair, "Fingerprint", namespace=wans)
+ self.ssh_keypairs.append((path, fingerprint))
+
diff --git a/azurelinuxagent/protocol/protocolFactory.py b/azurelinuxagent/protocol/protocolFactory.py
new file mode 100644
index 0000000..d2ca201
--- /dev/null
+++ b/azurelinuxagent/protocol/protocolFactory.py
@@ -0,0 +1,114 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+import os
+import traceback
+import threading
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.fileutil as fileutil
+from azurelinuxagent.utils.osutil import OSUTIL
+from azurelinuxagent.protocol.common import *
+from azurelinuxagent.protocol.v1 import WireProtocol
+from azurelinuxagent.protocol.v2 import MetadataProtocol
+
+WIRE_SERVER_ADDR_FILE_NAME = "WireServer"
+
+def get_wire_protocol_endpoint():
+ path = os.path.join(OSUTIL.get_lib_dir(), WIRE_SERVER_ADDR_FILE_NAME)
+ try:
+ endpoint = fileutil.read_file(path)
+ except IOError as e:
+ raise ProtocolNotFound("Wire server endpoint not found: {0}".format(e))
+
+ if endpoint is None:
+ raise ProtocolNotFound("Wire server endpoint is None")
+
+ return endpoint
+
+def detect_wire_protocol():
+ endpoint = get_wire_protocol_endpoint()
+
+ OSUTIL.gen_transport_cert()
+ protocol = WireProtocol(endpoint)
+ protocol.initialize()
+ logger.info("Protocol V1 found.")
+ return protocol
+
+def detect_metadata_protocol():
+ protocol = MetadataProtocol()
+ protocol.initialize()
+
+ logger.info("Protocol V2 found.")
+ return protocol
+
+def detect_available_protocols(prob_funcs=[detect_wire_protocol,
+ detect_metadata_protocol]):
+ available_protocols = []
+ for probe_func in prob_funcs:
+ try:
+ protocol = probe_func()
+ available_protocols.append(protocol)
+ except ProtocolNotFound as e:
+ logger.info(text(e))
+ return available_protocols
+
+def detect_default_protocol():
+ logger.info("Detect default protocol.")
+ available_protocols = detect_available_protocols()
+ return choose_default_protocol(available_protocols)
+
+def choose_default_protocol(protocols):
+ if len(protocols) > 0:
+ return protocols[0]
+ else:
+ raise ProtocolNotFound("No available protocol detected.")
+
+def get_wire_protocol():
+ endpoint = get_wire_protocol_endpoint()
+ return WireProtocol(endpoint)
+
+def get_metadata_protocol():
+ return MetadataProtocol()
+
+def get_available_protocols(getters=[get_wire_protocol, get_metadata_protocol]):
+ available_protocols = []
+ for getter in getters:
+ try:
+ protocol = getter()
+ available_protocols.append(protocol)
+ except ProtocolNotFound as e:
+ logger.info(text(e))
+ return available_protocols
+
+class ProtocolFactory(object):
+ def __init__(self):
+ self._protocol = None
+ self._lock = threading.Lock()
+
+ def get_default_protocol(self):
+ if self._protocol is None:
+ self._lock.acquire()
+ if self._protocol is None:
+ available_protocols = get_available_protocols()
+ self._protocol = choose_default_protocol(available_protocols)
+ self._lock.release()
+
+ return self._protocol
+
+FACTORY = ProtocolFactory()
diff --git a/azurelinuxagent/protocol/v1.py b/azurelinuxagent/protocol/v1.py
new file mode 100644
index 0000000..54a80b6
--- /dev/null
+++ b/azurelinuxagent/protocol/v1.py
@@ -0,0 +1,964 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+
+import os
+import json
+import re
+import time
+import traceback
+import xml.sax.saxutils as saxutils
+import xml.etree.ElementTree as ET
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text, httpclient
+import azurelinuxagent.utils.restutil as restutil
+from azurelinuxagent.utils.textutil import parse_doc, findall, find, findtext, \
+ getattrib, gettext, remove_bom
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+from azurelinuxagent.protocol.common import *
+
+VERSION_INFO_URI = "http://{0}/?comp=versions"
+GOAL_STATE_URI = "http://{0}/machine/?comp=goalstate"
+HEALTH_REPORT_URI = "http://{0}/machine?comp=health"
+ROLE_PROP_URI = "http://{0}/machine?comp=roleProperties"
+TELEMETRY_URI = "http://{0}/machine?comp=telemetrydata"
+
+WIRE_SERVER_ADDR_FILE_NAME = "WireServer"
+INCARNATION_FILE_NAME = "Incarnation"
+GOAL_STATE_FILE_NAME = "GoalState.{0}.xml"
+HOSTING_ENV_FILE_NAME = "HostingEnvironmentConfig.xml"
+SHARED_CONF_FILE_NAME = "SharedConfig.xml"
+CERTS_FILE_NAME = "Certificates.xml"
+P7M_FILE_NAME = "Certificates.p7m"
+PEM_FILE_NAME = "Certificates.pem"
+EXT_CONF_FILE_NAME = "ExtensionsConfig.{0}.xml"
+MANIFEST_FILE_NAME = "{0}.{1}.manifest.xml"
+TRANSPORT_CERT_FILE_NAME = "TransportCert.pem"
+TRANSPORT_PRV_FILE_NAME = "TransportPrivate.pem"
+
+PROTOCOL_VERSION = "2012-11-30"
+
+class WireProtocolResourceGone(ProtocolError):
+ pass
+
+class WireProtocol(Protocol):
+
+ def __init__(self, endpoint):
+ self.client = WireClient(endpoint)
+
+ def initialize(self):
+ self.client.check_wire_protocol_version()
+ self.client.update_goal_state(forced=True)
+
+ def get_vminfo(self):
+ hosting_env = self.client.get_hosting_env()
+ vminfo = VMInfo()
+ vminfo.subscriptionId = None
+ vminfo.vmName = hosting_env.vm_name
+ return vminfo
+
+ def get_certs(self):
+ certificates = self.client.get_certs()
+ return certificates.cert_list
+
+ def get_extensions(self):
+ #Update goal state to get latest extensions config
+ self.client.update_goal_state()
+ ext_conf = self.client.get_ext_conf()
+ return ext_conf.ext_list
+
+ def get_extension_pkgs(self, extension):
+ goal_state = self.client.get_goal_state()
+ man = self.client.get_ext_manifest(extension, goal_state)
+ return man.pkg_list
+
+ def report_provision_status(self, provisionStatus):
+ validata_param("provisionStatus", provisionStatus, ProvisionStatus)
+
+ if provisionStatus.status is not None:
+ self.client.report_health(provisionStatus.status,
+ provisionStatus.subStatus,
+ provisionStatus.description)
+ if provisionStatus.properties.certificateThumbprint is not None:
+ thumbprint = provisionStatus.properties.certificateThumbprint
+ self.client.report_role_prop(thumbprint)
+
+ def report_status(self, vmStatus):
+ validata_param("vmStatus", vmStatus, VMStatus)
+ self.client.upload_status_blob(vmStatus)
+
+ def report_event(self, events):
+ validata_param("events", events, TelemetryEventList)
+ self.client.report_event(events)
+
+def _fetch_cache(local_file):
+ if not os.path.isfile(local_file):
+ raise ProtocolError("{0} is missing.".format(local_file))
+ return fileutil.read_file(local_file)
+
+def _fetch_uri(uri, headers, chk_proxy=False):
+ try:
+ resp = restutil.http_get(uri, headers, chk_proxy=chk_proxy)
+ except restutil.HttpError as e:
+ raise ProtocolError(text(e))
+
+ if(resp.status == httpclient.GONE):
+ raise WireProtocolResourceGone(uri)
+ if(resp.status != httpclient.OK):
+ raise ProtocolError("{0} - {1}".format(resp.status, uri))
+ data = resp.read()
+ if data is None:
+ return None
+ data = remove_bom(data)
+ xml_text = text(data, encoding='utf-8')
+ return xml_text
+
+def _fetch_manifest(version_uris):
+ for version_uri in version_uris:
+ try:
+ xml_text = _fetch_uri(version_uri.uri, None, chk_proxy=True)
+ return xml_text
+ except IOError as e:
+ logger.warn("Failed to fetch ExtensionManifest: {0}, {1}", e,
+ version_uri.uri)
+ raise ProtocolError("Failed to fetch ExtensionManifest from all sources")
+
+def _build_role_properties(container_id, role_instance_id, thumbprint):
+ xml = ("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<RoleProperties>"
+ "<Container>"
+ "<ContainerId>{0}</ContainerId>"
+ "<RoleInstances>"
+ "<RoleInstance>"
+ "<Id>{1}</Id>"
+ "<Properties>"
+ "<Property name=\"CertificateThumbprint\" value=\"{2}\" />"
+ "</Properties>"
+ "</RoleInstance>"
+ "</RoleInstances>"
+ "</Container>"
+ "</RoleProperties>"
+ "").format(container_id, role_instance_id, thumbprint)
+ return xml
+
+def _build_health_report(incarnation, container_id, role_instance_id,
+ status, substatus, description):
+ detail = ''
+ if substatus is not None:
+ detail = ("<Details>"
+ "<SubStatus>{0}</SubStatus>"
+ "<Description>{1}</Description>"
+ "</Details>").format(substatus, description)
+ xml = ("<?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>{0}</GoalStateIncarnation>"
+ "<Container>"
+ "<ContainerId>{1}</ContainerId>"
+ "<RoleInstanceList>"
+ "<Role>"
+ "<InstanceId>{2}</InstanceId>"
+ "<Health>"
+ "<State>{3}</State>"
+ "{4}"
+ "</Health>"
+ "</Role>"
+ "</RoleInstanceList>"
+ "</Container>"
+ "</Health>"
+ "").format(incarnation,
+ container_id,
+ role_instance_id,
+ status,
+ detail)
+ return xml
+
+"""
+Convert VMStatus object to status blob format
+"""
+def guest_agent_status_to_v1(ga_status):
+ formatted_msg = {
+ 'lang' : 'en-US',
+ 'message' : ga_status.message
+ }
+ v1_ga_status = {
+ 'version' : ga_status.agentVersion,
+ 'status' : ga_status.status,
+ 'formattedMessage' : formatted_msg
+ }
+ return v1_ga_status
+
+def extension_substatus_to_v1(sub_status_list):
+ status_list = []
+ for substatus in sub_status_list:
+ status = {
+ "name": substatus.name,
+ "status": substatus.status,
+ "code": substatus.code,
+ "formattedMessage":{
+ "lang": "en-US",
+ "message": substatus.message
+ }
+ }
+ status_list.append(status)
+ return status_list
+
+def extension_handler_status_to_v1(handler_status, timestamp):
+ if handler_status is None or len(handler_status.extensionStatusList) == 0:
+ return
+ ext_status = handler_status.extensionStatusList[0]
+ sub_status = extension_substatus_to_v1(ext_status.substatusList)
+ ext_in_status = {
+ "status":{
+ "name": ext_status.name,
+ "configurationAppliedTime": ext_status.configurationAppliedTime,
+ "operation": ext_status.operation,
+ "status": ext_status.status,
+ "code": ext_status.code,
+ "formattedMessage": {
+ "lang":"en-US",
+ "message": ext_status.message
+ }
+ },
+ "timestampUTC": timestamp
+ }
+
+ if len(sub_status) != 0:
+ ext_in_status['substatus'] = sub_status
+
+ v1_handler_status = {
+ 'handlerVersion' : handler_status.handlerVersion,
+ 'handlerName' : handler_status.handlerName,
+ 'status' : handler_status.status,
+ 'runtimeSettingsStatus' : {
+ 'settingsStatus' : ext_in_status,
+ 'sequenceNumber' : ext_status.sequenceNumber
+ }
+ }
+ return v1_handler_status
+
+
+def vm_status_to_v1(vm_status):
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+
+ v1_ga_status = guest_agent_status_to_v1(vm_status.vmAgent)
+ v1_handler_status_list = []
+ for handler_status in vm_status.extensionHandlers:
+ v1_handler_status = extension_handler_status_to_v1(handler_status,
+ timestamp)
+ v1_handler_status_list.append(v1_handler_status)
+
+ v1_agg_status = {
+ 'guestAgentStatus': v1_ga_status,
+ 'handlerAggregateStatus' : v1_handler_status_list
+ }
+ v1_vm_status = {
+ 'version' : '1.0',
+ 'timestampUTC' : timestamp,
+ 'aggregateStatus' : v1_agg_status
+ }
+ return v1_vm_status
+
+
+class StatusBlob(object):
+ def __init__(self, vm_status):
+ self.vm_status = vm_status
+
+ def to_json(self):
+ report = vm_status_to_v1(self.vm_status)
+ return json.dumps(report)
+
+ __storage_version__ = "2014-02-14"
+
+ def upload(self, url):
+ logger.info("Upload status blob")
+ blob_type = self.get_blob_type(url)
+
+ data = self.to_json()
+ if blob_type == "BlockBlob":
+ self.put_block_blob(url, data)
+ elif blob_type == "PageBlob":
+ self.put_page_blob(url, data)
+ else:
+ raise ProtocolError("Unknown blob type: {0}".format(blob_type))
+
+ def get_blob_type(self, url):
+ #Check blob type
+ logger.verb("Check blob type.")
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ resp = restutil.http_head(url, {
+ "x-ms-date" : timestamp,
+ 'x-ms-version' : self.__class__.__storage_version__
+ })
+ if resp is None or resp.status != httpclient.OK:
+ raise ProtocolError(("Failed to get status blob type: {0}"
+ "").format(resp.status))
+
+ blob_type = resp.getheader("x-ms-blob-type")
+ logger.verb("Blob type={0}".format(blob_type))
+ return blob_type
+
+ def put_block_blob(self, url, data):
+ logger.verb("Upload block blob")
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ resp = restutil.http_put(url, data, {
+ "x-ms-date" : timestamp,
+ "x-ms-blob-type" : "BlockBlob",
+ "Content-Length": text(len(data)),
+ "x-ms-version" : self.__class__.__storage_version__
+ })
+ if resp is None or resp.status != httpclient.CREATED:
+ raise ProtocolError(("Failed to upload block blob: {0}"
+ "").format(resp.status))
+
+ def put_page_blob(self, url, data):
+ logger.verb("Replace old page blob")
+ timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ #Align to 512 bytes
+ page_blob_size = ((len(data) + 511) / 512) * 512
+ resp = restutil.http_put(url, "", {
+ "x-ms-date" : timestamp,
+ "x-ms-blob-type" : "PageBlob",
+ "Content-Length": "0",
+ "x-ms-blob-content-length" : text(page_blob_size),
+ "x-ms-version" : self.__class__.__storage_version__
+ })
+ if resp is None or resp.status != httpclient.CREATED:
+ raise ProtocolError(("Failed to clean up page blob: {0}"
+ "").format(resp.status))
+
+ if '?' in url < 0:
+ url = "{0}?comp=page".format(url)
+ else:
+ url = "{0}&comp=page".format(url)
+
+ logger.verb("Upload page blob")
+ page_max = 4 * 1024 * 1024 #Max page size: 4MB
+ start = 0
+ end = 0
+ while end < len(data):
+ end = min(len(data), start + page_max)
+ content_size = end - start
+ #Align to 512 bytes
+ page_end = int((end + 511) / 512) * 512
+ buf_size = page_end - start
+ buf = bytearray(source=data[start:end], encoding="utf-8")
+ #TODO buffer is not defined in python3, however we need this to make httplib to work on python 2.6
+ resp = restutil.http_put(url, buf, {
+ "x-ms-date" : timestamp,
+ "x-ms-range" : "bytes={0}-{1}".format(start, page_end - 1),
+ "x-ms-page-write" : "update",
+ "x-ms-version" : self.__class__.__storage_version__,
+ "Content-Length": text(page_end - start)
+ })
+ if resp is None or resp.status != httpclient.CREATED:
+ raise ProtocolError(("Failed to upload page blob: {0}"
+ "").format(resp.status))
+ start = end
+
+def event_param_to_v1(param):
+ param_format = '<Param Name="{0}" Value={1} T="{2}" />'
+ param_type = type(param.value)
+ attr_type = ""
+ if param_type is int:
+ attr_type = 'mt:uint64'
+ elif param_type is str:
+ attr_type = 'mt:wstr'
+ elif text(param_type).count("'unicode'") > 0:
+ attr_type = 'mt:wstr'
+ elif param_type is bool:
+ attr_type = 'mt:bool'
+ elif param_type is float:
+ attr_type = 'mt:float64'
+ return param_format.format(param.name, saxutils.quoteattr(text(param.value)),
+ attr_type)
+
+def event_to_v1(event):
+ params = ""
+ for param in event.parameters:
+ params += event_param_to_v1(param)
+ event_str = ('<Event id="{0}">'
+ '<![CDATA[{1}]]>'
+ '</Event>').format(event.eventId, params)
+ return event_str
+
+class WireClient(object):
+ def __init__(self, endpoint):
+ self.endpoint = endpoint
+ self.goal_state = None
+ self.updated = None
+ self.hosting_env = None
+ self.shared_conf = None
+ self.certs = None
+ self.ext_conf = None
+ self.req_count = 0
+
+ def update_hosting_env(self, goal_state):
+ if goal_state.hosting_env_uri is None:
+ raise ProtocolError("HostingEnvironmentConfig uri is empty")
+ local_file = HOSTING_ENV_FILE_NAME
+ xml_text = _fetch_uri(goal_state.hosting_env_uri, self.get_header())
+ fileutil.write_file(local_file, xml_text)
+ self.hosting_env = HostingEnv(xml_text)
+
+ def update_shared_conf(self, goal_state):
+ if goal_state.shared_conf_uri is None:
+ raise ProtocolError("SharedConfig uri is empty")
+ local_file = SHARED_CONF_FILE_NAME
+ xml_text = _fetch_uri(goal_state.shared_conf_uri, self.get_header())
+ fileutil.write_file(local_file, xml_text)
+ self.shared_conf = SharedConfig(xml_text)
+
+ def update_certs(self, goal_state):
+ if goal_state.certs_uri is None:
+ return
+ local_file = CERTS_FILE_NAME
+ xml_text = _fetch_uri(goal_state.certs_uri, self.get_header_for_cert())
+ fileutil.write_file(local_file, xml_text)
+ self.certs = Certificates(xml_text)
+
+ def update_ext_conf(self, goal_state):
+ if goal_state.ext_uri is None:
+ raise ProtocolError("ExtensionsConfig uri is empty")
+ incarnation = goal_state.incarnation
+ local_file = EXT_CONF_FILE_NAME.format(incarnation)
+ xml_text = _fetch_uri(goal_state.ext_uri,
+ self.get_header())
+ fileutil.write_file(local_file, xml_text)
+ self.ext_conf = ExtensionsConfig(xml_text)
+ for extension in self.ext_conf.ext_list.extensions:
+ self.update_ext_manifest(extension, goal_state)
+
+ def update_ext_manifest(self, extension, goal_state):
+ local_file = MANIFEST_FILE_NAME.format(extension.name,
+ goal_state.incarnation)
+ xml_text = _fetch_manifest(extension.version_uris)
+ fileutil.write_file(local_file, xml_text)
+
+ def update_goal_state(self, forced=False, max_retry=3):
+ uri = GOAL_STATE_URI.format(self.endpoint)
+ xml_text = _fetch_uri(uri, self.get_header())
+ goal_state = GoalState(xml_text)
+
+ if not forced:
+ last_incarnation = None
+ if(os.path.isfile(INCARNATION_FILE_NAME)):
+ last_incarnation = fileutil.read_file(INCARNATION_FILE_NAME)
+ new_incarnation = goal_state.incarnation
+ if last_incarnation is not None and \
+ last_incarnation == new_incarnation:
+ #Goalstate is not updated.
+ return
+
+ #Start updating goalstate, retry on 410
+ for retry in range(0, max_retry):
+ try:
+ self.goal_state = goal_state
+ goal_state_file = GOAL_STATE_FILE_NAME.format(goal_state.incarnation)
+ fileutil.write_file(goal_state_file, xml_text)
+ fileutil.write_file(INCARNATION_FILE_NAME,
+ goal_state.incarnation)
+ self.update_hosting_env(goal_state)
+ self.update_shared_conf(goal_state)
+ self.update_certs(goal_state)
+ self.update_ext_conf(goal_state)
+ return
+ except WireProtocolResourceGone:
+ logger.info("Incarnation is out of date. Update goalstate.")
+ xml_text = _fetch_uri(GOAL_STATE_URI, self.get_header())
+ goal_state = GoalState(xml_text)
+
+ raise ProtocolError("Exceeded max retry updating goal state")
+
+ def get_goal_state(self):
+ if(self.goal_state is None):
+ incarnation = _fetch_cache(INCARNATION_FILE_NAME)
+ goal_state_file = GOAL_STATE_FILE_NAME.format(incarnation)
+ xml_text = _fetch_cache(goal_state_file)
+ self.goal_state = GoalState(xml_text)
+ return self.goal_state
+
+ def get_hosting_env(self):
+ if(self.hosting_env is None):
+ xml_text = _fetch_cache(HOSTING_ENV_FILE_NAME)
+ self.hosting_env = HostingEnv(xml_text)
+ return self.hosting_env
+
+ def get_shared_conf(self):
+ if(self.shared_conf is None):
+ xml_text = _fetch_cache(SHARED_CONF_FILE_NAME)
+ self.shared_conf = SharedConfig(xml_text)
+ return self.shared_conf
+
+ def get_certs(self):
+ if(self.certs is None):
+ xml_text = _fetch_cache(Certificates)
+ self.certs = Certificates(xml_text)
+ if self.certs is None:
+ return None
+ return self.certs
+
+ def get_ext_conf(self):
+ if(self.ext_conf is None):
+ goal_state = self.get_goal_state()
+ local_file = EXT_CONF_FILE_NAME.format(goal_state.incarnation)
+ xml_text = _fetch_cache(local_file)
+ self.ext_conf = ExtensionsConfig(xml_text)
+ return self.ext_conf
+
+ def get_ext_manifest(self, extension, goal_state):
+ local_file = MANIFEST_FILE_NAME.format(extension.name,
+ goal_state.incarnation)
+ xml_text = _fetch_cache(local_file)
+ return ExtensionManifest(xml_text)
+
+ def check_wire_protocol_version(self):
+ uri = VERSION_INFO_URI.format(self.endpoint)
+ version_info_xml = _fetch_uri(uri, None)
+ version_info = VersionInfo(version_info_xml)
+
+ preferred = version_info.get_preferred()
+ if PROTOCOL_VERSION == preferred:
+ logger.info("Wire protocol version:{0}", PROTOCOL_VERSION)
+ elif PROTOCOL_VERSION in version_info.get_supported():
+ logger.info("Wire protocol version:{0}", PROTOCOL_VERSION)
+ logger.warn("Server prefered version:{0}", preferred)
+ else:
+ error = ("Agent supported wire protocol version: {0} was not "
+ "advised by Fabric.").format(PROTOCOL_VERSION)
+ raise ProtocolNotFound(error)
+
+ def upload_status_blob(self, vm_status):
+ ext_conf = self.get_ext_conf()
+ status_blob = StatusBlob(vm_status)
+ status_blob.upload(ext_conf.status_upload_blob)
+
+ def report_role_prop(self, thumbprint):
+ goal_state = self.get_goal_state()
+ role_prop = _build_role_properties(goal_state.container_id,
+ goal_state.role_instance_id,
+ thumbprint)
+ role_prop_uri = ROLE_PROP_URI.format(self.endpoint)
+ ret = restutil.http_post(role_prop_uri,
+ role_prop,
+ headers=self.get_header_for_xml_content())
+
+
+ def report_health(self, status, substatus, description):
+ goal_state = self.get_goal_state()
+ health_report = _build_health_report(goal_state.incarnation,
+ goal_state.container_id,
+ goal_state.role_instance_id,
+ status,
+ substatus,
+ description)
+ health_report_uri = HEALTH_REPORT_URI.format(self.endpoint)
+ headers = self.get_header_for_xml_content()
+ resp = restutil.http_post(health_report_uri,
+ health_report,
+ headers=headers)
+ def prevent_throttling(self):
+ self.req_count += 1
+ if self.req_count % 3 == 0:
+ logger.info("Sleep 15 before sending event to avoid throttling.")
+ self.req_count = 0
+ time.sleep(15)
+
+ def send_event(self, provider_id, event_str):
+ uri = TELEMETRY_URI.format(self.endpoint)
+ data_format = ('<?xml version="1.0"?>'
+ '<TelemetryData version="1.0">'
+ '<Provider id="{0}">{1}'
+ '</Provider>'
+ '</TelemetryData>')
+ data = data_format.format(provider_id, event_str)
+ try:
+ self.prevent_throttling()
+ header = self.get_header_for_xml_content()
+ resp = restutil.http_post(uri, data, header)
+ except restutil.HttpError as e:
+ raise ProtocolError("Failed to send events:{0}".format(e))
+
+ if resp.status != httpclient.OK:
+ logger.verb(resp.read())
+ raise ProtocolError("Failed to send events:{0}".format(resp.status))
+
+ def report_event(self, event_list):
+ buf = {}
+ #Group events by providerId
+ for event in event_list.events:
+ if event.providerId not in buf:
+ buf[event.providerId] = ""
+ event_str = event_to_v1(event)
+ if len(event_str) >= 63 * 1024:
+ logger.warn("Single event too large: {0}", event_str[300:])
+ continue
+ if len(buf[event.providerId] + event_str) >= 63 * 1024:
+ self.send_event(event.providerId, buf[event.providerId])
+ buf[event.providerId] = ""
+ buf[event.providerId] = buf[event.providerId] + event_str
+
+ #Send out all events left in buffer.
+ for provider_id in list(buf.keys()):
+ if len(buf[provider_id]) > 0:
+ self.send_event(provider_id, buf[provider_id])
+
+ def get_header(self):
+ return {
+ "x-ms-agent-name":"WALinuxAgent",
+ "x-ms-version":PROTOCOL_VERSION
+ }
+
+ def get_header_for_xml_content(self):
+ return {
+ "x-ms-agent-name":"WALinuxAgent",
+ "x-ms-version":PROTOCOL_VERSION,
+ "Content-Type":"text/xml;charset=utf-8"
+ }
+
+ def get_header_for_cert(self):
+ cert = ""
+ content = _fetch_cache(TRANSPORT_CERT_FILE_NAME)
+ for line in content.split('\n'):
+ if "CERTIFICATE" not in line:
+ cert += line.rstrip()
+ return {
+ "x-ms-agent-name":"WALinuxAgent",
+ "x-ms-version":PROTOCOL_VERSION,
+ "x-ms-cipher-name": "DES_EDE3_CBC",
+ "x-ms-guest-agent-public-x509-cert":cert
+ }
+
+class VersionInfo(object):
+ def __init__(self, xml_text):
+ """
+ Query endpoint server for wire protocol version.
+ Fail if our desired protocol version is not seen.
+ """
+ logger.verb("Load Version.xml")
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ xml_doc = parse_doc(xml_text)
+ preferred = find(xml_doc, "Preferred")
+ self.preferred = findtext(preferred, "Version")
+ logger.info("Fabric preferred wire protocol version:{0}", self.preferred)
+
+ self.supported = []
+ supported = find(xml_doc, "Supported")
+ supported_version = findall(supported, "Version")
+ for node in supported_version:
+ version = gettext(node)
+ logger.verb("Fabric supported wire protocol version:{0}", version)
+ self.supported.append(version)
+
+ def get_preferred(self):
+ return self.preferred
+
+ def get_supported(self):
+ return self.supported
+
+
+class GoalState(object):
+
+ def __init__(self, xml_text):
+ if xml_text is None:
+ raise ValueError("GoalState.xml is None")
+ logger.verb("Load GoalState.xml")
+ self.incarnation = None
+ self.expected_state = None
+ self.hosting_env_uri = None
+ self.shared_conf_uri = None
+ self.certs_uri = None
+ self.ext_uri = None
+ self.role_instance_id = None
+ self.container_id = None
+ self.load_balancer_probe_port = None
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ """
+ Request configuration data from endpoint server.
+ """
+ self.xml_text = xml_text
+ xml_doc = parse_doc(xml_text)
+ self.incarnation = findtext(xml_doc, "Incarnation")
+ self.expected_state = findtext(xml_doc, "ExpectedState")
+ self.hosting_env_uri = findtext(xml_doc, "HostingEnvironmentConfig")
+ self.shared_conf_uri = findtext(xml_doc, "SharedConfig")
+ self.certs_uri = findtext(xml_doc, "Certificates")
+ self.ext_uri = findtext(xml_doc, "ExtensionsConfig")
+ role_instance = find(xml_doc, "RoleInstance")
+ self.role_instance_id = findtext(role_instance, "InstanceId")
+ container = find(xml_doc, "Container")
+ self.container_id = findtext(container, "ContainerId")
+ lbprobe_ports = find(xml_doc, "LBProbePorts")
+ self.load_balancer_probe_port = findtext(lbprobe_ports, "Port")
+ return self
+
+
+class HostingEnv(object):
+ """
+ parse Hosting enviromnet config and store in
+ HostingEnvironmentConfig.xml
+ """
+ def __init__(self, xml_text):
+ if xml_text is None:
+ raise ValueError("HostingEnvironmentConfig.xml is None")
+ logger.verb("Load HostingEnvironmentConfig.xml")
+ self.vm_name = None
+ self.role_name = None
+ self.deployment_name = None
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ """
+ parse and create HostingEnvironmentConfig.xml.
+ """
+ self.xml_text = xml_text
+ xml_doc = parse_doc(xml_text)
+ incarnation = find(xml_doc, "Incarnation")
+ self.vm_name = getattrib(incarnation, "instance")
+ role = find(xml_doc, "Role")
+ self.role_name = getattrib(role, "name")
+ deployment = find(xml_doc, "Deployment")
+ self.deployment_name = getattrib(deployment, "name")
+ return self
+
+class SharedConfig(object):
+ """
+ parse role endpoint server and goal state config.
+ """
+ def __init__(self, xml_text):
+ logger.verb("Load SharedConfig.xml")
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ """
+ parse and write configuration to file SharedConfig.xml.
+ """
+ #Not used currently
+ return self
+
+class Certificates(object):
+
+ """
+ Object containing certificates of host and provisioned user.
+ """
+ def __init__(self, xml_text=None):
+ if xml_text is None:
+ raise ValueError("Certificates.xml is None")
+ logger.verb("Load Certificates.xml")
+ self.lib_dir = OSUTIL.get_lib_dir()
+ self.openssl_cmd = OSUTIL.get_openssl_cmd()
+ self.cert_list = CertList()
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ """
+ Parse multiple certificates into seperate files.
+ """
+ xml_doc = parse_doc(xml_text)
+ data = findtext(xml_doc, "Data")
+ if data is None:
+ return
+
+ p7m = ("MIME-Version:1.0\n"
+ "Content-Disposition: attachment; filename=\"{0}\"\n"
+ "Content-Type: application/x-pkcs7-mime; name=\"{1}\"\n"
+ "Content-Transfer-Encoding: base64\n"
+ "\n"
+ "{2}").format(P7M_FILE_NAME, P7M_FILE_NAME, data)
+
+ fileutil.write_file(os.path.join(self.lib_dir, P7M_FILE_NAME), p7m)
+ #decrypt certificates
+ cmd = ("{0} cms -decrypt -in {1} -inkey {2} -recip {3}"
+ "| {4} pkcs12 -nodes -password pass: -out {5}"
+ "").format(self.openssl_cmd, P7M_FILE_NAME,
+ TRANSPORT_PRV_FILE_NAME, TRANSPORT_CERT_FILE_NAME,
+ self.openssl_cmd, PEM_FILE_NAME)
+ shellutil.run(cmd)
+
+ #The parsing process use public key to match prv and crt.
+ buf = []
+ begin_crt = False
+ begin_prv = False
+ prvs = {}
+ thumbprints = {}
+ index = 0
+ v1_cert_list = []
+ with open(PEM_FILE_NAME) as pem:
+ for line in pem.readlines():
+ buf.append(line)
+ if re.match(r'[-]+BEGIN.*KEY[-]+', line):
+ begin_prv = True
+ elif re.match(r'[-]+BEGIN.*CERTIFICATE[-]+', line):
+ begin_crt = True
+ elif re.match(r'[-]+END.*KEY[-]+', line):
+ tmp_file = self.write_to_tmp_file(index, 'prv', buf)
+ pub = OSUTIL.get_pubkey_from_prv(tmp_file)
+ prvs[pub] = tmp_file
+ buf = []
+ index += 1
+ begin_prv = False
+ elif re.match(r'[-]+END.*CERTIFICATE[-]+', line):
+ tmp_file = self.write_to_tmp_file(index, 'crt', buf)
+ pub = OSUTIL.get_pubkey_from_crt(tmp_file)
+ thumbprint = OSUTIL.get_thumbprint_from_crt(tmp_file)
+ thumbprints[pub] = thumbprint
+ #Rename crt with thumbprint as the file name
+ crt = "{0}.crt".format(thumbprint)
+ v1_cert_list.append({
+ "name":None,
+ "thumbprint":thumbprint
+ })
+ os.rename(tmp_file, os.path.join(self.lib_dir, crt))
+ buf = []
+ index += 1
+ begin_crt = False
+
+ #Rename prv key with thumbprint as the file name
+ for pubkey in prvs:
+ thumbprint = thumbprints[pubkey]
+ if thumbprint:
+ tmp_file = prvs[pubkey]
+ prv = "{0}.prv".format(thumbprint)
+ os.rename(tmp_file, os.path.join(self.lib_dir, prv))
+
+ for v1_cert in v1_cert_list:
+ cert = Cert()
+ set_properties(cert, v1_cert)
+ self.cert_list.certificates.append(cert)
+
+ def write_to_tmp_file(self, index, suffix, buf):
+ file_name = os.path.join(self.lib_dir, "{0}.{1}".format(index, suffix))
+ with open(file_name, 'w') as tmp:
+ tmp.writelines(buf)
+ return file_name
+
+
+class ExtensionsConfig(object):
+ """
+ parse ExtensionsConfig, downloading and unpacking them to /var/lib/waagent.
+ Install if <enabled>true</enabled>, remove if it is set to false.
+ """
+
+ def __init__(self, xml_text):
+ if xml_text is None:
+ raise ValueError("ExtensionsConfig is None")
+ logger.verb("Load ExtensionsConfig.xml")
+ self.ext_list = ExtensionList()
+ self.status_upload_blob = None
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ """
+ Write configuration to file ExtensionsConfig.xml.
+ """
+ xml_doc = parse_doc(xml_text)
+ plugins_list = find(xml_doc, "Plugins")
+ plugins = findall(plugins_list, "Plugin")
+ plugin_settings_list = find(xml_doc, "PluginSettings")
+ plugin_settings = findall(plugin_settings_list, "Plugin")
+
+ for plugin in plugins:
+ ext = self.parse_ext(plugin)
+ self.ext_list.extensions.append(ext)
+ self.parse_ext_settings(ext, plugin_settings)
+
+ self.status_upload_blob = findtext(xml_doc, "StatusUploadBlob")
+
+ def parse_ext(self, plugin):
+ ext = Extension()
+ ext.name = getattrib(plugin, "name")
+ ext.properties.version = getattrib(plugin, "version")
+ ext.properties.state = getattrib(plugin, "state")
+
+ auto_upgrade = getattrib(plugin, "autoUpgrade")
+ if auto_upgrade is not None and auto_upgrade.lower() == "true":
+ ext.properties.upgradePolicy = "auto"
+ else:
+ ext.properties.upgradePolicy = "manual"
+
+ location = getattrib(plugin, "location")
+ failover_location = getattrib(plugin, "failoverlocation")
+ for uri in [location, failover_location]:
+ version_uri = ExtensionVersionUri()
+ version_uri.uri = uri
+ ext.version_uris.append(version_uri)
+ return ext
+
+ def parse_ext_settings(self, ext, plugin_settings):
+ if plugin_settings is None:
+ return
+
+ name = ext.name
+ version = ext.properties.version
+ settings = [x for x in plugin_settings \
+ if getattrib(x, "name") == name and \
+ getattrib(x ,"version") == version]
+
+ if settings is None or len(settings) == 0:
+ return
+
+ runtime_settings = None
+ runtime_settings_node = find(settings[0], "RuntimeSettings")
+ seqNo = getattrib(runtime_settings_node, "seqNo")
+ runtime_settings_str = gettext(runtime_settings_node)
+ try:
+ runtime_settings = json.loads(runtime_settings_str)
+ except ValueError as e:
+ logger.error("Invalid extension settings")
+ return
+
+ for plugin_settings_list in runtime_settings["runtimeSettings"]:
+ handler_settings = plugin_settings_list["handlerSettings"]
+ ext_settings = ExtensionSettings()
+ ext_settings.sequenceNumber = seqNo
+ ext_settings.publicSettings = handler_settings.get("publicSettings", None)
+ ext_settings.privateSettings = handler_settings.get("protectedSettings", None)
+ thumbprint = handler_settings.get("protectedSettingsCertThumbprint", None)
+ ext_settings.certificateThumbprint = thumbprint
+ ext.properties.extensions.append(ext_settings)
+
+class ExtensionManifest(object):
+ def __init__(self, xml_text):
+ if xml_text is None:
+ raise ValueError("ExtensionManifest is None")
+ logger.verb("Load ExtensionManifest.xml")
+ self.pkg_list = ExtensionPackageList()
+ self.parse(xml_text)
+
+ def parse(self, xml_text):
+ xml_doc = parse_doc(xml_text)
+ packages = findall(xml_doc, "Plugin")
+ for package in packages:
+ version = findtext(package, "Version")
+ uris = find(package, "Uris")
+ uri_list = findall(uris, "Uri")
+ uri_list = [gettext(x) for x in uri_list]
+ package = ExtensionPackage()
+ package.version = version
+ for uri in uri_list:
+ pkg_uri = ExtensionPackageUri()
+ pkg_uri.uri = uri
+ package.uris.append(pkg_uri)
+ self.pkg_list.versions.append(package)
+
diff --git a/azurelinuxagent/protocol/v2.py b/azurelinuxagent/protocol/v2.py
new file mode 100644
index 0000000..d7c9143
--- /dev/null
+++ b/azurelinuxagent/protocol/v2.py
@@ -0,0 +1,122 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+
+import json
+from azurelinuxagent.future import httpclient, text
+import azurelinuxagent.utils.restutil as restutil
+from azurelinuxagent.protocol.common import *
+
+ENDPOINT='169.254.169.254'
+#TODO use http for azure pack test
+#ENDPOINT='localhost'
+APIVERSION='2015-05-01-preview'
+BASE_URI = "http://{0}/Microsoft.Compute/{1}?api-version={{{2}}}{3}"
+
+def _add_content_type(headers):
+ if headers is None:
+ headers = {}
+ headers["content-type"] = "application/json"
+ return headers
+
+class MetadataProtocol(Protocol):
+
+ def __init__(self, apiversion=APIVERSION, endpoint=ENDPOINT):
+ self.apiversion = apiversion
+ self.endpoint = endpoint
+ self.identity_uri = BASE_URI.format(self.endpoint, "identity",
+ self.apiversion, "&$expand=*")
+ self.cert_uri = BASE_URI.format(self.endpoint, "certificates",
+ self.apiversion, "&$expand=*")
+ self.ext_uri = BASE_URI.format(self.endpoint, "extensionHandlers",
+ self.apiversion, "&$expand=*")
+ self.provision_status_uri = BASE_URI.format(self.endpoint,
+ "provisioningStatus",
+ self.apiversion, "")
+ self.status_uri = BASE_URI.format(self.endpoint, "status",
+ self.apiversion, "")
+ self.event_uri = BASE_URI.format(self.endpoint, "status/telemetry",
+ self.apiversion, "")
+
+ def _get_data(self, data_type, url, headers=None):
+ try:
+ resp = restutil.http_get(url, headers=headers)
+ except restutil.HttpError as e:
+ raise ProtocolError(text(e))
+
+ if resp.status != httpclient.OK:
+ raise ProtocolError("{0} - GET: {1}".format(resp.status, url))
+ try:
+ data = resp.read()
+ if data is None:
+ return None
+ data = json.loads(text(data, encoding="utf-8"))
+ except ValueError as e:
+ raise ProtocolError(text(e))
+ obj = data_type()
+ set_properties(obj, data)
+ return obj
+
+ def _put_data(self, url, obj, headers=None):
+ headers = _add_content_type(headers)
+ data = get_properties(obj)
+ try:
+ resp = restutil.http_put(url, json.dumps(data), headers=headers)
+ except restutil.HttpError as e:
+ raise ProtocolError(text(e))
+ if resp.status != httpclient.OK:
+ raise ProtocolError("{0} - PUT: {1}".format(resp.status, url))
+
+ def _post_data(self, url, obj, headers=None):
+ headers = _add_content_type(headers)
+ data = get_properties(obj)
+ try:
+ resp = restutil.http_post(url, json.dumps(data), headers=headers)
+ except restutil.HttpError as e:
+ raise ProtocolError(text(e))
+ if resp.status != httpclient.CREATED:
+ raise ProtocolError("{0} - POST: {1}".format(resp.status, url))
+
+ def initialize(self):
+ pass
+
+ def get_vminfo(self):
+ return self._get_data(VMInfo, self.identity_uri)
+
+ def get_certs(self):
+ #TODO walk arround for azure pack test
+ return CertList()
+
+ certs = self._get_data(CertList, self.cert_uri)
+ #TODO download pfx and convert to pem
+ return certs
+
+ def get_extensions(self):
+ return self._get_data(ExtensionList, self.ext_uri)
+
+ def report_provision_status(self, status):
+ validata_param('status', status, ProvisionStatus)
+ self._put_data(self.provision_status_uri, status)
+
+ def report_status(self, status):
+ validata_param('status', status, VMStatus)
+ self._put_data(self.status_uri, status)
+
+ def report_event(self, events):
+ validata_param('events', events, TelemetryEventList)
+ self._post_data(self.event_uri, events)
+
diff --git a/azurelinuxagent/utils/__init__.py b/azurelinuxagent/utils/__init__.py
new file mode 100644
index 0000000..4b2b9e1
--- /dev/null
+++ b/azurelinuxagent/utils/__init__.py
@@ -0,0 +1,19 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
diff --git a/azurelinuxagent/utils/fileutil.py b/azurelinuxagent/utils/fileutil.py
new file mode 100644
index 0000000..5e7fecf
--- /dev/null
+++ b/azurelinuxagent/utils/fileutil.py
@@ -0,0 +1,186 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+"""
+File operation util functions
+"""
+
+import os
+import re
+import shutil
+import pwd
+import tempfile
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.textutil as textutil
+
+def read_file(filepath, asbin=False, remove_bom=False, encoding='utf-8'):
+ """
+ Read and return contents of 'filepath'.
+ """
+ mode = 'rb'
+ with open(filepath, mode) as in_file:
+ data = in_file.read()
+ if data is None:
+ return None
+
+ if asbin:
+ return data
+
+ if remove_bom:
+ #Remove bom on bytes data before it is converted into string.
+ data = textutil.remove_bom(data)
+ data = text(data, encoding=encoding)
+ return data
+
+def write_file(filepath, contents, asbin=False, encoding='utf-8', append=False):
+ """
+ Write 'contents' to 'filepath'.
+ """
+ mode = "ab" if append else "wb"
+ data = contents
+ if not asbin:
+ data = contents.encode(encoding)
+ with open(filepath, mode) as out_file:
+ out_file.write(data)
+
+def append_file(filepath, contents, asbin=False, encoding='utf-8'):
+ """
+ Append 'contents' to 'filepath'.
+ """
+ write_file(filepath, contents, asbin=asbin, encoding=encoding, append=True)
+
+def replace_file(filepath, contents):
+ """
+ Write 'contents' to 'filepath' by creating a temp file,
+ and replacing original.
+ """
+ handle, temp = tempfile.mkstemp(dir=os.path.dirname(filepath))
+ #if type(contents) == str:
+ #contents=contents.encode('latin-1')
+ try:
+ os.write(handle, contents)
+ except IOError as err:
+ logger.error('Write to file {0}, Exception is {1}', filepath, err)
+ return 1
+ finally:
+ os.close(handle)
+
+ try:
+ os.rename(temp, filepath)
+ except IOError as err:
+ logger.info('Rename {0} to {1}, Exception is {2}', temp, filepath, err)
+ logger.info('Remove original file and retry')
+ try:
+ os.remove(filepath)
+ except IOError as err:
+ logger.error('Remove {0}, Exception is {1}', temp, filepath, err)
+
+ try:
+ os.rename(temp, filepath)
+ except IOError as err:
+ logger.error('Rename {0} to {1}, Exception is {2}', temp, filepath,
+ err)
+ return 1
+ return 0
+
+def base_name(path):
+ head, tail = os.path.split(path)
+ return tail
+
+def get_line_startingwith(prefix, filepath):
+ """
+ Return line from 'filepath' if the line startswith 'prefix'
+ """
+ for line in read_file(filepath).split('\n'):
+ if line.startswith(prefix):
+ return line
+ return None
+
+#End File operation util functions
+
+def mkdir(dirpath, mode=None, owner=None):
+ if not os.path.isdir(dirpath):
+ os.makedirs(dirpath)
+ if mode is not None:
+ chmod(dirpath, mode)
+ if owner is not None:
+ chowner(dirpath, owner)
+
+def chowner(path, owner):
+ owner_info = pwd.getpwnam(owner)
+ os.chown(path, owner_info[2], owner_info[3])
+
+def chmod(path, mode):
+ os.chmod(path, mode)
+
+def rm_files(*args):
+ for path in args:
+ if os.path.isfile(path):
+ os.remove(path)
+
+def rm_dirs(*args):
+ """
+ Remove all the contents under the directry
+ """
+ for dir_name in args:
+ if os.path.isdir(dir_name):
+ for item in os.listdir(dir_name):
+ path = os.path.join(dir_name, item)
+ if os.path.isfile(path):
+ os.remove(path)
+ elif os.path.isdir(path):
+ shutil.rmtree(path)
+
+def update_conf_file(path, line_start, val, chk_err=False):
+ conf = []
+ if not os.path.isfile(path) and chk_err:
+ raise Exception("Can't find config file:{0}".format(path))
+ conf = read_file(path).split('\n')
+ conf = [x for x in conf if not x.startswith(line_start)]
+ conf.append(val)
+ replace_file(path, '\n'.join(conf))
+
+def search_file(target_dir_name, target_file_name):
+ for root, dirs, files in os.walk(target_dir_name):
+ for file_name in files:
+ if file_name == target_file_name:
+ return os.path.join(root, file_name)
+ return None
+
+def chmod_tree(path, mode):
+ for root, dirs, files in os.walk(path):
+ for file_name in files:
+ os.chmod(os.path.join(root, file_name), mode)
+
+def findstr_in_file(file_path, pattern_str):
+ """
+ Return match object if found in file.
+ """
+ try:
+ pattern = re.compile(pattern_str)
+ for line in (open(file_path, 'r')).readlines():
+ match = re.search(pattern, line)
+ if match:
+ return match
+ except:
+ raise
+
+ return None
+
diff --git a/azurelinuxagent/utils/osutil.py b/azurelinuxagent/utils/osutil.py
new file mode 100644
index 0000000..756400c
--- /dev/null
+++ b/azurelinuxagent/utils/osutil.py
@@ -0,0 +1,27 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+"""
+Load OSUtil implementation from azurelinuxagent.distro
+"""
+from azurelinuxagent.distro.default.osutil import OSUtilError
+import azurelinuxagent.distro.loader as loader
+
+OSUTIL = loader.get_osutil()
+
diff --git a/azurelinuxagent/utils/restutil.py b/azurelinuxagent/utils/restutil.py
new file mode 100644
index 0000000..1015f71
--- /dev/null
+++ b/azurelinuxagent/utils/restutil.py
@@ -0,0 +1,154 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import time
+import platform
+import os
+import subprocess
+import azurelinuxagent.logger as logger
+import azurelinuxagent.conf as conf
+from azurelinuxagent.future import httpclient, urlparse
+
+"""
+REST api util functions
+"""
+
+RETRY_WAITING_INTERVAL = 10
+
+class HttpError(Exception):
+ pass
+
+def _parse_url(url):
+ o = urlparse(url)
+ rel_uri = o.path
+ if o.fragment:
+ rel_uri = "{0}#{1}".format(rel_uri, o.fragment)
+ if o.query:
+ rel_uri = "{0}?{1}".format(rel_uri, o.query)
+ secure = False
+ if o.scheme.lower() == "https":
+ secure = True
+ return o.hostname, o.port, secure, rel_uri
+
+def get_http_proxy():
+ """
+ Get http_proxy and https_proxy from environment variables.
+ Username and password is not supported now.
+ """
+ host = conf.get("HttpProxy.Host", None)
+ port = conf.get("HttpProxy.Port", None)
+ return (host, port)
+
+def _http_request(method, host, rel_uri, port=None, data=None, secure=False,
+ headers=None, proxy_host=None, proxy_port=None):
+ url, conn = None, None
+ if secure:
+ port = 443 if port is None else port
+ if proxy_host is not None and proxy_port is not None:
+ conn = httpclient.HTTPSConnection(proxy_host, proxy_port)
+ conn.set_tunnel(host, port)
+ #If proxy is used, full url is needed.
+ url = "https://{0}:{1}{2}".format(host, port, rel_uri)
+ else:
+ conn = httpclient.HTTPSConnection(host, port)
+ url = rel_uri
+ else:
+ port = 80 if port is None else port
+ if proxy_host is not None and proxy_port is not None:
+ conn = httpclient.HTTPConnection(proxy_host, proxy_port)
+ #If proxy is used, full url is needed.
+ url = "http://{0}:{1}{2}".format(host, port, rel_uri)
+ else:
+ conn = httpclient.HTTPConnection(host, port)
+ url = rel_uri
+ if headers == None:
+ conn.request(method, url, data)
+ else:
+ conn.request(method, url, data, headers)
+ resp = conn.getresponse()
+ return resp
+
+def http_request(method, url, data, headers=None, max_retry=3, chk_proxy=False):
+ """
+ Sending http request to server
+ On error, sleep 10 and retry max_retry times.
+ """
+ logger.verb("HTTP Req: {0} {1}", method, url)
+ logger.verb(" Data={0}", data)
+ logger.verb(" Header={0}", headers)
+ host, port, secure, rel_uri = _parse_url(url)
+
+ #Check proxy
+ proxy_host, proxy_port = (None, None)
+ if chk_proxy:
+ proxy_host, proxy_port = get_http_proxy()
+
+ #If httplib module is not built with ssl support. Fallback to http
+ if secure and not hasattr(httpclient, "HTTPSConnection"):
+ logger.warn("httplib is not built with ssl support")
+ secure = False
+
+ #If httplib module doesn't support https tunnelling. Fallback to http
+ if secure and \
+ proxy_host is not None and \
+ proxy_port is not None and \
+ not hasattr(httpclient.HTTPSConnection, "set_tunnel"):
+ logger.warn("httplib doesn't support https tunnelling(new in python 2.7)")
+ secure = False
+
+ for retry in range(0, max_retry):
+ try:
+ resp = _http_request(method, host, rel_uri, port=port, data=data,
+ secure=secure, headers=headers,
+ proxy_host=proxy_host, proxy_port=proxy_port)
+ logger.verb("HTTP Resp: Status={0}", resp.status)
+ logger.verb(" Header={0}", resp.getheaders())
+ return resp
+ except httpclient.HTTPException as e:
+ logger.warn('HTTPException {0}, args:{1}', e, repr(e.args))
+ except IOError as e:
+ logger.warn('Socket IOError {0}, args:{1}', e, repr(e.args))
+
+ if retry < max_retry - 1:
+ logger.info("Retry={0}, {1} {2}", retry, method, url)
+ time.sleep(RETRY_WAITING_INTERVAL)
+
+ raise HttpError("HTTP Err: {0} {1}".format(method, url))
+
+def http_get(url, headers=None, max_retry=3, chk_proxy=False):
+ return http_request("GET", url, data=None, headers=headers,
+ max_retry=max_retry, chk_proxy=chk_proxy)
+
+def http_head(url, headers=None, max_retry=3, chk_proxy=False):
+ return http_request("HEAD", url, None, headers=headers,
+ max_retry=max_retry, chk_proxy=chk_proxy)
+
+def http_post(url, data, headers=None, max_retry=3, chk_proxy=False):
+ return http_request("POST", url, data, headers=headers,
+ max_retry=max_retry, chk_proxy=chk_proxy)
+
+def http_put(url, data, headers=None, max_retry=3, chk_proxy=False):
+ return http_request("PUT", url, data, headers=headers,
+ max_retry=max_retry, chk_proxy=chk_proxy)
+
+def http_delete(url, headers=None, max_retry=3, chk_proxy=False):
+ return http_request("DELETE", url, None, headers=headers,
+ max_retry=max_retry, chk_proxy=chk_proxy)
+
+#End REST api util functions
diff --git a/azurelinuxagent/utils/shellutil.py b/azurelinuxagent/utils/shellutil.py
new file mode 100644
index 0000000..f4305d9
--- /dev/null
+++ b/azurelinuxagent/utils/shellutil.py
@@ -0,0 +1,85 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import platform
+import os
+import subprocess
+from azurelinuxagent.future import text
+import azurelinuxagent.logger as logger
+
+if not hasattr(subprocess,'check_output'):
+ def check_output(*popenargs, **kwargs):
+ r"""Backport from subprocess module from python 2.7"""
+ if 'stdout' in kwargs:
+ raise ValueError('stdout argument not allowed, '
+ 'it will be overridden.')
+ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+ output, unused_err = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = popenargs[0]
+ raise subprocess.CalledProcessError(retcode, cmd, output=output)
+ return output
+
+ # Exception classes used by this module.
+ class CalledProcessError(Exception):
+ def __init__(self, returncode, cmd, output=None):
+ self.returncode = returncode
+ self.cmd = cmd
+ self.output = output
+ def __str__(self):
+ return ("Command '{0}' returned non-zero exit status {1}"
+ "").format(self.cmd, self.returncode)
+
+ subprocess.check_output=check_output
+ subprocess.CalledProcessError=CalledProcessError
+
+
+"""
+Shell command util functions
+"""
+def run(cmd, chk_err=True):
+ """
+ Calls run_get_output on 'cmd', returning only the return code.
+ If chk_err=True then errors will be reported in the log.
+ If chk_err=False then errors will be suppressed from the log.
+ """
+ retcode,out=run_get_output(cmd,chk_err)
+ return retcode
+
+def run_get_output(cmd, chk_err=True):
+ """
+ Wrapper for subprocess.check_output.
+ Execute 'cmd'. Returns return code and STDOUT, trapping expected exceptions.
+ Reports exceptions to Error if chk_err parameter is True
+ """
+ logger.verb("run cmd '{0}'", cmd)
+ try:
+ output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
+ except subprocess.CalledProcessError as e :
+ if chk_err :
+ logger.error("run cmd '{0}' failed", e.cmd)
+ logger.error("Error Code:{0}", e.returncode)
+ logger.error("Result:{0}", e.output[:-1].decode('latin-1'))
+ return e.returncode, e.output.decode('latin-1')
+ return 0, text(output, encoding="utf-8")
+
+#End shell command util functions
diff --git a/azurelinuxagent/utils/textutil.py b/azurelinuxagent/utils/textutil.py
new file mode 100644
index 0000000..2e66b0e
--- /dev/null
+++ b/azurelinuxagent/utils/textutil.py
@@ -0,0 +1,228 @@
+# Windows Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+
+import crypt
+import random
+import string
+import struct
+import xml.dom.minidom as minidom
+import sys
+
+def parse_doc(xml_text):
+ """
+ Parse xml document from string
+ """
+ #The minidom lib has some issue with unicode in python2.
+ #Encode the string into utf-8 first
+ xml_text = xml_text.encode('utf-8')
+ return minidom.parseString(xml_text)
+
+def findall(root, tag, namespace=None):
+ """
+ Get all nodes by tag and namespace under Node root.
+ """
+ if root is None:
+ return []
+
+ if namespace is None:
+ return root.getElementsByTagName(tag)
+ else:
+ return root.getElementsByTagNameNS(namespace, tag)
+
+def find(root, tag, namespace=None):
+ """
+ Get first node by tag and namespace under Node root.
+ """
+ nodes = findall(root, tag, namespace=namespace)
+ if nodes is not None and len(nodes) >= 1:
+ return nodes[0]
+ else:
+ return None
+
+def gettext(node):
+ """
+ Get node text
+ """
+ if node is None:
+ return None
+
+ for child in node.childNodes:
+ if child.nodeType == child.TEXT_NODE:
+ return child.data
+ return None
+
+def findtext(root, tag, namespace=None):
+ """
+ Get text of node by tag and namespace under Node root.
+ """
+ node = find(root, tag, namespace=namespace)
+ return gettext(node)
+
+def getattrib(node, attr_name):
+ """
+ Get attribute of xml node
+ """
+ if node is not None:
+ return node.getAttribute(attr_name)
+ else:
+ return None
+
+def unpack(buf, offset, range):
+ """
+ Unpack bytes into python values.
+ """
+ result = 0
+ for i in range:
+ result = (result << 8) | str_to_ord(buf[offset + i])
+ return result
+
+def unpack_little_endian(buf, offset, length):
+ """
+ Unpack little endian bytes into python values.
+ """
+ return unpack(buf, offset, list(range(length - 1, -1, -1)))
+
+def unpack_big_endian(buf, offset, length):
+ """
+ Unpack big endian bytes into python values.
+ """
+ return unpack(buf, offset, list(range(0, length)))
+
+def hex_dump3(buf, offset, length):
+ """
+ Dump range of buf in formatted hex.
+ """
+ return ''.join(['%02X' % str_to_ord(char) for char in buf[offset:offset + length]])
+
+def hex_dump2(buf):
+ """
+ Dump buf in formatted hex.
+ """
+ return hex_dump3(buf, 0, len(buf))
+
+def is_in_range(a, low, high):
+ """
+ Return True if 'a' in 'low' <= a >= 'high'
+ """
+ return (a >= low and a <= high)
+
+def is_printable(ch):
+ """
+ Return True if character is displayable.
+ """
+ return (is_in_range(ch, str_to_ord('A'), str_to_ord('Z'))
+ or is_in_range(ch, str_to_ord('a'), str_to_ord('z'))
+ or is_in_range(ch, str_to_ord('0'), str_to_ord('9')))
+
+def hex_dump(buffer, size):
+ """
+ Return Hex formated dump of a 'buffer' of 'size'.
+ """
+ if size < 0:
+ size = len(buffer)
+ result = ""
+ for i in range(0, size):
+ if (i % 16) == 0:
+ result += "%06X: " % i
+ byte = buffer[i]
+ if type(byte) == str:
+ byte = ord(byte.decode('latin1'))
+ result += "%02X " % byte
+ if (i & 15) == 7:
+ result += " "
+ if ((i + 1) % 16) == 0 or (i + 1) == size:
+ j = i
+ while ((j + 1) % 16) != 0:
+ result += " "
+ if (j & 7) == 7:
+ result += " "
+ j += 1
+ result += " "
+ for j in range(i - (i % 16), i + 1):
+ byte=buffer[j]
+ if type(byte) == str:
+ byte = str_to_ord(byte.decode('latin1'))
+ k = '.'
+ if is_printable(byte):
+ k = chr(byte)
+ result += k
+ if (i + 1) != size:
+ result += "\n"
+ return result
+
+def str_to_ord(a):
+ """
+ Allows indexing into a string or an array of integers transparently.
+ Generic utility function.
+ """
+ if type(a) == type(b'') or type(a) == type(u''):
+ a = ord(a)
+ return a
+
+def compare_bytes(a, b, start, length):
+ for offset in range(start, start + length):
+ if str_to_ord(a[offset]) != str_to_ord(b[offset]):
+ return False
+ return True
+
+def int_to_ip4_addr(a):
+ """
+ Build DHCP request string.
+ """
+ return "%u.%u.%u.%u" % ((a >> 24) & 0xFF,
+ (a >> 16) & 0xFF,
+ (a >> 8) & 0xFF,
+ (a) & 0xFF)
+
+def hexstr_to_bytearray(a):
+ """
+ Return hex string packed into a binary struct.
+ """
+ b = b""
+ for c in range(0, len(a) // 2):
+ b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
+ return b
+
+def set_ssh_config(config, name, val):
+ notfound = True
+ for i in range(0, len(config)):
+ if config[i].startswith(name):
+ config[i] = "{0} {1}".format(name, val)
+ notfound = False
+ elif config[i].startswith("Match"):
+ #Match block must be put in the end of sshd config
+ break
+ if notfound:
+ config.insert(i, "{0} {1}".format(name, val))
+ return config
+
+def remove_bom(c):
+ if str_to_ord(c[0]) > 128 and str_to_ord(c[1]) > 128 and \
+ str_to_ord(c[2]) > 128:
+ c = c[3:]
+ return c
+
+def gen_password_hash(password, use_salt, salt_type, salt_len):
+ salt="$6$"
+ if use_salt:
+ collection = string.ascii_letters + string.digits
+ salt = ''.join(random.choice(collection) for _ in range(salt_len))
+ salt = "${0}${1}".format(salt_type, salt)
+ return crypt.crypt(password, salt)
+
+
diff --git a/bin/waagent b/bin/waagent
new file mode 100755
index 0000000..e65bc0c
--- /dev/null
+++ b/bin/waagent
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+#
+# Azure Linux Agent
+#
+# Copyright 2015 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.6+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+#
+
+import os
+import imp
+import sys
+
+if __name__ == '__main__' :
+ import azurelinuxagent.agent as agent
+ """
+ Invoke main method of agent
+ """
+ agent.main()
+
+if __name__ == 'waagent':
+ """
+ Load waagent2.0 to support old version of extensions
+ """
+ if sys.version_info[0] == 3:
+ raise ImportError("waagent2.0 doesn't support python3")
+ bin_path = os.path.dirname(os.path.abspath(__file__))
+ agent20_path = os.path.join(bin_path, "waagent2.0")
+ if not os.path.isfile(agent20_path):
+ raise ImportError("Can't load waagent")
+ agent20 = imp.load_source('waagent', agent20_path)
+ __all__ = dir(agent20)
+
diff --git a/waagent b/bin/waagent2.0
index 2b616bf..52e022f 100644..100755
--- a/waagent
+++ b/bin/waagent2.0
@@ -80,7 +80,7 @@ if not hasattr(subprocess,'check_output'):
GuestAgentName = "WALinuxAgent"
GuestAgentLongName = "Windows Azure Linux Agent"
-GuestAgentVersion = "WALinuxAgent-2.0.14"
+GuestAgentVersion = "WALinuxAgent-2.0.15-pre"
ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol.
Config = None
@@ -1029,6 +1029,19 @@ class centosDistro(redhatDistro):
super(centosDistro,self).__init__()
+############################################################
+# asianuxDistro
+############################################################
+
+class asianuxDistro(redhatDistro):
+ """
+ Asianux Distro concrete class
+ Put Asianux specific behavior here...
+ """
+ def __init__(self):
+ super(asianuxDistro,self).__init__()
+
+
############################################################
# CoreOSDistro
############################################################
diff --git a/config/99-azure-product-uuid.rules b/config/99-azure-product-uuid.rules
deleted file mode 100644
index a5af9b1..0000000
--- a/config/99-azure-product-uuid.rules
+++ /dev/null
@@ -1,9 +0,0 @@
-SUBSYSTEM!="dmi", GOTO="product_uuid-exit"
-ATTR{sys_vendor}!="Microsoft Corporation", GOTO="product_uuid-exit"
-ATTR{product_name}!="Virtual Machine", GOTO="product_uuid-exit"
-TEST!="/sys/devices/virtual/dmi/id/product_uuid", GOTO="product_uuid-exit"
-
-RUN+="/bin/chmod 0444 /sys/devices/virtual/dmi/id/product_uuid"
-
-LABEL="product_uuid-exit"
-
diff --git a/config/docker-waagent.conf b/config/suse/waagent.conf
index def7231..e429c74 100644
--- a/config/docker-waagent.conf
+++ b/config/suse/waagent.conf
@@ -20,7 +20,7 @@ Provisioning.Enabled=y
Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
-Provisioning.RegenerateSshHostKeyPair=n
+Provisioning.RegenerateSshHostKeyPair=y
# Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.SshHostKeyPairType=rsa
@@ -39,7 +39,7 @@ ResourceDisk.Format=y
# File system on the resource disk
# Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
-ResourceDisk.Filesystem=ext4
+ResourceDisk.Filesystem=ext3
# Mount point for the resource disk
ResourceDisk.MountPoint=/mnt/resource
@@ -53,23 +53,18 @@ ResourceDisk.SwapSizeMB=0
# Respond to load balancer probes if requested by Windows Azure.
LBProbeResponder=y
-# File to write log to.
-# '/var/log/waagent.log' if not set
-Logs.File=/dev/stdout
-
-# Enable logging to serial console (y|n)
-# When stdout is not enough...
-# 'y' if not set
-Logs.Console=n
-
# Enable verbose logging (y|n)
Logs.Verbose=n
-# Preferred network interface to communicate with Azure platform
-Network.Interface=eth0
-
# Root device timeout in seconds.
OS.RootDeviceScsiTimeout=300
# If "None", the system default version is used.
OS.OpensslPath=None
+
+# If set, agent will use proxy server to access internet
+#HttpProxy.Host=None
+#HttpProxy.Port=None
+
+# Detect Scvmm environment, default is n
+# DetectScvmmEnv=n
diff --git a/config/ubuntu/waagent.conf b/config/ubuntu/waagent.conf
new file mode 100644
index 0000000..ab50418
--- /dev/null
+++ b/config/ubuntu/waagent.conf
@@ -0,0 +1,70 @@
+#
+# Windows Azure Linux Agent Configuration
+#
+
+# Specified program is invoked with the argument "Ready" when we report ready status
+# to the endpoint server.
+Role.StateConsumer=None
+
+# Specified program is invoked with XML file argument specifying role
+# configuration.
+Role.ConfigurationConsumer=None
+
+# Specified program is invoked with XML file argument specifying role topology.
+Role.TopologyConsumer=None
+
+# Enable instance creation
+Provisioning.Enabled=n
+
+# Password authentication for root account will be unavailable.
+Provisioning.DeleteRootPassword=y
+
+# Generate fresh host key pair.
+Provisioning.RegenerateSshHostKeyPair=n
+
+# Supported values are "rsa", "dsa" and "ecdsa".
+Provisioning.SshHostKeyPairType=rsa
+
+# Monitor host name changes and publish changes via DHCP requests.
+Provisioning.MonitorHostName=n
+
+# Decode CustomData from Base64.
+Provisioning.DecodeCustomData=n
+
+# Execute CustomData after provisioning.
+Provisioning.ExecuteCustomData=n
+
+# Format if unformatted. If 'n', resource disk will not be mounted.
+ResourceDisk.Format=n
+
+# File system on the resource disk
+# Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
+ResourceDisk.Filesystem=ext4
+
+# Mount point for the resource disk
+ResourceDisk.MountPoint=/mnt/resource
+
+# Create and use swapfile on resource disk.
+ResourceDisk.EnableSwap=n
+
+# Size of the swapfile.
+ResourceDisk.SwapSizeMB=0
+
+# Respond to load balancer probes if requested by Windows Azure.
+LBProbeResponder=y
+
+# Enable verbose logging (y|n)
+Logs.Verbose=n
+
+# Root device timeout in seconds.
+OS.RootDeviceScsiTimeout=300
+
+# If "None", the system default version is used.
+OS.OpensslPath=None
+
+# If set, agent will use proxy server to access internet
+#HttpProxy.Host=None
+#HttpProxy.Port=None
+
+# Detect Scvmm environment, default is n
+# DetectScvmmEnv=n
diff --git a/config/waagent.conf b/config/waagent.conf
index 97da2b9..4435d56 100644
--- a/config/waagent.conf
+++ b/config/waagent.conf
@@ -50,24 +50,9 @@ ResourceDisk.EnableSwap=n
# Size of the swapfile.
ResourceDisk.SwapSizeMB=0
-# Respond to load balancer probes if requested by Windows Azure.
-LBProbeResponder=y
-
-# File to write log to.
-# '/var/log/waagent.log' if not set
-Logs.File=/var/log/waagent.log
-
-# Enable logging to serial console (y|n)
-# When stdout is not enough...
-# 'y' if not set
-Logs.Console=y
-
# Enable verbose logging (y|n)
Logs.Verbose=n
-# Preferred network interface to communicate with Azure platform
-#Network.Interface=eth0
-
# Root device timeout in seconds.
OS.RootDeviceScsiTimeout=300
@@ -78,3 +63,5 @@ OS.OpensslPath=None
#HttpProxy.Host=None
#HttpProxy.Port=None
+# Detect Scvmm environment, default is n
+# DetectScvmmEnv=n
diff --git a/debian/changelog b/debian/changelog
index 9c9260d..566015a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+walinuxagent (2.1.1-0ubuntu1) wily; urgency=medium
+
+ * New upstream release for Ubuntu.
+ - Switch to Python3
+ - Applies Ubuntu specific patches
+
+ -- Ben Howard <ben.howard@ubuntu.com> Fri, 14 Aug 2015 16:40:41 -0600
+
walinuxagent (2.0.14-0ubuntu1) wily; urgency=medium
* New upstream release.
diff --git a/debian/control b/debian/control
index 226c96f..a10e57e 100644
--- a/debian/control
+++ b/debian/control
@@ -3,9 +3,9 @@ Section: python
Priority: extra
Maintainer: Ben Howard <ben.howard@ubuntu.com>
XSBC-Original-Maintainer: Microsoft Corporation <walinuxagent@microsoft.com>
-Build-Depends: debhelper (>= 8), python-all, python-setuptools, dh-systemd, dh-python
+Build-Depends: debhelper (>= 8), python3-all, python3-setuptools, dh-systemd
Standards-Version: 3.9.6
-XS-Python-Version: all
+X-Python3-Version: >= 3.2
Homepage: http://go.microsoft.com/fwlink/?LinkId=250998
Package: walinuxagent
diff --git a/debian/install b/debian/install
index 21a75f4..5f441de 100644
--- a/debian/install
+++ b/debian/install
@@ -1,4 +1,3 @@
-config/99-azure-product-uuid.rules lib/udev/rules.d
config/91_walinuxagent.cfg etc/cloud/cloud.cfg.d
debian/ephemeral-disk-warning.service lib/systemd/system
debian/ephemeral-disk-warning.conf etc/init
diff --git a/debian/patches/build_info.txt b/debian/patches/build_info.txt
deleted file mode 100644
index f02ab12..0000000
--- a/debian/patches/build_info.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-serial=20150629
-orig_prefix=trusty-server-cloudimg
-suite=trusty
-build_name=server
diff --git a/debian/patches/disable-provisioning.patch b/debian/patches/disable-provisioning.patch
new file mode 100644
index 0000000..716ae12
--- /dev/null
+++ b/debian/patches/disable-provisioning.patch
@@ -0,0 +1,11 @@
+--- a/config/ubuntu/waagent.conf
++++ b/config/ubuntu/waagent.conf
+@@ -42,7 +42,7 @@
+ ResourceDisk.Filesystem=ext4
+
+ # Mount point for the resource disk
+-ResourceDisk.MountPoint=/mnt/resource
++ResourceDisk.MountPoint=/mnt
+
+ # Create and use swapfile on resource disk.
+ ResourceDisk.EnableSwap=n
diff --git a/debian/patches/disable-udev-rules-removal.patch b/debian/patches/disable-udev-rules-removal.patch
new file mode 100644
index 0000000..3f3adfb
--- /dev/null
+++ b/debian/patches/disable-udev-rules-removal.patch
@@ -0,0 +1,12 @@
+--- a/azurelinuxagent/distro/ubuntu/osutil.py
++++ b/azurelinuxagent/distro/ubuntu/osutil.py
+@@ -44,6 +44,9 @@
+ def start_agent_service(self):
+ return shellutil.run("service walinuxagent start", chk_err=False)
+
++ def remove_rules_files(self, *args):
++ return
++
+ class Ubuntu1204OSUtil(Ubuntu14xOSUtil):
+ def __init__(self):
+ super(Ubuntu1204OSUtil, self).__init__()
diff --git a/debian/patches/disable-udev-rules.patch b/debian/patches/disable-udev-rules.patch
deleted file mode 100644
index 8157f40..0000000
--- a/debian/patches/disable-udev-rules.patch
+++ /dev/null
@@ -1,12 +0,0 @@
---- a/waagent
-+++ b/waagent
-@@ -92,8 +92,7 @@
- VMM_STARTUP_SCRIPT_NAME='install'
- VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml'
- global RulesFiles
--RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
-- "/etc/udev/rules.d/70-persistent-net.rules" ]
-+RulesFiles = []
- VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
- EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
- global LibDir
diff --git a/debian/patches/disable_provisioning.patch b/debian/patches/disable_provisioning.patch
deleted file mode 100644
index 956d30b..0000000
--- a/debian/patches/disable_provisioning.patch
+++ /dev/null
@@ -1,55 +0,0 @@
---- a/config/waagent.conf
-+++ b/config/waagent.conf
-@@ -2,6 +2,11 @@
- # Windows Azure Linux Agent Configuration
- #
-
-+# NOTE: Ubuntu uses Cloud-init for disk-provisioning. This will
-+# unless you disable Cloud-init disk provisioning. Please see
-+# /usr/share/doc/walinuxagent/99-cloud-init-disable-diskprovisioning.conf
-+#
-+
- # Specified program is invoked with the argument "Ready" when we report ready status
- # to the endpoint server.
- Role.StateConsumer=None
-@@ -14,19 +19,19 @@
- Role.TopologyConsumer=None
-
- # Enable instance creation
--Provisioning.Enabled=y
-+Provisioning.Enabled=n
-
- # Password authentication for root account will be unavailable.
--Provisioning.DeleteRootPassword=y
-+Provisioning.DeleteRootPassword=n
-
- # Generate fresh host key pair.
--Provisioning.RegenerateSshHostKeyPair=y
-+Provisioning.RegenerateSshHostKeyPair=n
-
- # Supported values are "rsa", "dsa" and "ecdsa".
- Provisioning.SshHostKeyPairType=rsa
-
- # Monitor host name changes and publish changes via DHCP requests.
--Provisioning.MonitorHostName=y
-+Provisioning.MonitorHostName=n
-
- # Decode CustomData from Base64.
- Provisioning.DecodeCustomData=n
-@@ -35,14 +40,14 @@
- Provisioning.ExecuteCustomData=n
-
- # Format if unformatted. If 'n', resource disk will not be mounted.
--ResourceDisk.Format=y
-+ResourceDisk.Format=n
-
- # File system on the resource disk
- # Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
- ResourceDisk.Filesystem=ext4
-
- # Mount point for the resource disk
--ResourceDisk.MountPoint=/mnt/resource
-+ResourceDisk.MountPoint=/mnt
-
- # Create and use swapfile on resource disk.
- ResourceDisk.EnableSwap=n
diff --git a/debian/patches/fixup_setup_file.patch b/debian/patches/fixup_setup_file.patch
deleted file mode 100644
index 845607d..0000000
--- a/debian/patches/fixup_setup_file.patch
+++ /dev/null
@@ -1,11 +0,0 @@
---- a/setup.py
-+++ b/setup.py
-@@ -51,7 +51,7 @@
-
- def initialize_options(self):
- install.initialize_options(self)
-- self.init_system = 'sysV'
-+ self.init_system = 'upstart'
- self.lnx_distro = None
-
- def finalize_options(self):
diff --git a/debian/patches/series b/debian/patches/series
index bddf5a0..9b3b2ca 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,6 +1,4 @@
-fixup_setup_file.patch
cloud-init-default-cfg.patch
-#disable_provisioning.patch
-disable-udev-rules.patch
-fix-waagent-service.patch
-disable_provisioning.patch
+start-after-cloudinit.patch
+disable-udev-rules-removal.patch
+disable-provisioning.patch
diff --git a/debian/patches/start-after-cloudinit.patch b/debian/patches/start-after-cloudinit.patch
new file mode 100644
index 0000000..235b70e
--- /dev/null
+++ b/debian/patches/start-after-cloudinit.patch
@@ -0,0 +1,12 @@
+--- a/init/ubuntu/walinuxagent.service
++++ b/init/ubuntu/walinuxagent.service
+@@ -1,7 +1,7 @@
+ [Unit]
+ Description=Azure Linux Agent
+-After=network.target
+-After=sshd.service
++After=network-online.target cloud-final.service
++Wants=network-online.target sshd.service sshd-keygen.service cloud-final.service
+ ConditionFileIsExecutable=/usr/sbin/waagent
+ ConditionPathExists=/etc/waagent.conf
+
diff --git a/debian/rules b/debian/rules
index 412afa9..bb65f43 100755
--- a/debian/rules
+++ b/debian/rules
@@ -1,7 +1,4 @@
#!/usr/bin/make -f
-INIT_SYSTEM ?= upstart,systemd
-export PYBUILD_INSTALL_ARGS=--init-system=$(INIT_SYSTEM)
-
DEB_VERSION=$(shell dpkg-parsechangelog | sed -rne 's,^Version: ([^-]+).*,\1,p')
ORIG_SRC=https://github.com/WindowsAzure/WALinuxAgent
@@ -15,4 +12,4 @@ get-packaged-orig-source:
rm -rf orig_source
%:
- dh $@ --with python2,systemd
+ dh $@ --with python3,systemd --buildsystem=pybuild
diff --git a/debian/walinuxagent.service b/debian/walinuxagent.service
index 1abb506..b1c7ed6 120000
--- a/debian/walinuxagent.service
+++ b/debian/walinuxagent.service
@@ -1 +1 @@
-../distro/systemd/walinuxagent.service \ No newline at end of file
+../init/ubuntu/walinuxagent.service \ No newline at end of file
diff --git a/distro/suse/waagent.sysV b/distro/suse/waagent.sysV
deleted file mode 100644
index 15c6c4b..0000000
--- a/distro/suse/waagent.sysV
+++ /dev/null
@@ -1,112 +0,0 @@
-#! /bin/sh
-#
-# Windows Azure Linux Agent sysV init script
-#
-# Copyright 2013 Microsoft Corporation
-# Copyright SUSE LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# /etc/init.d/waagent
-#
-# and symbolic link
-#
-# /usr/sbin/rcwaagent
-#
-# System startup script for the waagent
-#
-### BEGIN INIT INFO
-# Provides: WindowsAzureLinuxAgent
-# Required-Start: $network sshd
-# Required-Stop: $network sshd
-# Default-Start: 3 5
-# Default-Stop: 0 1 2 6
-# Description: Start the WindowsAzureLinuxAgent
-### END INIT INFO
-
-PYTHON=/usr/bin/python
-WAZD_BIN=/usr/sbin/waagent
-WAZD_CONF=/etc/waagent.conf
-WAZD_PIDFILE=/var/run/waagent.pid
-
-test -x "$WAZD_BIN" || { echo "$WAZD_BIN not installed"; exit 5; }
-test -e "$WAZD_CONF" || { echo "$WAZD_CONF not found"; exit 6; }
-
-. /etc/rc.status
-
-# First reset status of this service
-rc_reset
-
-# Return values acc. to LSB for all commands but status:
-# 0 - success
-# 1 - misc error
-# 2 - invalid or excess args
-# 3 - unimplemented feature (e.g. reload)
-# 4 - insufficient privilege
-# 5 - program not installed
-# 6 - program not configured
-#
-# Note that starting an already running service, stopping
-# or restarting a not-running service as well as the restart
-# with force-reload (in case signalling is not supported) are
-# considered a success.
-
-
-case "$1" in
- start)
- echo -n "Starting WindowsAzureLinuxAgent"
- ## Start daemon with startproc(8). If this fails
- ## the echo return value is set appropriate.
- startproc -f ${PYTHON} ${WAZD_BIN} -daemon
- rc_status -v
- ;;
- stop)
- echo -n "Shutting down WindowsAzureLinuxAgent"
- ## Stop daemon with killproc(8) and if this fails
- ## set echo the echo return value.
- killproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
- rc_status -v
- ;;
- try-restart)
- ## Stop the service and if this succeeds (i.e. the
- ## service was running before), start it again.
- $0 status >/dev/null && $0 restart
- rc_status
- ;;
- restart)
- ## Stop the service and regardless of whether it was
- ## running or not, start it again.
- $0 stop
- sleep 1
- $0 start
- rc_status
- ;;
- force-reload|reload)
- rc_status
- ;;
- status)
- echo -n "Checking for service WindowsAzureLinuxAgent "
- ## Check status with checkproc(8), if process is running
- ## checkproc will return with exit status 0.
-
- checkproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
- rc_status -v
- ;;
- probe)
- ;;
- *)
- echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
- exit 1
- ;;
-esac
-rc_exit
diff --git a/get-agent.py b/get-agent.py
deleted file mode 100755
index 8f82a23..0000000
--- a/get-agent.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2014 Microsoft Corporation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# Implements parts of RFC 2131, 1541, 1497 and
-# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
-# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
-#
-
-import sys
-import os
-import shutil
-import imp
-import subprocess
-import time
-import re
-import platform
-
-def upgrade(account='Azure', ref='2.0'):
- #Define variables
- agentUri = ('https://raw.githubusercontent.com/{0}/'
- 'WALinuxAgent/{1}/waagent').format(account, ref)
- distro = platform.linux_distribution()
- cmd = ['service', 'waagent', 'restart']
- agent_file="/usr/sbin/waagent"
-
- if "Ubuntu" in distro[0]:
- cmd[1]='walinuxagent'
- if "CoreOS" in distro[0]:
- cmd = ['systemctl', 'restart', 'waagent']
- agent_file = "/usr/share/oem/bin/waagent"
-
- if os.path.isfile('waagent'):
- os.remove('waagent')
- print "Download WAAgent from: {0}".format(agentUri)
- try:
- import urllib2
- response = urllib2.urlopen(agentUri)
- html = response.read()
- with open("waagent", "w+") as F:
- F.write(html)
- except:
- subprocess.call(['wget', agentUri])
-
- print "Upgrade WAAgent"
-
- shutil.copyfile("waagent", agent_file)
- os.chmod(agent_file, 0700)
-
- job = subprocess.Popen(cmd)
- job.wait()
-
-if __name__ == '__main__':
- if len(sys.argv) == 3:
- upgrade(sys.argv[1], sys.argv[2])
- else:
- upgrade()
diff --git a/init/coreos/cloud-config.yml b/init/coreos/cloud-config.yml
new file mode 100644
index 0000000..461dc1a
--- /dev/null
+++ b/init/coreos/cloud-config.yml
@@ -0,0 +1,52 @@
+#cloud-config
+
+coreos:
+ units:
+ - name: etcd.service
+ runtime: true
+ drop-ins:
+ - name: 10-oem.conf
+ content: |
+ [Service]
+ Environment=ETCD_PEER_ELECTION_TIMEOUT=1200
+
+ - name: etcd2.service
+ runtime: true
+ drop-ins:
+ - name: 10-oem.conf
+ content: |
+ [Service]
+ Environment=ETCD_ELECTION_TIMEOUT=1200
+
+ - name: waagent.service
+ command: start
+ runtime: true
+ content: |
+ [Unit]
+ Description=Microsoft Azure Agent
+ Wants=network-online.target sshd-keygen.service
+ After=network-online.target sshd-keygen.service
+
+ [Service]
+ Type=simple
+ Restart=always
+ RestartSec=5s
+ ExecStart=/usr/share/oem/python/bin/python /usr/share/oem/bin/waagent -daemon
+
+ - name: oem-cloudinit.service
+ command: restart
+ runtime: yes
+ content: |
+ [Unit]
+ Description=Cloudinit from Azure metadata
+
+ [Service]
+ Type=oneshot
+ ExecStart=/usr/bin/coreos-cloudinit --oem=azure
+
+ oem:
+ id: azure
+ name: Microsoft Azure
+ version-id: 2.1.0
+ home-url: https://azure.microsoft.com/
+ bug-report-url: https://github.com/coreos/bugs/issues
diff --git a/init/ubuntu/walinuxagent b/init/ubuntu/walinuxagent
new file mode 100644
index 0000000..a17174c
--- /dev/null
+++ b/init/ubuntu/walinuxagent
@@ -0,0 +1,2 @@
+# To disable the Windows Azure Agent, set WALINUXAGENT_ENABLED=0
+WALINUXAGENT_ENABLED=1
diff --git a/init/ubuntu/walinuxagent.conf b/init/ubuntu/walinuxagent.conf
new file mode 100644
index 0000000..2ce6608
--- /dev/null
+++ b/init/ubuntu/walinuxagent.conf
@@ -0,0 +1,26 @@
+description "Windows Azure Linux agent"
+author "Ben Howard <ben.howard@canonical.com>"
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+pre-start script
+
+ [ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent
+
+ if [ "$WALINUXAGENT_ENABLED" != "1" ]; then
+ stop ; exit 0
+ fi
+
+ if [ ! -x /usr/sbin/waagent ]; then
+ stop ; exit 0
+ fi
+
+ #Load the udf module
+ modprobe -b udf
+
+end script
+
+exec /usr/sbin/waagent -daemon
+
+respawn
diff --git a/init/ubuntu/walinuxagent.service b/init/ubuntu/walinuxagent.service
new file mode 100755
index 0000000..1a67835
--- /dev/null
+++ b/init/ubuntu/walinuxagent.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=Azure Linux Agent
+After=network.target
+After=sshd.service
+ConditionFileIsExecutable=/usr/sbin/waagent
+ConditionPathExists=/etc/waagent.conf
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/python3 /usr/sbin/waagent -daemon
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/distro/redhat/waagent.sysV b/init/waagent
index d1278e7..fe30d4d 100644..100755
--- a/distro/redhat/waagent.sysV
+++ b/init/waagent
@@ -1,22 +1,25 @@
#!/bin/bash
#
-# Init file for WindowsAzureLinuxAgent.
+# Init file for AzureLinuxAgent.
#
# chkconfig: 2345 60 80
-# description: WindowsAzureLinuxAgent
+# description: AzureLinuxAgent
#
# source function library
. /etc/rc.d/init.d/functions
RETVAL=0
-FriendlyName="WindowsAzureLinuxAgent"
+FriendlyName="AzureLinuxAgent"
WAZD_BIN=/usr/sbin/waagent
start()
{
echo -n $"Starting $FriendlyName: "
- $WAZD_BIN -daemon &
+ $WAZD_BIN -start
+ RETVAL=$?
+ echo
+ return $RETVAL
}
stop()
diff --git a/distro/systemd/waagent.service b/init/waagent.service
index a61fc9d..f465f79 100644..100755
--- a/distro/systemd/waagent.service
+++ b/init/waagent.service
@@ -1,5 +1,5 @@
[Unit]
-Description=Windows Azure Linux Agent
+Description=Azure Linux Agent
After=network.target
After=sshd.service
ConditionFileIsExecutable=/usr/sbin/waagent
@@ -8,6 +8,7 @@ ConditionPathExists=/etc/waagent.conf
[Service]
Type=simple
ExecStart=/usr/sbin/waagent -daemon
+Restart=always
[Install]
WantedBy=multi-user.target
diff --git a/rpm/README b/rpm/README
index b731b9b..70770c1 100644
--- a/rpm/README
+++ b/rpm/README
@@ -1,46 +1,19 @@
-The preferred method of installing the Windows Azure Linux Agent for
+The preferred method of installing the Azure Linux Agent for
CentOS and other RPM-based distributions is to use the RPM packaging.
Platform images in the Azure Gallery will already include the agent
package. This guide is primarily for individuals who would like to
build their own custom packages.
-OpenLogic provides an RPM package for CentOS-based distributions
-in their package repositories, for example
-http://olcentgbl.trafficmanager.net/openlogic/6/openlogic/x86_64/RPMS/
-http://olcentgbl.trafficmanager.net/openlogic/7/openlogic/x86_64/RPMS/
-
-Note: Official packaging and other patches for SLES and OpenSUSE can be
-found on the OpenSUSE Build Service:
-https://build.opensuse.org/package/show?package=WALinuxAgent&project=Cloud%3ATools
-
The instructions below will describe how you can build your own RPM
-package:
-
- 1. Install required rpmbuild package:
-
- yum -y install rpm-build
-
- 2. Set up the rpmbuild environment:
-
- mkdir -p ~/rpmbuild/{SPECS,SOURCES}
-
- 3. Download the WALinuxAgent source code as a tar.gz compressed
- file from Github, or create it yourself:
-
- tar -czf WALinuxAgent-1.x.x.tar.gz WALinuxAgent-1.x.x
-
- 4. Copy the files to the rpmbuild environment:
-
- cp walinuxagent.spec ~/rpmbuild/SPECS
- cp WALinuxAgent-1.x.x.tar.gz ~/rpmbuild/SOURCES
-
- 5. If necessary, edit the ~/rpmbuild/SPECS/walinuxagent.spec file
- and ensure that the 'Version', 'Source0' and other information
- is accurate.
+package.
- 6. The following command will build the binary and source RPMs:
+ 1. Install setuptools
- rpmbuild -ba ~/rpmbuild/SPECS/walinuxagent.spec
+ curl https://bootstrap.pypa.io/ez_setup.py -o - | python
+
+ 2. The following command will build the binary and source RPMs:
+
+ python setup.py bdist_rpm --post-inst rpm/post-inst
-Enjoy! \ No newline at end of file
+Enjoy!
diff --git a/rpm/post-inst b/rpm/post-inst
new file mode 100644
index 0000000..d515589
--- /dev/null
+++ b/rpm/post-inst
@@ -0,0 +1 @@
+waagent register-service
diff --git a/rpm/walinuxagent.spec b/rpm/walinuxagent.spec
deleted file mode 100644
index 1d6f794..0000000
--- a/rpm/walinuxagent.spec
+++ /dev/null
@@ -1,118 +0,0 @@
-#===============================================================================
-# Name: walinuxagent.spec
-#-------------------------------------------------------------------------------
-# Purpose : RPM Spec file for Python script packaging
-# Version : 2.0.13
-# Created : April 20 2012
-#===============================================================================
-
-Name: WALinuxAgent
-Summary: The Windows Azure Linux Agent
-Version: 2.0.13
-Release: 1
-License: Apache License Version 2.0
-Group: System/Daemons
-Url: http://go.microsoft.com/fwlink/?LinkId=250998
-Source0: WALinuxAgent-2.0.13.tar.gz
-Requires: python python-pyasn1 openssh openssl util-linux sed grep sudo iptables parted
-BuildRoot: %{_tmppath}/%{name}-%{version}-build
-BuildArch: noarch
-Vendor: Microsoft Corporation
-Packager: Microsoft Corporation <walinuxagent@microsoft.com>
-
-%if 0%{?rhel} < 7
-Conflicts: NetworkManager
-%endif
-
-%description
-The Windows Azure Linux Agent supports the provisioning and running of Linux
-VMs in the Windows Azure cloud. This package should be installed on Linux disk
-images that are built to run in the Windows Azure environment.
-
-%prep
-%setup -q
-find . -type f -exec sed -i 's/\r//' {} +
-find . -type f -exec chmod 0644 {} +
-
-%pre -p /bin/sh
-
-%build
-# Nothing to do
-
-%install
-python setup.py install --prefix=%{_prefix} --lnx-distro='redhat' --init-system='sysV' --root=%{buildroot}
-mkdir -p %{buildroot}/%{_localstatedir}/log
-mkdir -p -m 0700 %{buildroot}/%{_sharedstatedir}/waagent
-touch %{buildroot}/%{_localstatedir}/log/waagent.log
-
-%post
-/sbin/chkconfig --add waagent
-
-%preun -p /bin/sh
-if [ $1 = 0 ]; then
- /sbin/service waagent stop >/dev/null 2>&1
- /sbin/chkconfig --del waagent
-fi
-
-%postun -p /bin/sh
-if [ "$1" -ge "1" ]; then
- /sbin/service waagent restart >/dev/null 2>&1 || :
-fi
-
-
-%files
-%attr(0755,root,root) %{_sysconfdir}/rc.d/init.d/waagent
-%attr(0755,root,root) %{_sysconfdir}/udev/rules.d/99-azure-product-uuid.rules
-%defattr(0644,root,root,0755)
-%doc Changelog LICENSE-2.0.txt NOTICE README
-%attr(0755,root,root) %{_sbindir}/waagent
-%config(noreplace) %{_sysconfdir}/logrotate.d/waagent
-%config %{_sysconfdir}/waagent.conf
-%ghost %{_localstatedir}/log/waagent.log
-%dir %attr(0700, root, root) %{_sharedstatedir}/waagent
-
-
-%changelog
-* Thu Sep 18 2014 - walinuxagent@microsoft.com
-- Remove NetworkManager conflict for EL7+
-
-* Sun Mar 25 2014 - walinuxagent@microsoft.com
-- Create directory /var/lib/waagent
-- Updated version to 2.0.4 for release
-
-* Thu Jan 16 2014 - walinuxagent@microsoft.com
-- Updated version to 2.0.3 for release
-
-* Wed Dec 18 2013 - walinuxagent@microsoft.com
-- Updated version to 2.0.2 for release
-
-* Tue Nov 05 2013 - walinuxagent@microsoft.com
-- Updated version to 2.0.1 for release
-
-* Fri Sep 20 2013 - walinuxagent@microsoft.com
-- Updated version to 2.0.0 for release
-
-* Fri Aug 23 2013 - walinuxagent@microsoft.com
-- Updated version to 1.4.0 for release
-
-* Thu May 30 2013 - walinuxagent@microsoft.com
-- Updated version to 1.3.3 for release
-
-* Tue Feb 26 2013 - walinuxagent@microsoft.com
-- Updated version to 1.3.2 for release
-
-* Fri Feb 15 2013 - walinuxagent@microsoft.com
-- Updated version to 1.3.1 for release
-
-* Fri Jan 18 2013 - walinuxagent@microsoft.com
-- Updated version to 1.3 for release
-
-* Fri Dec 07 2012 - walinuxagent@microsoft.com
-- Updated version to 1.2 for release
-
-* Fri Nov 09 2012 - walinuxagent@microsoft.com
-- Added README and Changelog
-- Updated version to 1.1 for release
-
-* Thu May 17 2012 - walinuxagent@microsoft.com
-- Initial WALinuxAgent packages.
diff --git a/script/buildrpm.sh b/script/buildrpm.sh
deleted file mode 100644
index 509a950..0000000
--- a/script/buildrpm.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-script=$(dirname $0)
-root=$script/..
-cd $root
-root=`pwd`
-
-version=WALinuxAgent-2.0.13
-
-mkdir -p ~/rpmbuild/TMP
-mkdir -p ~/rpmbuild/SPECS
-mkdir -p ~/rpmbuild/SOURCES
-
-
-echo "rsync -a --exclude '.*' $root/ ~/rpmbuild/TMP/$version"
-rsync -a --exclude '.*' $root/ ~/rpmbuild/TMP/$version
-
-echo "cd ~/rpmbuild/TMP"
-cd ~/rpmbuild/TMP
-
-echo "tar -czf ${version}.tar.gz $version"
-tar -czf ${version}.tar.gz $version
-
-echo "cp $root/rpm/walinuxagent.spec ~/rpmbuild/SPECS"
-cp $root/rpm/walinuxagent.spec ~/rpmbuild/SPECS
-
-echo "cp ~/rpmbuild/TMP/${version}.tar.gz ~/rpmbuild/SOURCES"
-cp ~/rpmbuild/TMP/${version}.tar.gz ~/rpmbuild/SOURCES
-
-echo "rpmbuild -ba ~/rpmbuild/SPECS/walinuxagent.spec"
-rpmbuild -ba ~/rpmbuild/SPECS/walinuxagent.spec
-
-
diff --git a/setup.py b/setup.py
index d2a2416..8a7a7c0 100755
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Windows Azure Linux Agent setup.py
#
@@ -16,200 +16,144 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-import glob
+
import os
-import sys
-import platform
+from azurelinuxagent.metadata import AGENT_NAME, AGENT_VERSION, \
+ AGENT_DESCRIPTION, \
+ DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME
+
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.agent as agent
import setuptools
-from setuptools.command.install import install
+from setuptools import find_packages
+from setuptools.command.install import install as _install
+
+root_dir = os.path.dirname(os.path.abspath(__file__))
+os.chdir(root_dir)
+
+def set_files(data_files, dest=None, src=None):
+ data_files.append((dest, src))
+
+def set_bin_files(data_files, dest="/usr/sbin",
+ src=["bin/waagent", "bin/waagent2.0"]):
+ data_files.append((dest, src))
-from distutils.errors import DistutilsArgError
+def set_conf_files(data_files, dest="/etc", src=["config/waagent.conf"]):
+ data_files.append((dest, src))
-def getDistro():
+def set_logrotate_files(data_files, dest="/etc/logrotate.d",
+ src=["config/waagent.logrotate"]):
+ data_files.append((dest, src))
+
+def set_sysv_files(data_files, dest="/etc/rc.d/init.d", src=["init/waagent"]):
+ data_files.append((dest, src))
+
+def set_systemd_files(data_files, dest="/lib/systemd/system",
+ src=["init/waagent.service"]):
+ data_files.append((dest, src))
+
+def get_data_files(name, version, fullname):
"""
- Try to figure out the distribution we are running on
+ Determine data_files according to distro name, version and init system type
"""
- distro = platform.linux_distribution()[0].lower()
- # Manipulate the distribution to meet our needs we treat
- # Fedora, RHEL, and CentOS the same
- # openSUSE and SLE the same
- if distro.find('suse') != -1:
- distro = 'suse'
- if (distro.find('fedora') != -1
- or distro.find('red hat') != -1
- or distro.find('centos') != -1):
- distro = 'redhat'
-
- return distro
-
-class InstallData(install):
- user_options = install.user_options + [
+ data_files=[]
+
+ if name == 'redhat' or name == 'centos':
+ set_bin_files(data_files)
+ set_conf_files(data_files)
+ set_logrotate_files(data_files)
+ if version >= "7.0":
+ #redhat7.0+ uses systemd
+ set_systemd_files(data_files, dest="/var/lib/systemd/system")
+ else:
+ set_sysv_files(data_files)
+
+ elif name == 'coreos':
+ set_bin_files(data_files, dest="/usr/share/oem/bin")
+ set_conf_files(data_files, dest="/usr/share/oem")
+ set_logrotate_files(data_files)
+ set_files(data_files, dest="/usr/share/oem",
+ src="init/coreos/cloud-config.yml")
+ elif name == 'ubuntu':
+ set_bin_files(data_files)
+ set_conf_files(data_files, src=["config/ubuntu/waagent.conf"])
+ set_logrotate_files(data_files)
+ if version < "15.04":
+ #Ubuntu15.04- uses upstart
+ set_files(data_files, dest="/etc/init",
+ src=["init/ubuntu/walinuxagent.conf"])
+ set_files(data_files, dest='/etc/default',
+ src=['init/ubuntu/walinuxagent'])
+ elif fullname == 'Snappy Ubuntu Core':
+ set_files(data_files, dest="<TODO>",
+ src=["init/ubuntu/snappy/walinuxagent.yml"])
+ else:
+ set_systemd_files(data_files,
+ src=["init/ubuntu/walinuxagent.service"])
+ elif name == 'suse':
+ set_bin_files(data_files)
+ set_conf_files(data_files, src=["config/suse/waagent.conf"])
+ set_logrotate_files(data_files)
+ if fullname == 'SUSE Linux Enterprise Server' and version >= '12' or \
+ fullname == 'openSUSE' and version >= '13.2':
+ set_systemd_files(data_files, dest='/var/lib/systemd/system')
+ else:
+ set_sysv_files(data_files, dest='/etc/init.d')
+ else:
+ #Use default setting
+ set_bin_files(data_files)
+ set_conf_files(data_files)
+ set_logrotate_files(data_files)
+ set_sysv_files(data_files)
+ return data_files
+
+class install(_install):
+ user_options = _install.user_options + [
# This will magically show up in member variable 'init_system'
- ('init-system=', None, 'init system to configure [default: sysV]'),
+ ('init-system=', None, 'deprecated, use --lnx-distro* instead'),
('lnx-distro=', None, 'target Linux distribution'),
+ ('lnx-distro-version=', None, 'target Linux distribution version'),
+ ('lnx-distro-fullname=', None, 'target Linux distribution full name'),
+ ('register-service', None, 'register as startup service'),
+ ('skip-data-files', None, 'skip data files installation'),
]
def initialize_options(self):
- install.initialize_options(self)
- self.init_system = 'sysV'
- self.lnx_distro = None
-
+ _install.initialize_options(self)
+ self.init_system=None
+ self.lnx_distro = DISTRO_NAME
+ self.lnx_distro_version = DISTRO_VERSION
+ self.lnx_distro_fullname = DISTRO_FULL_NAME
+ self.register_service = False
+ self.skip_data_files = False
+
def finalize_options(self):
- install.finalize_options(self)
- if not self.lnx_distro:
- self.lnx_distro = getDistro()
- if self.init_system not in ['sysV', 'systemd', 'upstart']:
- print 'Do not know how to handle %s init system' %self.init_system
- sys.exit(1)
- if self.init_system == 'sysV':
- if not os.path.exists('distro/%s' %self.lnx_distro):
- msg = 'Unknown distribution "%s"' %self.lnx_distro
- msg += ', no entry in distro directory'
- sys.exit(1)
-
- def run(self):
- """
- Install the files for the Windows Azure Linux Agent
- """
- distro = self.lnx_distro
- init = self.init_system
- prefix = self.prefix
- tgtDir = self.root
- if prefix and prefix[-1] != '/':
- prefix += '/'
- else:
- prefix = '/'
- if tgtDir and tgtDir[-1] != '/':
- tgtDir += '/'
- else:
- tgtDir = '/'
- # Handle the different init systems
- if init == 'sysV':
- initdir = 'etc/init.d'
- if self.lnx_distro == 'redhat':
- initdir = 'etc/rc.d/init.d'
- if not os.path.exists(tgtDir + initdir):
- try:
- self.mkpath(tgtDir + initdir, 0755)
- except:
- msg = 'Could not create init script directory '
- msg += tgtDir
- msg += initdir
- print msg
- print sys.exc_info()[0]
- sys.exit(1)
- initScripts = glob.glob('distro/%s/*.sysV' %distro)
- try:
- for f in initScripts:
- newName = f.split('/')[-1].split('.')[0]
- self.copy_file(f, tgtDir + initdir + '/' + newName)
- except:
- print 'Could not install systemV init script',
- sys.exit(1)
- elif init == 'systemd':
- if not os.path.exists(tgtDir + prefix +'lib/systemd/system'):
- try:
- self.mkpath(tgtDir + prefix + 'lib/systemd/system', 0755)
- except:
- msg = 'Could not create systemd service directory '
- msg += tgtDir + prefix
- msg += 'lib/systemd/system'
- print msg
- sys.exit(1)
- services = glob.glob('distro/systemd/*')
- for f in services:
- try:
- baseName = f.split('/')[-1]
- self.copy_file(f,
- tgtDir + prefix +'lib/systemd/system/' + baseName)
- except:
- print 'Could not install systemd service files'
- sys.exit(1)
- elif init == 'upstart':
- print 'Upstart init files installation not supported at this time.'
- print 'Need an implementation, please submit a patch ;)'
- print 'See WALinuxAgent/debian directory for Debian/Ubuntu packaging'
-
- # Configuration file
- if not os.path.exists(tgtDir + 'etc'):
- try:
- self.mkpath(tgtDir + 'etc', 0755)
- except:
- msg = 'Could not create config dir '
- msg += tgtDir
- msg += 'etc'
- print msg
- sys.exit(1)
- try:
- self.copy_file('config/waagent.conf', tgtDir + 'etc/waagent.conf')
- except:
- print 'Could not install configuration file %setc' %tgtDir
- sys.exit(1)
-
- if not os.path.exists(tgtDir + 'etc/udev/rules.d'):
- try:
- self.mkpath(tgtDir + 'etc/udev/rules.d', 0755)
- except Exception as e:
- print e
-
- try:
- self.copy_file('config/99-azure-product-uuid.rules', tgtDir + 'etc/udev/rules.d/99-azure-product-uuid.rules')
- except Exception as e:
- print e
- print 'Could not install product uuid rules file %setc' %tgtDir
- sys.exit(1)
-
- if not os.path.exists(tgtDir + 'etc/logrotate.d'):
- try:
- self.mkpath(tgtDir + 'etc/logrotate.d', 0755)
- except:
- msg = 'Could not create ' + tgtDir + 'etc/logrotate.d'
- print msg
- sys.exit(1)
- try:
- self.copy_file('config/waagent.logrotate',
- tgtDir + 'etc/logrotate.d/waagent')
- except:
- msg = 'Could not install logrotate file in '
- msg += tgtDir + 'etc/logrotate.d'
- print msg
- sys.exit(1)
-
- # Daemon
- if not os.path.exists(tgtDir + prefix + 'sbin'):
- try:
- self.mkpath(tgtDir + prefix + 'sbin', 0755)
- except:
- msg = 'Could not create target daemon dir '
- msg+= tgtDir + prefix + 'sbin'
- print msg
- sys.exit(1)
- try:
- self.copy_file('waagent', tgtDir + prefix + 'sbin/waagent')
- except:
- print 'Could not install daemon %s%ssbin/waagent' %(tgtDir,prefix)
- sys.exit(1)
- os.chmod('%s%ssbin/waagent' %(tgtDir,prefix), 0755)
-
-def readme():
- with open('README') as f:
- return f.read()
-
-setuptools.setup(name = 'waagent',
- version = '1.4.0',
- description = 'Windows Azure Linux Agent',
- long_description = readme(),
- author = 'Stephen Zarkos, Eric Gable',
- author_email = 'walinuxagent@microsoft.com',
- platforms = 'Linux',
- url = 'https://github.com/Windows-Azure/',
- license = 'Apache License Version 2.0',
- cmdclass = {
- # Use a subclass for install that handles
- # install, we do not have a "true" python package
- 'install': InstallData,
- },
-)
+ _install.finalize_options(self)
+ if self.skip_data_files:
+ return
+ if self.init_system is not None:
+ print("WARNING: --init-system is deprecated,"
+ "use --lnx-distro* instead")
+ data_files = get_data_files(self.lnx_distro, self.lnx_distro_version,
+ self.lnx_distro_fullname)
+ self.distribution.data_files = data_files
+ self.distribution.reinitialize_command('install_data', True)
+ def run(self):
+ _install.run(self)
+ if self.register_service:
+ agent.register_service()
+setuptools.setup(name=AGENT_NAME,
+ version=AGENT_VERSION,
+ long_description=AGENT_DESCRIPTION,
+ author= 'Yue Zhang, Stephen Zarkos, Eric Gable',
+ author_email = 'walinuxagent@microsoft.com',
+ platforms = 'Linux',
+ url='https://github.com/Azure/WALinuxAgent',
+ license = 'Apache License Version 2.0',
+ packages=find_packages(exclude=["tests"]),
+ cmdclass = {
+ 'install': install
+ })
diff --git a/tests/azure_test.py b/tests/azure_test.py
deleted file mode 100755
index fd152b7..0000000
--- a/tests/azure_test.py
+++ /dev/null
@@ -1,687 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import sys
-import imp
-import json
-import time
-import pwd
-
-# waagent has no '.py' therefore create waagent module import manually.
-waagent=imp.load_source('waagent','waagent')
-
-from waagent import RunGetOutput, Run, LoggerInit
-
-"""
-Test waagent in azure using azure-cli
-Usage:
-
-./azure_test.py --stable_vm_image b4590d9e3ed742e4a1d46e5424aa335e__openSUSE-12.3-v120 --source_disk "http://mystorage.blob.core.windows.net/vhds/my-suse2.vhd" --acct "<storage acct key>" --testname my-osuse --mount_point /mnt/disk --agent_path ../waagent --stable_vm_acct_name MyUserName --stable_vm_acct_pass 'myp455wd' --test_vm_acct_name MyUserName --test_vm_acct_pass 'myp455wd' --azure_location "East US" --part_num 1 --retries 20 --fstype scsi --test_vm_acct_cert /root/.ssh/myCert.pem --stable_vm_acct_cert /root/.ssh/myCert.pem --keep_test_vm_vhd no --teardown_test_vm always --prompt no
-
-azure_test --vm <stable vm name> --testname <testname> [--acct <storage account>]
-[--disk <vhd url to use as initial disk image>]
-If --disk is specified, use this vhd as starting point,
-otherwise use a copy of the stable vm as the starting vhd.
-
-Starting VHD is attached to stable vm and the sources are copied to it.
-Spin up a new VM using the VHD.
-Loop waiting for provisioned.
-If not provisioned:
- Destroy vm and attach the disk to the stable vm.
- Copy the logs to the localhost.
- Destroy all created objects except the starting vhd.
- Exit(1)
-If Provosioned:
- Copy the logs to the local host.
- Exit(0)
-
-EXAMPLE:
-
-sudo ./azure_test.py --vm my-stable-vm-name --disk "http://mystorageaccount.blob.core.windows.net/myvhds/my-new-os.vhd" --acct mylong-unquoted-starage-account-id --testname my-vm-test --mount_point /my-stablevm-mountpoint --agent_path ../waagent --vm_acct_name root --testvm_acct_name azureuser --testvm_acct_pass 'azureuserpassword' --location "East US" --part_num 2 --retry 20 --fstype bsd --testvm_acct_cert /home/azureuser/.ssh/myCert.pem --keep_vhd "once" --teardown "always" --prompt "no"
-"""
-
-def makeDiskImage(di_name,vhd_url,location,copy=False):
- """
- Create new data disk image of the VHD. If 'copy'
- is set to True, then create a new VHD in the form of myvhd-di.vhd
- based on the VHD source name. If 'copy is set to False, re-use the
- vhd. Returns return code and diskimageVHD path. Code is 0 on
- success or the azure-cli error code upon error.
- """
- if copy :
- target = os.path.dirname(vhd_url)
- target = target+ '/' + di_name + '.vhd'
- else :
- target = vhd_url
- cmd='azure vm disk create --json ' + di_name + ' --blob-url ' + target + ' ' + vhd_url
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log(output)
- waagent.Log(str(code))
- return target,code
-
-def makeVMImage(vmi_name,vhd_url,copy=False):
- """
- Create new VM Image based on Disk Image.
- Returns 0 on success or error code upon error.
- """
- if copy :
- target = os.path.dirname(vhd_url)
- target = target+ '/' + vmi_name + '.vhd'
- else :
- target = vhd_url
- cmd='azure vm image create --json ' + vmi_name + ' --base-vhd ' + target + ' --os Linux --blob-url ' + vhd_url
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log(str(code))
- waagent.Log(output)
- return code
-
-def makeVM(vm_name,vmi_name,vhd_url,test_name,location,vmuser_name,vmuser_pass,vmuser_cert,copy=False):
- """
- Create new VM from the VM Image.
- Returns 0 on success or error code upon error.
- """
- target=os.path.dirname(vhd_url)
- target = target + '/' + test_name + '.vhd'
- cmd='azure vm create --json '
- if copy :
- cmd += ' --blob-url "' + target + '"'
- else :
- target=vhd_url
- cmd += ' --location "' + location + '"'
- if os.path.exists(vmuser_cert):
- cmd += ' -t "' + vmuser_cert + '"'
- cmd += ' -e 22 ' + vm_name + ' ' + vmi_name + ' ' + vmuser_name + ' \'' +vmuser_pass + '\''
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log(str(code))
- waagent.Log(output)
- retry=3
- while code !=0 and retry > 0 :
- time.sleep(5)
- code,output=RunGetOutput(cmd,False)
- retry -=1
- return target
-
-def flushDiskImage(di_name,dele=True):
- """
- Delete the VM Image.
- On error we asume the VM disk image is deleted
- """
- cmd='azure vm disk delete --json '
- if dele :
- cmd += '--blob-delete '
- cmd+= di_name
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- return output,code
-
-def flushVMImage(vmi_name):
- """
- Delete the VM Image.
- Always delete the underlying blob.
- On error we asume the VM image is deleted.
- """
- cmd='azure vm image delete --blob-delete --json ' + vmi_name
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- return output,code
-
-def flushVM(vm_name,dele=False):
- """
- Delete the VM.
- On error we asume the VM is deleted
- """
- cmd='azure vm delete --json '
- if dele :
- cmd += ' --blob-delete '
- cmd += vm_name
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- return output,code
-
-
-def createStableVMFromVMImage(vm_image,vhd_url):
- """
- Create a new stable vm, provisioned with acct and certificate from
- the VMImage, using the basepath of vhd_url for the new VM's vhd.
- """
- stableVM=testname+'-stable-vm'
- return makeVM(stableVM,vm_image,vhd_url,testname+'-stable',location,stableVMaccount,stableVMpass,stableVMCert,copy=True)
-
-def createStableVMFromVHD(vmi_name,vhd_url):
- """
- Create a new stable vm, provisioned with acct and certificate from
- the VHD, using the basepath of vhd_url for the new VM's vhd.
- """
- makeVMImage(vmi_name,vhd_url,False)
- return createStableVMFromVMImage(vmi_name,vhd_url)
-
-def createDiskImageFromStableVMDisk(vm_name):
- """
- Determine the media link for the os disk of the stable vm.
- Create vhd disk image copy. <vm_name>-<testname>-di
- Return new disk_image_media_path or None on error.
- """
- cmd='azure vm disk list --json'
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- if code:
- print 'Error is ' + str(code)
- waagent.Log( 'Error is ' + str(code))
- return None
- j=json.loads(output)
- source_media_link=None
- for i in j :
- if i.has_key('AttachedTo'):
- if i['AttachedTo']['RoleName'] == vm_name:
- source_media_link=i['MediaLink']
- break
- if not source_media_link:
- print 'Unable to locate OS disk for ' + vm_name
- waagent.Log( 'Unable to locate OS disk for ' + vm_name )
- return None
- target_name= testname + '-di'
- makeDiskImage(target_name,source_media_link,location,copy=True)
- target_media_link=os.path.dirname(source_media_link) + '/' + target_name + '.vhd'
- return target_media_link
-
-def addDiskImageToVM(vm_name,di_name):
- """
- Attach the disk image to the 'stableVM'.
- Returns the LUN if successful otherwise returns None
- NOTE: azure vm show may return json matching the disk image
- name yet missing the LUN. When this occurs, the LUN is '0'.
- """
- cmd='azure vm disk attach --json ' + vm_name + ' ' + di_name
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log(str(code))
- waagent.Log(output)
- cmd='azure vm show --json ' + vm_name
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- retries=3
- while code != 0 and retries:
- retries-=1
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
-
- if code == 0:
- jsn=json.loads(output)
- for i in jsn['DataDisks']:
- if i['DiskName'] == di_name:
- if 'Lun' in i :
- return i['Lun']
- else :
- return u'0'
- return None
-
-
-def dropDiskImageFromVM(vm_name,lun):
- """
- Detach the disk image from the 'stableVM'.
- On Error we assume the disk is no longer attached.
- """
- cmd='azure vm disk detach --json ' + vm_name + ' ' + lun
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- return output,code
-
-def checkVMProvisioned(vm_name):
- cmd='azure vm show --json ' + vm_name
- print cmd
- waagent.Log( cmd)
- code,output=RunGetOutput(cmd,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- if code ==0 :
- j=json.loads(output)
- print vm_name+' instance status: ', j['InstanceStatus']
- waagent.Log( vm_name+' instance status: ' + j['InstanceStatus'])
- if j['InstanceStatus'] == 'ReadyRole':
- return True, j['InstanceStatus']
- else :
- print 'Error: ' + output , code
- waagent.Log( 'Error: ' + output + str(code))
- return False, j['InstanceStatus']
-
-def updateAgent(agent_path,vm_name,account,cert,disk_mountpoint,mnt_opts,lun,partnum,provisioned_account):
- """
- Copy the agent specified in 'agent' to the Disk
- using the 'stableVM'.
- """
- retries=30
- retry=0
- cmd='uptime'
- while ssh_command(vm_name,account,cmd)[1] != 0 and retry < retries :
- time.sleep(10)
- retry+=1
- #setup sudo NOPASSWD
- pss=stableVMpass.replace('$','\$')
- cmd='echo \'' + pss + '\' > /home/' + account + '/pswd '
- ssh_command(vm_name,account,cmd)
- cmd='echo \'#!/bin/bash\ncat /home/' + account + '/pswd\n\' > /home/' + account + '/pw.sh'
- ssh_command(vm_name,account,cmd)
- cmd='chmod +x /home/' + account + '/pw.sh'
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A mkdir -p ' + disk_mountpoint
- ssh_command(vm_name,account,cmd)
- retries=3
- # TODO retires here for the mount
- #mount
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A mount ' +mnt_opts + ' ' + lunToDiskName(lun,partnum) + ' ' +disk_mountpoint
- waagent.Log( cmd)
- retry=0
- while ssh_command(vm_name,account,cmd)[1] not in (0,32) and retry < retries :
- if retry == 0:
- if 'bsd' in fstype:
- fcmd = "export SUDO_ASKPASS=./pw.sh && sudo -A fsck_ffs -y "
- else :
- fcmd = "export SUDO_ASKPASS=./pw.sh && sudo -A fsck -y "
- fcmd += lunToDiskName(lun,partnum)
- ssh_command(vm_name,account,fcmd)
- time.sleep(2)
- retry+=1
-
- # remove packaged agent service if present.
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chroot '+ disk_mountpoint+' dpkg -r walinuxagent'
- ssh_command(vm_name,account,cmd) # remove Ubuntu walinuxagent agent service if present.
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A rm '+ disk_mountpoint+'/etc/default/walinuxagent'
- ssh_command(vm_name,account,cmd) # remove Ubuntu walinuxagent agent service if present.
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chroot '+ disk_mountpoint+' rpm -e WALinuxAgent'
- ssh_command(vm_name,account,cmd)
- #copy agent
- remote_path='/tmp'
- print 'scp ' + agent_path + ' to ' + vm_name + ' ' + account + ':' + remote_path
- waagent.Log( 'scp ' + agent_path + ' to ' + vm_name + ' ' + account + ':' + remote_path)
- retry=0
- while scp_to_host_command(account,vm_name,remote_path,agent_path)[1] != 0 and retry < retries :
- time.sleep(2)
- retry+=1
- # move agent to /usr/sbin
- cmd= 'export SUDO_ASKPASS=./pw.sh && sudo -A cp ' + remote_path +'/waagent '+ disk_mountpoint+'/usr/sbin/waagent'
- ssh_command(vm_name,account,cmd)
- cmd= 'export SUDO_ASKPASS=./pw.sh && sudo -A chmod 755 '+ disk_mountpoint+'/usr/sbin/waagent'
- ssh_command(vm_name,account,cmd)
- # Fix the password file
- if 'bsd' in fstype:
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A cp /etc/master.passwd ' + disk_mountpoint + '/etc/master.passwd'
- ssh_command(vm_name,account,cmd)
- else :
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A cp /etc/passwd ' + disk_mountpoint + '/etc/passwd'
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A cp /etc/shadow ' + disk_mountpoint + '/etc/shadow'
- ssh_command(vm_name,account,cmd)
- #remove /var/lib/waagent
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A rm -rf ' + disk_mountpoint + '/var/lib/waagent'
- ssh_command(vm_name,account,cmd)
- #remove /var/log/waagent*
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A rm -rf ' + disk_mountpoint + '/var/log/waagent*'
- ssh_command(vm_name,account,cmd)
- #delete the provisioning user
- if 'bsd' in fstype:
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chroot '+ disk_mountpoint+' rmuser -y ' + provisioned_account
- ssh_command(vm_name,account,cmd)
- else :
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chroot '+ disk_mountpoint+' userdel -f ' + provisioned_account
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chroot '+ disk_mountpoint+' groupdel ' + provisioned_account
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A rm -rf ' + disk_mountpoint + '/home/' + provisioned_account
- ssh_command(vm_name,account,cmd)
- # install agent
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chroot '+ disk_mountpoint+' /usr/sbin/waagent verbose install '
- ssh_command(vm_name,account,cmd)
- cmd="export SUDO_ASKPASS=./pw.sh && sudo -A sed -i 's/Verbose=n/Verbose=y/' " + disk_mountpoint+"/etc/waagent.conf"
- ssh_command(vm_name,account,cmd)
- #umount
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A umount ' + lunToDiskName(lun,partnum)
- ssh_command(vm_name,account,cmd)
-
-def gatherAgentInfo(localpath,vm_name,account,cert,disk_mountpoint,mnt_opts,lun,partnum):
- """
- Copy the /var/lib/waagent, and /var/log directories to
- localhost:localpath.
- """
- retries=30
- retry=0
- cmd='uptime'
- while ssh_command(vm_name,account,cmd)[1] != 0 and retry < retries :
- time.sleep(10)
- retry+=1
- #mount
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A mount ' +mnt_opts + ' ' + lunToDiskName(lun,partnum) + ' ' +disk_mountpoint
- print cmd
- waagent.Log( cmd)
- ssh_command(vm_name,account,cmd)
- #copy info
- Run("mkdir -p "+ localpath)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A mkdir -p /tmp/results'
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A cp -r ' + disk_mountpoint + '/var/log /tmp/results/'
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A cp -r ' + disk_mountpoint + '/var/lib/waagent /tmp/results/'
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chmod -R 777 /tmp/results'
- ssh_command(vm_name,account,cmd)
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A chown -R ' + account + ' /tmp/results'
- ssh_command(vm_name,account,cmd)
- scp_from_host_command(account,vm_name,'/tmp/results/*',localpath)
- #umount
- cmd='export SUDO_ASKPASS=./pw.sh && sudo -A umount ' + lunToDiskName(lun,partnum)
- print cmd
- waagent.Log( cmd)
- ssh_command(vm_name,account,cmd)
-
-def lunToDiskName(lun,partnum):
- if 'bsd' in fstype :
- return lunToFreeBSDDiskName(lun,partnum)
- else :
- return lunToScsiDiskName(lun,partnum)
-
-def lunToScsiDiskName(lun,partnum):
- """
- Convert lun to '/dev/sd[chr(ord('c')+lun)]partnum'
- """
- return str('/dev/sd'+chr( (ord('c')+int(lun)) ) +str(partnum))
-
-def lunToFreeBSDDiskName(lun,partnum):
- """
- Convert lun to '/dev/da' + str(lun) + 'p' + partnum
- """
- return '/dev/da'+ str(int(lun)) + 'p' + str(partnum)
-
-def ssh_command(host,account,cmd):
- """
- Wrapper for an ssh operation.
- """
- if stableVMCert == None:
- if not os.path.exists('./pw.sh'):
- with open('./pw.sh','w') as F:
- F.write('#!/bin/bash\ncat ./pswd\n')
- os.system('chmod +x ./pw.sh')
- with open('./pswd','w') as F:
- F.write(stableVMpass)
- req = "export SSH_ASKPASS=./pw.sh && setsid ssh -T -o StrictHostKeyChecking='no' " + account + "@" + host.lower() + ".cloudapp.net \"" + cmd + "\""
- else :
- req = "ssh -t -o StrictHostKeyChecking='no' " + account + "@" + host.lower() + ".cloudapp.net \"" + cmd + "\""
- print req
- waagent.Log(req)
- code,output=RunGetOutput(req,False)
- print output,code
- waagent.Log(str(code))
- waagent.Log(output.encode('ascii','ignore'))
- return output,code
-
-def scp_to_host_command(account,host,remote_path,local_path):
- """
- Wrapper for an scp operation. Always uses -r.
- Requires key authentication configured.
- """
- req="scp -o StrictHostKeyChecking='no' -r " + local_path + " " + account + "@" + host.lower() + ".cloudapp.net:" + remote_path
- print req
- waagent.Log( req)
- code,output=RunGetOutput(req,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- return output,code
-
-def scp_from_host_command(account,host,remote_path,local_path):
- """
- Wrapper for an scp operation. Always uses -r.
- Requires key authentication configured.
- """
- req="scp -r " + account + "@" + host.lower() + ".cloudapp.net:" + remote_path + " " + local_path
- print req
- waagent.Log( req)
- code,output=RunGetOutput(req,False)
- print output,code
- waagent.Log( str(code))
- waagent.Log(output)
- return output,code
-
-def teardown(name):
- diskImageName=os.path.splitext(os.path.basename(sourceVHD))[0]+'-di'
- while makeDiskImage(diskImageName,sourceVHD,location,True)[1] !=0 :
- time.sleep(20)
- lun=addDiskImageToVM(stableVM,diskImageName)
- while lun == None :
- time.sleep(2)
- lun=addDiskImageToVM(stableVM,diskImageName)
- out,code=flushVM(vmName,True)
- if code != 0 :
- vmDisk=out[out.find('disk with name ')+len('disk with name '):out.find(' is currently in use')]
- while flushDiskImage(vmDisk,True)[1] != 0 :
- time.sleep(5)
- out,code=flushVMImage(vmImageName)
- if code != 0 :
- vmDisk=out[out.find('disk with name ')+len('disk with name '):out.find(' is currently in use')]
- while flushDiskImage(vmDisk,True)[1] != 0 :
- time.sleep(5)
- gatherAgentInfo(localInfo+'/'+name,stableVM,stableVMaccount,stableVMCert,stableVMMountpoint,mountOptions,lun,partNum)
- print 'Logs for ' + vmName + ' copied to ' + localInfo + '/' + name
- waagent.Log( 'Logs for ' + vmName + ' copied to ' + localInfo + '/' + name)
- # detach and delete the disk image
- while dropDiskImageFromVM(stableVM,lun)[1] != 0 :
- time.sleep(2)
- while flushDiskImage(diskImageName,('no' in keep_vhd))[1] != 0 :
- time.sleep(2)
- out,code=flushVM(stableVM,True)
- if code != 0 :
- stableVMDisk=out[out.find('disk with name ')+len('disk with name '):out.find(' is currently in use')]
- while flushDiskImage(stableVMDisk,True)[1] != 0 :
- time.sleep(5)
- if stableVMImageName:
- while flushVMImage(stableVMImageName,True)[1] != 0 :
- time.sleep(2)
-
-def doPrompt() :
- if prompt in ('yes','on'):
- raw_input('Press enter to continue:')
-
-if __name__ == '__main__' :
- """
- Create a disk image and attach it to StableVM.
- Copy the current sources to it.
- Detach and delete the disk image container
- Delete the vm image container and the VM.
- Create a vm image container, and the VM.
- Check the VM for provision succedded:
- if so, exit
- if provisioning failed:
- delete the vm, delete the vm image, create a disk image
- from the VM's vhd, attach the disk to the stable VM, copy
- /var/log and /var/lib/waagent to the localhost.
- """
- stableVM=''
- mountOptions=''
- stableVMMountpoint='/mnt/disk' # need to ensure this is created if not existing.
- sourceVHD=''
- location=""
- localAgent='/home/ericg/Public/git_repos/private/WALinuxAgent-Private/waagent_freebsd'
- localInfo='./logs'
- stableVMaccount='' # Need to ensure root permissions for this to work
- stableVMpass=''
- stableVMCert=''
- provisionedVMaccount=''
- provisionedVMpass=''
- provisionedVMCert=''
- account=''
- testname='azuretest'
- partNum=1
- provision_retries=1
- fstype='scsi'
- keep_vhd='no'
- teardown_test_vm='fail'
- teardown_stable_vm='fail'
- prompt = 'yes'
- stable_vm_vhd=None
- stableVMImageName=None
- stable_vm_image=None
- logfile='azure_test.log'
- """
- We need to create a disk image container and attach it to a stable
- vm in order to copy the current sources to it. Then we detach it,
- delete the disk image container, create a vm image container, and
- the VM.
- """
-
- for i in range(len(sys.argv)) :
- if '--stable_vm' == sys.argv[i] : stableVM=sys.argv[i+1]
- elif '--source_disk' == sys.argv[i]: sourceVHD=sys.argv[i+1]
- elif '--storage_acct' == sys.argv[i]: account=sys.argv[i+1]
- elif '--testname' == sys.argv[i] : testname=sys.argv[i+1]
- elif '--stable_vm_mount_point' == sys.argv[i] : stableVMMountpoint=sys.argv[i+1]
- elif '--agent_path' == sys.argv[i] : localAgent=sys.argv[i+1]
- elif '--stable_vm_acct_name' == sys.argv[i] : stableVMaccount=sys.argv[i+1]
- elif '--stable_vm_acct_pass' == sys.argv[i] : stableVMpass=sys.argv[i+1]
- elif '--stable_vm_acct_cert' == sys.argv[i] : stableVMCert=sys.argv[i+1]
- elif '--test_vm_acct_name' == sys.argv[i] : provisionedVMaccount=sys.argv[i+1]
- elif '--test_vm_acct_pass' == sys.argv[i] : provisionedVMpass=sys.argv[i+1]
- elif '--test_vm_acct_cert' == sys.argv[i] : provisionedVMCert=sys.argv[i+1]
- elif '--azure_location' == sys.argv[i] : location=sys.argv[i+1]
- elif '--mount_opts' == sys.argv[i] : mountOptions=sys.argv[i+1]
- elif '--part_num' == sys.argv[i] : partNum=sys.argv[i+1]
- elif '--retries' == sys.argv[i] : provision_retries=int(sys.argv[i+1])
- elif '--fs_type' == sys.argv[i] : fstype=sys.argv[i+1]
- elif '--keep_test_vm_vhd' == sys.argv[i] : keep_vhd=sys.argv[i+1]
- elif '--teardown_test_vm' == sys.argv[i] : teardown_test_vm=sys.argv[i+1]
- elif '--teardown_stable_vm' == sys.argv[i] : teardown_stable_vm=sys.argv[i+1]
- elif '--prompt' == sys.argv[i] : prompt=sys.argv[i+1]
- elif '--stable_vm_image' == sys.argv[i] : stable_vm_image=sys.argv[i+1]
- elif '--stable_vm_vhd' == sys.argv[i] : stable_vm_vhd=sys.argv[i+1]
- elif '--logfile' == sys.argv[i] : logfile=sys.argv[i+1]
-
- LoggerInit(logfile,'')
- waagent.Log("User: "+ pwd.getpwuid(os.geteuid()).pw_name +" Running Command :\n" + reduce(lambda x, y: x+' '+y,sys.argv))
-
- if len(stableVM) == 0 and not ( stable_vm_image or stable_vm_vhd ):
- print '--vm <stable vm> must be provided unless --stable_vm_image or --stable_vm_vhd'
- waagent.Log( '--vm <stable vm> must be provided!')
- sys.exit(1)
- else:
- if stable_vm_image:
- sourceVHD=createStableVMFromVMImage(stable_vm_image,sourceVHD)
- stableVM=testname+'-stable-vm'
- elif stable_vm_vhd:
- stableVMImageName=testname+'-stable-vi'
- sourceVHD=createStableVMFromVHD(stableVMImageName,stable_vm_vhd)
- stableVM=testname+'-stable-vm'
- p = False
- retries = provision_retries
- while not p and retries > 0:
- p,out = checkVMProvisioned(stableVM)
- if not p:
- if 'Failed' in out or 'Timeout' in out :
- break
- print stableVM + ' Not Provisioned - sleeping on retry:' + str( provision_retries - retries )
- waagent.Log( stableVM + ' Not Provisioned - sleeping on retry:' + str( provision_retries - retries ) )
- time.sleep(30)
- retries -= 1
- else :
- print stableVM + ' Provision SUCCEEDED.'
- waagent.Log( stableVM + ' Provision SUCCEEDED.')
- # done creating the stable vm
- vmImageName=os.path.splitext(os.path.basename(sourceVHD))[0]+'-vi'
- #flushVMImage(vmImageName)
-
- # if no disk image name is provided we want to clone the stable vm disk.
- if not sourceVHD:
- sourceVHD=createDiskImageFromStableVMDisk(stableVM)
- if not sourceVHD:
- print 'Errors - unable to create disk image - assuming created'
- waagent.Log( 'Errors - unable to create disk image - assuming created')
- diskImageName=os.path.splitext(os.path.basename(sourceVHD))[0]
- else :
- diskImageName=os.path.splitext(os.path.basename(sourceVHD))[0]+'-di'
- diskImageVHD,code=makeDiskImage(diskImageName,sourceVHD,location,True)
- if code:
- print 'Error - unable to make ' + diskImageName
- waagent.Log( 'Error - unable to make ' + diskImageName)
-
- lun=addDiskImageToVM(stableVM,diskImageName)
- while lun == None :
- time.sleep(2)
- lun=addDiskImageToVM(stableVM,diskImageName)
-
- doPrompt()
- updateAgent(localAgent,stableVM,stableVMaccount,stableVMCert,stableVMMountpoint,mountOptions,lun,partNum,provisionedVMaccount)
- doPrompt()
- #reboot to prevent stale mount bugs
- cmd= 'export SUDO_ASKPASS=./pw.sh && sudo -A reboot'
- ssh_command(stableVM,stableVMaccount,cmd)
-
- while dropDiskImageFromVM(stableVM,lun)[1] != 0 :
- time.sleep(2)
- while flushDiskImage(diskImageName,False)[1] != 0 :
- time.sleep(2)
- vmImageName=os.path.splitext(os.path.basename(sourceVHD))[0]+'-vi'
- flushVMImage(vmImageName)
- vmName=testname+'-vm'
- flushVM(vmName)
- makeVMImage(vmImageName,diskImageVHD,True)
- sourceVHD=makeVM(vmName,vmImageName,sourceVHD,testname,location,provisionedVMaccount,provisionedVMpass,provisionedVMCert,True)
- print 'The new source vhd is ' + sourceVHD
- waagent.Log( 'The new source vhd is ' + sourceVHD)
- p = False
- retries = provision_retries
- while not p and retries > 0:
- p,out = checkVMProvisioned(vmName)
- if not p:
- if 'Failed' in out or 'Timeout' in out :
- break
- print vmName + ' Not Provisioned - sleeping on retry:' + str( provision_retries - retries )
- waagent.Log( vmName + ' Not Provisioned - sleeping on retry:' + str( provision_retries - retries ) )
- time.sleep(30)
- else :
- print vmName + ' Provision SUCCEEDED.'
- waagent.Log( vmName + ' Provision SUCCEEDED.')
- doPrompt()
- if teardown_test_vm in ('success','always'):
- teardown(testname+'_pass')
- sys.exit(0)
- retries -= 1
-
- print vmName + ' Provision FAILED.'
- waagent.Log( vmName + ' Provision FAILED.')
- doPrompt()
- if teardown_test_vm in ('fail','always'):
- teardown(testname+'_fail')
- sys.exit(1)
diff --git a/tests/dhcp b/tests/dhcp
new file mode 100644
index 0000000..8c9d127
--- /dev/null
+++ b/tests/dhcp
Binary files differ
diff --git a/tests/env.py b/tests/env.py
index 513df7f..5bc6eb8 100644
--- a/tests/env.py
+++ b/tests/env.py
@@ -12,14 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
-import imp
import os
import sys
-project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-waagent = imp.load_source('waagent', os.path.join(project_root, 'waagent'))
+test_root = os.path.dirname(os.path.abspath(__file__))
+project_root = os.path.dirname(test_root)
sys.path.insert(0, project_root)
-
-waagent.LoggerInit('/dev/stdout', '/dev/null')
-
diff --git a/tests/run_all.sh b/tests/run_all.sh
new file mode 100755
index 0000000..9e88c46
--- /dev/null
+++ b/tests/run_all.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# This script is used to set up a test env for extensions
+#
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+script=$(dirname $0)
+root=$script
+cd $root
+root=`pwd`
+
+echo "Run unit test:"
+tests=`ls test_*.py | sed -e 's/\.py//'`
+for test in $tests ; do
+ echo $test
+done
+
+if [ "$1" == "-c" ] ; then
+ echo $tests | xargs coverage run -m unittest
+else
+ echo $tests | xargs python -m unittest
+fi
diff --git a/tests/sshd_config b/tests/sshd_config
new file mode 100644
index 0000000..77fb290
--- /dev/null
+++ b/tests/sshd_config
@@ -0,0 +1,90 @@
+# Package generated configuration file
+# See the sshd_config(5) manpage for details
+
+# What ports, IPs and protocols we listen for
+Port 22
+# Use these options to restrict which interfaces/protocols sshd will bind to
+#ListenAddress ::
+#ListenAddress 0.0.0.0
+Protocol 2
+# HostKeys for protocol version 2
+HostKey /etc/ssh/ssh_host_rsa_key
+HostKey /etc/ssh/ssh_host_dsa_key
+HostKey /etc/ssh/ssh_host_ecdsa_key
+HostKey /etc/ssh/ssh_host_ed25519_key
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 1024
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 120
+PermitRootLogin without-password
+StrictModes yes
+
+RSAAuthentication yes
+PubkeyAuthentication yes
+#AuthorizedKeysFile %h/.ssh/authorized_keys
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts yes
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication no
+# similar for protocol version 2
+HostbasedAuthentication no
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords no
+
+# Change to yes to enable challenge-response passwords (beware issues with
+# some PAM modules and threads)
+ChallengeResponseAuthentication no
+
+# Change to no to disable tunnelled clear text passwords
+#PasswordAuthentication yes
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosGetAFSToken no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+
+# GSSAPI options
+#GSSAPIAuthentication no
+#GSSAPICleanupCredentials yes
+
+X11Forwarding yes
+X11DisplayOffset 10
+PrintMotd no
+PrintLastLog yes
+TCPKeepAlive yes
+#UseLogin no
+
+#MaxStartups 10:30:60
+#Banner /etc/issue.net
+
+# Allow client to pass locale environment variables
+AcceptEnv LANG LC_*
+
+Subsystem sftp /usr/lib/openssh/sftp-server
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM yes
+
+Match group root
diff --git a/tests/test.crt b/tests/test.crt
new file mode 100644
index 0000000..e87c5f9
--- /dev/null
+++ b/tests/test.crt
@@ -0,0 +1,17 @@
+Bag Attributes: <Empty Attributes>
+subject=/C=ab/ST=ab/L=ab/O=ab/OU=ab/CN=ab/emailAddress=ab
+issuer=/C=ab/ST=ab/L=ab/O=ab/OU=ab/CN=ab/emailAddress=ab
+-----BEGIN CERTIFICATE-----
+MIICOTCCAaICCQD7F0nb+GtpcTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJh
+YjELMAkGA1UECAwCYWIxCzAJBgNVBAcMAmFiMQswCQYDVQQKDAJhYjELMAkGA1UE
+CwwCYWIxCzAJBgNVBAMMAmFiMREwDwYJKoZIhvcNAQkBFgJhYjAeFw0xNDA4MDUw
+ODIwNDZaFw0xNTA4MDUwODIwNDZaMGExCzAJBgNVBAYTAmFiMQswCQYDVQQIDAJh
+YjELMAkGA1UEBwwCYWIxCzAJBgNVBAoMAmFiMQswCQYDVQQLDAJhYjELMAkGA1UE
+AwwCYWIxETAPBgkqhkiG9w0BCQEWAmFiMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQC4Vugyj4uAKGYHW/D1eAg1DmLAv01e+9I0zIi8HzJxP87MXmS8EdG5SEzR
+N6tfQQie76JBSTYI4ngTaVCKx5dVT93LiWxLV193Q3vs/HtwwH1fLq0rAKUhREQ6
++CsRGNyeVfJkNsxAvNvQkectnYuOtcDxX5n/25eWAofobxVbSQIDAQABMA0GCSqG
+SIb3DQEBCwUAA4GBAF20gkq/DeUSXkZA+jjmmbCPioB3KL63GpoTXfP65d6yU4xZ
+TlMoLkqGKe3WoXmhjaTOssulgDAGA24IeWy/u7luH+oHdZEmEufFhj4M7tQ1pAhN
+CT8JCL2dI3F76HD6ZutTOkwRar3PYk5q7RsSJdAemtnwVpgp+RBMtbmct7MQ
+-----END CERTIFICATE-----
diff --git a/tests/test.prv b/tests/test.prv
new file mode 100644
index 0000000..9d6ab87
--- /dev/null
+++ b/tests/test.prv
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC4Vugyj4uAKGYHW/D1eAg1DmLAv01e+9I0zIi8HzJxP87MXmS8
+EdG5SEzRN6tfQQie76JBSTYI4ngTaVCKx5dVT93LiWxLV193Q3vs/HtwwH1fLq0r
+AKUhREQ6+CsRGNyeVfJkNsxAvNvQkectnYuOtcDxX5n/25eWAofobxVbSQIDAQAB
+AoGAIakE506c238E+m0Id9o+LWn+EFIeT6zN+oQqp6dOr61GFr1ZyZm7YQjZtg5j
+RZZ7e4Iob6Fts3ufD3RYl67QbBzRwsKwI7sAmzdCmqkopY2H6xv421cEGjkqZIJV
+2Xyp9Idji6GfUB6+t1SZDOssbZx3SUkyim0hixK2HCJT4u0CQQDw6rNLZwEmwuhY
+z1jSERyeTtIcRJ47+Y79tX2xmkyKxZ2Kf28V3Fw/6biCIlmuvxHNhlLijimOME7/
+rkqDiscnAkEAw+FpkM96xLlDCqNL2AcNxVnmNyO0Boxw0AKrogfcnDh6S3rD5tZQ
+IdcIAsEYNjhEJ+/hVCByIUArC885PTzQDwJBAMaDfm3ZWHeKD05uvG+MLhq8NCGa
+4Q/mWU7xZ7sau4t1vpTK4MwQoesAOUrx5xg41QCXeGC6Z7+ESvQft8Kgbe0CQAkS
+OExPf3T6y2MDuvBvKzEXf7TP/3dKK7NGXGJtkMbfSrKSJd5b0GwwxBs0jAV+x5E9
+56Z4tjBaA2RRnWn7lfsCQA5SWuDMtlOzyWir09fparnnRL1JFvOwDAHTE0iwS8dO
+UFHIIw4nqqUYuHb+r/eyRzVtokJ9bSPZOjtTWSVL4W4=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/test_agent.py b/tests/test_agent.py
new file mode 100644
index 0000000..721b9f0
--- /dev/null
+++ b/tests/test_agent.py
@@ -0,0 +1,45 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import json
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.agent as agent
+
+class TestAgent(unittest.TestCase):
+ def test_parse_args(self):
+ cmd, force, verbose = agent.parse_args(["deprovision+user",
+ "-force",
+ "/verbose"])
+ self.assertEquals("deprovision+user", cmd)
+ self.assertTrue(force)
+ self.assertTrue(verbose)
+
+ cmd, force, verbose = agent.parse_args(["wrong cmd"])
+ self.assertEquals("help", cmd)
+ self.assertFalse(force)
+ self.assertFalse(verbose)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_certificates.py b/tests/test_certificates.py
new file mode 100644
index 0000000..2950c6b
--- /dev/null
+++ b/tests/test_certificates.py
@@ -0,0 +1,198 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from .tools import *
+import uuid
+import unittest
+import os
+import json
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.protocol.v1 as v1
+
+certs_sample=u"""\
+<?xml version="1.0" encoding="utf-8"?>
+<CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
+ <Version>2012-11-30</Version>
+ <Incarnation>12</Incarnation>
+ <Format>Pkcs7BlobWithPfxContents</Format>
+ <Data>MIINswYJKoZIhvcNAQcDoIINpDCCDaACAQIxggEwMIIBLAIBAoAUvyL+x6GkZXog
+QNfsXRZAdD9lc7IwDQYJKoZIhvcNAQEBBQAEggEArhMPepD/RqwdPcHEVqvrdZid
+72vXrOCuacRBhwlCGrNlg8oI+vbqmT6CSv6thDpet31ALUzsI4uQHq1EVfV1+pXy
+NlYD1CKhBCoJxs2fSPU4rc8fv0qs5JAjnbtW7lhnrqFrXYcyBYjpURKfa9qMYBmj
+NdijN+1T4E5qjxPr7zK5Dalp7Cgp9P2diH4Nax2nixotfek3MrEFBaiiegDd+7tE
+ux685GWYPqB5Fn4OsDkkYOdb0OE2qzLRrnlCIiBCt8VubWH3kMEmSCxBwSJupmQ8
+sxCWk+sBPQ9gJSt2sIqfx/61F8Lpu6WzP+ZOnMLTUn2wLU/d1FN85HXmnQALzTCC
+DGUGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIbEcBfddWPv+AggxAAOAt/kCXiffe
+GeJG0P2K9Q18XZS6Rz7Xcz+Kp2PVgqHKRpPjjmB2ufsRO0pM4z/qkHTOdpfacB4h
+gz912D9U04hC8mt0fqGNTvRNAFVFLsmo7KXc/a8vfZNrGWEnYn7y1WfP52pqA/Ei
+SNFf0NVtMyqg5Gx+hZ/NpWAE5vcmRRdoYyWeg13lhlW96QUxf/W7vY/D5KpAGACI
+ok79/XI4eJkbq3Dps0oO/difNcvdkE74EU/GPuL68yR0CdzzafbLxzV+B43TBRgP
+jH1hCdRqaspjAaZL5LGfp1QUM8HZIKHuTze/+4dWzS1XR3/ix9q/2QFI7YCuXpuE
+un3AFYXE4QX/6kcPklZwh9FqjSie3I5HtC1vczqYVjqT4oHrs8ktkZ7oAzeXaXTF
+k6+JQNNa/IyJw24I1MR77q7HlHSSfhXX5cFjVCd/+SiA4HJQjJgeIuXZ+dXmSPdL
+9xLbDbtppifFyNaXdlSzcsvepKy0WLF49RmbL7Bnd46ce/gdQ6Midwi2MTnUtapu
+tHmu/iJtaUpwXXC0B93PHfAk7Y3SgeY4tl/gKzn9/x5SPAcHiNRtOsNBU8ZThzos
+Wh41xMLZavmX8Yfm/XWtl4eU6xfhcRAbJQx7E1ymGEt7xGqyPV7hjqhoB9i3oR5N
+itxHgf1+jw/cr7hob+Trd1hFqZO6ePMyWpqUg97G2ThJvWx6cv+KRtTlVA6/r/UH
+gRGBArJKBlLpXO6dAHFztT3Y6DFThrus4RItcfA8rltfQcRm8d0nPb4lCa5kRbCx
+iudq3djWtTIe64sfk8jsc6ahWYSovM+NmhbpxEUbZVWLVEcHAYOeMbKgXSu5sxNO
+JZNeFdzZqDRRY9fGjYNS7DdNOmrMmWKH+KXuMCItpNZsZS/3W7QxAo3ugYLdUylU
+Zg8H/BjUGZCGn1rEBAuQX78m0SZ1xHlgHSwJIOmxOJUDHLPHtThfbELY9ec14yi5
+so1aQwhhfhPvF+xuXBrVeTAfhFNYkf2uxcEp7+tgFAc5W0QfT9SBn5vSvIxv+dT4
+7B2Pg1l/zjdsM74g58lmRJeDoz4psAq+Uk7n3ImBhIku9qX632Q1hanjC8D4xM4W
+sI/W0ADCuAbY7LmwMpAMdrGg//SJUnBftlom7C9VA3EVf8Eo+OZH9hze+gIgUq+E
+iEUL5M4vOHK2ttsYrSkAt8MZzjQiTlDr1yzcg8fDIrqEAi5arjTPz0n2s0NFptNW
+lRD+Xz6pCXrnRgR8YSWpxvq3EWSJbZkSEk/eOmah22sFnnBZpDqn9+UArAznXrRi
+nYK9w38aMGPKM39ymG8kcbY7jmDZlRgGs2ab0Fdj1jl3CRo5IUatkOJwCEMd/tkB
+eXLQ8hspJhpFnVNReX0oithVZir+j36epk9Yn8d1l+YlKmuynjunKl9fhmoq5Q6i
+DFzdYpqBV+x9nVhnmPfGyrOkXvGL0X6vmXAEif/4JoOW4IZpyXjgn+VoCJUoae5J
+Djl45Bcc2Phrn4HW4Gg/+pIwTFqqZZ2jFrznNdgeIxTGjBrVsyJUeO3BHI0mVLaq
+jtjhTshYCI7mXOis9W3ic0RwE8rgdDXOYKHhLVw9c4094P/43utSVXE7UzbEhhLE
+Ngb4H5UGrQmPTNbq40tMUMUCej3zIKuVOvamzeE0IwLhkjNrvKhCG1EUhX4uoJKu
+DQ++3KVIVeYSv3+78Jfw9F3usAXxX1ICU74/La5DUNjU7DVodLDvCAy5y1jxP3Ic
+If6m7aBYVjFSQAcD8PZPeIEl9W4ZnbwyBfSDd11P2a8JcZ7N99GiiH3yS1QgJnAO
+g9XAgjT4Gcn7k4lHPHLULgijfiDSvt94Ga4/hse0F0akeZslVN/bygyib7x7Lzmq
+JkepRianrvKHbatuxvcajt/d+dxCnr32Q1qCEc5fcgDsjvviRL2tKR0qhuYjn1zR
+Vk/fRtYOmlaGBVzUXcjLRAg3gC9+Gy8KvXIDrnHxD+9Ob+DUP9fgbKqMeOzKcCK8
+NSfSQ+tQjBYD5Ku4zAPUQJoRGgx43vXzcl2Z2i3E2otpoH82Kx8S9WlVEUlTtBjQ
+QIGM5aR0QUNt8z34t2KWRA8SpP54VzBmEPdwLnzna+PkrGKsKiHVn4K+HfjDp1uW
+xyO8VjrolAOYosTPXMpNp2u/FoFxaAPTa/TvmKc0kQ3ED9/sGLS2twDnEccvHP+9
+zzrnzzN3T2CWuXveDpuyuAty3EoAid1nuC86WakSaAZoa8H2QoRgsrkkBCq+K/yl
+4FO9wuP+ksZoVq3mEDQ9qv6H4JJEWurfkws3OqrA5gENcLmSUkZie4oqAxeOD4Hh
+Zx4ckG5egQYr0PnOd2r7ZbIizv3MKT4RBrfOzrE6cvm9bJEzNWXdDyIxZ/kuoLA6
+zX7gGLdGhg7dqzKqnGtopLAsyM1b/utRtWxOTGO9K9lRxyX82oCVT9Yw0DwwA+cH
+Gutg1w7JHrIAYEtY0ezHgxhqMGuuTyJMX9Vr0D+9DdMeBK7hVOeSnxkaQ0f9HvF6
+0XI/2OTIoBSCBpUXjpgsYt7m7n2rFJGJmtqgLAosCAkacHnHLwX0EnzBw3sdDU6Q
+jFXUWIDd5xUsNkFDCbspLMFs22hjNI6f/GREwd23Q4ujF8pUIcxcfbs2myjbK45s
+tsn/jrkxmKRgwCIeN/H7CM+4GXSkEGLWbiGCxWzWt9wW1F4M7NW9nho3D1Pi2LBL
+1ByTmjfo/9u9haWrp53enDLJJbcaslfe+zvo3J70Nnzu3m3oJ3dmUxgJIstG10g3
+lhpUm1ynvx04IFkYJ3kr/QHG/xGS+yh/pMZlwcUSpjEgYFmjFHU4A1Ng4LGI4lnw
+5wisay4J884xmDgGfK0sdVQyW5rExIg63yYXp2GskRdDdwvWlFUzPzGgCNXQU96A
+ljZfjs2u4IiVCC3uVsNbGqCeSdAl9HC5xKuPNbw5yTxPkeRL1ouSdkBy7rvdFaFf
+dMPw6sBRNW8ZFInlgOncR3+xT/rZxru87LCq+3hRN3kw3hvFldrW2QzZSksO759b
+pJEP+4fxuG96Wq25fRmzHzE0bdJ+2qF3fp/hy4oRi+eVPa0vHdtkymE4OUFWftb6
++P++JVOzZ4ZxYA8zyUoJb0YCaxL+Jp/QqiUiH8WZVmYZmswqR48sUUKr7TIvpNbY
+6jEH6F7KiZCoWfKH12tUC69iRYx3UT/4Bmsgi3S4yUxfieYRMIwihtpP4i0O+OjB
+/DPbb13qj8ZSfXJ+jmF2SRFfFG+2T7NJqm09JvT9UcslVd+vpUySNe9UAlpcvNGZ
+2+j180ZU7YAgpwdVwdvqiJxkeVtAsIeqAvIXMFm1PDe7FJB0BiSVZdihB6cjnKBI
+dv7Lc1tI2sQe7QSfk+gtionLrEnto+aXF5uVM5LMKi3gLElz7oXEIhn54OeEciB1
+cEmyX3Kb4HMRDMHyJxqJXwxm88RgC6RekoPvstu+AfX/NgSpRj5beaj9XkweJT3H
+rKWhkjq4Ghsn1LoodxluMMHd61m47JyoqIP9PBKoW+Na0VUKIVHw9e9YeW0nY1Zi
+5qFA/pHPAt9AbEilRay6NEm8P7TTlNo216amc8byPXanoNrqBYZQHhZ93A4yl6jy
+RdpYskMivT+Sh1nhZAioKqqTZ3HiFR8hFGspAt5gJc4WLYevmxSicGa6AMyhrkvG
+rvOSdjY6JY/NkxtcgeycBX5MLF7uDbhUeqittvmlcrVN6+V+2HIbCCrvtow9pcX9
+EkaaNttj5M0RzjQxogCG+S5TkhCy04YvKIkaGJFi8xO3icdlxgOrKD8lhtbf4UpR
+cDuytl70JD95mSUWL53UYjeRf9OsLRJMHQOpS02japkMwCb/ngMCQuUXA8hGkBZL
+Xw7RwwPuM1Lx8edMXn5C0E8UK5e0QmI/dVIl2aglXk2oBMBJbnyrbfUPm462SG6u
+ke4gQKFmVy2rKICqSkh2DMr0NzeYEUjZ6KbmQcV7sKiFxQ0/ROk8eqkYYxGWUWJv
+ylPF1OTLH0AIbGlFPLQO4lMPh05yznZTac4tmowADSHY9RCxad1BjBeine2pj48D
+u36OnnuQIsedxt5YC+h1bs+mIvwMVsnMLidse38M/RayCDitEBvL0KeG3vWYzaAL
+h0FCZGOW0ilVk8tTF5+XWtsQEp1PpclvkcBMkU3DtBUnlmPSKNfJT0iRr2T0sVW1
+h+249Wj0Bw==
+</Data>
+</CertificateFile>
+"""
+
+transport_cert=u"""\
+-----BEGIN CERTIFICATE-----
+MIIDBzCCAe+gAwIBAgIJANujJuVt5eC8MA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNV
+BAMMDkxpbnV4VHJhbnNwb3J0MCAXDTE0MTAyNDA3MjgwN1oYDzIxMDQwNzEyMDcy
+ODA3WjAZMRcwFQYDVQQDDA5MaW51eFRyYW5zcG9ydDCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBANPcJAkd6V5NeogSKjIeTXOWC5xzKTyuJPt4YZMVSosU
+0lI6a0wHp+g2fP22zrVswW+QJz6AVWojIEqLQup3WyCXZTv8RUblHnIjkvX/+J/G
+aLmz0G5JzZIpELL2C8IfQLH2IiPlK9LOQH00W74WFcK3QqcJ6Kw8GcVaeSXT1r7X
+QcGMqEjcWJkpKLoMJv3LMufE+JMdbXDUGY+Ps7Zicu8KXvBPaKVsc6H2jrqBS8et
+jXbzLyrezTUDz45rmyRJzCO5Sk2pohuYg73wUykAUPVxd7L8WnSyqz1v4zrObqnw
+BAyor67JR/hjTBfjFOvd8qFGonfiv2Vnz9XsYFTZsXECAwEAAaNQME4wHQYDVR0O
+BBYEFL8i/sehpGV6IEDX7F0WQHQ/ZXOyMB8GA1UdIwQYMBaAFL8i/sehpGV6IEDX
+7F0WQHQ/ZXOyMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAMPLrimT
+Gptu5pLRHPT8OFRN+skNSkepYaUaJuq6cSKxLumSYkD8++rohu+1+a7t1YNjjNSJ
+8ohRAynRJ7aRqwBmyX2OPLRpOfyRZwR0rcFfAMORm/jOE6WBdqgYD2L2b+tZplGt
+/QqgQzebaekXh/032FK4c74Zg5r3R3tfNSUMG6nLauWzYHbQ5SCdkuQwV0ehGqh5
+VF1AOdmz4CC2237BNznDFQhkeU0LrqqAoE/hv5ih7klJKZdS88rOYEnVJsFFJb0g
+qaycXjOm5Khgl4hKrd+DBD/qj4IVVzsmdpFli72k6WLBHGOXusUGo/3isci2iAIt
+DsfY6XGSEIhZnA4=
+-----END CERTIFICATE-----
+"""
+
+transport_private=u"""\
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDT3CQJHeleTXqI
+EioyHk1zlguccyk8riT7eGGTFUqLFNJSOmtMB6foNnz9ts61bMFvkCc+gFVqIyBK
+i0Lqd1sgl2U7/EVG5R5yI5L1//ifxmi5s9BuSc2SKRCy9gvCH0Cx9iIj5SvSzkB9
+NFu+FhXCt0KnCeisPBnFWnkl09a+10HBjKhI3FiZKSi6DCb9yzLnxPiTHW1w1BmP
+j7O2YnLvCl7wT2ilbHOh9o66gUvHrY128y8q3s01A8+Oa5skScwjuUpNqaIbmIO9
+8FMpAFD1cXey/Fp0sqs9b+M6zm6p8AQMqK+uyUf4Y0wX4xTr3fKhRqJ34r9lZ8/V
+7GBU2bFxAgMBAAECggEBAM4hsfog3VAAyIieS+npq+gbhH6bWfMNaTQ3g5CNNbMu
+9hhFeOJHzKnWYjSlamgBQhAfTN+2E+Up+iAtcVUZ/lMumrQLlwgMo1vgmvu5Kxmh
+/YE5oEG+k0JzrCjD1trwd4zvc3ZDYyk/vmVTzTOc311N248UyArUiyqHBbq1a4rP
+tJhCLn2c4S7flXGF0MDVGZyV9V7J8N8leq/dRGMB027Li21T+B4mPHXa6b8tpRPL
+4vc8sHoUJDa2/+mFDJ2XbZfmlgd3MmIPlRn1VWoW7mxgT/AObsPl7LuQx7+t80Wx
+hIMjuKUHRACQSLwHxJ3SQRFWp4xbztnXSRXYuHTscLUCgYEA//Uu0qIm/FgC45yG
+nXtoax4+7UXhxrsWDEkbtL6RQ0TSTiwaaI6RSQcjrKDVSo/xo4ZySTYcRgp5GKlI
+CrWyNM+UnIzTNbZOtvSIAfjxYxMsq1vwpTlOB5/g+cMukeGg39yUlrjVNoFpv4i6
+9t4yYuEaF4Vww0FDd2nNKhhW648CgYEA0+UYH6TKu03zDXqFpwf4DP2VoSo8OgfQ
+eN93lpFNyjrfzvxDZkGF+7M/ebyYuI6hFplVMu6BpgpFP7UVJpW0Hn/sXkTq7F1Q
+rTJTtkTp2+uxQVP/PzSOqK0Twi5ifkfoEOkPkNNtTiXzwCW6Qmmcvln2u893pyR5
+gqo5BHR7Ev8CgYAb7bXpN9ZHLJdMHLU3k9Kl9YvqOfjTxXA3cPa79xtEmsrTys4q
+4HuL22KSII6Fb0VvkWkBAg19uwDRpw78VC0YxBm0J02Yi8b1AaOhi3dTVzFFlWeh
+r6oK/PAAcMKxGkyCgMAZ3hstsltGkfXMoBwhW+yL6nyOYZ2p9vpzAGrjkwKBgQDF
+0huzbyXVt/AxpTEhv07U0enfjI6tnp4COp5q8zyskEph8yD5VjK/yZh5DpmFs6Kw
+dnYUFpbzbKM51tToMNr3nnYNjEnGYVfwWgvNHok1x9S0KLcjSu3ki7DmmGdbfcYq
+A2uEyd5CFyx5Nr+tQOwUyeiPbiFG6caHNmQExLoiAQKBgFPy9H8///xsadYmZ18k
+r77R2CvU7ArxlLfp9dr19aGYKvHvnpsY6EuChkWfy8Xjqn3ogzgrHz/rn3mlGUpK
+vbtwtsknAHtTbotXJwfaBZv2RGgGRr3DzNo6ll2Aez0lNblZFXq132h7+y5iLvar
+4euORaD/fuM4UPlR5mN+bypU
+-----END PRIVATE KEY-----
+"""
+
+def MockGetOpensslCmd():
+ return 'openssl'
+
+class TestCertificates(unittest.TestCase):
+
+ def test_certificates(self):
+ crt1 = '/tmp/33B0ABCE4673538650971C10F7D7397E71561F35.crt'
+ crt2 = '/tmp/4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3.crt'
+ prv2 = '/tmp/4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3.prv'
+ os.chdir('/tmp')
+ if os.path.isfile(crt1):
+ os.remove(crt1)
+ if os.path.isfile(crt2):
+ os.remove(crt2)
+ if os.path.isfile(prv2):
+ os.remove(prv2)
+ fileutil.write_file(os.path.join('/tmp', "TransportCert.pem"),
+ transport_cert)
+ fileutil.write_file(os.path.join('/tmp', "TransportPrivate.pem"),
+ transport_private)
+ config = v1.Certificates(certs_sample)
+ self.assertNotEquals(None, config)
+ self.assertTrue(os.path.isfile(crt1))
+ self.assertTrue(os.path.isfile(crt2))
+ self.assertTrue(os.path.isfile(prv2))
+
+ self.assertNotEquals(0, len(config.cert_list.certificates))
+ cert = config.cert_list.certificates[0]
+ self.assertNotEquals(None, cert.thumbprint)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_conf.py b/tests/test_conf.py
new file mode 100644
index 0000000..204e4f5
--- /dev/null
+++ b/tests/test_conf.py
@@ -0,0 +1,69 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import os
+import tests.env
+import uuid
+import unittest
+import tests.tools as tools
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.conf as conf
+from azurelinuxagent.exception import *
+
+TestConf="""\
+#
+# This is comment
+#
+foo.bar.switch=y
+foo.bar.switch2=n
+foo.bar.str=foobar
+foo.bar.int=300
+
+"""
+
+class TestConfiguration(unittest.TestCase):
+ def test_parse_conf(self):
+ config = conf.ConfigurationProvider()
+ config.load(TestConf)
+ self.assertEquals(True, config.get_switch("foo.bar.switch"))
+ self.assertEquals(False, config.get_switch("foo.bar.switch2"))
+ self.assertEquals(False, config.get_switch("foo.bar.switch3"))
+ self.assertEquals(True, config.get_switch("foo.bar.switch4", True))
+ self.assertEquals("foobar", config.get("foo.bar.str"))
+ self.assertEquals("foobar1", config.get("foo.bar.str1", "foobar1"))
+ self.assertEquals(300, config.get_int("foo.bar.int"))
+ self.assertEquals(-1, config.get_int("foo.bar.int2"))
+ self.assertEquals(-1, config.get_int("foo.bar.str"))
+
+ def test_parse_malformed_conf(self):
+ config = conf.ConfigurationProvider()
+ self.assertRaises(AgentConfigError, config.load, None)
+
+ def test_load_conf_file(self):
+ with open('/tmp/test_conf', 'w') as F:
+ F.write(TestConf)
+ F.close()
+
+ config = conf.ConfigurationProvider()
+ conf.load_conf('/tmp/test_conf', conf=config)
+ self.assertEquals(True, config.get_switch("foo.bar.switch"), False)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_deprovision.py b/tests/test_deprovision.py
new file mode 100644
index 0000000..8bad6b9
--- /dev/null
+++ b/tests/test_deprovision.py
@@ -0,0 +1,54 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import unittest
+import azurelinuxagent.distro.default.deprovision as deprovision_handler
+
+def MockAction(param):
+ #print param
+ pass
+
+def MockSetup(self, deluser):
+ warnings = ["Print warning to console"]
+ actions = [
+ deprovision_handler.DeprovisionAction(MockAction, ['Take action'])
+ ]
+ return warnings, actions
+
+class TestDeprovisionHandler(unittest.TestCase):
+ def test_setup(self):
+ handler = deprovision_handler.DeprovisionHandler()
+ warnings, actions = handler.setup(False)
+ self.assertNotEquals(None, warnings)
+ self.assertNotEquals(0, len(warnings))
+ self.assertNotEquals(None, actions)
+ self.assertNotEquals(0, len(actions))
+ self.assertEquals(deprovision_handler.DeprovisionAction, type(actions[0]))
+
+
+ @mock(deprovision_handler.DeprovisionHandler, 'setup', MockSetup)
+ def test_deprovision(self):
+ handler = deprovision_handler.DeprovisionHandler()
+ handler.deprovision(force=True)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_dhcp.py b/tests/test_dhcp.py
new file mode 100644
index 0000000..2206325
--- /dev/null
+++ b/tests/test_dhcp.py
@@ -0,0 +1,68 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env as env
+from tests.tools import *
+import uuid
+import unittest
+import os
+import json
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.distro.default.dhcp as dhcp_handler
+
+SampleDhcpResponse = None
+with open(os.path.join(env.test_root, "dhcp"), 'rb') as F:
+ SampleDhcpResponse = F.read()
+
+mock_socket_send = MockFunc('socket_send', SampleDhcpResponse)
+mock_gen_trans_id = MockFunc('gen_trans_id', "\xC6\xAA\xD1\x5D")
+mock_get_mac_addr = MockFunc('get_mac_addr', "\x00\x15\x5D\x38\xAA\x38")
+mock_send_dhcp_failed = MockFunc(retval=None)
+
+class TestdhcpHandler(unittest.TestCase):
+
+ def test_build_dhcp_req(self):
+ req = dhcp_handler.build_dhcp_request(mock_get_mac_addr())
+ self.assertNotEquals(None, req)
+
+ @mock(dhcp_handler, "gen_trans_id", mock_gen_trans_id)
+ @mock(dhcp_handler, "socket_send", mock_socket_send)
+ def test_send_dhcp_req(self):
+ req = dhcp_handler.build_dhcp_request(mock_get_mac_addr())
+ resp = dhcp_handler.send_dhcp_request(req)
+ self.assertNotEquals(None, resp)
+
+ @mock(dhcp_handler, "send_dhcp_request", mock_send_dhcp_failed)
+ def test_send_dhcp_failed(self):
+ dhcp = dhcp_handler.DhcpHandler()
+ dhcp.probe()
+
+ @mock(dhcp_handler, "socket_send", mock_socket_send)
+ @mock(dhcp_handler, "gen_trans_id", mock_gen_trans_id)
+ @mock(dhcp_handler.OSUTIL, "get_mac_addr", mock_get_mac_addr)
+ @mock(dhcp_handler.fileutil, "write_file", MockFunc())
+ def test_handle_dhcp(self):
+ dh = dhcp_handler.DhcpHandler()
+ dh.probe()
+ self.assertEquals("10.62.144.1", dh.gateway)
+ self.assertEquals("10.62.144.140", dh.endpoint)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_distroLoader.py b/tests/test_distroLoader.py
new file mode 100644
index 0000000..16987c5
--- /dev/null
+++ b/tests/test_distroLoader.py
@@ -0,0 +1,42 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import unittest
+from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError
+from azurelinuxagent.handler import HANDLERS
+import azurelinuxagent.distro.default.osutil as osutil
+
+class TestDistroLoader(unittest.TestCase):
+ def test_loader(self):
+ self.assertNotEquals(osutil.DefaultOSUtil, type(OSUTIL))
+ self.assertNotEquals(None, HANDLERS.init_handler)
+ self.assertNotEquals(None, HANDLERS.main_handler)
+ self.assertNotEquals(None, HANDLERS.scvmm_handler)
+ self.assertNotEquals(None, HANDLERS.dhcp_handler)
+ self.assertNotEquals(None, HANDLERS.env_handler)
+ self.assertNotEquals(None, HANDLERS.provision_handler)
+ self.assertNotEquals(None, HANDLERS.resource_disk_handler)
+ self.assertNotEquals(None, HANDLERS.env_handler)
+ self.assertNotEquals(None, HANDLERS.deprovision_handler)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_envmon.py b/tests/test_envmon.py
new file mode 100644
index 0000000..74b61ee
--- /dev/null
+++ b/tests/test_envmon.py
@@ -0,0 +1,52 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import unittest
+import time
+from azurelinuxagent.future import text
+from azurelinuxagent.utils.osutil import OSUTIL
+from azurelinuxagent.distro.default.env import EnvMonitor
+
+class MockDhcpHandler(object):
+ def conf_routes(self):
+ pass
+
+def mock_get_dhcp_pid():
+ return "1234"
+
+def mock_dhcp_pid_change():
+ return text(time.time())
+
+class TestEnvMonitor(unittest.TestCase):
+
+ @mock(OSUTIL, 'get_dhcp_pid', mock_get_dhcp_pid)
+ def test_dhcp_pid_not_change(self):
+ monitor = EnvMonitor(MockDhcpHandler())
+ monitor.handle_dhclient_restart()
+
+ @mock(OSUTIL, 'get_dhcp_pid', mock_dhcp_pid_change)
+ def test_dhcp_pid_change(self):
+ monitor = EnvMonitor(MockDhcpHandler())
+ monitor.handle_dhclient_restart()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_event.py b/tests/test_event.py
new file mode 100644
index 0000000..fcf67c9
--- /dev/null
+++ b/tests/test_event.py
@@ -0,0 +1,53 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from .tools import *
+import uuid
+import unittest
+import os
+import shutil
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.event as evt
+import azurelinuxagent.protocol as prot
+
+class MockProtocol(object):
+ def get_vminfo(self):
+ return prot.VMInfo(subscriptionId='foo', vmName='bar')
+ def report_event(self, data): pass
+
+class TestEvent(unittest.TestCase):
+ def test_save(self):
+ if not os.path.exists("/tmp/events"):
+ os.mkdir("/tmp/events")
+ evt.add_event("Test", "Test", True)
+ eventsFile = os.listdir("/tmp/events")
+ self.assertNotEquals(0, len(eventsFile))
+ shutil.rmtree("/tmp/events")
+
+ @mock(evt.prot.FACTORY, 'get_default_protocol',
+ MockFunc(retval=MockProtocol()))
+ def test_init_sys_info(self):
+ monitor = evt.EventMonitor()
+ monitor.init_sysinfo()
+ self.assertNotEquals(0, len(monitor.sysinfo))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_ext.py b/tests/test_ext.py
new file mode 100644
index 0000000..126c0ec
--- /dev/null
+++ b/tests/test_ext.py
@@ -0,0 +1,178 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import uuid
+import unittest
+import os
+import json
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.protocol as prot
+import azurelinuxagent.distro.default.extension as ext
+
+ext_sample_json = {
+ "name":"TestExt",
+ "properties":{
+ "version":"2.0",
+ "state":"enabled",
+ "upgradePolicy":"auto",
+ "extensions":[{
+ "sequenceNumber": 0,
+ "publicSettings": "",
+ "protectedSettings": "",
+ "certificateThumbprint": ""
+ }],
+ "versionUris":[{
+ "version":"2.1",
+ "uris":["http://foo.bar"]
+ },{
+ "version":"2.0",
+ "uris":["http://foo.bar"]
+ }]
+ }
+}
+ext_sample = prot.Extension()
+prot.set_properties(ext_sample, ext_sample_json)
+
+pkd_list_sample_str={
+ "versions": [{
+ "version": "2.0",
+ "uris":[{
+ "uri":"http://foo.bar"
+ }]
+ },{
+ "version": "2.1",
+ "uris":[{
+ "uri":"http://foo.bar"
+ }]
+ }]
+}
+pkg_list_sample = prot.ExtensionPackageList()
+prot.set_properties(pkg_list_sample, pkd_list_sample_str)
+
+manifest_sample_str = {
+ "handlerManifest":{
+ "installCommand": "echo 'install'",
+ "uninstallCommand": "echo 'uninstall'",
+ "updateCommand": "echo 'update'",
+ "enableCommand": "echo 'enable'",
+ "disableCommand": "echo 'disable'",
+ }
+}
+manifest_sample = ext.HandlerManifest(manifest_sample_str)
+
+def mock_load_manifest(self):
+ return manifest_sample
+
+mock_launch_command = MockFunc()
+mock_set_handler_status = MockFunc()
+
+def mock_download(self):
+ fileutil.mkdir(self.get_base_dir())
+ fileutil.write_file(self.get_manifest_file(), json.dumps(manifest_sample_str))
+
+#logger.LoggerInit("/dev/null", "/dev/stdout")
+class TestExtensions(unittest.TestCase):
+
+ def test_load_ext(self):
+ libDir = OSUTIL.get_lib_dir()
+ test_ext1 = os.path.join(libDir, 'TestExt-1.0')
+ test_ext2 = os.path.join(libDir, 'TestExt-2.0')
+ test_ext2 = os.path.join(libDir, 'TestExt-2.1')
+ for path in [test_ext1, test_ext2]:
+ if not os.path.isdir(path):
+ os.mkdir(path)
+ test_ext = ext.get_installed_version('TestExt')
+ self.assertEqual('2.1', test_ext)
+
+ def test_getters(self):
+ test_ext = ext.ExtensionInstance(ext_sample, pkg_list_sample,
+ ext_sample.properties.version, False)
+ self.assertEqual("/tmp/TestExt-2.0", test_ext.get_base_dir())
+ self.assertEqual("/tmp/TestExt-2.0/status", test_ext.get_status_dir())
+ self.assertEqual("/tmp/TestExt-2.0/status/0.status",
+ test_ext.get_status_file())
+ self.assertEqual("/tmp/TestExt-2.0/config/HandlerState",
+ test_ext.get_handler_state_file())
+ self.assertEqual("/tmp/TestExt-2.0/config", test_ext.get_conf_dir())
+ self.assertEqual("/tmp/TestExt-2.0/config/0.settings",
+ test_ext.get_settings_file())
+ self.assertEqual("/tmp/TestExt-2.0/heartbeat.log",
+ test_ext.get_heartbeat_file())
+ self.assertEqual("/tmp/TestExt-2.0/HandlerManifest.json",
+ test_ext.get_manifest_file())
+ self.assertEqual("/tmp/TestExt-2.0/HandlerEnvironment.json",
+ test_ext.get_env_file())
+ self.assertEqual("/tmp/log/TestExt/2.0", test_ext.get_log_dir())
+
+ test_ext = ext.ExtensionInstance(ext_sample, pkg_list_sample, "2.1", False)
+ self.assertEqual("/tmp/TestExt-2.1", test_ext.get_base_dir())
+ self.assertEqual("2.1", test_ext.get_target_version())
+
+ @mock(ext.ExtensionInstance, 'load_manifest', mock_load_manifest)
+ @mock(ext.ExtensionInstance, 'launch_command', mock_launch_command)
+ @mock(ext.ExtensionInstance, 'set_handler_status', mock_set_handler_status)
+ def test_handle_uninstall(self):
+ mock_launch_command.args = None
+ mock_set_handler_status.args = None
+ test_ext = ext.ExtensionInstance(ext_sample, pkg_list_sample,
+ ext_sample.properties.version, False)
+ test_ext.handle_uninstall()
+ self.assertEqual(None, mock_launch_command.args)
+ self.assertEqual(None, mock_set_handler_status.args)
+ self.assertEqual(None, test_ext.get_curr_op())
+
+ test_ext = ext.ExtensionInstance(ext_sample, pkg_list_sample,
+ ext_sample.properties.version, True)
+ test_ext.handle_uninstall()
+ self.assertEqual(manifest_sample.get_uninstall_command(), mock_launch_command.args[0])
+ self.assertEqual("UnInstall", test_ext.get_curr_op())
+ self.assertEqual("NotReady", mock_set_handler_status.args[0])
+
+ @mock(ext.ExtensionInstance, 'load_manifest', mock_load_manifest)
+ @mock(ext.ExtensionInstance, 'launch_command', mock_launch_command)
+ @mock(ext.ExtensionInstance, 'download', mock_download)
+ @mock(ext.ExtensionInstance, 'get_handler_status', MockFunc(retval="enabled"))
+ @mock(ext.ExtensionInstance, 'set_handler_status', mock_set_handler_status)
+ def test_handle(self):
+ #Test enable
+ test_ext = ext.ExtensionInstance(ext_sample, pkg_list_sample,
+ ext_sample.properties.version, False)
+ test_ext.init_logger()
+ self.assertEqual(1, len(test_ext.logger.appenders) - len(logger.DEFAULT_LOGGER.appenders))
+ test_ext.handle()
+
+ #Test upgrade
+ test_ext = ext.ExtensionInstance(ext_sample, pkg_list_sample,
+ ext_sample.properties.version, False)
+ test_ext.init_logger()
+ self.assertEqual(1, len(test_ext.logger.appenders) - len(logger.DEFAULT_LOGGER.appenders))
+ test_ext.handle()
+
+ def test_status_convert(self):
+ ext_status = json.loads('[{"status": {"status": "success", "formattedMessage": {"lang": "en-US", "message": "Script is finished"}, "operation": "Enable", "code": "0", "name": "Microsoft.OSTCExtensions.CustomScriptForLinux"}, "version": "1.0", "timestampUTC": "2015-06-27T08:34:50Z"}]')
+ ext.ext_status_to_v2(ext_status[0], 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_extensionsconfig.py b/tests/test_extensionsconfig.py
new file mode 100644
index 0000000..4dbf30d
--- /dev/null
+++ b/tests/test_extensionsconfig.py
@@ -0,0 +1,159 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import json
+import azurelinuxagent.protocol.v1 as v1
+
+ext_conf_sample=u"""\
+<Extensions version="1.0.0.0" goalStateIncarnation="9"><GuestAgentExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <GAFamilies>
+ <GAFamily>
+ <Name>Win8</Name>
+ <Uris>
+ <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri>
+ </Uris>
+ </GAFamily>
+ <GAFamily>
+ <Name>Win7</Name>
+ <Uris>
+ <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri>
+ </Uris>
+ </GAFamily>
+ </GAFamilies>
+</GuestAgentExtension>
+<Plugins>
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" />
+</Plugins>
+<PluginSettings>
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4">
+ <RuntimeSettings seqNo="6">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK","publicSettings":{"foo":"bar"}}}]}</RuntimeSettings>
+ </Plugin>
+</PluginSettings>
+<StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&amp;sp=rw&amp;se=9999-01-01&amp;sk=key1&amp;sv=2014-02-14&amp;sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions>
+"""
+
+manifest_sample=u"""\
+<?xml version="1.0" encoding="utf-8"?>
+<PluginVersionManifest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <Plugins>
+ <Plugin>
+ <Version>1.0</Version>
+ <Uris>
+ <Uri>http://blahblah</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
+ <Version>1.1</Version>
+ <Uris>
+ <Uri>http://blahblah</Uri>
+ </Uris>
+ </Plugin>
+ </Plugins>
+ <InternalPlugins>
+ <Plugin>
+ <Version>1.2</Version>
+ <Uris>
+ <Uri>http://blahblah</Uri>
+ </Uris>
+ </Plugin>
+</InternalPlugins>
+</PluginVersionManifest>
+"""
+
+EmptySettings=u"""\
+<Extensions>
+ <Plugins>
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" />
+ </Plugins>
+ <StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&amp;sp=rw&amp;se=9999-01-01&amp;sk=key1&amp;sv=2014-02-14&amp;sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob>
+</Extensions>
+"""
+
+EmptyPublicSettings=u"""\
+<Extensions>
+ <Plugins>
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" />
+ </Plugins>
+ <PluginSettings>
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4">
+ <RuntimeSettings seqNo="6">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK"}}]}</RuntimeSettings>
+ </Plugin>
+ </PluginSettings>
+ <StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&amp;sp=rw&amp;se=9999-01-01&amp;sk=key1&amp;sv=2014-02-14&amp;sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob>
+</Extensions>
+"""
+
+class TestExtensionsConfig(unittest.TestCase):
+ def test_extensions_config(self):
+ config = v1.ExtensionsConfig(ext_conf_sample)
+ extensions = config.ext_list.extensions
+ self.assertNotEquals(None, extensions)
+ self.assertEquals(1, len(extensions))
+ self.assertNotEquals(None, extensions[0])
+ extension = extensions[0]
+ self.assertEquals("OSTCExtensions.ExampleHandlerLinux",
+ extension.name)
+ self.assertEquals("1.4", extension.properties.version)
+ self.assertEquals('auto', extension.properties.upgradePolicy)
+ self.assertEquals("enabled", extension.properties.state)
+ settings = extension.properties.extensions[0]
+ self.assertEquals("4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3",
+ settings.certificateThumbprint)
+ self.assertEquals("MIICWgYJK", settings.privateSettings)
+ self.assertEquals(json.loads('{"foo":"bar"}'),
+ settings.publicSettings)
+
+ man = v1.ExtensionManifest(manifest_sample)
+ self.assertNotEquals(None, man.pkg_list)
+ self.assertEquals(3, len(man.pkg_list.versions))
+
+ def test_empty_settings(self):
+ config = v1.ExtensionsConfig(EmptySettings)
+
+ def test_empty_public_settings(self):
+ config = v1.ExtensionsConfig(EmptyPublicSettings)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_file_util.py b/tests/test_file_util.py
new file mode 100644
index 0000000..1eeb784
--- /dev/null
+++ b/tests/test_file_util.py
@@ -0,0 +1,75 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import sys
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.fileutil as fileutil
+
+class TestFileOperations(unittest.TestCase):
+ def test_read_write_file(self):
+ test_file='/tmp/test_file'
+ content = text(uuid.uuid4())
+ fileutil.write_file(test_file, content)
+ self.assertTrue(tools.simple_file_grep(test_file, content))
+
+ content_read = fileutil.read_file('/tmp/test_file')
+ self.assertEquals(content, content_read)
+ os.remove(test_file)
+
+ def test_rw_utf8_file(self):
+ test_file='/tmp/test_file3'
+ content = "\u6211"
+ fileutil.write_file(test_file, content, encoding="utf-8")
+ self.assertTrue(tools.simple_file_grep(test_file, content))
+
+ content_read = fileutil.read_file('/tmp/test_file3')
+ self.assertEquals(content, content_read)
+ os.remove(test_file)
+
+ def test_remove_bom(self):
+ test_file= '/tmp/test_file4'
+ data = b'\xef\xbb\xbfhehe'
+ fileutil.write_file(test_file, data, asbin=True)
+ data = fileutil.read_file(test_file, remove_bom=True)
+ self.assertNotEquals(0xbb, ord(data[0]))
+
+ def test_append_file(self):
+ test_file='/tmp/test_file2'
+ content = text(uuid.uuid4())
+ fileutil.append_file(test_file, content)
+ self.assertTrue(tools.simple_file_grep(test_file, content))
+ os.remove(test_file)
+
+ def test_get_last_path_element(self):
+ filepath = '/tmp/abc.def'
+ filename = fileutil.base_name(filepath)
+ self.assertEquals('abc.def', filename)
+
+ filepath = '/tmp/abc'
+ filename = fileutil.base_name(filepath)
+ self.assertEquals('abc', filename)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/part-gpt.py b/tests/test_future.py
index 373e1c6..20edc93 100644
--- a/tests/part-gpt.py
+++ b/tests/test_future.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Copyright 2014 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,18 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# Requires Python 2.4+ and Openssl 1.0+
+#
# Implements parts of RFC 2131, 1541, 1497 and
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
-#
-import subprocess
+import tests.env
+from tests.tools import *
+import uuid
+import unittest
+import os
+import shutil
+import time
+import azurelinuxagent.future as future
+
+class TestFuture(unittest.TestCase):
+ def test_future_pkgs(self):
+ future.httpclient
+ future.urlparse
+ future.text(b"asdf", encoding="utf-8")
if __name__ == '__main__':
- subprocess.call(['umount', '/mnt/resource'])
- subprocess.call(['umount', '/mnt'])
- subprocess.call(['parted', '/dev/sdb', 'print'])
- subprocess.call(['parted', '/dev/sdb', 'rm', '1'])
- subprocess.call(['parted', '/dev/sdb', 'mklabel', 'gpt'])
- subprocess.call(['parted', '/dev/sdb', 'mkpart', 'primary', '0%', '50%'])
- subprocess.call(['parted', '/dev/sdb', 'mkpart', 'primary', '50%', '100%'])
+ unittest.main()
diff --git a/tests/test_goalstate.py b/tests/test_goalstate.py
new file mode 100644
index 0000000..a18ce8d
--- /dev/null
+++ b/tests/test_goalstate.py
@@ -0,0 +1,71 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import test
+import azurelinuxagent.protocol.v1 as v1
+
+goal_state_sample=u"""\
+<?xml version="1.0" encoding="utf-8"?>
+<GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
+ <Version>2010-12-15</Version>
+ <Incarnation>1</Incarnation>
+ <Machine>
+ <ExpectedState>Started</ExpectedState>
+ <LBProbePorts>
+ <Port>16001</Port>
+ </LBProbePorts>
+ </Machine>
+ <Container>
+ <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId>
+ <RoleInstanceList>
+ <RoleInstance>
+ <InstanceId>MachineRole_IN_0</InstanceId>
+ <State>Started</State>
+ <Configuration>
+ <HostingEnvironmentConfig>http://hostingenvuri/</HostingEnvironmentConfig>
+ <SharedConfig>http://sharedconfiguri/</SharedConfig>
+ <ExtensionsConfig>http://extensionsconfiguri/</ExtensionsConfig>
+ <FullConfig>http://fullconfiguri/</FullConfig>
+ </Configuration>
+ </RoleInstance>
+ </RoleInstanceList>
+ </Container>
+ </GoalState>
+"""
+
+class TestGoalState(unittest.TestCase):
+ def test_goal_state(self):
+ goal_state = v1.GoalState(goal_state_sample)
+ self.assertEquals('1', goal_state.incarnation)
+ self.assertNotEquals(None, goal_state.expected_state)
+ self.assertNotEquals(None, goal_state.hosting_env_uri)
+ self.assertNotEquals(None, goal_state.shared_conf_uri)
+ self.assertEquals(None, goal_state.certs_uri)
+ self.assertNotEquals(None, goal_state.ext_uri)
+ self.assertNotEquals(None, goal_state.role_instance_id)
+ self.assertNotEquals(None, goal_state.container_id)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_hostingenv.py b/tests/test_hostingenv.py
new file mode 100644
index 0000000..3d2ce73
--- /dev/null
+++ b/tests/test_hostingenv.py
@@ -0,0 +1,66 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import azurelinuxagent.protocol.v1 as v1
+
+hosting_env_sample=u"""
+ <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1">
+ <StoredCertificates>
+ <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" />
+ </StoredCertificates>
+ <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
+ <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
+ <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
+ </Deployment>
+ <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
+ <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" hostingEnvironmentVersion="1" software="" softwareType="ApplicationPackage" entryPoint="" parameters="" settleTimeSeconds="10" />
+ <HostingEnvironmentSettings name="full" runtime="rd_fabric_stable.110217-1402.runtimePackage_1.0.0.8.zip">
+ <CAS mode="full" />
+ <PrivilegeLevel mode="max" />
+ <AdditionalProperties><CgiHandlers></CgiHandlers></AdditionalProperties>
+ </HostingEnvironmentSettings>
+ <ApplicationSettings>
+ <Setting name="__ModelData" value="&lt;m role=&quot;MachineRole&quot; xmlns=&quot;urn:azure:m:v1&quot;>&lt;r name=&quot;MachineRole&quot;>&lt;e name=&quot;a&quot; />&lt;e name=&quot;b&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp&quot; />&lt;e name=&quot;Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput&quot; />&lt;/r>&lt;/m>" />
+ <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="DefaultEndpointsProtocol=http;AccountName=osimages;AccountKey=DNZQ..." />
+ <Setting name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.Enabled" value="true" />
+ </ApplicationSettings>
+ <ResourceReferences>
+ <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" />
+ </ResourceReferences>
+ </HostingEnvironmentConfig>
+"""
+
+class TestHostingEvn(unittest.TestCase):
+ def test_hosting_env(self):
+ hosting_env = v1.HostingEnv(hosting_env_sample)
+ self.assertNotEquals(None, hosting_env)
+ self.assertEquals("MachineRole_IN_0", hosting_env.vm_name)
+ self.assertEquals("MachineRole", hosting_env.role_name)
+ self.assertEquals("db00a7755a5e4e8a8fe4b19bc3b330c3",
+ hosting_env.deployment_name)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_http.py b/tests/test_http.py
deleted file mode 100644
index eab1382..0000000
--- a/tests/test_http.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# Copyright 2014 Microsoft Corporation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-from env import waagent
-import sys
-from tests.tools import *
-
-class MockHTTPResponse(object):
- def __init__(self, status=200):
- self.status = status
- self.reason = "foo"
-
- def getheaders(*args, **kwargs):
- return {"hehe" : "haha"}
-
- def read(*args, **kwargs):
- return "bar"
-
-class MockOldHTTPConnection(object):
- MockHost=None
- MockPort=None
- MockUrl=None
- MockCallCount=0
-
- def __init__(self, host, port):
- self.__class__.MockHost = host
- self.__class__.MockPort = port
-
- def request(self, method, url, data, headers = None):
- self.__class__.MockUrl = url
- self.__class__.MockCallCount += 1
-
- def getresponse(*args, **kwargs):
- return MockHTTPResponse()
-
-class MockHTTPConnection(MockOldHTTPConnection):
- def set_tunnel(*args, **kwargs):
- pass
-
-class MockBadHTTPConnection(MockHTTPConnection):
- def getresponse(*args, **kwargs):
- return MockHTTPResponse(500)
-
-class MockHttpLib(object):
- def __init__(self):
- self.HTTPConnection = MockHTTPConnection
- self.OK = 200
-
-MockOSEnv = {
- "http_proxy":"http://httpproxy:8888",
- "https_proxy":"https://httpsproxy:8888"
-}
-
-class TestHttp(unittest.TestCase):
-
- def test_parseurl(self):
- httputil = waagent.Util()
- host, port, secure, path = httputil._ParseUrl("http://foo:8/bar?hehe")
- self.assertEquals("foo", host)
- self.assertEquals(8, port)
- self.assertEquals(False, secure)
- self.assertEquals("/bar?hehe", path)
-
- host, port, secure, path = httputil._ParseUrl("http://foo.bar/")
- self.assertEquals("foo.bar", host)
- self.assertEquals(80, port)
- self.assertEquals(False, secure)
- self.assertEquals("/", path)
-
- host, port, secure, path= httputil._ParseUrl("https://foo.bar/")
- self.assertEquals("foo.bar", host)
- self.assertEquals(80, port)
- self.assertEquals(True, secure)
- self.assertEquals("/", path)
-
- self.assertRaises(ValueError, httputil._ParseUrl,
- "https://a:b@foo.bar/")
-
- host, port, secure, path = httputil._ParseUrl("https://foo.bar")
- self.assertEquals("foo.bar", host)
- self.assertEquals(80, port)
- self.assertEquals(True, secure)
- self.assertEquals("/", path)
-
- host, port, secure, path = httputil._ParseUrl("http://a:b@foo.bar:8888")
- self.assertEquals("a:b@foo.bar", host)
- self.assertEquals(8888, port)
- self.assertEquals(False, secure)
- self.assertEquals("/", path)
-
- @Mockup(waagent.httplib, "HTTPConnection", MockHTTPConnection)
- @Mockup(waagent.os, "environ", MockOSEnv)
- def test_http_request(self):
- httputil = waagent.Util()
-
- #If chkProxy is on, host and port should point to proxy server
- httputil.HttpRequest("GET", "http://foo.bar/get", chkProxy=True)
- self.assertEquals("httpproxy", MockHTTPConnection.MockHost)
- self.assertEquals(8888, MockHTTPConnection.MockPort)
- self.assertEquals("http://foo.bar:80/get", MockHTTPConnection.MockUrl)
-
- #If chkProxy is off, ignore proxy
- httputil.HttpRequest("GET", "http://foo.bar/get", chkProxy=False)
- self.assertEquals("foo.bar", MockHTTPConnection.MockHost)
- self.assertEquals(80, MockHTTPConnection.MockPort)
- self.assertEquals("/get", MockHTTPConnection.MockUrl)
-
- @Mockup(waagent, "httplib" , MockHttpLib())
- def test_https_fallback(self):
- httputil = waagent.Util()
- print "The bellowing warning log is expected:"
- httputil.HttpRequest("GET", "https://foo.bar/get")
- self.assertEquals("/get", MockHTTPConnection.MockUrl)
-
- @Mockup(waagent.httplib, "HTTPConnection", MockOldHTTPConnection)
- @Mockup(waagent.httplib, "HTTPSConnection", MockOldHTTPConnection)
- @Mockup(waagent.os, "environ", MockOSEnv)
- def test_https_fallback2(self):
- httputil = waagent.Util()
- print "The bellowing warning log is expected:"
- httputil.HttpRequest("GET", "https://foo.bar/get", chkProxy=True)
- self.assertEquals("http://foo.bar:80/get", MockOldHTTPConnection.MockUrl)
-
- @Mockup(waagent.Util, "RetryWaitingInterval", 0)
- @Mockup(waagent.httplib, "HTTPConnection", MockBadHTTPConnection)
- def test_retry(self):
- httputil = waagent.Util()
- MockBadHTTPConnection.MockCallCount=0
- print "The bellowing error log is expected:"
- httputil.HttpRequest("GET", "http://foo.bar", chkProxy=False, maxRetry=1)
- self.assertEquals(2, MockBadHTTPConnection.MockCallCount)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/upload_status_blob.py b/tests/test_import_waagent.py
index 59cb732..ec3f923 100644
--- a/tests/upload_status_blob.py
+++ b/tests/test_import_waagent.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Copyright 2014 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,30 +12,29 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# Requires Python 2.4+ and Openssl 1.0+
+#
# Implements parts of RFC 2131, 1541, 1497 and
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
-#
+import tests.env
+import tests.tools as tools
import os
-from env import waagent
-"""
-To run the test, you need to create a file under the same directory called:
- status_blob_url.py
-and defined the following 2 variables like:
- blockBlobUrl="<sas link to a block blob with w/r access>"
- pageBlobUrl="<sas link to a page blob with w/r access>"
-"""
-from status_blob_url import blockBlobUrl, pageBlobUrl
-
-class MockConfig(object):
- def get(self, keyName):
- return None
+import imp
+import sys
+import uuid
+import unittest
-waagent.Config = MockConfig()
+class TestImportWAAgent(unittest.TestCase):
+ def test_import_waagent(self):
+ agent_path = os.path.join(tools.parent, 'bin/waagent')
+ if sys.version_info[0] == 2:
+ waagent = imp.load_source('waagent', agent_path)
+ self.assertNotEquals(None, waagent.LoggerInit)
+ else:
+ self.assertRaises(ImportError, imp.load_source, 'waagent',
+ agent_path)
if __name__ == '__main__':
- waagent.LoggerInit('/dev/stdout', '/dev/null', verbose=True)
- status = "a" * 512
- waagent.UploadStatusBlob(blockBlobUrl, status.encode("utf-8"))
- #waagent.UploadStatusBlob(pageBlobUrl, status.encode("utf-8"))
+ unittest.main()
diff --git a/tests/test_logger.py b/tests/test_logger.py
index d188277..121a8fe 100644
--- a/tests/test_logger.py
+++ b/tests/test_logger.py
@@ -12,24 +12,91 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+import tests.env
+import tests.tools as tools
+import uuid
import unittest
-from env import waagent
+import azurelinuxagent.logger as logger
+from azurelinuxagent.future import text
+
+class TestLogger(unittest.TestCase):
+
+ def test_no_appender(self):
+ #The logger won't throw exception even if no appender.
+ _logger = logger.Logger()
+ _logger.verbose("Assert no exception")
+ _logger.info("Assert no exception")
+ _logger.warn("Assert no exception")
+ _logger.error("Assert no exception")
+
+ def test_logger_format(self):
+ _logger = logger.Logger()
+ _logger.info("This is an exception {0}", Exception("Test"))
+ _logger.info("This is an number {0}", 0)
+ _logger.info("This is an boolean {0}", True)
+ _logger.verbose("{0}")
+ _logger.verbose("{0} {1}", 0, 1)
+ _logger.info("{0} {1}", 0, 1)
+ _logger.warn("{0} {1}", 0, 1)
+ _logger.error("{0} {1}", 0, 1)
+ _logger.info("this is a unicode {0}", '\u6211')
+ _logger.info("this is a utf-8 {0}", '\u6211'.encode('utf-8'))
+ _logger.info("this is a gbk {0}", 0xff )
+
+ def test_file_appender(self):
+ _logger = logger.Logger()
+ _logger.add_appender(logger.AppenderType.FILE,
+ logger.LogLevel.INFO,
+ '/tmp/testlog')
+
+ msg = text(uuid.uuid4())
+ _logger.info("Test logger: {0}", msg)
+ self.assertTrue(tools.simple_file_grep('/tmp/testlog', msg))
+
+ msg = text(uuid.uuid4())
+ _logger.verbose("Verbose should not be logged: {0}", msg)
+ self.assertFalse(tools.simple_file_grep('/tmp/testlog', msg))
+
+ _logger.info("this is a unicode {0}", '\u6211')
+ _logger.info("this is a utf-8 {0}", '\u6211'.encode('utf-8'))
+ _logger.info("this is a gbk {0}", 0xff)
+
+ def test_concole_appender(self):
+ _logger = logger.Logger()
+ _logger.add_appender(logger.AppenderType.CONSOLE,
+ logger.LogLevel.VERBOSE,
+ '/tmp/testlog')
+
+ msg = text(uuid.uuid4())
+ _logger.info("Test logger: {0}", msg)
+ self.assertTrue(tools.simple_file_grep('/tmp/testlog', msg))
+
+ msg = text(uuid.uuid4())
+ _logger.verbose("Test logger: {0}", msg)
+ self.assertFalse(tools.simple_file_grep('/tmp/testlog', msg))
-class TestWAAgentLogger(unittest.TestCase):
-
def test_log_to_non_exists_dev(self):
- logger = waagent.Logger('/tmp/testlog', '/dev/nonexists')
- logger.Log("something")
+ _logger = logger.Logger()
+ _logger.add_appender(logger.AppenderType.CONSOLE,
+ logger.LogLevel.INFO,
+ '/dev/nonexists')
+ _logger.info("something")
def test_log_to_non_exists_file(self):
- logger = waagent.Logger('/tmp/nonexists/testlog', '/tmp/testconsole')
- logger.Log("something")
+ _logger = logger.Logger()
+ _logger.add_appender(logger.AppenderType.FILE,
+ logger.LogLevel.INFO,
+ '/tmp/nonexists')
+ _logger.info("something")
+
- def test_log_unicode(self):
- logger = waagent.Logger('/tmp/testlog', '/tmp/testconsole')
- logger.Log(u"anything\u6211\u7231\u5201\u831C".encode("utf-8"))
if __name__ == '__main__':
unittest.main()
diff --git a/fix-gpt-ubuntu.py b/tests/test_metadata.py
index dd061f7..8c34acc 100644
--- a/fix-gpt-ubuntu.py
+++ b/tests/test_metadata.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
# Copyright 2014 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,26 +12,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# Requires Python 2.4+ and Openssl 1.0+
+#
# Implements parts of RFC 2131, 1541, 1497 and
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
-#
-import subprocess
+import tests.env
+from tests.tools import *
+import unittest
+from azurelinuxagent.metadata import AGENT_NAME, AGENT_VERSION, \
+ DISTRO_NAME, DISTRO_VERSION, DISTRO_CODE_NAME, \
+ DISTRO_FULL_NAME
-"""
-WARNING: This script will remove all partitions in resource disk and create
-a new one using the entire disk space.
-"""
-if __name__ == '__main__':
- print 'Unmount resource disk...'
- subprocess.call(['umount', '/dev/sdb1'])
- print 'Remove old partitions...'
- subprocess.call(['parted', '/dev/sdb', 'rm', '1'])
- subprocess.call(['parted', '/dev/sdb', 'rm', '2'])
- print 'Create new partition using the entire resource disk...'
- subprocess.call(['parted', '/dev/sdb','mkpart', 'primary', '0%', '100%'])
- subprocess.call(['mkfs.ext4', '/dev/sdb1'])
- subprocess.call(['mount', '/dev/sdb1', '/mnt'])
- print 'Resource disk(/dev/sdb1) is mounted at /mnt'
+class TestOSInfo(unittest.TestCase):
+ def test_curr_os_info(self):
+ self.assertNotEquals(None, DISTRO_NAME)
+ self.assertNotEquals(None, DISTRO_VERSION)
+ self.assertNotEquals(None, DISTRO_CODE_NAME)
+ self.assertNotEquals(None, DISTRO_FULL_NAME)
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_osutil.py b/tests/test_osutil.py
new file mode 100644
index 0000000..95b8e17
--- /dev/null
+++ b/tests/test_osutil.py
@@ -0,0 +1,174 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env as env
+from tests.tools import *
+import uuid
+import unittest
+import os
+import shutil
+import time
+import azurelinuxagent.utils.fileutil as fileutil
+import azurelinuxagent.utils.shellutil as shellutil
+import azurelinuxagent.conf as conf
+from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError
+import test
+
+class TestOSUtil(unittest.TestCase):
+ def test_current_distro(self):
+ self.assertNotEquals(None, OSUTIL)
+
+mount_list_sample="""\
+/dev/sda1 on / type ext4 (rw)
+proc on /proc type proc (rw)
+sysfs on /sys type sysfs (rw)
+devpts on /dev/pts type devpts (rw,gid=5,mode=620)
+tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
+none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
+/dev/sdb1 on /mnt/resource type ext4 (rw)
+"""
+
+class TestCurrOS(unittest.TestCase):
+#class TestCurrOS(object):
+ def test_get_paths(self):
+ self.assertNotEquals(None, OSUTIL.get_home())
+ self.assertNotEquals(None, OSUTIL.get_lib_dir())
+ self.assertNotEquals(None, OSUTIL.get_agent_pid_file_path())
+ self.assertNotEquals(None, OSUTIL.get_conf_file_path())
+ self.assertNotEquals(None, OSUTIL.get_dvd_mount_point())
+ self.assertNotEquals(None, OSUTIL.get_ovf_env_file_path_on_dvd())
+
+ @mock(fileutil, 'write_file', MockFunc())
+ @mock(fileutil, 'append_file', MockFunc())
+ @mock(fileutil, 'chmod', MockFunc())
+ @mock(fileutil, 'read_file', MockFunc(retval=''))
+ @mock(shellutil, 'run', MockFunc())
+ @mock(shellutil, 'run_get_output', MockFunc(retval=[0, '']))
+ def test_update_user_account(self):
+ OSUTIL.useradd('foo')
+ OSUTIL.chpasswd('foo', 'bar')
+ OSUTIL.del_account('foo')
+
+ @mock(fileutil, 'read_file', MockFunc(retval='root::::'))
+ @mock(fileutil, 'write_file', MockFunc())
+ def test_delete_root_password(self):
+ OSUTIL.del_root_password()
+ self.assertEquals('root:*LOCK*:14600::::::',
+ fileutil.write_file.args[1])
+
+ def test_cert_operation(self):
+ if os.path.isfile('/tmp/test.prv'):
+ os.remove('/tmp/test.prv')
+ shutil.copyfile(os.path.join(env.test_root, 'test.prv'),
+ '/tmp/test.prv')
+ if os.path.isfile('/tmp/test.crt'):
+ os.remove('/tmp/test.crt')
+ shutil.copyfile(os.path.join(env.test_root, 'test.crt'),
+ '/tmp/test.crt')
+ pub1 = OSUTIL.get_pubkey_from_prv('/tmp/test.prv')
+ pub2 = OSUTIL.get_pubkey_from_crt('/tmp/test.crt')
+ self.assertEquals(pub1, pub2)
+ thumbprint = OSUTIL.get_thumbprint_from_crt('/tmp/test.crt')
+ self.assertEquals('33B0ABCE4673538650971C10F7D7397E71561F35', thumbprint)
+
+ def test_selinux(self):
+ if not OSUTIL.is_selinux_system():
+ return
+ isrunning = OSUTIL.is_selinux_enforcing()
+ if not OSUTIL.is_selinux_enforcing():
+ OSUTIL.set_selinux_enforce(0)
+ self.assertEquals(False, OSUTIL.is_selinux_enforcing())
+ OSUTIL.set_selinux_enforce(1)
+ self.assertEquals(True, OSUTIL.is_selinux_enforcing())
+ if os.path.isfile('/tmp/abc'):
+ os.remove('/tmp/abc')
+ fileutil.write_file('/tmp/abc', '')
+ OSUTIL.set_selinux_context('/tmp/abc','unconfined_u:object_r:ssh_home_t:s')
+ OSUTIL.set_selinux_enforce(1 if isrunning else 0)
+
+ @mock(shellutil, 'run_get_output', MockFunc(retval=[0, '']))
+ @mock(fileutil, 'write_file', MockFunc())
+ def test_network_operation(self):
+ OSUTIL.start_network()
+ OSUTIL.allow_dhcp_broadcast()
+ OSUTIL.gen_transport_cert()
+ mac = OSUTIL.get_mac_addr()
+ self.assertNotEquals(None, mac)
+ OSUTIL.is_missing_default_route()
+ OSUTIL.set_route_for_dhcp_broadcast('api')
+ OSUTIL.remove_route_for_dhcp_broadcast('api')
+ OSUTIL.route_add('', '', '')
+ OSUTIL.get_dhcp_pid()
+ OSUTIL.set_hostname('api')
+ OSUTIL.publish_hostname('api')
+
+ @mock(OSUTIL, 'get_home', MockFunc(retval='/tmp/home'))
+ @mock(OSUTIL, 'get_pubkey_from_prv', MockFunc(retval=''))
+ @mock(fileutil, 'chowner', MockFunc())
+ def test_deploy_key(self):
+ if os.path.isdir('/tmp/home'):
+ shutil.rmtree('/tmp/home')
+ fileutil.write_file('/tmp/foo.prv', '')
+ OSUTIL.deploy_ssh_keypair("foo", ('$HOME/.ssh/id_rsa', 'foo'))
+ OSUTIL.deploy_ssh_pubkey("foo", ('$HOME/.ssh/authorized_keys', None,
+ 'ssh-rsa asdf'))
+ OSUTIL.deploy_ssh_pubkey("foo", ('$HOME/.ssh/authorized_keys', 'foo',
+ 'ssh-rsa asdf'))
+ self.assertRaises(OSUtilError, OSUTIL.deploy_ssh_pubkey, "foo",
+ ('$HOME/.ssh/authorized_keys', 'foo','hehe-rsa asdf'))
+ self.assertTrue(os.path.isfile('/tmp/home/.ssh/id_rsa'))
+ self.assertTrue(os.path.isfile('/tmp/home/.ssh/id_rsa.pub'))
+ self.assertTrue(os.path.isfile('/tmp/home/.ssh/authorized_keys'))
+
+ @mock(shellutil, 'run_get_output', MockFunc(retval=[0, '']))
+ @mock(OSUTIL, 'get_sshd_conf_file_path', MockFunc(retval='/tmp/sshd_config'))
+ def test_ssh_operation(self):
+ shellutil.run_get_output.retval=[0,
+ '2048 f1:fe:14:66:9d:46:9a:60:8b:8c:'
+ '80:43:39:1c:20:9e root@api (RSA)']
+ sshd_conf = OSUTIL.get_sshd_conf_file_path()
+ self.assertEquals('/tmp/sshd_config', sshd_conf)
+ if os.path.isfile(sshd_conf):
+ os.remove(sshd_conf)
+ shutil.copyfile(os.path.join(env.test_root, 'sshd_config'), sshd_conf)
+ OSUTIL.set_ssh_client_alive_interval()
+ OSUTIL.conf_sshd(True)
+ self.assertTrue(simple_file_grep(sshd_conf,
+ 'PasswordAuthentication no'))
+ self.assertTrue(simple_file_grep(sshd_conf,
+ 'ChallengeResponseAuthentication no'))
+ self.assertTrue(simple_file_grep(sshd_conf,
+ 'ClientAliveInterval 180'))
+
+ @mock(shellutil, 'run_get_output', MockFunc(retval=[0, '']))
+ @mock(OSUTIL, 'get_dvd_device', MockFunc(retval=[0, 'abc']))
+ @mock(OSUTIL, 'get_mount_point', MockFunc(retval='/tmp/cdrom'))
+ def test_mount(self):
+ OSUTIL.mount_dvd()
+ OSUTIL.umount_dvd()
+ mount_point = OSUTIL.get_mount_point(mount_list_sample, '/dev/sda')
+ self.assertNotEquals(None, mount_point)
+
+ def test_getdvd(self):
+ fileutil.write_file("/tmp/sr0", '')
+ OSUTIL.get_dvd_device(dev_dir='/tmp')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_ovfxml.py b/tests/test_ovfxml.py
new file mode 100644
index 0000000..7b6990b
--- /dev/null
+++ b/tests/test_ovfxml.py
@@ -0,0 +1,81 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import json
+from azurelinuxagent.future import text
+import azurelinuxagent.protocol.ovfenv as ovfenv
+
+ExtensionsConfigSample="""\
+<?xml version="1.0" encoding="utf-8"?>
+ <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <wa:ProvisioningSection>
+ <wa:Version>1.0</wa:Version>
+ <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
+ <HostName>HostName</HostName>
+ <UserName>UserName</UserName>
+ <UserPassword>UserPassword</UserPassword>
+ <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
+ <SSH>
+ <PublicKeys>
+ <PublicKey>
+ <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
+ <Path>$HOME/UserName/.ssh/authorized_keys</Path>
+ <Value>ssh-rsa AAAANOTAREALKEY== foo@bar.local</Value>
+ </PublicKey>
+ </PublicKeys>
+ <KeyPairs>
+ <KeyPair>
+ <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint>
+ <Path>$HOME/UserName/.ssh/id_rsa</Path>
+ </KeyPair>
+ </KeyPairs>
+ </SSH>
+ <CustomData>CustomData</CustomData>
+ </LinuxProvisioningConfigurationSet>
+ </wa:ProvisioningSection>
+ </Environment>
+"""
+
+class TestOvf(unittest.TestCase):
+ def test_ovf(self):
+ config = ovfenv.OvfEnv(ExtensionsConfigSample)
+ self.assertEquals("HostName", config.hostname)
+ self.assertEquals("UserName", config.username)
+ self.assertEquals("UserPassword", config.user_password)
+ self.assertEquals(False, config.disable_ssh_password_auth)
+ self.assertEquals("CustomData", config.customdata)
+ self.assertNotEquals(None, config.ssh_pubkeys)
+ self.assertEquals(1, len(config.ssh_pubkeys))
+ pubkey = config.ssh_pubkeys[0]
+ path, fingerprint, value = pubkey
+ self.assertEquals(path, "$HOME/UserName/.ssh/authorized_keys")
+ self.assertEquals(fingerprint, "EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62"),
+ self.assertEquals(value, "ssh-rsa AAAANOTAREALKEY== foo@bar.local")
+ self.assertNotEquals(None, config.ssh_keypairs)
+ self.assertEquals(1, len(config.ssh_keypairs))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_protocol.py b/tests/test_protocol.py
new file mode 100644
index 0000000..3eff197
--- /dev/null
+++ b/tests/test_protocol.py
@@ -0,0 +1,88 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+from .tools import *
+import uuid
+import unittest
+import os
+import time
+import json
+from azurelinuxagent.protocol.common import *
+
+extensionDataStr = """
+{
+ "vmAgent": {
+ "agentVersion": "2.4.1198.689",
+ "status": "Ready",
+ "message": "GuestAgent is running and accepting new configurations."
+ },
+ "extensionHandlers": [{
+ "handlerName": "Microsoft.Compute.CustomScript",
+ "handlerVersion": "1.0.0.0",
+ "status": "Ready",
+ "message": "Plugin enabled (name: Microsoft.Compute.CustomScript, version: 1.0.0.0).",
+ "extensionStatusList": [{
+ "name": "MyDomainJoinScript",
+ "configurationAppliedTime": "2014-08-12T19:20:18Z",
+ "operation": "CommandExecutionFinished",
+ "status": "Success",
+ "sequenceNumber": "0",
+ "substatusList": [{
+ "name": "StdOut",
+ "status": "Info",
+ "code": "0",
+ "message": "Joiningdomainfoo"
+ }]
+ }]
+ }
+
+ ]
+}
+"""
+
+class TestProtocolContract(unittest.TestCase):
+ def test_get_properties(self):
+ data = get_properties(VMInfo())
+ data = get_properties(Cert())
+ data = get_properties(ExtensionPackageList())
+ data = get_properties(InstanceMetadata())
+ data = get_properties(VMStatus())
+ data = get_properties(TelemetryEventList())
+ data = get_properties(Extension(name="hehe"))
+ self.assertTrue("name" in data)
+ self.assertTrue("properties" in data)
+ self.assertEquals(dict, type(data["properties"]))
+ self.assertTrue("versionUris" not in data)
+
+ def test_set_properties(self):
+ data = json.loads(extensionDataStr)
+ obj = VMStatus()
+ set_properties(obj, data)
+ self.assertNotEquals(None, obj.vmAgent)
+ self.assertEquals(VMAgentStatus, type(obj.vmAgent))
+ self.assertNotEquals(None, obj.vmAgent.status)
+ self.assertNotEquals(None, obj.extensionHandlers)
+ self.assertEquals(DataContractList, type(obj.extensionHandlers))
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/tests/test_protocolFactory.py b/tests/test_protocolFactory.py
new file mode 100644
index 0000000..9928b88
--- /dev/null
+++ b/tests/test_protocolFactory.py
@@ -0,0 +1,37 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import uuid
+import unittest
+import os
+import azurelinuxagent.protocol as protocol
+import azurelinuxagent.protocol.protocolFactory as protocolFactory
+
+class TestWireProtocolEndpoint(unittest.TestCase):
+ def test_get_available_protocols(self):
+ mockGetV1 = MockFunc(retval="Mock protocol")
+ protocols = protocolFactory.get_available_protocols([mockGetV1])
+ self.assertNotEquals(None, protocols)
+ self.assertNotEquals(0, len(protocols))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_redhat.py b/tests/test_redhat.py
new file mode 100644
index 0000000..d9ea4ec
--- /dev/null
+++ b/tests/test_redhat.py
@@ -0,0 +1,49 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import unittest
+from azurelinuxagent.distro.redhat.osutil import RedhatOSUtil
+
+test_pubkey="""\
+-----BEGIN PUBLIC KEY-----
+MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA2wo22vf1N8NWE+5lLfit
+T7uzkfwqdw0IAoHZ0l2BtP0ajy6f835HCR3w3zLWw5ut7Xvyo26x1OMOzjo5lqtM
+h8iyQwfHtWf6Cekxfkf+6Pca99bNuDgwRopOTOyoVgwDzJB0+slpn/sJjeGbhxJl
+ToT8tNPLrBmnnpaMZLMIANcPQtTRCQcV/ycv+/omKXFB+zULYkN8v22o5mysoCuQ
+fzXiJP3Mlnf+V2XMl1WAJylhOJif04K8j+G8oF5ECBIQiph4ZLQS1yTYlozPXU8k
+8vB6A5+UiOGxBnOQYnp42cS5d4qSQ8LORCRGXrCj4DCP+lvkUDLUHx2WN+1ivZkO
+fQIBIw==
+-----END PUBLIC KEY-----
+"""
+
+expected_ssh_rsa_pubkey="""\
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2wo22vf1N8NWE+5lLfitT7uzkfwqdw0IAoHZ0l2BtP0ajy6f835HCR3w3zLWw5ut7Xvyo26x1OMOzjo5lqtMh8iyQwfHtWf6Cekxfkf+6Pca99bNuDgwRopOTOyoVgwDzJB0+slpn/sJjeGbhxJlToT8tNPLrBmnnpaMZLMIANcPQtTRCQcV/ycv+/omKXFB+zULYkN8v22o5mysoCuQfzXiJP3Mlnf+V2XMl1WAJylhOJif04K8j+G8oF5ECBIQiph4ZLQS1yTYlozPXU8k8vB6A5+UiOGxBnOQYnp42cS5d4qSQ8LORCRGXrCj4DCP+lvkUDLUHx2WN+1ivZkOfQ==
+"""
+
+class TestRedhat(unittest.TestCase):
+ def test_RsaPublicKeyToSshRsa(self):
+ OSUtil = RedhatOSUtil()
+ ssh_rsa_pubkey = OSUtil.asn1_to_ssh_rsa(test_pubkey)
+ self.assertEquals(expected_ssh_rsa_pubkey, ssh_rsa_pubkey)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_resourcedisk.py b/tests/test_resourcedisk.py
new file mode 100644
index 0000000..de54fd3
--- /dev/null
+++ b/tests/test_resourcedisk.py
@@ -0,0 +1,63 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import unittest
+import azurelinuxagent.distro.default.resourceDisk as rdh
+import azurelinuxagent.logger as logger
+from azurelinuxagent.utils.osutil import OSUTIL
+
+#logger.LoggerInit("/dev/null", "/dev/stdout")
+
+gpt_output_sample="""
+Model: Msft Virtual Disk (scsi)
+Disk /dev/sda: 32.2GB
+Sector size (logical/physical): 512B/4096B
+Partition Table: gpt
+
+Number Start End Size Type File system Flags
+ 1 2097kB 29.4GB 29.4GB primary ext4 boot
+ 2 2097kB 29.4GB 29.4GB primary ext4 boot
+"""
+
+class TestResourceDisk(unittest.TestCase):
+
+ @mock(rdh.OSUTIL, 'device_for_ide_port', MockFunc(retval='foo'))
+ @mock(rdh.shellutil, 'run_get_output', MockFunc(retval=(0, gpt_output_sample)))
+ @mock(rdh.shellutil, 'run', MockFunc(retval=0))
+ def test_mountGPT(self):
+ handler = rdh.ResourceDiskHandler()
+ handler.mount_resource_disk('/tmp/foo', 'ext4')
+
+ @mock(rdh.OSUTIL, 'device_for_ide_port', MockFunc(retval='foo'))
+ @mock(rdh.shellutil, 'run_get_output', MockFunc(retval=(0, "")))
+ @mock(rdh.shellutil, 'run', MockFunc(retval=0))
+ def test_mountMBR(self):
+ handler = rdh.ResourceDiskHandler()
+ handler.mount_resource_disk('/tmp/foo', 'ext4')
+
+ @mock(rdh.shellutil, 'run', MockFunc(retval=0))
+ def test_createSwapSpace(self):
+ handler = rdh.ResourceDiskHandler()
+ handler.create_swap_space('/tmp/foo', 512)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_rest_util.py b/tests/test_rest_util.py
new file mode 100644
index 0000000..d07a1df
--- /dev/null
+++ b/tests/test_rest_util.py
@@ -0,0 +1,63 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import uuid
+import unittest
+import os
+import azurelinuxagent.utils.restutil as restutil
+from azurelinuxagent.future import text
+import test
+import socket
+import azurelinuxagent.logger as logger
+
+class MockResponse(object):
+ def __init__(self, status=restutil.httpclient.OK):
+ self.status = status
+
+ def getheaders(self):
+ pass
+
+class TestHttpOperations(unittest.TestCase):
+
+ def test_parse_url(self):
+ host, port, secure, rel_uri = restutil._parse_url("http://abc.def/ghi#hash?jkl=mn")
+ self.assertEquals("abc.def", host)
+ self.assertEquals("/ghi#hash?jkl=mn", rel_uri)
+
+ host, port, secure, rel_uri = restutil._parse_url("http://abc.def/")
+ self.assertEquals("abc.def", host)
+ self.assertEquals("/", rel_uri)
+ self.assertEquals(False, secure)
+
+ host, port, secure, rel_uri = restutil._parse_url("https://abc.def/ghi?jkl=mn")
+ self.assertEquals(True, secure)
+
+ host, port, secure, rel_uri = restutil._parse_url("http://abc.def:80/")
+ self.assertEquals("abc.def", host)
+
+ @mock(restutil.httpclient.HTTPConnection, 'request', MockFunc())
+ @mock(restutil.httpclient.HTTPConnection, 'getresponse', MockFunc(retval=MockResponse()))
+ def test_http_request(self):
+ restutil.http_get("https://httpbin.org/get")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_shared_config.py b/tests/test_shared_config.py
deleted file mode 100644
index 8252b3b..0000000
--- a/tests/test_shared_config.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright 2014 Microsoft Corporation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import os
-import re
-import unittest
-from env import waagent
-
-class MockDistro(object):
- def getInterfaceNameByMac(self, mac):
- pass
-
- def configIpV4(self, ifName, addr):
- pass
-
-class TestSharedConfig(unittest.TestCase):
-
- def test_reg(self):
- mac = "00:15:5D:34:00:08"
- output = Ifconfig_Out
- output = output.replace('\n', '')
- reg = r"(eth\d).*(HWaddr|ether) {0}".format(mac)
- match = re.search(reg, output, re.IGNORECASE)
- output = match.group(0)
- eths = re.findall(r"eth\d", output)
- self.assertNotEquals(0, len(eths))
-
- def test_parse_shared_config(self):
- conf = waagent.SharedConfig().Parse(SharedConfigText)
- self.assertNotEquals(None, conf)
- self.assertNotEquals(None, conf.RdmaMacAddress)
- self.assertNotEquals(None, conf.RdmaIPv4Address)
- self.assertEquals("00:15:5D:34:00:44", conf.RdmaMacAddress)
- return conf
-
- def test_config_rdma(self):
- waagent.MyDistro= MockDistro()
- waagent.LibDir="/tmp"
-
- test_dev = "/tmp/hvnd_rdma"
- test_dat_conf_files = ["/tmp/dat.conf"]
- if os.path.isfile("/tmp/rdmaconfiged"):
- os.remove("/tmp/rdmaconfiged")
- waagent.SetFileContents(test_dev, "")
- old = ("ofa-v2-ib0 u2.0 nonthreadsafe default libdaplofa.so.2 "
- "dapl.2.0 \"oldip 0\"")
- waagent.SetFileContents(test_dat_conf_files[0], old)
- conf = self.test_parse_shared_config()
- handler = waagent.RdmaHandler(conf.RdmaMacAddress, conf.RdmaIPv4Address,
- test_dev, test_dat_conf_files)
- handler.set_dat_conf()
- handler.set_rdma_dev()
-
- rdma_conf = waagent.GetFileContents(test_dev)
- self.assertNotEquals(None, rdma_conf)
- self.assertNotEquals(0, rdma_conf.count(conf.RdmaIPv4Address))
- self.assertNotEquals(0, rdma_conf.count(conf.RdmaMacAddress))
-
- dat_conf = waagent.GetFileContents(test_dat_conf_files[0])
- self.assertNotEquals(None, dat_conf)
- self.assertNotEquals(0, dat_conf.count(conf.RdmaIPv4Address))
- self.assertEquals(0, dat_conf.count("oldip"))
-
-SharedConfigText="""\
-<?xml version="1.0" encoding="utf-8"?>
-<SharedConfig version="1.0.0.0" goalStateIncarnation="1">
- <Deployment name="698f959e434c41cc9d72a2c67c044463" guid="{ba92e945-0302-4030-9710-257c03c07e22}" incarnation="0" isNonCancellableTopologyChangeEnabled="false">
- <Service name="test-rdms" guid="{00000000-0000-0000-0000-000000000000}" />
- <ServiceInstance name="698f959e434c41cc9d72a2c67c044463.0" guid="{6f157bcb-b6ac-4fdd-9789-2ca466220e17}" />
- </Deployment>
- <Incarnation number="1" instance="test-rdms" guid="{33d19bb6-f34d-4dfb-966c-2bade1714cc5}" />
- <Role guid="{dad0becc-5d1d-3c55-3285-0136e9933bbe}" name="test-rdms" settleTimeSeconds="0" />
- <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
- <Probes>
- <Probe name="D41D8CD98F00B204E9800998ECF8427E" />
- <Probe name="423A4BBA20CEBE79BA641B20A03ED6F9" />
- </Probes>
- </LoadBalancerSettings>
- <OutputEndpoints>
- <Endpoint name="test-rdms:openInternalEndpoint" type="SFS">
- <Target instance="test-rdms" endpoint="openInternalEndpoint" />
- </Endpoint>
- </OutputEndpoints>
- <Instances>
- <Instance id="test-rdms" address="100.74.58.20" primaryMacAddress="000D3A101ED4" rdmaMacAddress="00155D340044" rdmaIPv4Address="172.16.2.59">
- <FaultDomains randomId="0" updateId="0" updateCount="0" />
- <InputEndpoints>
- <Endpoint name="openInternalEndpoint" address="100.74.58.20" protocol="any" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
- <LocalPorts>
- <LocalPortSelfManaged />
- </LocalPorts>
- </Endpoint>
- <Endpoint name="SSH" address="100.74.58.20:22" protocol="tcp" hostName="test-rdmsContractContract" isPublic="true" loadBalancedPublicAddress="104.45.128.35:22" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
- <LocalPorts>
- <LocalPortRange from="22" to="22" />
- </LocalPorts>
- </Endpoint>
- <Endpoint name="test-rdms_A9_Infiniband" address="100.74.58.20" protocol="any" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
- <LocalPorts>
- <LocalPortSelfManaged />
- </LocalPorts>
- </Endpoint>
- </InputEndpoints>
- </Instance>
- </Instances>
-</SharedConfig>
-"""
-Ifconfig_Out="""\
-eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
-inet 100.74.52.8 netmask 255.255.255.0 broadcast 100.74.52.255
-inet6 fe80::20d:3aff:fe10:672f prefixlen 64 scopeid 0x20<link>
-ether 00:0d:3a:10:67:2f txqueuelen 1000 (Ethernet)
-RX packets 9911 bytes 4451278 (4.2 MiB)
-RX errors 0 dropped 0 overruns 0 frame 0
-TX packets 10505 bytes 1643251 (1.5 MiB)
-TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
-
-eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
-inet6 fe80::215:5dff:fe34:8 prefixlen 64 scopeid 0x20<link>
-ether 00:15:5d:34:00:08 txqueuelen 1000 (Ethernet)
-RX packets 16 bytes 672 (672.0 B)
-RX errors 0 dropped 0 overruns 0 frame 0
-TX packets 16 bytes 2544 (2.4 KiB)
-TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
-
-lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
-inet 127.0.0.1 netmask 255.0.0.0
-inet6 ::1 prefixlen 128 scopeid 0x10<host>
-loop txqueuelen 0 (Local Loopback)
-RX packets 0 bytes 0 (0.0 B)
-RX errors 0 dropped 0 overruns 0 frame 0
-TX packets 0 bytes 0 (0.0 B)
-"""
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_sharedconfig.py b/tests/test_sharedconfig.py
new file mode 100644
index 0000000..f480253
--- /dev/null
+++ b/tests/test_sharedconfig.py
@@ -0,0 +1,79 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import azurelinuxagent.protocol.v1 as v1
+
+shared_config_sample=u"""
+
+ <SharedConfig version="1.0.0.0" goalStateIncarnation="1">
+ <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
+ <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
+ <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
+ </Deployment>
+ <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
+ <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
+ <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
+ <Probes>
+ <Probe name="MachineRole" />
+ <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
+ <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
+ </Probes>
+ </LoadBalancerSettings>
+ <OutputEndpoints>
+ <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
+ <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
+ </Endpoint>
+ </OutputEndpoints>
+ <Instances>
+ <Instance id="MachineRole_IN_0" address="10.115.153.75">
+ <FaultDomains randomId="0" updateId="0" updateCount="0" />
+ <InputEndpoints>
+ <Endpoint name="a" address="10.115.153.75:80" protocol="http" isPublic="true" loadBalancedPublicAddress="70.37.106.197:80" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="80" to="80" />
+ </LocalPorts>
+ </Endpoint>
+ <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="3389" to="3389" />
+ </LocalPorts>
+ </Endpoint>
+ <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" address="10.115.153.75:20000" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.106.197:3389" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
+ <LocalPorts>
+ <LocalPortRange from="20000" to="20000" />
+ </LocalPorts>
+ </Endpoint>
+ </InputEndpoints>
+ </Instance>
+ </Instances>
+ </SharedConfig>
+"""
+
+class TestSharedConfig(unittest.TestCase):
+ def test_sharedconfig(self):
+ shared_conf = v1.SharedConfig(shared_config_sample)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_shell_util.py b/tests/test_shell_util.py
new file mode 100644
index 0000000..9862745
--- /dev/null
+++ b/tests/test_shell_util.py
@@ -0,0 +1,39 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import azurelinuxagent.utils.shellutil as shellutil
+import test
+
+class TestrunCmd(unittest.TestCase):
+ def test_run_get_output(self):
+ output = shellutil.run_get_output("ls /")
+ self.assertNotEquals(None, output)
+ self.assertEquals(0, output[0])
+
+ err = shellutil.run_get_output("ls /not-exists")
+ self.assertNotEquals(0, err[0])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_text_util.py b/tests/test_text_util.py
new file mode 100644
index 0000000..b29beff
--- /dev/null
+++ b/tests/test_text_util.py
@@ -0,0 +1,47 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+from tests.tools import *
+import uuid
+import unittest
+import os
+from azurelinuxagent.future import text
+import azurelinuxagent.utils.textutil as textutil
+
+class TestTextUtil(unittest.TestCase):
+ def test_get_password_hash(self):
+ password_hash = textutil.gen_password_hash("asdf", True, 6, 10)
+ self.assertNotEquals(None, password_hash)
+
+ def test_remove_bom(self):
+ #Test bom could be removed
+ data = text(b'\xef\xbb\xbfhehe', encoding='utf-8')
+ data = textutil.remove_bom(data)
+ self.assertNotEquals(0xbb, data[0])
+
+ #Test string without BOM is not affected
+ data = u"hehe"
+ data = textutil.remove_bom(data)
+ self.assertEquals(u"h", data[0])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_util.py b/tests/test_util.py
deleted file mode 100644
index 6e3ff27..0000000
--- a/tests/test_util.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright 2014 Microsoft Corporation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-from env import waagent
-import sys
-from tests.tools import *
-
-SampleInterfaceInfo="""\
-eth0 Link encap:Ethernet HWaddr ff:ff:ff:ff:ff:ff
- inet addr:10.94.20.249 Bcast:10.94.23.255 Mask:255.255.252.0
- inet6 addr: fe80::215:5dff:fe5f:bf03/64 Scope:Link
- UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
- RX packets:3789880 errors:0 dropped:0 overruns:0 frame:0
- TX packets:80973 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:1000
- RX bytes:388563383 (388.5 MB) TX bytes:21484571 (21.4 MB)
-
-eth1 Link encap:Ethernet HWaddr 00:00:00:00:00:00
- inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0
- inet6 addr: fe80::215:5dff:fe5f:bf08/64 Scope:Link
- UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
- RX packets:386614 errors:0 dropped:0 overruns:0 frame:0
- TX packets:201356 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:1000
- RX bytes:32507619 (32.5 MB) TX bytes:78342503 (78.3 MB)
-
-lo Link encap:Local Loopback
- inet addr:127.0.0.1 Mask:255.0.0.0
- inet6 addr: ::1/128 Scope:Host
- UP LOOPBACK RUNNING MTU:65536 Metric:1
- RX packets:2561 errors:0 dropped:0 overruns:0 frame:0
- TX packets:2561 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:0
-"""
-
-class TestUtil(unittest.TestCase):
-
- @Mockup(waagent, "RunGetOutput", MockFunc('', (0, SampleInterfaceInfo)))
- def test_getInterfaceNameByMac(self):
- distro = waagent.AbstractDistro()
- ifName = distro.getInterfaceNameByMac("ff:ff:ff:ff:ff:ff")
- self.assertEquals("eth0", ifName)
- ifName = distro.getInterfaceNameByMac("00:00:00:00:00:00")
- self.assertEquals("eth1", ifName)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_utils.py b/tests/test_utils.py
deleted file mode 100644
index e4c1c45..0000000
--- a/tests/test_utils.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright 2014 Microsoft Corporation
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-import unittest
-import tempfile
-import os
-from env import waagent
-
-sample_mount_list = """\
-/dev/sda1 on / type ext4 (rw)
-proc on /proc type proc (rw)
-sysfs on /sys type sysfs (rw)
-devpts on /dev/pts type devpts (rw,gid=5,mode=620)
-tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
-none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
-/dev/sdb1 on /mnt/resource type ext4 (rw)
-"""
-
-device_name="/dev/sdb"
-
-class TestWAAgentUtils(unittest.TestCase):
-
- def test_get_mount_point(self):
- normal = sample_mount_list
- mp = waagent.GetMountPoint(normal, device_name)
- self.assertEqual(mp, '/mnt/resource')
-
- null = None
- mp = waagent.GetMountPoint(null, device_name)
- self.assertEqual(mp, None)
-
- malformed = 'asdfasdfasdfa aasdf'
- mp = waagent.GetMountPoint(malformed, device_name)
- self.assertEqual(mp, None)
-
- def test_replace_in_file_found(self):
- tmpfilename = tempfile.mkstemp('', 'tmp', None, True)[1]
- try:
- tmpfile = open(tmpfilename, 'w')
- tmpfile.write('Replace Me')
- tmpfile.close()
-
- result = waagent.ReplaceStringInFile(tmpfilename, r'c. ', 'ced ')
-
- tmpfile = open(tmpfilename, 'r')
- newcontents = tmpfile.read();
- tmpfile.close()
-
- self.assertEqual('Replaced Me', str(newcontents))
- finally:
- os.remove(tmpfilename)
-
- def test_replace_in_file_not_found(self):
- tmpfilename = tempfile.mkstemp('', 'tmp', None, True)[1]
- try:
- tmpfile = open(tmpfilename, 'w')
- tmpfile.write('Replace Me')
- tmpfile.close()
-
- result = waagent.ReplaceStringInFile(tmpfilename, r'not here ', 'ced ')
-
- tmpfile = open(tmpfilename, 'r')
- newcontents = tmpfile.read();
- tmpfile.close()
-
- self.assertEqual('Replace Me', str(newcontents))
- finally:
- os.remove(tmpfilename)
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/test_v1.py b/tests/test_v1.py
new file mode 100644
index 0000000..02225af
--- /dev/null
+++ b/tests/test_v1.py
@@ -0,0 +1,173 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+from tests.tools import *
+import uuid
+import unittest
+import os
+import time
+from azurelinuxagent.utils.restutil import httpclient
+import azurelinuxagent.logger as logger
+import azurelinuxagent.protocol.v1 as v1
+from tests.test_version import VersionInfoSample
+from tests.test_goalstate import goal_state_sample
+from tests.test_hostingenv import hosting_env_sample
+from tests.test_sharedconfig import shared_config_sample
+from tests.test_certificates import certs_sample, transport_cert
+from tests.test_extensionsconfig import ext_conf_sample, manifest_sample
+
+def mock_fetch_uri(url, headers=None, chk_proxy=False):
+ content = None
+ if "versions" in url:
+ content = VersionInfoSample
+ elif "goalstate" in url:
+ content = goal_state_sample
+ elif "hostingenvuri" in url:
+ content = hosting_env_sample
+ elif "sharedconfiguri" in url:
+ content = shared_config_sample
+ elif "certificatesuri" in url:
+ content = certs_sample
+ elif "extensionsconfiguri" in url:
+ content = ext_conf_sample
+ elif "manifest.xml" in url:
+ content = manifest_sample
+ else:
+ raise Exception("Bad url {0}".format(url))
+ return content
+
+def mock_fetch_manifest(uris):
+ return manifest_sample
+
+def mock_fetch_cache(file_path):
+ content = None
+ if "Incarnation" in file_path:
+ content = 1
+ elif "GoalState" in file_path:
+ content = goal_state_sample
+ elif "HostingEnvironmentConfig" in file_path:
+ content = hosting_env_sample
+ elif "SharedConfig" in file_path:
+ content = shared_config_sample
+ elif "Certificates" in file_path:
+ content = certs_sample
+ elif "TransportCert" in file_path:
+ content = transport_cert
+ elif "ExtensionsConfig" in file_path:
+ content = ext_conf_sample
+ elif "manifest" in file_path:
+ content = manifest_sample
+ else:
+ raise Exception("Bad filepath {0}".format(file_path))
+ return content
+
+data_with_bom = b'\xef\xbb\xbfhehe'
+
+class MockResp(object):
+ def __init__(self, status=v1.httpclient.OK, data=None):
+ self.status = status
+ self.data = data
+
+ def read(self):
+ return self.data
+
+class TestWireClint(unittest.TestCase):
+
+ @mock(v1.restutil, 'http_get', MockFunc(retval=MockResp(data=data_with_bom)))
+ def test_fetch_uri_with_bom(self):
+ v1._fetch_uri("http://foo.bar", None)
+
+ @mock(v1, '_fetch_cache', mock_fetch_cache)
+ def test_get(self):
+ os.chdir('/tmp')
+ client = v1.WireClient("foobar")
+ goalState = client.get_goal_state()
+ self.assertNotEquals(None, goalState)
+ hostingEnv = client.get_hosting_env()
+ self.assertNotEquals(None, hostingEnv)
+ sharedConfig = client.get_shared_conf()
+ self.assertNotEquals(None, sharedConfig)
+ extensionsConfig = client.get_ext_conf()
+ self.assertNotEquals(None, extensionsConfig)
+
+
+ @mock(v1, '_fetch_cache', mock_fetch_cache)
+ def test_get_head_for_cert(self):
+ client = v1.WireClient("foobar")
+ header = client.get_header_for_cert()
+ self.assertNotEquals(None, header)
+
+ @mock(v1.WireClient, 'get_header_for_cert', MockFunc())
+ @mock(v1, '_fetch_uri', mock_fetch_uri)
+ @mock(v1.fileutil, 'write_file', MockFunc())
+ def test_update_goal_state(self):
+ client = v1.WireClient("foobar")
+ client.update_goal_state()
+ goal_state = client.get_goal_state()
+ self.assertNotEquals(None, goal_state)
+ hosting_env = client.get_hosting_env()
+ self.assertNotEquals(None, hosting_env)
+ shared_config = client.get_shared_conf()
+ self.assertNotEquals(None, shared_config)
+ ext_conf = client.get_ext_conf()
+ self.assertNotEquals(None, ext_conf)
+
+class TestStatusBlob(unittest.TestCase):
+ def testToJson(self):
+ vm_status = v1.VMStatus()
+ status_blob = v1.StatusBlob(vm_status)
+ self.assertNotEquals(None, status_blob.to_json())
+
+ @mock(v1.restutil, 'http_put', MockFunc(retval=MockResp(httpclient.CREATED)))
+ @mock(v1.restutil, 'http_head', MockFunc(retval=MockResp(httpclient.OK)))
+ def test_put_page_blob(self):
+ vm_status = v1.VMStatus()
+ status_blob = v1.StatusBlob(vm_status)
+ data = 'a' * 100
+ status_blob.put_page_blob("http://foo.bar", data)
+
+class TestConvert(unittest.TestCase):
+ def test_status(self):
+ vm_status = v1.VMStatus()
+ handler_status = v1.ExtensionHandlerStatus()
+ substatus = v1.ExtensionSubStatus()
+ ext_status = v1.ExtensionStatus()
+
+ vm_status.extensionHandlers.append(handler_status)
+ v1.vm_status_to_v1(vm_status)
+
+ handler_status.extensionStatusList.append(ext_status)
+ v1.vm_status_to_v1(vm_status)
+
+ ext_status.substatusList.append(substatus)
+ v1.vm_status_to_v1(vm_status)
+
+ def test_param(self):
+ param = v1.TelemetryEventParam()
+ event = v1.TelemetryEvent()
+ event.parameters.append(param)
+
+ v1.event_to_v1(event)
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/tests/test_version.py b/tests/test_version.py
new file mode 100644
index 0000000..72d9599
--- /dev/null
+++ b/tests/test_version.py
@@ -0,0 +1,53 @@
+# Copyright 2014 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+# Implements parts of RFC 2131, 1541, 1497 and
+# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
+# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx
+
+import tests.env
+import tests.tools as tools
+import uuid
+import unittest
+import os
+import json
+import azurelinuxagent.protocol.v1 as v1
+from azurelinuxagent.future import text
+
+VersionInfoSample=u"""\
+<?xml version="1.0" encoding="utf-8"?>
+<Versions>
+ <Preferred>
+ <Version>2012-11-30</Version>
+ </Preferred>
+ <Supported>
+ <Version>2010-12-15</Version>
+ <Version>2010-28-10</Version>
+ </Supported>
+</Versions>
+"""
+
+class TestVersionInfo(unittest.TestCase):
+ def test_version_info(self):
+ config = v1.VersionInfo(VersionInfoSample)
+ self.assertEquals("2012-11-30", config.get_preferred())
+ self.assertNotEquals(None, config.get_supported())
+ self.assertEquals(2, len(config.get_supported()))
+ self.assertEquals("2010-12-15", config.get_supported()[0])
+ self.assertEquals("2010-28-10", config.get_supported()[1])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_waagent.py b/tests/test_waagent.py
deleted file mode 100755
index 11510b6..0000000
--- a/tests/test_waagent.py
+++ /dev/null
@@ -1,386 +0,0 @@
-#!/usr/bin/python
-
-import os
-import sys
-import platform
-import socket
-import fcntl
-import struct
-import array
-import re
-import tempfile
-import unittest
-import random
-import string
-import threading
-from time import ctime, sleep
-import imp
-
-# waagent has no '.py' therefore create waagent module import manually.
-waagent=imp.load_source('waagent','../waagent')
-
-TestingVersion = "$CommitBranch:future$|$LastCommitDate:2013-04-16 15:52:17 -0700$|$LastCommitHash:7ad7c643b2adbac40b1ea4a5b6eb19f0fe971623$"
-
-
-class WaagentTestCases(unittest.TestCase):
- """
- Test cases for waagent
- """
- def setUp(self):
- """
- Check for root permissions.
- Check Distro is supported.
- Create a waagent.conf file.
- """
- waagent.LoggerInit('/var/log/waagent.log','/dev/console')
- if not self.AmIRoot():
- raise Exception('I need to run as root')
- DistroName=platform.dist()[0]
- self.failUnless(hasattr(waagent,DistroName+'Distro') == True,DistroName+' is not a supported linux distribution.')
- waagent.MyDistro=getattr(waagent,DistroName+'Distro')()
- # set up /etc/waagent.conf
- with open('/etc/waagent.conf','wb') as f:
- f.write(waagent.WaagentConf)
- f.close()
-
- def tearDown(self):
- """
- Remove test resources.
- This is a stub
- """
- pass
-
- def AmIRoot(self):
- """
- Check that our uid is root.
- """
- return 'root' in waagent.RunGetOutput('id')[1]
-
- def writetothelog(self,id):
- """
- Convienence function.
- Used by testTwoLogWritingThreads()
- Write 'start', sleep for id seconds and
- write 'end' to the logfile.
- """
- waagent.Log(str(id)+' start ')
- sleep(id)
- waagent.Log(str(id)+' end ')
-
- def noop(self,arg2):
- """
- Set a method to noop() to prevent its operation.
- """
- pass
-
-##############TESTCASES##########################
-
-###############Astract Distro - Concrete Distro Tests##############
-
- def testMyDistroMemberVariables(self):
- """
- Ensure that required Distro properties are not None.
- """
- assert waagent.MyDistro.agent_service_name is not None , 'MyDistro.agent_service_name must not be None'
- assert waagent.MyDistro.selinux is not None , 'MyDistro.selinux must not be None'
- assert waagent.MyDistro.ssh_service_name is not None , 'MyDistro.ssh_service_name must not be None'
- assert waagent.MyDistro.ssh_config_file is not None , 'MyDistro.ssh_config_file must not be None'
- assert waagent.MyDistro.hostname_file_path is not None , 'MyDistro.hostname_file_path must not be None'
- assert waagent.MyDistro.dhcp_client_name is not None , 'MyDistro.dhcp_client_name must not be None'
- assert waagent.MyDistro.requiredDeps is not None , 'MyDistro.requiredDeps must not be None'
- assert waagent.MyDistro.init_script_file is not None , 'MyDistro.init_script_file must not be None'
- assert waagent.MyDistro.agent_package_name is not None , 'MyDistro.agent_package_name must not be None'
- assert waagent.MyDistro.fileBlackList is not None , 'MyDistro.fileBlackList must not be None'
- assert waagent.MyDistro.agent_files_to_uninstall is not None , 'MyDistro.agent_files_to_uninstall must not be None'
- assert waagent.MyDistro.grubKernelBootOptionsFile is not None , 'MyDistro.grubKernelBootOptionsFile must not be None'
- assert waagent.MyDistro.grubKernelBootOptionsLine is not None , 'MyDistro.grubKernelBootOptionsLine must not be None'
-
-
- def testMyDistro_restartSshService(self):
- """
- Test MyDistro.restartSshService()
- """
- cmd = 'service '+ waagent.MyDistro.ssh_service_name + ' status'
- sshpid=string.rsplit(waagent.RunGetOutput(cmd)[1],' ',1)
- waagent.MyDistro.restartSshService()
- assert sshpid is not string.rsplit(waagent.RunGetOutput(cmd)[1],' ',1),'ssh server pid is unchanged.'
-
-# def testMyDistro_checkPackageInstalled(self):
-# """MyDistro can check if WaLinuxAgent package is installed"""
-# assert waagent.MyDistro.checkPackageInstalled(waagent.MyDistro.agent_package_name) != 0, waagent.MyDistro.agent_package_name+' is Not Installed.'
-
-# def testMyDistro_checkPackageUpdateable(self):
-# """MyDistro can check if WaLinuxAgent package is updateable to new version."""
-# assert waagent.MyDistro.checkPackageUpdateable(waagent.MyDistro.agent_package_name) == 0 , waagent.MyDistro.agent_package_name+' is not updateable.'
-
-
- def testMyDistro_isSelinuxSystem(self):
- """
- MyDistro can perform Selinux operations.
- Test MyDistro.isSelinuxSystem, if true then also test:
- MyDistro.isSelinuxRunning
- MyDistro.setSelinuxEnforce
- MyDistro.setSelinuxContext
- """
- selinux=waagent.MyDistro.isSelinuxSystem()
- if selinux:
- assert waagent.MyDistro.isSelinuxRunning(), 'Selinux not running.'
- assert waagent.MyDistro.setSelinuxEnforce(0), 'Unable to call setenforce(0).'
- assert waagent.MyDistro.setSelinuxContext('./test_waagent.py','unconfined_u:object_r:ssh_home_t:s0'), 'Unable to set Selinux context.'
- assert waagent.MyDistro.setSelinuxEnforce(0), 'Unable to call setenforce(1).'
- else:
- print 'Selinux not installed. - skipping Selinux tests'
-
- def testMyDistro_load_unload_ata_piix(self):
- """
- Attempt to insert and remove ata_piix.ko
- by calling MyDistro.load_ata_piix
- and MyDistro.unload_ata_piix.
- """
- assert waagent.MyDistro.load_ata_piix() == 0, 'Unable to load ata_piix.ko.'
- assert waagent.MyDistro.unload_ata_piix() == 0, 'Unable to unload ata_piix.ko.'
-
- def testMyDistro_publishHostname(self):
- """
- Test MyDistro.publishHostname
- on success, the distro dependent config
- contains the hostname, but currently
- this test suceeds if the config files were written
- without error.
- """
- assert waagent.MyDistro.publishHostname('LENG') == 0, 'Error setting hostname to LENG.'
-
-# def testMyDistro_registerAgentService(self):
-# assert waagent.MyDistro.registerAgentService() == 0, 'Unable to register agent as service.'
-
- def testMyDistro_setHostname(self):
- """
- Test MyDistro.setHostname.
- Successfull if hostname is changed.
- Reset hostname when finished.
- """
- code,oldname = waagent.RunGetOutput('hostname')
- waagent.MyDistro.setHostname('HOSTNAMETEST')
- code,newname = waagent.RunGetOutput('hostname')
- assert 'HOSTNAMETEST' == newname.strip(), 'Unable to set hostname.'
- waagent.MyDistro.setHostname(oldname)
-
- def testMyDistro_checkDependencies(self):
- """
- Test MyDistro.checkDependencies succeeds
- """
- assert waagent.MyDistro.checkDependencies() == 0 , 'Dependency Check failed.'
-
- def testMyDistro_startAgentService(self):
- """
- Test MyDistro.startAgentService.
- """
- assert waagent.MyDistro.startAgentService() == 0, 'Unable to start ' + waagent.MyDistro.agent_service_name
-
- def testMyDistro_stopAgentService(self):
- """
- Test MyDistro.stopAgentService.
- """
- assert waagent.MyDistro.stopAgentService() == 0, 'Unable to stop ' + waagent.MyDistro.agent_service_name
-
- def testMyDistro_deleteRootPassword(self):
- """
- Test MyDistro.deleteRootPassword.
- Restore the shadow file to previous state when finished.
- """
- #copy the shadow
- waagent.Run('cp /etc/shadow /etc/shadow.keep')
- waagent.MyDistro.deleteRootPassword()
- assert waagent.Run('grep LOCK /etc/shadow') == 0 , 'Error removing root password.'
- # put shadow back
- waagent.Run('mv /etc/shadow.keep /etc/shadow')
-
- def testFindIn_AppendTo_RemoveFrom_LinuxKernelCmdline(self):
- """
- Test LinuxKernelCmdline operations.
- Search for 'splish=splash' in the kernel boot options, expect fail.
- Add 'splish=splash'. Search for splish=splash expect success.
- Remove 'splish=splash', confirm splish=splash absent
- """
- m=waagent.FindInLinuxKernelCmdline('splish=splash')
- assert not m, '"splish=splash" was found before i put it there!!! edit it to remove "splish=splash" please.'
-
- waagent.AppendToLinuxKernelCmdline('splish=splash')
- m=waagent.FindInLinuxKernelCmdline('splish=splash')
- assert m, 'AppendToLinuxKernelCmdline failed, "splish=splash" still not found.'
-
- waagent.RemoveFromLinuxKernelCmdline('splish=splash')
- m=waagent.FindInLinuxKernelCmdline('splish=splash')
- assert not m, 'RemoveFromLinuxKernelCmdline failed, "splish=splash" still found.'
-
-###############Generic waagent tests##############
-
- def testLogFile(self):
- """
- Write a random number with waagent.Log() and read it back.
- """
- rnds=str(random.random())
- waagent.Log('testLogFile: '+rnds)
- found = rnds in (open('/var/log/waagent.log','rb').read())
- assert found,'Unable to find '+rnds+' in /var/log/waagent.log'
-
- def testFindReplaceStringInFile(self):
- """
- Test file/string operations using
- string literals and regular expressions.
- Tests:
- FindStringInFile
- ReplaceStringInFile
-
- """
- fn='/tmp/junk'
- if os.path.exists(fn):
- os.remove(fn)
- sp='splish splash'
- yb='yabba dabba do'
- open(fn,'wb').write(sp+' I was taking a bath.')
- m=waagent.FindStringInFile(fn,sp)
- assert m is not None,'waagent.FindStringInFile() Failed: '+sp+' not found in ' + fn + '.'
- src=r'^(.*)('+sp+')(.*)$'
- rpl=r'\1 '+sp+'\2 '+yb+' \3'
- waagent.ReplaceStringInFile(fn,src,rpl)
- m=waagent.FindStringInFile(fn,yb)
- assert m is not None,'waagent.ReplaceStringInFile() Failed: '+yb+' not found in ' + fn + '.'
-
- def testGetFirstActiveNetworkInterfaceNonLoopback(self):
- """
- Test GetFirstActiveNetworkInterfaceNonLoopback.
- Fail if iface is 'lo'
- """
- addr='null'
- iface=waagent.GetFirstActiveNetworkInterfaceNonLoopback()[0]
- addr=waagent.GetFirstActiveNetworkInterfaceNonLoopback()[1]
- assert len(iface)>1,'Interface name too short'
- assert iface is not 'lo','Loopback Interface was returned'
- print 'iface=' + iface + ' addr=' + addr
-
- def testTwoLogWritingThreads(self):
- """
- Test that two threads writing to the same log
- function do not block or scramble messages.
- TODO - there is no check for success !!!
- """
- for j in range(5):
- t1=threading.Thread(target=self.writetothelog,args=(4,))
- t2=threading.Thread(target=self.writetothelog,args=(2,))
- t1.start()
- t2.start()
- t1.join()
- t2.join()
-
- def testCertificatesParse(self):
- """
- TODO - need cert xml from test...
- """
- pass
-
- def testSharedConfigParse(self):
-
- """
- Test SharedConfig().Parse returns without error.
- """
- assert waagent.SharedConfig().Parse(SHAREDCONFIG), 'Error parsing SharedConfig.xml'
-
-
- def testOvfEnvParse(self):
- """
- Test OvfEnv().Parse returns without error.
- """
- assert waagent.OvfEnv().Parse(OVFXML) is not None , 'Failed to Parse ovfxml'
-
- def testOvfEnvProcess(self):
- """
- We expect the /var/lib/waagent/Certificates.p7m file exists.
- Test ovfenv.Process() return other than None.
- """
- assert os.path.exists('/var/lib/waagent/Certificates.p7m') , 'We expect the /var/lib/waagent/Certificates.p7m file exists.'
- waagent.WaAgent = waagent.Agent()
- ovfenv=waagent.OvfEnv().Parse(OVFXML)
- waagent.WaAgent.EnvMonitor = waagent.EnvMonitor()
- assert ovfenv.Process() is None , 'Failed to Process ovfxml'
- waagent.Run("userdel -f -r myUserName")
-
- def testAgentProvision(self):
- """
- TODO - Test Provision in non-fabric environment
- """
- waagent.verbose = True
- waagent.WaAgent = waagent.Agent()
- waagent.WaAgent.EnvMonitor = waagent.EnvMonitor()
- waagent.Config = waagent.ConfigurationProvider()
- # we cant report our role unless we have one.
- waagent.WaAgent.ReportRoleProperties=self.noop
- err=waagent.WaAgent.Provision()
- assert err == None, 'Provision Failed error ' + str(err)
-
-
-########################################
-
-
-
-OVFXML="""<?xml version="1.0" encoding="utf-8"?>
-<Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-
- <wa:ProvisioningSection><wa:Version>1.0</wa:Version><LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType><HostName>egub13-vm</HostName><UserName>myUserName</UserName><UserPassword>mypassword</UserPassword><DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication><SSH><PublicKeys><PublicKey><Fingerprint>2D97B25D49B98ECC90BF1600D66D68799CFB361E</Fingerprint><Path>/home/myUserName/.ssh/authorized_keys</Path></PublicKey></PublicKeys></SSH></LinuxProvisioningConfigurationSet></wa:ProvisioningSection>
-
- <wa:PlatformSettingsSection><wa:Version>1.0</wa:Version><PlatformSettings xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><KmsServerHostname>kms.core.windows.net</KmsServerHostname></PlatformSettings></wa:PlatformSettingsSection>
-</Environment>
-"""
-
-SHAREDCONFIG="""
-<SharedConfig version="1.0.0.0" goalStateIncarnation="1">
- <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2">
- <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" />
- <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" />
- </Deployment>
- <Incarnation number="1" instance="MachineRole_IN_0" guid="{a0faca35-52e5-4ec7-8fd1-63d2bc107d9b}" />
- <Role guid="{73d95f1c-6472-e58e-7a1a-523554e11d46}" name="MachineRole" settleTimeSeconds="10" />
- <LoadBalancerSettings timeoutSeconds="0" waitLoadBalancerProbeCount="8">
- <Probes>
- <Probe name="MachineRole" />
- <Probe name="55B17C5E41A1E1E8FA991CF80FAC8E55" />
- <Probe name="3EA4DBC19418F0A766A4C19D431FA45F" />
- </Probes>
- </LoadBalancerSettings>
- <OutputEndpoints>
- <Endpoint name="MachineRole:Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" type="SFS">
- <Target instance="MachineRole_IN_0" endpoint="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" />
- </Endpoint>
- </OutputEndpoints>
- <Instances>
- <Instance id="MachineRole_IN_0" address="10.115.153.75">
- <FaultDomains randomId="0" updateId="0" updateCount="0" />
- <InputEndpoints>
- <Endpoint name="a" address="10.115.153.75:80" protocol="http" isPublic="true" loadBalancedPublicAddress="70.37.106.197:80" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
- <LocalPorts>
- <LocalPortRange from="80" to="80" />
- </LocalPorts>
- </Endpoint>
- <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteAccess.Rdp" address="10.115.153.75:3389" protocol="tcp" isPublic="false" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
- <LocalPorts>
- <LocalPortRange from="3389" to="3389" />
- </LocalPorts>
- </Endpoint>
- <Endpoint name="Microsoft.WindowsAzure.Plugins.RemoteForwarder.RdpInput" address="10.115.153.75:20000" protocol="tcp" isPublic="true" loadBalancedPublicAddress="70.37.106.197:3389" enableDirectServerReturn="false" isDirectAddress="false" disableStealthMode="false">
- <LocalPorts>
- <LocalPortRange from="20000" to="20000" />
- </LocalPorts>
- </Endpoint>
- </InputEndpoints>
- </Instance>
- </Instances>
-</SharedConfig>
-"""
-
-
-if __name__ == '__main__':
- s=unittest.TestLoader().loadTestsFromTestCase(WaagentTestCases)
- unittest.TextTestRunner(verbosity=2).run(s)
- # import cProfile
- # cProfile.run('unittest.TextTestRunner(verbosity=2).run(s)','profile.out')
-
diff --git a/tests/tools.py b/tests/tools.py
index 27e16d3..392f395 100644
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -21,6 +21,8 @@
import os
import sys
+from functools import wraps
+from azurelinuxagent.utils.osutil import OSUTIL
parent = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(parent)
@@ -30,9 +32,10 @@ def simple_file_grep(file_path, search_str):
if search_str in line:
return line
-def Mockup(target, name, mock):
- def Decorator(func):
- def Wrapper(*args, **kwargs):
+def mock(target, name, mock):
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
origin = getattr(target, name)
setattr(target, name, mock)
try:
@@ -42,10 +45,10 @@ def Mockup(target, name, mock):
finally:
setattr(target, name, origin)
return result
- return Wrapper
- return Decorator
+ return wrapper
+ return decorator
-class MockFunc():
+class MockFunc(object):
def __init__(self, name='', retval=None):
self.name = name
self.retval = retval
@@ -56,6 +59,7 @@ class MockFunc():
self.kwargs = kwargs
return self.retval
-def Dummy():
- pass
+#Mock osutil so that the test of other part will be os unrelated
+OSUTIL.get_lib_dir = MockFunc(retval='/tmp')
+OSUTIL.get_ext_log_dir = MockFunc(retval='/tmp/log')