pax_global_header 0000666 0000000 0000000 00000000064 12651710031 0014506 g ustar 00root root 0000000 0000000 52 comment=b05e389088d5a6d741dd4dfd7d05624bad09e297
WALinuxAgent-2.1.3/ 0000775 0000000 0000000 00000000000 12651710031 0014017 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/.gitattributes 0000664 0000000 0000000 00000004726 12651710031 0016723 0 ustar 00root root 0000000 0000000 ###############################################################################
# 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
WALinuxAgent-2.1.3/.gitignore 0000664 0000000 0000000 00000001372 12651710031 0016012 0 ustar 00root root 0000000 0000000 # 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
*.pyproj
*.sln
*.suo
waagentc
bin/waagent2.0c
# rope project
.ropeproject/
WALinuxAgent-2.1.3/.travis.yml 0000664 0000000 0000000 00000000366 12651710031 0016135 0 ustar 00root root 0000000 0000000 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
WALinuxAgent-2.1.3/Changelog 0000664 0000000 0000000 00000001245 12651710031 0015633 0 ustar 00root root 0000000 0000000 WALinuxAgent Changelog
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
29 Jan 2016, WALinuxAgent 2.1.3
. Fixed endpoint probing for Azure Stack
. Multiple fixes for extension handling
07 Dec 2015, WALinuxAgent 2.1.2
. Multiple fixes for extension handling and provisioning
07 Aug 2015, WALinuxAgent 2.1.1
. Support python3
. Fixed bugs for metadata protocol
. Fixed a few pylint warnings
. Enabled travis-ci
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
01 Jul 2015, WALinuxAgent 2.1.0
. Divide waagent into different modules
WALinuxAgent-2.1.3/LICENSE-2.0.txt 0000664 0000000 0000000 00000026136 12651710031 0016147 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
WALinuxAgent-2.1.3/MANIFEST 0000664 0000000 0000000 00000000570 12651710031 0015152 0 ustar 00root root 0000000 0000000 # 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
WALinuxAgent-2.1.3/MANIFEST.in 0000664 0000000 0000000 00000000114 12651710031 0015551 0 ustar 00root root 0000000 0000000 recursive-include bin *
recursive-include init *
recursive-include config *
WALinuxAgent-2.1.3/NOTICE 0000664 0000000 0000000 00000000241 12651710031 0014720 0 ustar 00root root 0000000 0000000 Microsoft Azure Linux Agent
Copyright 2012 Microsoft Corporation
This product includes software developed at
Microsoft Corporation (http://www.microsoft.com/).
WALinuxAgent-2.1.3/README.md 0000664 0000000 0000000 00000045303 12651710031 0015303 0 ustar 00root root 0000000 0000000 ## Microsoft Azure Linux Agent README
### INTRODUCTION
The Microsoft Azure Linux Agent (waagent) manages Linux & FreeBSD provisioning,
and VM interaction with the Azure Fabric Controller. It provides the following
functionality for Linux and FreeBSD IaaS deployments:
* Image Provisioning
- Creation of a user account
- Configuring SSH authentication types
- Deployment of SSH public keys and key pairs
- Setting the host name
- Publishing the host name to the platform DNS
- Reporting SSH host key fingerprint to the platform
- Resource Disk Management
- Formatting and mounting the resource disk
- Configuring swap space
* Networking
- Manages routes to improve compatibility with platform DHCP servers
- Ensures the stability of the network interface name
* Kernel
- Configure virtual NUMA (disable for kernel <2.6.37)
- Consume Hyper-V entropy for /dev/random
- Configure SCSI timeouts for the root device (which could be remote)
* Diagnostics
- Console redirection to the serial port
* SCVMM Deployments
- Detect and bootstrap the VMM agent for Linux when running in a System
Center Virtual Machine Manager 2012R2 environment
* VM Extension
- Inject component authored by Microsoft and Partners into Linux VM (IaaS)
to enable software and configuration automation
- VM Extension reference implementation on https://github.com/Azure/azure-linux-extensions
### COMMUNICATION
The information flow from the platform to the agent occurs via two channels:
* A boot-time attached DVD for IaaS deployments.
This DVD includes an OVF-compliant configuration file that includes all
provisioning information other than the actual SSH keypairs.
* A TCP endpoint exposing a REST API used to obtain deployment and topology
configuration.
### REQUIREMENTS
The following systems have been tested and are known to work with the Azure
Linux Agent. Please note that this list may differ from the official list
of supported systems on the Microsoft Azure Platform as described here:
http://support.microsoft.com/kb/2805216
Supported Linux Distributions:
* CoreOS
* CentOS 6.2+
* Debian 7.0+
* Ubuntu 12.04+
* openSUSE 12.3+
* SLES 11 SP2+
* Oracle Linux 6.4+
Other Supported Systems:
* FreeBSD 10+ (Azure Linux Agent v2.0.10+)
Waagent depends on some system packages in order to function properly:
* Python 2.6+
* OpenSSL 1.0+
* OpenSSH 5.3+
* Filesystem utilities: sfdisk, fdisk, mkfs, parted
* Password tools: chpasswd, sudo
* Text processing tools: sed, grep
* Network tools: ip-route
### INSTALLATION
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).
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
You can view more installation options by running:
#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 by running:
#sudo python setup.py install -force
Restart waagent service,for most of linux distributions:
#sudo service waagent restart
For Ubuntu, use:
#sudo service walinuxagent restart
For CoreOS, use:
#sudo systemctl restart waagent
The agent's log file is kept at /var/log/waagent.log.
### COMMAND LINE OPTIONS
Flags:
-verbose: Increase verbosity of specified command
-force: Skip interactive confirmation for some commands
Commands:
-help: Lists the supported commands and flags.
-deprovision: Attempt to clean the system and make it suitable for
re-provisioning. Deletes the following:
* All SSH host keys
(if Provisioning.RegenerateSshHostKeyPair is 'y' in the configuration
file)
* Nameserver configuration in /etc/resolv.conf
* Root password from /etc/shadow
(if Provisioning.DeleteRootPassword is 'y' in the configuration file)
* Cached DHCP client leases.
* Resets host name to localhost.localdomain.
WARNING! Deprovision does not guarantee that the image is cleared of all
sensitive information and suitable for redistribution.
-deprovision+user: Performs everything under deprovision (above) and also
deletes the last provisioned user account and associated data.
-version: Displays the version of waagent
-serialconsole: Configures GRUB to mark ttyS0 (the first serial port) as
the boot console. This ensures that kernel bootup logs are sent to the
serial port and made available for debugging.
-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
A configuration file (/etc/waagent.conf) controls the actions of
waagent. A sample configuration file is shown below:
```
Role.StateConsumer=None
Role.ConfigurationConsumer=None
Role.TopologyConsumer=None
Provisioning.Enabled=y
Provisioning.DeleteRootPassword=n
Provisioning.RegenerateSshHostKeyPair=y
Provisioning.SshHostKeyPairType=rsa
Provisioning.MonitorHostName=y
Provisioning.DecodeCustomData=n
Provisioning.ExecuteCustomData=n
Provisioning.PasswordCryptId=6
Provisioning.PasswordCryptSaltLength=10
ResourceDisk.Format=y
ResourceDisk.Filesystem=ext4
ResourceDisk.MountPoint=/mnt/resource
ResourceDisk.EnableSwap=n
ResourceDisk.SwapSizeMB=0
LBProbeResponder=y
Logs.Verbose=n
OS.RootDeviceScsiTimeout=300
OS.OpensslPath=None
HttpProxy.Host=None
HttpProxy.Port=None
```
The various configuration options are described in detail below. Configuration
options are of three types : Boolean, String or Integer. The Boolean
configuration options can be specified as "y" or "n". The special keyword "None"
may be used for some string type configuration entries as detailed below.
Configuration File Options:
Role.StateConsumer:
Type: String Default: None
If a path to an executable program is specified, it is invoked when waagent has
provisioned the image and the "Ready" state is about to be reported to the
Fabric. The argument specified to the program will be "Ready". The agent will
not wait for the program to return before continuing.
Role.ConfigurationConsumer:
Type: String Default: None
If a path to an executable program is specified, the program is invoked when the
Fabric indicates that a configuration file is available for the VM. The path to
the XML configuration file is provided as an argument to the executable. This
may be invoked multiple times whenever the configuration file changes. A sample
file is provided in the Appendix. Please note that the XML schema used in this
file may change in the future. The current path of this file is
/var/lib/waagent/HostingEnvironmentConfig.xml.
Role.TopologyConsumer:
Type: String Default: None
If a path to an executable program is specified, the program is invoked when the
Fabric indicates that a new network topology layout is available for the VM. The
path to the XML configuration file is provided as an argument to the executable.
This may be invoked multiple times whenever the network topology changes (due to
service healing for example). A sample file is provided in the Appendix. Please
note that the XML schema used in this file may change in the future. The
current location of this file is /var/lib/waagent/SharedConfig.xml.
Provisioning.Enabled:
Type: Boolean Default: y
This allows the user to enable or disable the provisioning functionality in the
agent. Valid values are "y" or "n". If provisioning is disabled, SSH host and
user keys in the image are preserved and any configuration specified in the
Azure provisioning API is ignored.
Provisioning.DeleteRootPassword:
Type: Boolean Default: n
If set, the root password in the /etc/shadow file is erased during the
provisioning process.
Provisioning.RegenerateSshHostKeyPair:
Type: Boolean Default: y
If set, all SSH host key pairs (ecdsa, dsa and rsa) are deleted during the
provisioning process from /etc/ssh/. And a single fresh key pair is generated.
The encryption type for the fresh key pair is configurable by the
Provisioning.SshHostKeyPairType entry. Please note that some distributions will
re-create SSH key pairs for any missing encryption types when the SSH daemon is
restarted (for example, upon a reboot).
Provisioning.SshHostKeyPairType:
Type: String Default: rsa
This can be set to an encryption algorithm type that is supported by the SSH
daemon on the VM. The typically supported values are "rsa", "dsa" and "ecdsa".
Note that "putty.exe" on Windows does not support "ecdsa". So, if you intend to
use putty.exe on Windows to connect to a Linux deployment, please use "rsa" or
"dsa".
Provisioning.MonitorHostName:
Type: Boolean Default: y
If set, waagent will monitor the Linux VM for hostname changes (as returned by
the "hostname" command) and automatically update the networking configuration in
the image to reflect the change. In order to push the name change to the DNS
servers, networking will be restarted in the VM. This will result in brief loss
of Internet connectivity.
Provisioning.DecodeCustomData:
Type: Boolean Default: n
If set, waagent will decode CustomData from Base64.
Provisioning.ExecuteCustomData:
Type: Boolean Default: n
If set, waagent will execute CustomData after provisioning.
Provisioning.PasswordCryptId:
Type:String Default:6
Algorithm used by crypt when generating password hash.
1 - MD5
2a - Blowfish
5 - SHA-256
6 - SHA-512
Provisioning.PasswordCryptSaltLength
Type:String Default:10
Length of random salt used when generating password hash.
ResourceDisk.Format:
Type: Boolean Default: y
If set, the resource disk provided by the platform will be formatted and mounted
by waagent if the filesystem type requested by the user in
"ResourceDisk.Filesystem" is anything other than "ntfs". A single partition of
type Linux (83) will be made available on the disk. Note that this partition
will not be formatted if it can be successfully mounted.
ResourceDisk.Filesystem:
Type: String Default: ext4
This specifies the filesystem type for the resource disk. Supported values vary
by Linux distribution. If the string is X, then mkfs.X should be present on the
Linux image. SLES 11 images should typically use 'ext3'. FreeBSD images should
use 'ufs2' here.
ResourceDisk.MountPoint:
Type: String Default: /mnt/resource
This specifies the path at which the resource disk is mounted.
ResourceDisk.EnableSwap:
Type: Boolean Default: n
If set, a swap file (/swapfile) is created on the resource disk and added to the
system swap space.
ResourceDisk.SwapSizeMB:
Type: Integer Default: 0
The size of the swap file in megabytes.
LBProbeResponder:
Type: Boolean Default: y
If set, waagent will respond to load balancer probes from the platform (if
present).
Logs.Verbose:
Type: Boolean Default: n
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
This configures the SCSI timeout in seconds on the root device. If not set, the
system defaults are used.
OS.OpensslPath:
Type: String Default: None
This can be used to specify an alternate path for the openssl binary to use for
cryptographic operations.
HttpProxy.Host=None
HttpProxy.Port=None
Type: String Default: None
If set, agent will use proxy server to access internet
### APPENDIX
Sample Role Configuration File:
```
```
Sample Role Topology File:
```
```
WALinuxAgent-2.1.3/__main__.py 0000664 0000000 0000000 00000001252 12651710031 0016111 0 ustar 00root root 0000000 0000000 # 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()
WALinuxAgent-2.1.3/azurelinuxagent/ 0000775 0000000 0000000 00000000000 12651710031 0017244 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/__init__.py 0000664 0000000 0000000 00000001166 12651710031 0021361 0 ustar 00root root 0000000 0000000 # 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/agent.py 0000664 0000000 0000000 00000010330 12651710031 0020711 0 ustar 00root root 0000000 0000000 # Microsoft 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.distro.loader import get_distro
class Agent(object):
def __init__(self, verbose):
"""
Initialize agent running environment.
"""
self.distro = get_distro();
self.distro.init_handler.run(verbose)
def daemon(self):
"""
Run agent daemon
"""
self.distro.daemon_handler.run()
def deprovision(self, force=False, deluser=False):
"""
Run deprovision command
"""
self.distro.deprovision_handler.run(force=force, deluser=deluser)
def register_service(self):
"""
Register agent as a service
"""
print("Register {0} service".format(AGENT_NAME))
self.distro.osutil.register_agent_service()
print("Start {0} service".format(AGENT_NAME))
self.distro.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()
elif command == "start":
start()
else:
agent = Agent(verbose)
if command == "deprovision+user":
agent.deprovision(force, deluser=True)
elif command == "deprovision":
agent.deprovision(force, deluser=False)
elif command == "register-service":
agent.register_service()
elif command == "daemon":
agent.daemon()
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)
WALinuxAgent-2.1.3/azurelinuxagent/conf.py 0000664 0000000 0000000 00000012576 12651710031 0020556 0 ustar 00root root 0000000 0000000 # Microsoft 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):
val = self.values.get(key)
return val if val is not None else default_val
def get_switch(self, key, default_val):
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):
try:
return int(self.values.get(key))
except TypeError:
return default_val
except ValueError:
return default_val
__conf__ = ConfigurationProvider()
def load_conf_from_file(conf_file_path, conf=__conf__):
"""
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_logs_verbose(conf=__conf__):
return conf.get_switch("Logs.Verbose", False)
def get_lib_dir(conf=__conf__):
return conf.get("Lib.Dir", "/var/lib/waagent")
def get_dvd_mount_point(conf=__conf__):
return conf.get("DVD.MountPoint", "/mnt/cdrom/secure")
def get_agent_pid_file_path(conf=__conf__):
return conf.get("Pid.File", "/var/run/waagent.pid")
def get_ext_log_dir(conf=__conf__):
return conf.get("Extension.LogDir", "/var/log/azure")
def get_openssl_cmd(conf=__conf__):
return conf.get("OS.OpensslPath", "/usr/bin/openssl")
def get_home_dir(conf=__conf__):
return conf.get("OS.HomeDir", "/home")
def get_passwd_file_path(conf=__conf__):
return conf.get("OS.PasswordPath", "/etc/shadow")
def get_sshd_conf_file_path(conf=__conf__):
return conf.get("OS.SshdConfigPath", "/etc/ssh/sshd_config")
def get_root_device_scsi_timeout(conf=__conf__):
return conf.get("OS.RootDeviceScsiTimeout", None)
def get_ssh_host_keypair_type(conf=__conf__):
return conf.get("Provisioning.SshHostKeyPairType", "rsa")
def get_provision_enabled(conf=__conf__):
return conf.get_switch("Provisioning.Enabled", True)
def get_allow_reset_sys_user(conf=__conf__):
return conf.get_switch("Provisioning.AllowResetSysUser", False)
def get_regenerate_ssh_host_key(conf=__conf__):
return conf.get_switch("Provisioning.RegenerateSshHostKeyPair", False)
def get_delete_root_password(conf=__conf__):
return conf.get_switch("Provisioning.DeleteRootPassword", False)
def get_decode_customdata(conf=__conf__):
return conf.get_switch("Provisioning.DecodeCustomData", False)
def get_execute_customdata(conf=__conf__):
return conf.get_switch("Provisioning.ExecuteCustomData", False)
def get_password_cryptid(conf=__conf__):
return conf.get("Provisioning.PasswordCryptId", "6")
def get_password_crypt_salt_len(conf=__conf__):
return conf.get_int("Provisioning.PasswordCryptSaltLength", 10)
def get_monitor_hostname(conf=__conf__):
return conf.get_switch("Provisioning.MonitorHostName", False)
def get_httpproxy_host(conf=__conf__):
return conf.get("HttpProxy.Host", None)
def get_httpproxy_port(conf=__conf__):
return conf.get("HttpProxy.Port", None)
def get_detect_scvmm_env(conf=__conf__):
return conf.get_switch("DetectScvmmEnv", False)
def get_resourcedisk_format(conf=__conf__):
return conf.get_switch("ResourceDisk.Format", False)
def get_resourcedisk_enable_swap(conf=__conf__):
return conf.get_switch("ResourceDisk.EnableSwap", False)
def get_resourcedisk_mountpoint(conf=__conf__):
return conf.get("ResourceDisk.MountPoint", "/mnt/resource")
def get_resourcedisk_filesystem(conf=__conf__):
return conf.get("ResourceDisk.Filesystem", "ext3")
def get_resourcedisk_swap_size_mb(conf=__conf__):
return conf.get_int("ResourceDisk.SwapSizeMB", 0)
WALinuxAgent-2.1.3/azurelinuxagent/distro/ 0000775 0000000 0000000 00000000000 12651710031 0020550 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/distro/__init__.py 0000664 0000000 0000000 00000001226 12651710031 0022662 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/distro/coreos/ 0000775 0000000 0000000 00000000000 12651710031 0022042 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/distro/coreos/__init__.py 0000664 0000000 0000000 00000001225 12651710031 0024153 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/distro/coreos/deprovision.py 0000664 0000000 0000000 00000002344 12651710031 0024760 0 ustar 00root root 0000000 0000000 # Microsoft 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 __init__(self, distro):
self.distro = distro
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
WALinuxAgent-2.1.3/azurelinuxagent/distro/coreos/distro.py 0000664 0000000 0000000 00000002060 12651710031 0023716 0 ustar 00root root 0000000 0000000 # Microsoft 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.default.distro import DefaultDistro
from azurelinuxagent.distro.coreos.osutil import CoreOSUtil
from azurelinuxagent.distro.coreos.deprovision import CoreOSDeprovisionHandler
class CoreOSDistro(DefaultDistro):
def __init__(self):
super(CoreOSDistro, self).__init__()
self.osutil = CoreOSUtil()
self.deprovision_handler = CoreOSDeprovisionHandler(self)
WALinuxAgent-2.1.3/azurelinuxagent/distro/coreos/osutil.py 0000664 0000000 0000000 00000006063 12651710031 0023740 0 ustar 00root root 0000000 0000000 #
# 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.agent_conf_file_path = '/usr/share/oem/waagent.conf'
self.waagent_path='/usr/share/oem/bin/waagent'
self.python_path='/usr/share/oem/python/bin'
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).is_sys_user(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 set_ssh_client_alive_interval(self):
#In CoreOS, /etc/sshd_config is mount readonly. Skip the setting
pass
def conf_sshd(self, disable_password):
#In CoreOS, /etc/sshd_config is mount readonly. Skip the setting
pass
WALinuxAgent-2.1.3/azurelinuxagent/distro/debian/ 0000775 0000000 0000000 00000000000 12651710031 0021772 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/distro/debian/__init__.py 0000664 0000000 0000000 00000001226 12651710031 0024104 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/distro/debian/distro.py 0000664 0000000 0000000 00000001643 12651710031 0023654 0 ustar 00root root 0000000 0000000 # Microsoft 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.default.distro import DefaultDistro
from azurelinuxagent.distro.debian.osutil import DebianOSUtil
class DebianDistro(DefaultDistro):
def __init__(self):
super(DebianDistro, self).__init__()
self.osutil = DebianOSUtil()
WALinuxAgent-2.1.3/azurelinuxagent/distro/debian/loader.py 0000664 0000000 0000000 00000001406 12651710031 0023613 0 ustar 00root root 0000000 0000000 # Microsoft 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()
WALinuxAgent-2.1.3/azurelinuxagent/distro/debian/osutil.py 0000664 0000000 0000000 00000002647 12651710031 0023674 0 ustar 00root root 0000000 0000000 #
# 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)
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/ 0000775 0000000 0000000 00000000000 12651710031 0022174 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/distro/default/__init__.py 0000664 0000000 0000000 00000001226 12651710031 0024306 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/daemon.py 0000664 0000000 0000000 00000006705 12651710031 0024021 0 ustar 00root root 0000000 0000000 # Microsoft 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 traceback
import azurelinuxagent.conf as conf
import azurelinuxagent.logger as logger
from azurelinuxagent.future import ustr
from azurelinuxagent.event import add_event, WALAEventOperation
from azurelinuxagent.exception import ProtocolError
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.event as event
import azurelinuxagent.utils.fileutil as fileutil
class DaemonHandler(object):
def __init__(self, distro):
self.distro = distro
self.running = True
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)
self.check_pid()
while self.running:
try:
self.daemon()
except Exception as e:
err_msg = traceback.format_exc()
add_event("WALA", is_success=False, message=ustr(err_msg),
op=WALAEventOperation.UnhandledError)
logger.info("Sleep 15 seconds and restart daemon")
time.sleep(15)
def check_pid(self):
"""Check whether daemon is already running"""
pid = None
pid_file = conf.get_agent_pid_file_path()
if os.path.isfile(pid_file):
pid = fileutil.read_file(pid_file)
if pid is not None and os.path.isdir(os.path.join("/proc", pid)):
logger.info("Daemon is already running: {0}", pid)
sys.exit(0)
fileutil.write_file(pid_file, ustr(os.getpid()))
def daemon(self):
logger.info("Run daemon")
#Create lib dir
if not os.path.isdir(conf.get_lib_dir()):
fileutil.mkdir(conf.get_lib_dir(), mode=0o700)
os.chdir(conf.get_lib_dir())
if conf.get_detect_scvmm_env():
if self.distro.scvmm_handler.run():
return
self.distro.provision_handler.run()
if conf.get_resourcedisk_format():
self.distro.resource_disk_handler.run()
try:
protocol = self.distro.protocol_util.detect_protocol()
except ProtocolError as e:
logger.error("Failed to detect protocol, exit", e)
return
self.distro.event_handler.run()
self.distro.env_handler.run()
while self.running:
#Handle extensions
self.distro.ext_handlers_handler.run()
time.sleep(25)
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/deprovision.py 0000664 0000000 0000000 00000010667 12651710031 0025121 0 ustar 00root root 0000000 0000000 # Microsoft 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.exception import ProtocolError
from azurelinuxagent.future import read_input
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 __init__(self, distro):
self.distro = distro
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(self.distro.osutil.del_root_password))
def del_user(self, warnings, actions):
try:
ovfenv = self.distro.protocol_util.get_ovf_env()
except 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(self.distro.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(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(self.distro.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 = [conf.get_lib_dir()]
actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del))
def reset_hostname(self, warnings, actions):
localhost = ["localhost.localdomain"]
actions.append(DeprovisionAction(self.distro.osutil.set_hostname,
localhost))
actions.append(DeprovisionAction(self.distro.osutil.set_dhcp_hostname,
localhost))
def setup(self, deluser):
warnings = []
actions = []
self.stop_agent_service(warnings, actions)
if conf.get_regenerate_ssh_host_key():
self.regen_ssh_host_key(warnings, actions)
self.del_dhcp_lease(warnings, actions)
self.reset_hostname(warnings, actions)
if conf.get_delete_root_password():
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 run(self, force=False, deluser=False):
warnings, actions = self.setup(deluser)
for warning in warnings:
print(warning)
if not force:
confirm = read_input("Do you want to proceed (y/n)")
if not confirm.lower().startswith('y'):
return
for action in actions:
action.invoke()
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/dhcp.py 0000664 0000000 0000000 00000027667 12651710031 0023506 0 ustar 00root root 0000000 0000000 # Microsoft 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 threading
import azurelinuxagent.logger as logger
import azurelinuxagent.conf as conf
import azurelinuxagent.utils.fileutil as fileutil
import azurelinuxagent.utils.shellutil as shellutil
from azurelinuxagent.utils.textutil import hex_dump, hex_dump2, hex_dump3, \
compare_bytes, str_to_ord, \
unpack_big_endian, \
unpack_little_endian, \
int_to_ip4_addr
from azurelinuxagent.exception import DhcpError
class DhcpHandler(object):
"""
Azure use DHCP option 245 to pass endpoint ip to VMs.
"""
def __init__(self, distro):
self.distro = distro
self.endpoint = None
self.gateway = None
self.routes = None
def run(self):
"""
Send dhcp request
Configure default gateway and routes
Save wire server endpoint if found
"""
self.send_dhcp_req()
self.conf_routes()
def wait_for_network(self):
"""
Wait for network stack to be initialized.
"""
ipv4 = self.distro.osutil.get_ip4_addr()
while ipv4 == '' or ipv4 == '0.0.0.0':
logger.info("Waiting for network.")
time.sleep(10)
logger.info("Try to start network interface.")
self.distro.osutil.start_network()
ipv4 = self.distro.osutil.get_ip4_addr()
def conf_routes(self):
logger.info("Configure routes")
logger.info("Gateway:{0}", self.gateway)
logger.info("Routes:{0}", self.routes)
#Add default gateway
if self.gateway is not None:
self.distro.osutil.route_add(0 , 0, self.gateway)
if self.routes is not None:
for route in self.routes:
self.distro.osutil.route_add(route[0], route[1], route[2])
def _send_dhcp_req(self, request):
__waiting_duration__ = [0, 10, 30, 60, 60]
for duration in __waiting_duration__:
try:
self.distro.osutil.allow_dhcp_broadcast()
response = socket_send(request)
validate_dhcp_resp(request, response)
return response
except DhcpError as e:
logger.warn("Failed to send DHCP request: {0}", e)
time.sleep(duration)
return None
def send_dhcp_req(self):
"""
Build dhcp request with mac addr
Configure route to allow dhcp traffic
Stop dhcp service if necessary
"""
logger.info("Send dhcp request")
mac_addr = self.distro.osutil.get_mac_addr()
req = build_dhcp_request(mac_addr)
# Temporary allow broadcast for dhcp. Remove the route when done.
missing_default_route = self.distro.osutil.is_missing_default_route()
ifname = self.distro.osutil.get_if_name()
if missing_default_route:
self.distro.osutil.set_route_for_dhcp_broadcast(ifname)
# In some distros, dhcp service needs to be shutdown before agent probe
# endpoint through dhcp.
if self.distro.osutil.is_dhcp_enabled():
self.distro.osutil.stop_dhcp_service()
resp = self._send_dhcp_req(req)
if self.distro.osutil.is_dhcp_enabled():
self.distro.osutil.start_dhcp_service()
if missing_default_route:
self.distro.osutil.remove_route_for_dhcp_broadcast(ifname)
if resp is None:
raise DhcpError("Failed to receive dhcp response.")
self.endpoint, self.gateway, self.routes = parse_dhcp_resp(resp)
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 DhcpError("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 DhcpError("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 DhcpError("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 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, ("", 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 DhcpError("{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)
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/distro.py 0000664 0000000 0000000 00000004222 12651710031 0024052 0 ustar 00root root 0000000 0000000 # Microsoft 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.conf import ConfigurationProvider
from azurelinuxagent.distro.default.osutil import DefaultOSUtil
from azurelinuxagent.distro.default.daemon import DaemonHandler
from azurelinuxagent.distro.default.init import InitHandler
from azurelinuxagent.distro.default.monitor import MonitorHandler
from azurelinuxagent.distro.default.dhcp import DhcpHandler
from azurelinuxagent.distro.default.protocolUtil import ProtocolUtil
from azurelinuxagent.distro.default.scvmm import ScvmmHandler
from azurelinuxagent.distro.default.env import EnvHandler
from azurelinuxagent.distro.default.provision import ProvisionHandler
from azurelinuxagent.distro.default.resourceDisk import ResourceDiskHandler
from azurelinuxagent.distro.default.extension import ExtHandlersHandler
from azurelinuxagent.distro.default.deprovision import DeprovisionHandler
class DefaultDistro(object):
"""
"""
def __init__(self):
self.osutil = DefaultOSUtil()
self.protocol_util = ProtocolUtil(self)
self.init_handler = InitHandler(self)
self.daemon_handler = DaemonHandler(self)
self.event_handler = MonitorHandler(self)
self.dhcp_handler = DhcpHandler(self)
self.scvmm_handler = ScvmmHandler(self)
self.env_handler = EnvHandler(self)
self.provision_handler = ProvisionHandler(self)
self.resource_disk_handler = ResourceDiskHandler(self)
self.ext_handlers_handler = ExtHandlersHandler(self)
self.deprovision_handler = DeprovisionHandler(self)
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/env.py 0000664 0000000 0000000 00000007003 12651710031 0023336 0 ustar 00root root 0000000 0000000 # Microsoft 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
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 timeout
"""
def __init__(self, distro):
self.distro = distro
self.stopped = True
self.hostname = None
self.dhcpid = None
self.server_thread=None
def run(self):
if not self.stopped:
logger.info("Stop existing env monitor service.")
self.stop()
self.stopped = False
logger.info("Start env monitor service.")
self.distro.dhcp_handler.conf_routes()
self.hostname = socket.gethostname()
self.dhcpid = self.distro.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:
self.distro.osutil.remove_rules_files()
timeout = conf.get_root_device_scsi_timeout()
if timeout is not None:
self.distro.osutil.set_scsi_disks_timeout(timeout)
if conf.get_monitor_hostname():
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)
self.distro.osutil.set_hostname(curr_hostname)
self.distro.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 = self.distro.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 = self.distro.osutil.get_dhcp_pid()
if newpid is not None and newpid != self.dhcpid:
logger.info("EnvMonitor: Detected dhcp client restart. "
"Restoring routing table.")
self.distro.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()
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/extension.py 0000664 0000000 0000000 00000076537 12651710031 0024604 0 ustar 00root root 0000000 0000000 # Microsoft 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 shutil
import azurelinuxagent.conf as conf
import azurelinuxagent.logger as logger
from azurelinuxagent.event import add_event, WALAEventOperation
from azurelinuxagent.exception import ExtensionError, ProtocolError, HttpError
from azurelinuxagent.future import ustr
from azurelinuxagent.metadata import AGENT_VERSION
from azurelinuxagent.protocol.restapi import ExtHandlerStatus, ExtensionStatus, \
ExtensionSubStatus, Extension, \
VMStatus, ExtHandler, \
get_properties, set_properties
import azurelinuxagent.utils.fileutil as fileutil
import azurelinuxagent.utils.restutil as restutil
import azurelinuxagent.utils.shellutil as shellutil
from azurelinuxagent.utils.textutil import Version
#HandlerEnvironment.json schema version
HANDLER_ENVIRONMENT_VERSION = 1.0
VALID_EXTENSION_STATUS = ['transitioning', 'error', 'success', 'warning']
VALID_HANDLER_STATUS = ['Ready', 'NotReady', "Installing", "Unresponsive"]
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 parse_formatted_message(formatted_message):
if formatted_message is None:
return None
validate_has_key(formatted_message, 'lang', 'formattedMessage/lang')
validate_has_key(formatted_message, 'message', 'formattedMessage/message')
return formatted_message.get('message')
def parse_ext_substatus(substatus):
#Check extension sub status format
validate_has_key(substatus, 'status', 'substatus/status')
validate_in_range(substatus['status'], VALID_EXTENSION_STATUS,
'substatus/status')
status = ExtensionSubStatus()
status.name = substatus.get('name')
status.status = substatus.get('status')
status.code = substatus.get('code', 0)
formatted_message = substatus.get('formattedMessage')
status.message = parse_formatted_message(formatted_message)
return status
def parse_ext_status(ext_status, data):
if data is None or len(data) is None:
return
#Currently, only the first status will be reported
data = data[0]
#Check extension status format
validate_has_key(data, 'status', 'status')
status_data = data['status']
validate_has_key(status_data, 'status', 'status/status')
validate_in_range(status_data['status'], VALID_EXTENSION_STATUS,
'status/status')
applied_time = status_data.get('configurationAppliedTime')
ext_status.configurationAppliedTime = applied_time
ext_status.operation = status_data.get('operation')
ext_status.status = status_data.get('status')
ext_status.code = status_data.get('code', 0)
formatted_message = status_data.get('formattedMessage')
ext_status.message = parse_formatted_message(formatted_message)
substatus_list = status_data.get('substatus')
if substatus_list is None:
return
for substatus in substatus_list:
ext_status.substatusList.append(parse_ext_substatus(substatus))
class ExtHandlerState(object):
NotInstalled = "NotInstalled"
Installed = "Installed"
Enabled = "Enabled"
class ExtHandlersHandler(object):
def __init__(self, distro):
self.distro = distro
self.ext_handlers = None
self.last_etag = None
self.log_report = False
def run(self):
ext_handlers, etag = None, None
try:
self.protocol = self.distro.protocol_util.get_protocol()
ext_handlers, etag = self.protocol.get_ext_handlers()
except ProtocolError as e:
add_event(name="WALA", is_success=False, message=ustr(e))
return
if self.last_etag is not None and self.last_etag == etag:
logger.verb("No change to ext handler config:{0}, skip", etag)
self.log_report = False
else:
logger.info("Handle new ext handler config")
self.log_report = True #Log status report success on new config
self.handle_ext_handlers(ext_handlers)
self.last_etag = etag
self.report_ext_handlers_status(ext_handlers)
def handle_ext_handlers(self, ext_handlers):
if ext_handlers.extHandlers is None or \
len(ext_handlers.extHandlers) == 0:
logger.info("No ext handler config found")
return
for ext_handler in ext_handlers.extHandlers:
#TODO handle install in sequence, enable in parallel
self.handle_ext_handler(ext_handler)
def handle_ext_handler(self, ext_handler):
ext_handler_i = ExtHandlerInstance(ext_handler, self.protocol)
try:
state = ext_handler.properties.state
ext_handler_i.logger.info("Expected handler state: {0}", state)
if state == "enabled":
self.handle_enable(ext_handler_i)
elif state == u"disabled":
self.handle_disable(ext_handler_i)
elif state == u"uninstall":
self.handle_uninstall(ext_handler_i)
else:
message = u"Unknown ext handler state:{0}".format(state)
raise ExtensionError(message)
except ExtensionError as e:
ext_handler_i.set_handler_status(message=ustr(e), code=-1)
ext_handler_i.report_event(message=ustr(e), is_success=False)
def handle_enable(self, ext_handler_i):
ext_handler_i.decide_version()
old_ext_handler_i = ext_handler_i.get_installed_ext_handler()
if old_ext_handler_i is not None and \
old_ext_handler_i.version_gt(ext_handler_i):
raise ExtensionError(u"Downgrade not allowed")
handler_state = ext_handler_i.get_handler_state()
ext_handler_i.logger.info("Current handler state is: {0}", handler_state)
if handler_state == ExtHandlerState.NotInstalled:
ext_handler_i.set_handler_state(ExtHandlerState.NotInstalled)
ext_handler_i.download()
ext_handler_i.update_settings()
if old_ext_handler_i is None:
ext_handler_i.install()
elif ext_handler_i.version_gt(old_ext_handler_i):
old_ext_handler_i.disable()
ext_handler_i.copy_status_files(old_ext_handler_i)
ext_handler_i.update()
old_ext_handler_i.uninstall()
old_ext_handler_i.rm_ext_handler_dir()
ext_handler_i.update_with_install()
else:
ext_handler_i.update_settings()
ext_handler_i.enable()
def handle_disable(self, ext_handler_i):
handler_state = ext_handler_i.get_handler_state()
ext_handler_i.logger.info("Current handler state is: {0}", handler_state)
if handler_state == ExtHandlerState.Enabled:
ext_handler_i.disable()
def handle_uninstall(self, ext_handler_i):
handler_state = ext_handler_i.get_handler_state()
ext_handler_i.logger.info("Current handler state is: {0}", handler_state)
if handler_state != ExtHandlerState.NotInstalled:
if handler_state == ExtHandlerState.Enabled:
ext_handler_i.disable()
ext_handler_i.uninstall()
ext_handler_i.rm_ext_handler_dir()
def report_ext_handlers_status(self, ext_handlers):
"""Go thru handler_state dir, collect and report status"""
vm_status = VMStatus()
vm_status.vmAgent.version = AGENT_VERSION
vm_status.vmAgent.status = "Ready"
vm_status.vmAgent.message = "Guest Agent is running"
if ext_handlers is not None:
for ext_handler in ext_handlers.extHandlers:
try:
self.report_ext_handler_status(vm_status, ext_handler)
except ExtensionError as e:
add_event(name="WALA", is_success=False, message=ustr(e))
logger.verb("Report vm agent status")
try:
self.protocol.report_vm_status(vm_status)
except ProtocolError as e:
message = "Failed to report vm agent status: {0}".format(e)
add_event(name="WALA", is_success=False, message=message)
if self.log_report:
logger.info("Successfully reported vm agent status")
def report_ext_handler_status(self, vm_status, ext_handler):
ext_handler_i = ExtHandlerInstance(ext_handler, self.protocol)
handler_status = ext_handler_i.get_handler_status()
if handler_status is None:
return
handler_state = ext_handler_i.get_handler_state()
if handler_state != ExtHandlerState.NotInstalled:
try:
active_exts = ext_handler_i.report_ext_status()
handler_status.extensions.extend(active_exts)
except ExtensionError as e:
ext_handler_i.set_handler_status(message=ustr(e), code=-1)
try:
heartbeat = ext_handler_i.collect_heartbeat()
if heartbeat is not None:
handler_status.status = heartbeat.get('status')
except ExtensionError as e:
ext_handler_i.set_handler_status(message=ustr(e), code=-1)
vm_status.vmAgent.extensionHandlers.append(handler_status)
class ExtHandlerInstance(object):
def __init__(self, ext_handler, protocol):
self.ext_handler = ext_handler
self.protocol = protocol
self.operation = None
self.pkg = None
prefix = "[{0}]".format(self.get_full_name())
self.logger = logger.Logger(logger.DEFAULT_LOGGER, prefix)
try:
fileutil.mkdir(self.get_log_dir(), mode=0o744)
except IOError as e:
self.logger.error(u"Failed to create extension log dir: {0}", e)
log_file = os.path.join(self.get_log_dir(), "CommandExecution.log")
self.logger.add_appender(logger.AppenderType.FILE,
logger.LogLevel.INFO, log_file)
def decide_version(self):
"""
If auto-upgrade, get the largest public extension version under
the requested major version family of currently installed plugin version
Else, get the highest hot-fix for requested version,
"""
self.logger.info("Decide which version to use")
try:
pkg_list = self.protocol.get_ext_handler_pkgs(self.ext_handler)
except ProtocolError as e:
raise ExtensionError("Failed to get ext handler pkgs", e)
version = self.ext_handler.properties.version
update_policy = self.ext_handler.properties.upgradePolicy
version_frag = version.split('.')
if len(version_frag) < 2:
raise ExtensionError("Wrong version format: {0}".format(version))
version_prefix = None
if update_policy is not None and update_policy == 'auto':
version_prefix = "{0}.".format(version_frag[0])
else:
version_prefix = "{0}.{1}.".format(version_frag[0], version_frag[1])
packages = [x for x in pkg_list.versions \
if x.version.startswith(version_prefix) or \
x.version == version]
packages = sorted(packages, key=lambda x: Version(x.version),
reverse=True)
if len(packages) <= 0:
raise ExtensionError("Failed to find and valid extension package")
self.pkg = packages[0]
self.ext_handler.properties.version = packages[0].version
self.logger.info("Use version: {0}", self.pkg.version)
def version_gt(self, other):
self_version = self.ext_handler.properties.version
other_version = other.ext_handler.properties.version
return Version(self_version) > Version(other_version)
def get_installed_ext_handler(self):
lastest_version = None
ext_handler_name = self.ext_handler.name
for dir_name in os.listdir(conf.get_lib_dir()):
path = os.path.join(conf.get_lib_dir(), dir_name)
if os.path.isdir(path) and dir_name.startswith(ext_handler_name):
seperator = dir_name.rfind('-')
if seperator < 0:
continue
installed_name = dir_name[0: seperator]
installed_version = dir_name[seperator + 1:]
if installed_name != ext_handler_name:
continue
if lastest_version is None or \
Version(lastest_version) < Version(installed_version):
lastest_version = installed_version
if lastest_version is None:
return None
data = get_properties(self.ext_handler)
old_ext_handler = ExtHandler()
set_properties("ExtHandler", old_ext_handler, data)
old_ext_handler.properties.version = lastest_version
return ExtHandlerInstance(old_ext_handler, self.protocol)
def copy_status_files(self, old_ext_handler_i):
self.logger.info("Copy status files from old plugin to new")
old_ext_dir = old_ext_handler_i.get_base_dir()
new_ext_dir = self.get_base_dir()
old_ext_mrseq_file = os.path.join(old_ext_dir, "mrseq")
if os.path.isfile(old_ext_mrseq_file):
shutil.copy2(old_ext_mrseq_file, new_ext_dir)
old_ext_status_dir = old_ext_handler_i.get_status_dir()
new_ext_status_dir = self.get_status_dir()
if os.path.isdir(old_ext_status_dir):
for status_file in os.listdir(old_ext_status_dir):
status_file = os.path.join(old_ext_status_dir, status_file)
if os.path.isfile(status_file):
shutil.copy2(status_file, new_ext_status_dir)
def set_operation(self, op):
self.operation = op
def report_event(self, message="", is_success=True):
version = self.ext_handler.properties.version
add_event(name=self.ext_handler.name, version=version, message=message,
op=self.operation, is_success=is_success)
def download(self):
self.logger.info("Download extension package")
self.set_operation(WALAEventOperation.Download)
if self.pkg is None:
raise ExtensionError("No package uri found")
package = None
for uri in self.pkg.uris:
try:
package = self.protocol.download_ext_handler_pkg(uri.uri)
except ProtocolError as e:
logger.warn("Failed download extension: {0}", e)
if package is None:
raise ExtensionError("Failed to download extension")
self.logger.info("Unpack extension package")
pkg_file = os.path.join(conf.get_lib_dir(),
os.path.basename(uri.uri) + ".zip")
try:
fileutil.write_file(pkg_file, bytearray(package), asbin=True)
zipfile.ZipFile(pkg_file).extractall(self.get_base_dir())
except IOError as e:
raise ExtensionError(u"Failed to write and unzip plugin", e)
chmod = "find {0} -type f | xargs chmod u+x".format(self.get_base_dir())
shellutil.run(chmod)
self.report_event(message="Download succeeded")
self.logger.info("Initialize extension directory")
#Save HandlerManifest.json
man_file = fileutil.search_file(self.get_base_dir(),
'HandlerManifest.json')
if man_file is None:
raise ExtensionError("HandlerManifest.json not found")
try:
man = fileutil.read_file(man_file, remove_bom=True)
fileutil.write_file(self.get_manifest_file(), man)
except IOError as e:
raise ExtensionError(u"Failed to save HandlerManifest.json", e)
#Create status and config dir
try:
status_dir = self.get_status_dir()
fileutil.mkdir(status_dir, mode=0o700)
conf_dir = self.get_conf_dir()
fileutil.mkdir(conf_dir, mode=0o700)
except IOError as e:
raise ExtensionError(u"Failed to create status or config dir", e)
#Save HandlerEnvironment.json
self.create_handler_env()
def enable(self):
self.logger.info("Enable extension.")
self.set_operation(WALAEventOperation.Enable)
man = self.load_manifest()
self.launch_command(man.get_enable_command())
self.set_handler_state(ExtHandlerState.Enabled)
self.set_handler_status(status="Ready", message="Plugin enabled")
def disable(self):
self.logger.info("Disable extension.")
self.set_operation(WALAEventOperation.Disable)
man = self.load_manifest()
self.launch_command(man.get_disable_command(), timeout=900)
self.set_handler_state(ExtHandlerState.Installed)
self.set_handler_status(status="NotReady", message="Plugin disabled")
def install(self):
self.logger.info("Install extension.")
self.set_operation(WALAEventOperation.Install)
man = self.load_manifest()
self.launch_command(man.get_install_command(), timeout=900)
self.set_handler_state(ExtHandlerState.Installed)
def uninstall(self):
self.logger.info("Uninstall extension.")
self.set_operation(WALAEventOperation.UnInstall)
try:
man = self.load_manifest()
self.launch_command(man.get_uninstall_command())
except ExtensionError as e:
self.report_event(message=ustr(e), is_success=False)
def rm_ext_handler_dir(self):
try:
handler_state_dir = self.get_handler_state_dir()
if os.path.isdir(handler_state_dir):
self.logger.info("Remove ext handler dir: {0}", handler_state_dir)
shutil.rmtree(handler_state_dir)
base_dir = self.get_base_dir()
if os.path.isdir(base_dir):
self.logger.info("Remove ext handler dir: {0}", base_dir)
shutil.rmtree(base_dir)
except IOError as e:
message = "Failed to rm ext handler dir: {0}".format(e)
self.report_event(message=message, is_success=False)
def update(self):
self.logger.info("Update extension.")
self.set_operation(WALAEventOperation.Update)
man = self.load_manifest()
self.launch_command(man.get_update_command(), timeout=900)
def update_with_install(self):
man = self.load_manifest()
if man.is_update_with_install():
self.install()
else:
self.logger.info("UpdateWithInstall not set. "
"Skip install during upgrade.")
self.set_handler_state(ExtHandlerState.Installed)
def get_largest_seq_no(self):
seq_no = -1
conf_dir = self.get_conf_dir()
for item in os.listdir(conf_dir):
item_path = os.path.join(conf_dir, item)
if os.path.isfile(item_path):
try:
seperator = item.rfind(".")
if seperator > 0 and item[seperator + 1:] == 'settings':
curr_seq_no = int(item.split('.')[0])
if curr_seq_no > seq_no:
seq_no = curr_seq_no
except Exception as e:
self.logger.verb("Failed to parse file name: {0}", item)
continue
return seq_no
def collect_ext_status(self, ext):
self.logger.verb("Collect extension status")
seq_no = self.get_largest_seq_no()
if seq_no == -1:
return None
status_dir = self.get_status_dir()
ext_status_file = "{0}.status".format(seq_no)
ext_status_file = os.path.join(status_dir, ext_status_file)
ext_status = ExtensionStatus(seq_no=seq_no)
try:
data_str = fileutil.read_file(ext_status_file)
data = json.loads(data_str)
parse_ext_status(ext_status, data)
except IOError as e:
ext_status.message = u"Failed to get status file {0}".format(e)
ext_status.code = -1
ext_status.status = "error"
except ValueError as e:
ext_status.message = u"Malformed status file {0}".format(e)
ext_status.code = -1
ext_status.status = "error"
return ext_status
def report_ext_status(self):
active_exts = []
for ext in self.ext_handler.properties.extensions:
ext_status = self.collect_ext_status(ext)
if ext_status is None:
continue
try:
self.protocol.report_ext_status(self.ext_handler.name, ext.name,
ext_status)
active_exts.append(ext.name)
except ProtocolError as e:
self.logger.error(u"Failed to report extension status: {0}", e)
return active_exts
def collect_heartbeat(self):
man = self.load_manifest()
if not man.is_report_heartbeat():
return
heartbeat_file = os.path.join(conf.get_lib_dir(),
self.get_heartbeat_file())
self.logger.info("Collect heart beat")
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()
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))
self.report_event(message="Launch command succeeded: {0}".format(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_file(self, settings_file, settings):
settings_file = os.path.join(self.get_conf_dir(), settings_file)
try:
fileutil.write_file(settings_file, settings)
except IOError as e:
raise ExtensionError(u"Failed to update settings file", e)
def update_settings(self):
if self.ext_handler.properties.extensions is None or \
len(self.ext_handler.properties.extensions) == 0:
#This is the behavior of waagent 2.0.x
#The new agent has to be consistent with the old one.
self.logger.info("Extension has no settings, write empty 0.settings")
self.update_settings_file("0.settings", "")
return
for ext in self.ext_handler.properties.extensions:
settings = {
'publicSettings': ext.publicSettings,
'protectedSettings': ext.protectedSettings,
'protectedSettingsCertThumbprint': ext.certificateThumbprint
}
ext_settings = {
"runtimeSettings":[{
"handlerSettings": settings
}]
}
settings_file = "{0}.settings".format(ext.sequenceNumber)
self.logger.info("Update settings file: {0}", settings_file)
self.update_settings_file(settings_file, json.dumps(ext_settings))
def create_handler_env(self):
env = [{
"name": self.ext_handler.name,
"version" : HANDLER_ENVIRONMENT_VERSION,
"handlerEnvironment" : {
"logFolder" : self.get_log_dir(),
"configFolder" : self.get_conf_dir(),
"statusFolder" : self.get_status_dir(),
"heartbeatFile" : self.get_heartbeat_file()
}
}]
try:
fileutil.write_file(self.get_env_file(), json.dumps(env))
except IOError as e:
raise ExtensionError(u"Failed to save handler environment", e)
def get_handler_state_dir(self):
return os.path.join(conf.get_lib_dir(), "handler_state",
self.get_full_name())
def set_handler_state(self, handler_state):
state_dir = self.get_handler_state_dir()
if not os.path.exists(state_dir):
try:
fileutil.mkdir(state_dir, 0o700)
except IOError as e:
self.logger.error("Failed to create state dir: {0}", e)
try:
state_file = os.path.join(state_dir, "state")
fileutil.write_file(state_file, handler_state)
except IOError as e:
self.logger.error("Failed to set state: {0}", e)
def get_handler_state(self):
state_dir = self.get_handler_state_dir()
state_file = os.path.join(state_dir, "state")
if not os.path.isfile(state_file):
return ExtHandlerState.NotInstalled
try:
return fileutil.read_file(state_file)
except IOError as e:
self.logger.error("Failed to get state: {0}", e)
return ExtHandlerState.NotInstalled
def set_handler_status(self, status="NotReady", message="",
code=0):
state_dir = self.get_handler_state_dir()
if not os.path.exists(state_dir):
try:
fileutil.mkdir(state_dir, 0o700)
except IOError as e:
self.logger.error("Failed to create state dir: {0}", e)
handler_status = ExtHandlerStatus()
handler_status.name = self.ext_handler.name
handler_status.version = self.ext_handler.properties.version
handler_status.message = message
handler_status.code = code
handler_status.status = status
status_file = os.path.join(state_dir, "status")
try:
fileutil.write_file(status_file,
json.dumps(get_properties(handler_status)))
except (IOError, ValueError, ProtocolError) as e:
self.logger.error("Failed to save handler status: {0}", e)
def get_handler_status(self):
state_dir = self.get_handler_state_dir()
status_file = os.path.join(state_dir, "status")
if not os.path.isfile(status_file):
return None
try:
data = json.loads(fileutil.read_file(status_file))
handler_status = ExtHandlerStatus()
set_properties("ExtHandlerStatus", handler_status, data)
return handler_status
except (IOError, ValueError) as e:
self.logger.error("Failed to get handler status: {0}", e)
def get_full_name(self):
return "{0}-{1}".format(self.ext_handler.name,
self.ext_handler.properties.version)
def get_base_dir(self):
return os.path.join(conf.get_lib_dir(), self.get_full_name())
def get_status_dir(self):
return os.path.join(self.get_base_dir(), "status")
def get_conf_dir(self):
return os.path.join(self.get_base_dir(), 'config')
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(conf.get_ext_log_dir(), self.ext_handler.name,
self.ext_handler.properties.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):
"""
Deprecated
"""
return False
def is_report_heartbeat(self):
return self.data['handlerManifest'].get('reportHeartbeat', False)
def is_update_with_install(self):
update_mode = self.data['handlerManifest'].get('updateMode')
if update_mode is None:
return True
return update_mode.low() == "updatewithinstall"
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/init.py 0000664 0000000 0000000 00000003422 12651710031 0023512 0 ustar 00root root 0000000 0000000 # Microsoft 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
import azurelinuxagent.event as event
class InitHandler(object):
def __init__(self, distro):
self.distro = distro
def run(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 = self.distro.osutil.get_agent_conf_file_path()
conf.load_conf_from_file(conf_file_path)
#Init log
verbose = verbose or conf.get_logs_verbose()
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")
#Init event reporter
event_dir = os.path.join(conf.get_lib_dir(), "events")
event.init_event_logger(event_dir)
event.enable_unhandled_err_dump("WALA")
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/monitor.py 0000664 0000000 0000000 00000015715 12651710031 0024246 0 ustar 00root root 0000000 0000000 # 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
import azurelinuxagent.conf as conf
from azurelinuxagent.event import WALAEventOperation, add_event
from azurelinuxagent.exception import EventError, ProtocolError, OSUtilError
from azurelinuxagent.future import ustr
from azurelinuxagent.utils.textutil import parse_doc, findall, find, getattrib
from azurelinuxagent.protocol.restapi import TelemetryEventParam, \
TelemetryEventList, \
TelemetryEvent, \
set_properties, get_properties
from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, \
DISTRO_CODE_NAME, AGENT_LONG_VERSION
def parse_event(data_str):
try:
return parse_json_event(data_str)
except ValueError:
return parse_xml_event(data_str)
def parse_xml_param(param_node):
name = getattrib(param_node, "Name")
value_str = getattrib(param_node, "Value")
attr_type = getattrib(param_node, "T")
value = value_str
if attr_type == 'mt:uint64':
value = int(value_str)
elif attr_type == 'mt:bool':
value = bool(value_str)
elif attr_type == 'mt:float64':
value = float(value_str)
return TelemetryEventParam(name, value)
def parse_xml_event(data_str):
try:
xml_doc = parse_doc(data_str)
event_id = getattrib(find(xml_doc, "Event"), 'id')
provider_id = getattrib(find(xml_doc, "Provider"), 'id')
event = TelemetryEvent(event_id, provider_id)
param_nodes = findall(xml_doc, 'Param')
for param_node in param_nodes:
event.parameters.append(parse_xml_param(param_node))
return event
except Exception as e:
raise ValueError(ustr(e))
def parse_json_event(data_str):
data = json.loads(data_str)
event = TelemetryEvent()
set_properties("TelemetryEvent", event, data)
return event
class MonitorHandler(object):
def __init__(self, distro):
self.distro = distro
self.sysinfo = []
def run(self):
event_thread = threading.Thread(target = self.daemon)
event_thread.setDaemon(True)
event_thread.start()
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(TelemetryEventParam("OSVersion", osversion))
self.sysinfo.append(TelemetryEventParam("GAVersion", AGENT_LONG_VERSION))
try:
ram = self.distro.osutil.get_total_mem()
processors = self.distro.osutil.get_processor_cores()
self.sysinfo.append(TelemetryEventParam("RAM", ram))
self.sysinfo.append(TelemetryEventParam("Processors", processors))
except OSUtilError as e:
logger.warn("Failed to get system info: {0}", e)
try:
protocol = self.distro.protocol_util.get_protocol()
vminfo = protocol.get_vminfo()
self.sysinfo.append(TelemetryEventParam("VMName",
vminfo.vmName))
self.sysinfo.append(TelemetryEventParam("TenantName",
vminfo.tenantName))
self.sysinfo.append(TelemetryEventParam("RoleName",
vminfo.roleName))
self.sysinfo.append(TelemetryEventParam("RoleInstanceName",
vminfo.roleInstanceName))
self.sysinfo.append(TelemetryEventParam("ContainerId",
vminfo.containerId))
except ProtocolError as e:
logger.warn("Failed to get system info: {0}", e)
def collect_event(self, evt_file_name):
try:
logger.verb("Found event file: {0}", evt_file_name)
with open(evt_file_name, "rb") as evt_file:
#if fail to open or delete the file, throw exception
data_str = evt_file.read().decode("utf-8",'ignore')
logger.verb("Processed event file: {0}", evt_file_name)
os.remove(evt_file_name)
return data_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 = TelemetryEventList()
event_dir = os.path.join(conf.get_lib_dir(), "events")
event_files = os.listdir(event_dir)
for event_file in event_files:
if not event_file.endswith(".tld"):
continue
event_file_path = os.path.join(event_dir, event_file)
try:
data_str = self.collect_event(event_file_path)
except EventError as e:
logger.error("{0}", e)
continue
try:
event = parse_event(data_str)
event.parameters.extend(self.sysinfo)
event_list.events.append(event)
except (ValueError, ProtocolError) as e:
logger.warn("Failed to decode event file: {0}", e)
continue
if len(event_list.events) == 0:
return
try:
protocol = self.distro.protocol_util.get_protocol()
protocol.report_event(event_list)
except ProtocolError as e:
logger.error("{0}", e)
def daemon(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)
try:
self.collect_and_send_events()
except Exception as e:
logger.warn("Failed to send events: {0}", e)
time.sleep(60)
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/osutil.py 0000664 0000000 0000000 00000057046 12651710031 0024101 0 ustar 00root root 0000000 0000000 #
# 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 base64
import azurelinuxagent.logger as logger
import azurelinuxagent.conf as conf
from azurelinuxagent.exception import OSUtilError
from azurelinuxagent.future import ustr
import azurelinuxagent.utils.fileutil as fileutil
import azurelinuxagent.utils.shellutil as shellutil
import azurelinuxagent.utils.textutil as textutil
from azurelinuxagent.utils.cryptutil import CryptUtil
__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 DefaultOSUtil(object):
def __init__(self):
self.agent_conf_file_path = '/etc/waagent.conf'
self.selinux=None
def get_agent_conf_file_path(self):
return self.agent_conf_file_path
def get_userentry(self, username):
try:
return pwd.getpwnam(username)
except KeyError:
return None
def is_sys_user(self, username):
"""
Check whether use is a system user.
If reset sys user is allowed in conf, return False
Otherwise, check whether UID is less than UID_MIN
"""
if conf.get_allow_reset_sys_user():
return False
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):
"""
Create user account with 'username'
"""
userentry = self.get_userentry(username)
if userentry is not None:
logger.info("User {0} already exists, skip useradd", username)
return
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, crypt_id=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, crypt_id, salt_len)
cmd = "usermod -p '{0}' {1}".format(passwd_hash, username)
ret, output = shellutil.run_get_output(cmd, log_cmd=False)
if ret != 0:
raise OSUtilError(("Failed to set password for {0}: {1}"
"").format(username, output))
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_file_path = conf.get_passwd_file_path()
passwd_content = fileutil.read_file(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(passwd_file_path, "\n".join(new_passwd))
except IOError as e:
raise OSUtilError("Failed to delete root password:{0}".format(e))
def _norm_path(self, filepath):
home = conf.get_home_dir()
# Expand HOME variable if present in path
path = os.path.normpath(filepath.replace("$HOME", home))
return path
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 = conf.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'
crytputil = CryptUtil(conf.get_openssl_cmd())
pub = crytputil.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):
cryptutil = CryptUtil(conf.get_openssl_cmd())
cryptutil.crt_to_ssh(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")
crytputil = CryptUtil(conf.get_openssl_cmd())
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 = conf.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 = crytputil.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 set_ssh_client_alive_interval(self):
conf_file_path = conf.get_sshd_conf_file_path()
conf_file = fileutil.read_file(conf_file_path).split("\n")
textutil.set_ssh_config(conf_file, "ClientAliveInterval", "180")
fileutil.write_file(conf_file_path, '\n'.join(conf_file))
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 = conf.get_sshd_conf_file_path()
conf_file = fileutil.read_file(conf_file_path).split("\n")
textutil.set_ssh_config(conf_file, "PasswordAuthentication", option)
textutil.set_ssh_config(conf_file, "ChallengeResponseAuthentication",
option)
fileutil.write_file(conf_file_path, "\n".join(conf_file))
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 = conf.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 = conf.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):
dvd = self.get_dvd_device()
retcode = shellutil.run("eject {0}".format(dvd))
if chk_err and retcode != 0:
raise OSUtilError("Failed to eject dvd: ret={0}".format(retcode))
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 remove_rules_files(self, rules_files=__RULES_FILES__):
lib_dir = conf.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 = conf.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.*?(|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" + ustr(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 base64.b64decode(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")
def set_admin_access_to_ip(self, dest_ip):
#This allows root to access dest_ip
rm_old= "iptables -D OUTPUT -d {0} -j ACCEPT -m owner --uid-owner 0"
rule = "iptables -A OUTPUT -d {0} -j ACCEPT -m owner --uid-owner 0"
shellutil.run(rm_old.format(dest_ip), chk_err=False)
shellutil.run(rule.format(dest_ip))
#This blocks all other users to access dest_ip
rm_old = "iptables -D OUTPUT -d {0} -j DROP"
rule = "iptables -A OUTPUT -d {0} -j DROP"
shellutil.run(rm_old.format(dest_ip), chk_err=False)
shellutil.run(rule.format(dest_ip))
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/protocolUtil.py 0000664 0000000 0000000 00000020577 12651710031 0025260 0 ustar 00root root 0000000 0000000 # Microsoft 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 shutil
import time
import threading
import azurelinuxagent.conf as conf
import azurelinuxagent.logger as logger
from azurelinuxagent.exception import ProtocolError, OSUtilError, \
ProtocolNotFoundError, DhcpError
from azurelinuxagent.future import ustr
import azurelinuxagent.utils.fileutil as fileutil
from azurelinuxagent.protocol.ovfenv import OvfEnv
from azurelinuxagent.protocol.wire import WireProtocol
from azurelinuxagent.protocol.metadata import MetadataProtocol, METADATA_ENDPOINT
import azurelinuxagent.utils.shellutil as shellutil
OVF_FILE_NAME = "ovf-env.xml"
#Tag file to indicate usage of metadata protocol
TAG_FILE_NAME = "useMetadataEndpoint.tag"
PROTOCOL_FILE_NAME = "Protocol"
#MAX retry times for protocol probing
MAX_RETRY = 360
PROBE_INTERVAL = 10
ENDPOINT_FILE_NAME = "WireServerEndpoint"
class ProtocolUtil(object):
"""
ProtocolUtil handles initialization for protocol instance. 2 protocol types
are invoked, wire protocol and metadata protocols.
"""
def __init__(self, distro):
self.distro = distro
self.protocol = None
self.lock = threading.Lock()
def copy_ovf_env(self):
"""
Copy ovf env file from dvd to hard disk.
Remove password before save it to the disk
"""
dvd_mount_point = conf.get_dvd_mount_point()
ovf_file_path_on_dvd = os.path.join(dvd_mount_point, OVF_FILE_NAME)
tag_file_path_on_dvd = os.path.join(dvd_mount_point, TAG_FILE_NAME)
try:
self.distro.osutil.mount_dvd()
ovfxml = fileutil.read_file(ovf_file_path_on_dvd, remove_bom=True)
ovfenv = OvfEnv(ovfxml)
ovfxml = re.sub(".*?<", "*<", ovfxml)
ovf_file_path = os.path.join(conf.get_lib_dir(), OVF_FILE_NAME)
fileutil.write_file(ovf_file_path, ovfxml)
if os.path.isfile(tag_file_path_on_dvd):
logger.info("Found {0} in provisioning ISO", TAG_FILE_NAME)
tag_file_path = os.path.join(conf.get_lib_dir(), TAG_FILE_NAME)
shutil.copyfile(tag_file_path_on_dvd, tag_file_path)
except (OSUtilError, IOError) as e:
raise ProtocolError(ustr(e))
try:
self.distro.osutil.umount_dvd()
self.distro.osutil.eject_dvd()
except OSUtilError as e:
logger.warn(ustr(e))
return ovfenv
def get_ovf_env(self):
"""
Load saved ovf-env.xml
"""
ovf_file_path = os.path.join(conf.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 _get_wireserver_endpoint(self):
try:
file_path = os.path.join(conf.get_lib_dir(), ENDPOINT_FILE_NAME)
return fileutil.read_file(file_path)
except IOError as e:
raise OSUtilError(ustr(e))
def _set_wireserver_endpoint(self, endpoint):
try:
file_path = os.path.join(conf.get_lib_dir(), ENDPOINT_FILE_NAME)
fileutil.write_file(file_path, endpoint)
except IOError as e:
raise OSUtilError(ustr(e))
def _detect_wire_protocol(self):
endpoint = self.distro.dhcp_handler.endpoint
if endpoint is None:
logger.info("WireServer endpoint is not found. Rerun dhcp handler")
try:
self.distro.dhcp_handler.run()
except DhcpError as e:
raise ProtocolError(ustr(e))
endpoint = self.distro.dhcp_handler.endpoint
try:
protocol = WireProtocol(endpoint)
protocol.detect()
self._set_wireserver_endpoint(endpoint)
return protocol
except ProtocolError as e:
logger.info("WireServer is not responding. Reset endpoint")
self.distro.dhcp_handler.endpoint = None
raise e
def _detect_metadata_protocol(self):
protocol = MetadataProtocol()
protocol.detect()
#Only allow root access METADATA_ENDPOINT
self.distro.osutil.set_admin_access_to_ip(METADATA_ENDPOINT)
return protocol
def _detect_protocol(self, protocols):
"""
Probe protocol endpoints in turn.
"""
protocol_file_path = os.path.join(conf.get_lib_dir(), PROTOCOL_FILE_NAME)
if os.path.isfile(protocol_file_path):
os.remove(protocol_file_path)
for retry in range(0, MAX_RETRY):
for protocol in protocols:
try:
if protocol == "WireProtocol":
return self._detect_wire_protocol()
if protocol == "MetadataProtocol":
return self._detect_metadata_protocol()
except ProtocolError as e:
logger.info("Protocol endpoint not found: {0}, {1}",
protocol, e)
if retry < MAX_RETRY -1:
logger.info("Retry detect protocols: retry={0}", retry)
time.sleep(PROBE_INTERVAL)
raise ProtocolNotFoundError("No protocol found.")
def _get_protocol(self):
"""
Get protocol instance based on previous detecting result.
"""
protocol_file_path = os.path.join(conf.get_lib_dir(),
PROTOCOL_FILE_NAME)
if not os.path.isfile(protocol_file_path):
raise ProtocolError("No protocl found")
protocol_name = fileutil.read_file(protocol_file_path)
if protocol_name == "WireProtocol":
endpoint = self._get_wireserver_endpoint()
return WireProtocol(endpoint)
elif protocol_name == "MetadataProtocol":
return MetadataProtocol()
else:
raise ProtocolNotFoundError(("Unknown protocol: {0}"
"").format(protocol_name))
def detect_protocol(self):
"""
Detect protocol by endpoints
:returns: protocol instance
"""
logger.info("Detect protocol endpoints")
protocols = ["WireProtocol", "MetadataProtocol"]
self.lock.acquire()
try:
if self.protocol is None:
self.protocol = self._detect_protocol(protocols)
return self.protocol
finally:
self.lock.release()
def detect_protocol_by_file(self):
"""
Detect protocol by tag file.
If a file "useMetadataEndpoint.tag" is found on provision iso,
metedata protocol will be used. No need to probe for wire protocol
:returns: protocol instance
"""
logger.info("Detect protocol by file")
self.lock.acquire()
try:
tag_file_path = os.path.join(conf.get_lib_dir(), TAG_FILE_NAME)
if self.protocol is None:
protocols = []
if os.path.isfile(tag_file_path):
protocols.append("MetadataProtocol")
else:
protocols.append("WireProtocol")
self.protocol = self._detect_protocol(protocols)
finally:
self.lock.release()
return self.protocol
def get_protocol(self):
"""
Get protocol instance based on previous detecting result.
:returns protocol instance
"""
self.lock.acquire()
try:
if self.protocol is None:
self.protocol = self._get_protocol()
return self.protocol
finally:
self.lock.release()
return self.protocol
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/provision.py 0000664 0000000 0000000 00000015726 12651710031 0024611 0 ustar 00root root 0000000 0000000 # 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 ustr
import azurelinuxagent.conf as conf
from azurelinuxagent.event import add_event, WALAEventOperation
from azurelinuxagent.exception import ProvisionError, ProtocolError, OSUtilError
from azurelinuxagent.protocol.restapi import ProvisionStatus
import azurelinuxagent.utils.shellutil as shellutil
import azurelinuxagent.utils.fileutil as fileutil
CUSTOM_DATA_FILE="CustomData"
class ProvisionHandler(object):
def __init__(self, distro):
self.distro = distro
def run(self):
#If provision is not enabled, return
if not conf.get_provision_enabled():
logger.info("Provisioning is disabled. Skip.")
return
provisioned = os.path.join(conf.get_lib_dir(), "provisioned")
if os.path.isfile(provisioned):
return
logger.info("Run provision handler.")
logger.info("Copy ovf-env.xml.")
try:
ovfenv = self.distro.protocol_util.copy_ovf_env()
except ProtocolError as e:
self.report_event("Failed to copy ovf-env.xml: {0}".format(e))
return
self.distro.protocol_util.detect_protocol_by_file()
self.report_not_ready("Provisioning", "Starting")
try:
logger.info("Start provisioning")
self.provision(ovfenv)
fileutil.write_file(provisioned, "")
thumbprint = self.reg_ssh_host_key()
logger.info("Finished provisioning")
except ProvisionError as e:
logger.error("Provision failed: {0}", e)
self.report_not_ready("ProvisioningFailed", ustr(e))
self.report_event(ustr(e))
return
self.report_ready(thumbprint)
self.report_event("Provision succeed", is_success=True)
def reg_ssh_host_key(self):
keypair_type = conf.get_ssh_host_keypair_type()
if conf.get_regenerate_ssh_host_key():
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, ovfenv):
logger.info("Handle ovf-env.xml.")
try:
logger.info("Set host name.")
self.distro.osutil.set_hostname(ovfenv.hostname)
logger.info("Publish host name.")
self.distro.osutil.publish_hostname(ovfenv.hostname)
self.config_user_account(ovfenv)
self.save_customdata(ovfenv)
if conf.get_delete_root_password():
self.distro.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")
self.distro.osutil.useradd(ovfenv.username)
if ovfenv.user_password is not None:
logger.info("Set user password.")
crypt_id = conf.get_password_cryptid()
salt_len = conf.get_password_crypt_salt_len()
self.distro.osutil.chpasswd(ovfenv.username, ovfenv.user_password,
crypt_id=crypt_id, salt_len=salt_len)
logger.info("Configure sudoer")
self.distro.osutil.conf_sudoer(ovfenv.username, ovfenv.user_password is None)
logger.info("Configure sshd")
self.distro.osutil.conf_sshd(ovfenv.disable_ssh_password_auth)
#Disable selinux temporary
sel = self.distro.osutil.is_selinux_enforcing()
if sel:
self.distro.osutil.set_selinux_enforce(0)
self.deploy_ssh_pubkeys(ovfenv)
self.deploy_ssh_keypairs(ovfenv)
if sel:
self.distro.osutil.set_selinux_enforce(1)
self.distro.osutil.restart_ssh_service()
def save_customdata(self, ovfenv):
customdata = ovfenv.customdata
if customdata is None:
return
logger.info("Save custom data")
lib_dir = conf.get_lib_dir()
if conf.get_decode_customdata():
customdata= self.distro.osutil.decode_customdata(customdata)
customdata_file = os.path.join(lib_dir, CUSTOM_DATA_FILE)
fileutil.write_file(customdata_file, customdata)
if conf.get_execute_customdata():
logger.info("Execute custom data")
os.chmod(customdata_file, 0o700)
shellutil.run(customdata_file)
def deploy_ssh_pubkeys(self, ovfenv):
for pubkey in ovfenv.ssh_pubkeys:
logger.info("Deploy ssh public key.")
self.distro.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.")
self.distro.osutil.deploy_ssh_keypair(ovfenv.username, keypair)
def report_event(self, message, is_success=False):
add_event(name="WALA", message=message, is_success=is_success,
op=WALAEventOperation.Provision)
def report_not_ready(self, sub_status, description):
status = ProvisionStatus(status="NotReady", subStatus=sub_status,
description=description)
try:
protocol = self.distro.protocol_util.get_protocol()
protocol.report_provision_status(status)
except ProtocolError as e:
self.report_event(ustr(e))
def report_ready(self, thumbprint=None):
status = ProvisionStatus(status="Ready")
status.properties.certificateThumbprint = thumbprint
try:
protocol = self.distro.protocol_util.get_protocol()
protocol.report_provision_status(status)
except ProtocolError as e:
self.report_event(ustr(e))
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/resourceDisk.py 0000664 0000000 0000000 00000015463 12651710031 0025221 0 ustar 00root root 0000000 0000000 # Microsoft 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 ustr
import azurelinuxagent.conf as conf
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 __init__(self, distro):
self.distro = distro
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_resourcedisk_format():
mount_point = self.activate_resource_disk()
if mount_point is not None and \
conf.get_resourcedisk_enable_swap():
self.enable_swap(mount_point)
def activate_resource_disk(self):
logger.info("Activate resource disk")
try:
mount_point = conf.get_resourcedisk_mountpoint()
fs = conf.get_resourcedisk_filesystem()
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=ustr(e),
op=WALAEventOperation.ActivateResourceDisk)
def enable_swap(self, mount_point):
logger.info("Enable swap")
try:
size_mb = conf.get_resourcedisk_swap_size_mb()
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 = self.distro.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 = self.distro.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))
WALinuxAgent-2.1.3/azurelinuxagent/distro/default/scvmm.py 0000664 0000000 0000000 00000003270 12651710031 0023675 0 ustar 00root root 0000000 0000000 # Microsoft 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
VMM_CONF_FILE_NAME = "linuxosconfiguration.xml"
VMM_STARTUP_SCRIPT_NAME= "install"
class ScvmmHandler(object):
def __init__(self, distro):
self.distro = distro
def detect_scvmm_env(self):
logger.info("Detecting Microsoft System Center VMM Environment")
self.distro.osutil.mount_dvd(max_retry=1, chk_err=False)
mount_point = self.distro.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:
self.distro.osutil.umount_dvd(chk_err=False)
return found
def start_scvmm_agent(self):
logger.info("Starting Microsoft System Center VMM Initialization "
"Process")
mount_point = self.distro.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])
WALinuxAgent-2.1.3/azurelinuxagent/distro/loader.py 0000664 0000000 0000000 00000005573 12651710031 0022402 0 ustar 00root root 0000000 0000000 # 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.utils.textutil import Version
from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, \
DISTRO_FULL_NAME
from azurelinuxagent.distro.default.distro import DefaultDistro
from azurelinuxagent.distro.ubuntu.distro import UbuntuDistro, \
Ubuntu14Distro, \
Ubuntu12Distro, \
UbuntuSnappyDistro
from azurelinuxagent.distro.redhat.distro import RedhatDistro, Redhat6xDistro
from azurelinuxagent.distro.coreos.distro import CoreOSDistro
from azurelinuxagent.distro.suse.distro import SUSE11Distro, SUSEDistro
from azurelinuxagent.distro.debian.distro import DebianDistro
def get_distro(distro_name=DISTRO_NAME, distro_version=DISTRO_VERSION,
distro_full_name=DISTRO_FULL_NAME):
if distro_name == "ubuntu":
if Version(distro_version) == Version("12.04") or \
Version(distro_version) == Version("12.10"):
return Ubuntu12Distro()
elif Version(distro_version) == Version("14.04") or \
Version(distro_version) == Version("14.10"):
return Ubuntu14Distro()
elif distro_full_name == "Snappy Ubuntu Core":
return UbuntuSnappyDistro()
else:
return UbuntuDistro()
if distro_name == "coreos":
return CoreOSDistro()
if distro_name == "suse":
if distro_full_name=='SUSE Linux Enterprise Server' and \
Version(distro_version) < Version('12') or \
distro_full_name == 'openSUSE' and \
Version(distro_version) < Version('13.2'):
return SUSE11Distro()
else:
return SUSEDistro()
elif distro_name == "debian":
return DebianDistro()
elif distro_name == "redhat" or distro_name == "centos" or \
distro_name == "oracle":
if Version(distro_version) < Version("7"):
return Redhat6xDistro()
else:
return RedhatDistro()
else:
logger.warn("Unable to load distro implemetation for {0}.", distro_name)
logger.warn("Use default distro implemetation instead.")
return DefaultDistro()
WALinuxAgent-2.1.3/azurelinuxagent/distro/redhat/ 0000775 0000000 0000000 00000000000 12651710031 0022017 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/distro/redhat/__init__.py 0000664 0000000 0000000 00000001226 12651710031 0024131 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/distro/redhat/distro.py 0000664 0000000 0000000 00000002225 12651710031 0023676 0 ustar 00root root 0000000 0000000 # Microsoft 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.default.distro import DefaultDistro
from azurelinuxagent.distro.redhat.osutil import RedhatOSUtil, Redhat6xOSUtil
from azurelinuxagent.distro.coreos.deprovision import CoreOSDeprovisionHandler
class Redhat6xDistro(DefaultDistro):
def __init__(self):
super(Redhat6xDistro, self).__init__()
self.osutil = Redhat6xOSUtil()
class RedhatDistro(DefaultDistro):
def __init__(self):
super(RedhatDistro, self).__init__()
self.osutil = RedhatOSUtil()
WALinuxAgent-2.1.3/azurelinuxagent/distro/redhat/osutil.py 0000664 0000000 0000000 00000007735 12651710031 0023724 0 ustar 00root root 0000000 0000000 #
# 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.conf as conf
import azurelinuxagent.logger as logger
from azurelinuxagent.future import ustr, bytebuffer
from azurelinuxagent.exception import OSUtilError, CryptError
import azurelinuxagent.utils.fileutil as fileutil
import azurelinuxagent.utils.shellutil as shellutil
import azurelinuxagent.utils.textutil as textutil
from azurelinuxagent.utils.cryptutil import CryptUtil
from azurelinuxagent.distro.default.osutil import DefaultOSUtil
class Redhat6xOSUtil(DefaultOSUtil):
def __init__(self):
super(Redhat6xOSUtil, self).__init__()
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 openssl_to_openssh(self, input_file, output_file):
pubkey = fileutil.read_file(input_file)
try:
cryptutil = CryptUtil(conf.get_openssl_cmd())
ssh_rsa_pubkey = cryptutil.asn1_to_ssh(pubkey)
except CryptError as e:
raise OSUtilError(ustr(e))
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
def set_hostname(self, hostname):
"""
Set /etc/sysconfig/network
"""
fileutil.update_conf_file('/etc/sysconfig/network',
'HOSTNAME',
'HOSTNAME={0}'.format(hostname))
shellutil.run("hostname {0}".format(hostname), chk_err=False)
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))
class RedhatOSUtil(Redhat6xOSUtil):
def __init__(self):
super(RedhatOSUtil, self).__init__()
def set_hostname(self, hostname):
"""
Set /etc/hostname
Unlike redhat 6.x, redhat 7.x will set hostname to /etc/hostname
"""
DefaultOSUtil.set_hostname(self, hostname)
def publish_hostname(self, hostname):
"""
Restart NetworkManager first before publishing hostname
"""
shellutil.run("service NetworkManager restart")
super(RedhatOSUtil, self).publish_hostname(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)
def openssl_to_openssh(self, input_file, output_file):
DefaultOSUtil.openssl_to_openssh(self, input_file, output_file)
WALinuxAgent-2.1.3/azurelinuxagent/distro/suse/ 0000775 0000000 0000000 00000000000 12651710031 0021527 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/distro/suse/__init__.py 0000664 0000000 0000000 00000001226 12651710031 0023641 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/distro/suse/distro.py 0000664 0000000 0000000 00000002065 12651710031 0023410 0 ustar 00root root 0000000 0000000 # Microsoft 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.default.distro import DefaultDistro
from azurelinuxagent.distro.suse.osutil import SUSE11OSUtil, SUSEOSUtil
class SUSE11Distro(DefaultDistro):
def __init__(self):
super(SUSE11Distro, self).__init__()
self.osutil = SUSE11OSUtil()
class SUSEDistro(DefaultDistro):
def __init__(self):
super(SUSEDistro, self).__init__()
self.osutil = SUSEOSUtil()
WALinuxAgent-2.1.3/azurelinuxagent/distro/suse/osutil.py 0000664 0000000 0000000 00000007062 12651710031 0023425 0 ustar 00root root 0000000 0000000 #
# 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 stop_dhcp_service(self):
cmd = "systemctl stop {0}".format(self.dhclient_name)
return shellutil.run(cmd, chk_err=False)
def start_dhcp_service(self):
cmd = "systemctl start {0}".format(self.dhclient_name)
return shellutil.run(cmd, chk_err=False)
def start_network(self) :
return shellutil.run("systemctl start network", chk_err=False)
def restart_ssh_service(self):
return shellutil.run("systemctl restart sshd", chk_err=False)
def stop_agent_service(self):
return shellutil.run("systemctl stop waagent", chk_err=False)
def start_agent_service(self):
return shellutil.run("systemctl start waagent", chk_err=False)
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)
WALinuxAgent-2.1.3/azurelinuxagent/distro/ubuntu/ 0000775 0000000 0000000 00000000000 12651710031 0022072 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/distro/ubuntu/__init__.py 0000664 0000000 0000000 00000001226 12651710031 0024204 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/distro/ubuntu/deprovision.py 0000664 0000000 0000000 00000003450 12651710031 0025007 0 ustar 00root root 0000000 0000000 # Microsoft 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 __init__(self, distro):
super(UbuntuDeprovisionHandler, self).__init__(distro)
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
WALinuxAgent-2.1.3/azurelinuxagent/distro/ubuntu/distro.py 0000664 0000000 0000000 00000004322 12651710031 0023751 0 ustar 00root root 0000000 0000000 # Microsoft 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.default.distro import DefaultDistro
from azurelinuxagent.distro.ubuntu.osutil import Ubuntu14OSUtil, \
Ubuntu12OSUtil, \
UbuntuOSUtil, \
UbuntuSnappyOSUtil
from azurelinuxagent.distro.ubuntu.provision import UbuntuProvisionHandler
from azurelinuxagent.distro.ubuntu.deprovision import UbuntuDeprovisionHandler
class UbuntuDistro(DefaultDistro):
def __init__(self):
super(UbuntuDistro, self).__init__()
self.osutil = UbuntuOSUtil()
self.provision_handler = UbuntuProvisionHandler(self)
self.deprovision_handler = UbuntuDeprovisionHandler(self)
class Ubuntu12Distro(DefaultDistro):
def __init__(self):
super(Ubuntu12Distro, self).__init__()
self.osutil = Ubuntu12OSUtil()
self.provision_handler = UbuntuProvisionHandler(self)
self.deprovision_handler = UbuntuDeprovisionHandler(self)
class Ubuntu14Distro(DefaultDistro):
def __init__(self):
super(Ubuntu14Distro, self).__init__()
self.osutil = Ubuntu14OSUtil()
self.provision_handler = UbuntuProvisionHandler(self)
self.deprovision_handler = UbuntuDeprovisionHandler(self)
class UbuntuSnappyDistro(DefaultDistro):
def __init__(self):
super(UbuntuSnappyDistro, self).__init__()
self.osutil = UbuntuSnappyOSUtil()
self.provision_handler = UbuntuProvisionHandler(self)
self.deprovision_handler = UbuntuDeprovisionHandler(self)
WALinuxAgent-2.1.3/azurelinuxagent/distro/ubuntu/osutil.py 0000664 0000000 0000000 00000004446 12651710031 0023773 0 ustar 00root root 0000000 0000000 #
# 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 Ubuntu14OSUtil(DefaultOSUtil):
def __init__(self):
super(Ubuntu14OSUtil, 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 Ubuntu12OSUtil(Ubuntu14OSUtil):
def __init__(self):
super(Ubuntu12OSUtil, 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(Ubuntu14OSUtil):
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)
class UbuntuSnappyOSUtil(Ubuntu14OSUtil):
def __init__(self):
super(UbuntuSnappyOSUtil, self).__init__()
self.conf_file_path = '/apps/walinuxagent/current/waagent.conf'
def remove_rules_files(self, rules_files=""):
pass
def restore_rules_files(self, rules_files=""):
pass
WALinuxAgent-2.1.3/azurelinuxagent/distro/ubuntu/provision.py 0000664 0000000 0000000 00000007214 12651710031 0024500 0 ustar 00root root 0000000 0000000 # Microsoft 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 ustr
import azurelinuxagent.conf as conf
import azurelinuxagent.protocol.ovfenv as ovfenv
from azurelinuxagent.event import add_event, WALAEventOperation
from azurelinuxagent.exception import ProvisionError, ProtocolError
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 __init__(self, distro):
self.distro = distro
def run(self):
#If provision is enabled, run default provision handler
if conf.get_provision_enabled():
super(UbuntuProvisionHandler, self).run()
return
logger.info("run Ubuntu provision handler")
provisioned = os.path.join(conf.get_lib_dir(), "provisioned")
if os.path.isfile(provisioned):
return
logger.info("Waiting cloud-init to copy ovf-env.xml.")
self.wait_for_ovfenv()
protocol = self.distro.protocol_util.detect_protocol()
self.report_not_ready("Provisioning", "Starting")
logger.info("Sleep 15 seconds to prevent throttling")
time.sleep(15) #Sleep to prevent throttling
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")
except ProvisionError as e:
logger.error("Provision failed: {0}", e)
self.report_not_ready("ProvisioningFailed", ustr(e))
self.report_event(ustr(e))
return
self.report_ready(thumbprint)
self.report_event("Provision succeed", is_success=True)
def wait_for_ovfenv(self, max_retry=60):
"""
Wait for cloud-init to copy ovf-env.xml file from provision ISO
"""
for retry in range(0, max_retry):
try:
self.distro.protocol_util.get_ovf_env()
return
except ProtocolError:
if retry < max_retry - 1:
logger.info("Wait for cloud-init to copy ovf-env.xml")
time.sleep(5)
raise ProvisionError("ovf-env.xml is not copied")
def wait_for_ssh_host_key(self, max_retry=60):
"""
Wait for cloud-init to generate ssh host key
"""
kepair_type = conf.get_ssh_host_keypair_type()
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.")
WALinuxAgent-2.1.3/azurelinuxagent/event.py 0000664 0000000 0000000 00000011465 12651710031 0020746 0 ustar 00root root 0000000 0000000 # 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.exception import EventError, ProtocolError
from azurelinuxagent.future import ustr
from azurelinuxagent.protocol.restapi import TelemetryEventParam, \
TelemetryEventList, \
TelemetryEvent, \
set_properties, get_properties
from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION, \
DISTRO_CODE_NAME, AGENT_VERSION
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 EventLogger(object):
def __init__(self):
self.event_dir = None
def save_event(self, data):
if self.event_dir is None:
logger.warn("Event reporter is not initialized.")
return
if not os.path.exists(self.event_dir):
os.mkdir(self.event_dir)
os.chmod(self.event_dir, 0o700)
if len(os.listdir(self.event_dir)) > 1000:
raise EventError("Too many files under: {0}".format(self.event_dir))
filename = os.path.join(self.event_dir, ustr(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(self, name, op="", is_success=True, duration=0, version="1.0",
message="", evt_type="", is_internal=False):
event = TelemetryEvent(1, "69B669B9-4AF8-4C50-BDC4-6006FA76E975")
event.parameters.append(TelemetryEventParam('Name', name))
event.parameters.append(TelemetryEventParam('Version', version))
event.parameters.append(TelemetryEventParam('IsInternal', is_internal))
event.parameters.append(TelemetryEventParam('Operation', op))
event.parameters.append(TelemetryEventParam('OperationSuccess',
is_success))
event.parameters.append(TelemetryEventParam('Message', message))
event.parameters.append(TelemetryEventParam('Duration', duration))
event.parameters.append(TelemetryEventParam('ExtensionType', evt_type))
data = get_properties(event)
try:
self.save_event(json.dumps(data))
except EventError as e:
logger.error("{0}", e)
__event_logger__ = EventLogger()
def add_event(name, op="", is_success=True, duration=0, version="1.0",
message="", evt_type="", is_internal=False,
reporter=__event_logger__):
log = logger.info if is_success else logger.error
log("Event: name={0}, op={1}, message={2}", name, op, message)
if reporter.event_dir is None:
logger.warn("Event reporter is not initialized.")
return
reporter.add_event(name, op=op, is_success=is_success, duration=duration,
version=version, message=message, evt_type=evt_type,
is_internal=is_internal)
def init_event_logger(event_dir, reporter=__event_logger__):
reporter.event_dir = event_dir
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)
add_event(name, is_success=False, message=message,
op=WALAEventOperation.UnhandledError)
def enable_unhandled_err_dump(name):
atexit.register(dump_unhandled_err, name)
WALinuxAgent-2.1.3/azurelinuxagent/exception.py 0000664 0000000 0000000 00000006437 12651710031 0021626 0 ustar 00root root 0000000 0000000 # Microsoft 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, inner=None):
msg = u"({0}){1}".format(errno, msg)
if inner is not None:
msg = u"{0} \n inner error: {1}".format(msg, inner)
super(AgentError, self).__init__(msg)
class AgentConfigError(AgentError):
"""
When configure file is not found or malformed.
"""
def __init__(self, msg=None, inner=None):
super(AgentConfigError, self).__init__('000001', msg, inner)
class AgentNetworkError(AgentError):
"""
When network is not avaiable.
"""
def __init__(self, msg=None, inner=None):
super(AgentNetworkError, self).__init__('000002', msg, inner)
class ExtensionError(AgentError):
"""
When failed to execute an extension
"""
def __init__(self, msg=None, inner=None):
super(ExtensionError, self).__init__('000003', msg, inner)
class ProvisionError(AgentError):
"""
When provision failed
"""
def __init__(self, msg=None, inner=None):
super(ProvisionError, self).__init__('000004', msg, inner)
class ResourceDiskError(AgentError):
"""
Mount resource disk failed
"""
def __init__(self, msg=None, inner=None):
super(ResourceDiskError, self).__init__('000005', msg, inner)
class DhcpError(AgentError):
"""
Failed to handle dhcp response
"""
def __init__(self, msg=None, inner=None):
super(DhcpError, self).__init__('000006', msg, inner)
class OSUtilError(AgentError):
"""
Failed to perform operation to OS configuration
"""
def __init__(self, msg=None, inner=None):
super(OSUtilError, self).__init__('000007', msg, inner)
class ProtocolError(AgentError):
"""
Azure protocol error
"""
def __init__(self, msg=None, inner=None):
super(ProtocolError, self).__init__('000008', msg, inner)
class ProtocolNotFoundError(ProtocolError):
"""
Azure protocol endpoint not found
"""
def __init__(self, msg=None, inner=None):
super(ProtocolNotFoundError, self).__init__(msg, inner)
class HttpError(AgentError):
"""
Http request failure
"""
def __init__(self, msg=None, inner=None):
super(HttpError, self).__init__('000009', msg, inner)
class EventError(AgentError):
"""
Event reporting error
"""
def __init__(self, msg=None, inner=None):
super(EventError, self).__init__('000010', msg, inner)
class CryptError(AgentError):
"""
Encrypt/Decrypt error
"""
def __init__(self, msg=None, inner=None):
super(CryptError, self).__init__('000011', msg, inner)
WALinuxAgent-2.1.3/azurelinuxagent/future.py 0000664 0000000 0000000 00000001106 12651710031 0021126 0 ustar 00root root 0000000 0000000 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
"""Rename Python3 str to ustr"""
ustr = str
bytebuffer = memoryview
read_input = input
elif sys.version_info[0] == 2:
import httplib as httpclient
from urlparse import urlparse
"""Rename Python2 unicode to ustr"""
ustr = unicode
bytebuffer = buffer
read_input = raw_input
else:
raise ImportError("Unknown python version:{0}".format(sys.version_info))
WALinuxAgent-2.1.3/azurelinuxagent/logger.py 0000664 0000000 0000000 00000011351 12651710031 0021076 0 ustar 00root root 0000000 0000000 # 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 os
import sys
from azurelinuxagent.future import ustr
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 verb(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 msg_format is not unicode convert it to unicode
if type(msg_format) is not ustr:
msg_format = ustr(msg_format, errors="backslashreplace")
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 = ustr(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.verb(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")
WALinuxAgent-2.1.3/azurelinuxagent/metadata.py 0000664 0000000 0000000 00000005203 12651710031 0021376 0 ustar 00root root 0000000 0000000 # Microsoft 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
import azurelinuxagent.utils.fileutil as fileutil
from azurelinuxagent.future import ustr
def get_distro():
if 'FreeBSD' in platform.system():
release = re.sub('\-.*\Z', '', ustr(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.3'
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 is_snappy():
if os.path.exists("/etc/motd"):
motd = fileutil.read_file("/etc/motd")
if "snappy" in motd:
return True
return False
if is_snappy():
DISTRO_FULL_NAME = "Snappy Ubuntu Core"
WALinuxAgent-2.1.3/azurelinuxagent/protocol/ 0000775 0000000 0000000 00000000000 12651710031 0021105 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/protocol/__init__.py 0000664 0000000 0000000 00000001225 12651710031 0023216 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/protocol/metadata.py 0000664 0000000 0000000 00000017264 12651710031 0023251 0 ustar 00root root 0000000 0000000 # Microsoft 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
import shutil
import os
import time
from azurelinuxagent.exception import ProtocolError, HttpError
from azurelinuxagent.future import httpclient, ustr
import azurelinuxagent.conf as conf
import azurelinuxagent.logger as logger
import azurelinuxagent.utils.restutil as restutil
import azurelinuxagent.utils.textutil as textutil
import azurelinuxagent.utils.fileutil as fileutil
from azurelinuxagent.utils.cryptutil import CryptUtil
from azurelinuxagent.protocol.restapi import *
METADATA_ENDPOINT='169.254.169.254'
APIVERSION='2015-05-01-preview'
BASE_URI = "http://{0}/Microsoft.Compute/{1}?api-version={2}{3}"
TRANSPORT_PRV_FILE_NAME = "V2TransportPrivate.pem"
TRANSPORT_CERT_FILE_NAME = "V2TransportCert.pem"
#TODO remote workarround for azure stack
MAX_PING = 30
RETRY_PING_INTERVAL = 10
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=METADATA_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.vm_status_uri = BASE_URI.format(self.endpoint, "status/vmagent",
self.apiversion, "")
self.ext_status_uri = BASE_URI.format(self.endpoint,
"status/extensions/{0}",
self.apiversion, "")
self.event_uri = BASE_URI.format(self.endpoint, "status/telemetry",
self.apiversion, "")
def _get_data(self, url, headers=None):
try:
resp = restutil.http_get(url, headers=headers)
except HttpError as e:
raise ProtocolError(ustr(e))
if resp.status != httpclient.OK:
raise ProtocolError("{0} - GET: {1}".format(resp.status, url))
data = resp.read()
etag = resp.getheader('ETag')
if data is None:
return None
data = json.loads(ustr(data, encoding="utf-8"))
return data, etag
def _put_data(self, url, data, headers=None):
headers = _add_content_type(headers)
try:
resp = restutil.http_put(url, json.dumps(data), headers=headers)
except HttpError as e:
raise ProtocolError(ustr(e))
if resp.status != httpclient.OK:
raise ProtocolError("{0} - PUT: {1}".format(resp.status, url))
def _post_data(self, url, data, headers=None):
headers = _add_content_type(headers)
try:
resp = restutil.http_post(url, json.dumps(data), headers=headers)
except HttpError as e:
raise ProtocolError(ustr(e))
if resp.status != httpclient.CREATED:
raise ProtocolError("{0} - POST: {1}".format(resp.status, url))
def _get_trans_cert(self):
trans_crt_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_CERT_FILE_NAME)
if not os.path.isfile(trans_crt_file):
raise ProtocolError("{0} is missing.".format(trans_crt_file))
content = fileutil.read_file(trans_crt_file)
return textutil.get_bytes_from_pem(content)
def detect(self):
self.get_vminfo()
trans_prv_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_PRV_FILE_NAME)
trans_cert_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_CERT_FILE_NAME)
cryptutil = CryptUtil(conf.get_openssl_cmd())
cryptutil.gen_transport_cert(trans_prv_file, trans_cert_file)
#"Install" the cert and private key to /var/lib/waagent
thumbprint = cryptutil.get_thumbprint_from_crt(trans_cert_file)
prv_file = os.path.join(conf.get_lib_dir(),
"{0}.prv".format(thumbprint))
crt_file = os.path.join(conf.get_lib_dir(),
"{0}.crt".format(thumbprint))
shutil.copyfile(trans_prv_file, prv_file)
shutil.copyfile(trans_cert_file, crt_file)
def get_vminfo(self):
vminfo = VMInfo()
data, etag = self._get_data(self.identity_uri)
set_properties("vminfo", vminfo, data)
return vminfo
def get_certs(self):
#TODO download and save certs
return CertList()
def get_ext_handlers(self):
headers = {
"x-ms-vmagent-public-x509-cert": self._get_trans_cert()
}
ext_list = ExtHandlerList()
data, etag = self._get_data(self.ext_uri, headers=headers)
set_properties("extensionHandlers", ext_list.extHandlers, data)
return ext_list, etag
def get_ext_handler_pkgs(self, ext_handler):
ext_handler_pkgs = ExtHandlerPackageList()
data = None
for version_uri in ext_handler.versionUris:
try:
data, etag = self._get_data(version_uri.uri)
break
except ProtocolError as e:
logger.warn("Failed to get version uris: {0}", e)
logger.info("Retry getting version uris")
set_properties("extensionPackages", ext_handler_pkgs, data)
return ext_handler_pkgs
def report_provision_status(self, provision_status):
validata_param('provisionStatus', provision_status, ProvisionStatus)
data = get_properties(provision_status)
self._put_data(self.provision_status_uri, data)
def report_vm_status(self, vm_status):
validata_param('vmStatus', vm_status, VMStatus)
data = get_properties(vm_status)
#TODO code field is not implemented for metadata protocol yet. Remove it
handler_statuses = data['vmAgent']['extensionHandlers']
for handler_status in handler_statuses:
try:
handler_status.pop('code', None)
except KeyError:
pass
self._put_data(self.vm_status_uri, data)
def report_ext_status(self, ext_handler_name, ext_name, ext_status):
validata_param('extensionStatus', ext_status, ExtensionStatus)
data = get_properties(ext_status)
uri = self.ext_status_uri.format(ext_name)
self._put_data(uri, data)
def report_event(self, events):
#TODO disable telemetry for azure stack test
#validata_param('events', events, TelemetryEventList)
#data = get_properties(events)
#self._post_data(self.event_uri, data)
pass
WALinuxAgent-2.1.3/azurelinuxagent/protocol/ovfenv.py 0000664 0000000 0000000 00000010257 12651710031 0022767 0 ustar 00root root 0000000 0000000 # Microsoft 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 provisioning ISO and local cache
"""
import os
import re
import shutil
import xml.dom.minidom as minidom
import azurelinuxagent.logger as logger
from azurelinuxagent.exception import ProtocolError
from azurelinuxagent.future import ustr
import azurelinuxagent.utils.fileutil as fileutil
from azurelinuxagent.utils.textutil import parse_doc, findall, find, findtext
OVF_VERSION = "1.0"
OVF_NAME_SPACE = "http://schemas.dmtf.org/ovf/environment/1"
WA_NAME_SPACE = "http://schemas.microsoft.com/windowsazure"
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))
WALinuxAgent-2.1.3/azurelinuxagent/protocol/restapi.py 0000664 0000000 0000000 00000020056 12651710031 0023131 0 ustar 00root root 0000000 0000000 # Microsoft 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
from azurelinuxagent.exception import ProtocolError, HttpError
from azurelinuxagent.future import ustr
import azurelinuxagent.utils.restutil as restutil
def validata_param(name, val, expected_type):
if val is None:
raise ProtocolError("{0} is None".format(name))
if not isinstance(val, expected_type):
raise ProtocolError(("{0} type should be {1} not {2}"
"").format(name, expected_type, type(val)))
def set_properties(name, obj, data):
if isinstance(obj, DataContract):
validata_param("Property '{0}'".format(name), data, dict)
for prob_name, prob_val in data.items():
prob_full_name = "{0}.{1}".format(name, prob_name)
try:
prob = getattr(obj, prob_name)
except AttributeError:
logger.warn("Unknown property: {0}", prob_full_name)
continue
prob = set_properties(prob_full_name, prob, prob_val)
setattr(obj, prob_name, prob)
return obj
elif isinstance(obj, DataContractList):
validata_param("List '{0}'".format(name), data, list)
for item_data in data:
item = obj.item_cls()
item = set_properties(name, item, item_data)
obj.append(item)
return obj
else:
return data
def get_properties(obj):
if isinstance(obj, DataContract):
data = {}
props = vars(obj)
for prob_name, prob in list(props.items()):
data[prob_name] = get_properties(prob)
return data
elif isinstance(obj, DataContractList):
data = []
for item in obj:
item_data = get_properties(item)
data.append(item_data)
return data
else:
return obj
class DataContract(object):
pass
class DataContractList(list):
def __init__(self, item_cls):
self.item_cls = item_cls
"""
Data contract between guest and host
"""
class VMInfo(DataContract):
def __init__(self, subscriptionId=None, vmName=None, containerId=None,
roleName=None, roleInstanceName=None, tenantName=None):
self.subscriptionId = subscriptionId
self.vmName = vmName
self.containerId = containerId
self.roleName = roleName
self.roleInstanceName = roleInstanceName
self.tenantName = tenantName
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 Extension(DataContract):
def __init__(self, name=None, sequenceNumber=None, publicSettings=None,
protectedSettings=None, certificateThumbprint=None):
self.name = name
self.sequenceNumber = sequenceNumber
self.publicSettings = publicSettings
self.protectedSettings = protectedSettings
self.certificateThumbprint = certificateThumbprint
class ExtHandlerProperties(DataContract):
def __init__(self):
self.version = None
self.upgradePolicy = None
self.state = None
self.extensions = DataContractList(Extension)
class ExtHandlerVersionUri(DataContract):
def __init__(self):
self.uri = None
class ExtHandler(DataContract):
def __init__(self, name=None):
self.name = name
self.properties = ExtHandlerProperties()
self.versionUris = DataContractList(ExtHandlerVersionUri)
class ExtHandlerList(DataContract):
def __init__(self):
self.extHandlers = DataContractList(ExtHandler)
class ExtHandlerPackageUri(DataContract):
def __init__(self, uri=None):
self.uri = uri
class ExtHandlerPackage(DataContract):
def __init__(self, version = None):
self.version = version
self.uris = DataContractList(ExtHandlerPackageUri)
class ExtHandlerPackageList(DataContract):
def __init__(self):
self.versions = DataContractList(ExtHandlerPackage)
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 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, configurationAppliedTime=None, operation=None,
status=None, seq_no=None, code=None, message=None):
self.configurationAppliedTime = configurationAppliedTime
self.operation = operation
self.status = status
self.sequenceNumber = seq_no
self.code = code
self.message = message
self.substatusList = DataContractList(ExtensionSubStatus)
class ExtHandlerStatus(DataContract):
def __init__(self, name=None, version=None, status=None, code=0,
message=None):
self.name = name
self.version = version
self.status = status
self.code = code
self.message = message
self.extensions = DataContractList(ustr)
class VMAgentStatus(DataContract):
def __init__(self, version=None, status=None, message=None):
self.version = version
self.status = status
self.message = message
self.extensionHandlers = DataContractList(ExtHandlerStatus)
class VMStatus(DataContract):
def __init__(self):
self.vmAgent = VMAgentStatus()
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 detect(self):
raise NotImplementedError()
def get_vminfo(self):
raise NotImplementedError()
def get_certs(self):
raise NotImplementedError()
def get_ext_handlers(self):
raise NotImplementedError()
def get_ext_handler_pkgs(self, extension):
raise NotImplementedError()
def download_ext_handler_pkg(self, uri):
try:
resp = restutil.http_get(uri, chk_proxy=True)
if resp.status == restutil.httpclient.OK:
return resp.read()
except HttpError as e:
raise ProtocolError("Failed to download from: {0}".format(uri), e)
def report_provision_status(self, provision_status):
raise NotImplementedError()
def report_vm_status(self, vm_status):
raise NotImplementedError()
def report_ext_status(self, ext_handler_name, ext_name, ext_status):
raise NotImplementedError()
def report_event(self, event):
raise NotImplementedError()
WALinuxAgent-2.1.3/azurelinuxagent/protocol/wire.py 0000664 0000000 0000000 00000131373 12651710031 0022435 0 ustar 00root root 0000000 0000000 # Microsoft 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 azurelinuxagent.conf as conf
import azurelinuxagent.logger as logger
from azurelinuxagent.exception import ProtocolError, HttpError, \
ProtocolNotFoundError
from azurelinuxagent.future import ustr, httpclient, bytebuffer
import azurelinuxagent.utils.restutil as restutil
from azurelinuxagent.utils.textutil import parse_doc, findall, find, findtext, \
getattrib, gettext, remove_bom, \
get_bytes_from_pem
import azurelinuxagent.utils.fileutil as fileutil
import azurelinuxagent.utils.shellutil as shellutil
from azurelinuxagent.utils.cryptutil import CryptUtil
from azurelinuxagent.protocol.restapi 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"
ENDPOINT_FINE_NAME = "WireServer"
SHORT_WAITING_INTERVAL = 1 # 1 second
LONG_WAITING_INTERVAL = 15 # 15 seconds
class WireProtocolResourceGone(ProtocolError):
pass
class WireProtocol(Protocol):
"""Slim layer to adapte wire protocol data to metadata protocol interface"""
def __init__(self, endpoint):
if endpoint is None:
raise ProtocolError("WireProtocl endpoint is None")
self.endpoint = endpoint
self.client = WireClient(self.endpoint)
def detect(self):
self.client.check_wire_protocol_version()
trans_prv_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_PRV_FILE_NAME)
trans_cert_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_CERT_FILE_NAME)
cryptutil = CryptUtil(conf.get_openssl_cmd())
cryptutil.gen_transport_cert(trans_prv_file, trans_cert_file)
self.client.update_goal_state(forced=True)
def get_vminfo(self):
goal_state = self.client.get_goal_state()
hosting_env = self.client.get_hosting_env()
vminfo = VMInfo()
vminfo.subscriptionId = None
vminfo.vmName = hosting_env.vm_name
vminfo.tenantName = hosting_env.deployment_name
vminfo.roleName = hosting_env.role_name
vminfo.roleInstanceName = goal_state.role_instance_id
vminfo.containerId = goal_state.container_id
return vminfo
def get_certs(self):
certificates = self.client.get_certs()
return certificates.cert_list
def get_ext_handlers(self):
logger.verb("Get extension handler config")
#Update goal state to get latest extensions config
self.client.update_goal_state()
goal_state = self.client.get_goal_state()
ext_conf = self.client.get_ext_conf()
#In wire protocol, incarnation is equivalent to ETag
return ext_conf.ext_handlers, goal_state.incarnation
def get_ext_handler_pkgs(self, ext_handler):
logger.verb("Get extension handler package")
goal_state = self.client.get_goal_state()
man = self.client.get_ext_manifest(ext_handler, goal_state)
return man.pkg_list
def report_provision_status(self, provision_status):
validata_param("provision_status", provision_status, ProvisionStatus)
if provision_status.status is not None:
self.client.report_health(provision_status.status,
provision_status.subStatus,
provision_status.description)
if provision_status.properties.certificateThumbprint is not None:
thumbprint = provision_status.properties.certificateThumbprint
self.client.report_role_prop(thumbprint)
def report_vm_status(self, vm_status):
validata_param("vm_status", vm_status, VMStatus)
self.client.status_blob.set_vm_status(vm_status)
self.client.upload_status_blob()
def report_ext_status(self, ext_handler_name, ext_name, ext_status):
validata_param("ext_status", ext_status, ExtensionStatus)
self.client.status_blob.set_ext_status(ext_handler_name, ext_status)
def report_event(self, events):
validata_param("events", events, TelemetryEventList)
self.client.report_event(events)
def _build_role_properties(container_id, role_instance_id, thumbprint):
xml = (u""
u""
u""
u"{0}"
u""
u""
u"{1}"
u""
u""
u""
u""
u""
u""
u""
u"").format(container_id, role_instance_id, thumbprint)
return xml
def _build_health_report(incarnation, container_id, role_instance_id,
status, substatus, description):
#Escape '&', '<' and '>'
description = saxutils.escape(ustr(description))
detail = u''
if substatus is not None:
substatus = saxutils.escape(ustr(substatus))
detail = (u""
u"{0}"
u"{1}"
u" ").format(substatus, description)
xml = (u""
u""
u"{0}"
u""
u"{1}"
u""
u""
u"{2}"
u""
u"{3}"
u"{4}"
u""
u""
u""
u""
u""
u"").format(incarnation,
container_id,
role_instance_id,
status,
detail)
return xml
"""
Convert VMStatus object to status blob format
"""
def ga_status_to_v1(ga_status):
formatted_msg = {
'lang' : 'en-US',
'message' : ga_status.message
}
v1_ga_status = {
'version' : ga_status.version,
'status' : ga_status.status,
'formattedMessage' : formatted_msg
}
return v1_ga_status
def ext_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 ext_status_to_v1(ext_name, ext_status):
if ext_status is None:
return None
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
v1_sub_status = ext_substatus_to_v1(ext_status.substatusList)
v1_ext_status = {
"status":{
"name": ext_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
}
},
"version": 1.0,
"timestampUTC": timestamp
}
if len(v1_sub_status) != 0:
v1_ext_status['substatus'] = v1_sub_status
return v1_ext_status
def ext_handler_status_to_v1(handler_status, ext_statuses, timestamp):
v1_handler_status = {
'handlerVersion' : handler_status.version,
'handlerName' : handler_status.name,
'status' : handler_status.status,
'code': handler_status.code
}
if handler_status.message is not None:
v1_handler_status["formattedMessage"] = {
"lang":"en-US",
"message": handler_status.message
}
if len(handler_status.extensions) > 0:
#Currently, no more than one extension per handler
ext_name = handler_status.extensions[0]
ext_status = ext_statuses.get(ext_name)
v1_ext_status = ext_status_to_v1(ext_name, ext_status)
if ext_status is not None and v1_ext_status is not None:
v1_handler_status["runtimeSettingsStatus"] = {
'settingsStatus' : v1_ext_status,
'sequenceNumber' : ext_status.sequenceNumber
}
return v1_handler_status
def vm_status_to_v1(vm_status, ext_statuses):
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
v1_ga_status = ga_status_to_v1(vm_status.vmAgent)
v1_handler_status_list = []
for handler_status in vm_status.vmAgent.extensionHandlers:
v1_handler_status = ext_handler_status_to_v1(handler_status,
ext_statuses, timestamp)
if v1_handler_status is not None:
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, client):
self.vm_status = None
self.ext_statuses = {}
self.client = client
def set_vm_status(self, vm_status):
validata_param("vmAgent", vm_status, VMStatus)
self.vm_status = vm_status
def set_ext_status(self, ext_handler_name, ext_status):
validata_param("extensionStatus", ext_status, ExtensionStatus)
self.ext_statuses[ext_handler_name]= ext_status
def to_json(self):
report = vm_status_to_v1(self.vm_status, self.ext_statuses)
return json.dumps(report)
__storage_version__ = "2014-02-14"
def upload(self, url):
#TODO upload extension only if content has changed
logger.verb("Upload status blob")
blob_type = self.get_blob_type(url)
data = self.to_json()
try:
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))
except HttpError as e:
raise ProtocolError("Failed to upload status blob: {0}".format(e))
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())
try:
resp = self.client.call_storage_service(restutil.http_head, url, {
"x-ms-date" : timestamp,
'x-ms-version' : self.__class__.__storage_version__
})
except HttpError as e:
raise ProtocolError((u"Failed to get status blob type: {0}"
u"").format(e))
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())
try:
resp = self.client.call_storage_service(restutil.http_put, url,
data, {
"x-ms-date" : timestamp,
"x-ms-blob-type" : "BlockBlob",
"Content-Length": ustr(len(data)),
"x-ms-version" : self.__class__.__storage_version__
})
except HttpError as e:
raise ProtocolError((u"Failed to upload block blob: {0}"
u"").format(e))
if 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")
#Convert string into bytes
data=bytearray(data, encoding='utf-8')
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
#Align to 512 bytes
page_blob_size = int((len(data) + 511) / 512) * 512
try:
resp = self.client.call_storage_service(restutil.http_put, url,
"", {
"x-ms-date" : timestamp,
"x-ms-blob-type" : "PageBlob",
"Content-Length": "0",
"x-ms-blob-content-length" : ustr(page_blob_size),
"x-ms-version" : self.__class__.__storage_version__
})
except HttpError as e:
raise ProtocolError((u"Failed to clean up page blob: {0}"
u"").format(e))
if resp.status != httpclient.CREATED:
raise ProtocolError(("Failed to clean up page blob: {0}"
"").format(resp.status))
if url.count("?") < 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(buf_size)
buf[0: content_size] = data[start: end]
try:
resp = self.client.call_storage_service(restutil.http_put, url,
bytebuffer(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": ustr(page_end - start)
})
except HttpError as e:
raise ProtocolError((u"Failed to upload page blob: {0}"
u"").format(e))
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_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 ustr(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(ustr(param.value)),
attr_type)
def event_to_v1(event):
params = ""
for param in event.parameters:
params += event_param_to_v1(param)
event_str = (''
''
'').format(event.eventId, params)
return event_str
class WireClient(object):
def __init__(self, endpoint):
logger.info("Wire server endpoint:{0}", 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.last_request = 0
self.req_count = 0
self.status_blob = StatusBlob(self)
def prevent_throttling(self):
"""
Try to avoid throttling of wire server
"""
now = time.time()
if now - self.last_request < 1:
logger.verb("Last request issued less than 1 second ago")
logger.verb("Sleep {0} second to avoid throttling.",
SHORT_WAITING_INTERVAL)
time.sleep(SHORT_WAITING_INTERVAL)
self.last_request = now
self.req_count += 1
if self.req_count % 3 == 0:
logger.verb("Sleep {0} second to avoid throttling.",
SHORT_WAITING_INTERVAL)
time.sleep(SHORT_WAITING_INTERVAL)
self.req_count = 0
def call_wireserver(self, http_req, *args, **kwargs):
"""
Call wire server. Handle throttling(403) and Resource Gone(410)
"""
self.prevent_throttling()
for retry in range(0, 3):
resp = http_req(*args, **kwargs)
if resp.status == httpclient.FORBIDDEN:
logger.warn("Sending too much request to wire server")
logger.info("Sleep {0} second to avoid throttling.",
LONG_WAITING_INTERVAL)
time.sleep(LONG_WAITING_INTERVAL)
elif resp.status == httpclient.GONE:
msg = args[0] if len(args) > 0 else ""
raise WireProtocolResourceGone(msg)
else:
return resp
raise ProtocolError(("Calling wire server failed: {0}"
"").format(resp.status))
def decode_config(self, data):
if data is None:
return None
data = remove_bom(data)
xml_text = ustr(data, encoding='utf-8')
return xml_text
def fetch_config(self, uri, headers):
try:
resp = self.call_wireserver(restutil.http_get, uri,
headers=headers)
except HttpError as e:
raise ProtocolError(ustr(e))
if(resp.status != httpclient.OK):
raise ProtocolError("{0} - {1}".format(resp.status, uri))
return self.decode_config(resp.read())
def fetch_cache(self, local_file):
if not os.path.isfile(local_file):
raise ProtocolError("{0} is missing.".format(local_file))
try:
return fileutil.read_file(local_file)
except IOError as e:
raise ProtocolError("Failed to read cache: {0}".format(e))
def save_cache(self, local_file, data):
try:
fileutil.write_file(local_file, data)
except IOError as e:
raise ProtocolError("Failed to write cache: {0}".format(e))
def call_storage_service(self, http_req, *args, **kwargs):
"""
Call storage service, handle SERVICE_UNAVAILABLE(503)
"""
for retry in range(0, 3):
resp = http_req(*args, **kwargs)
if resp.status == httpclient.SERVICE_UNAVAILABLE:
logger.warn("Storage service is not avaible temporaryly")
logger.info("Will retry later, in {0} seconds",
LONG_WAITING_INTERVAL)
time.sleep(LONG_WAITING_INTERVAL)
else:
return resp
raise ProtocolError(("Calling storage endpoint failed: {0}"
"").format(resp.status))
def fetch_manifest(self, version_uris):
for version_uri in version_uris:
logger.verb("Fetch ext handler manifest: {0}", version_uri.uri)
try:
resp = self.call_storage_service(restutil.http_get,
version_uri.uri, None,
chk_proxy=True)
except HttpError as e:
raise ProtocolError(ustr(e))
if resp.status == httpclient.OK:
return self.decode_config(resp.read())
logger.warn("Failed to fetch ExtensionManifest: {0}, {1}",
resp.status, version_uri.uri)
logger.info("Will retry later, in {0} seconds",
LONG_WAITING_INTERVAL)
time.sleep(LONG_WAITING_INTERVAL)
raise ProtocolError(("Failed to fetch ExtensionManifest from "
"all sources"))
def update_hosting_env(self, goal_state):
if goal_state.hosting_env_uri is None:
raise ProtocolError("HostingEnvironmentConfig uri is empty")
local_file = os.path.join(conf.get_lib_dir(), HOSTING_ENV_FILE_NAME)
xml_text = self.fetch_config(goal_state.hosting_env_uri,
self.get_header())
self.save_cache(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 = os.path.join(conf.get_lib_dir(), SHARED_CONF_FILE_NAME)
xml_text = self.fetch_config(goal_state.shared_conf_uri,
self.get_header())
self.save_cache(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 = os.path.join(conf.get_lib_dir(), CERTS_FILE_NAME)
xml_text = self.fetch_config(goal_state.certs_uri,
self.get_header_for_cert())
self.save_cache(local_file, xml_text)
self.certs = Certificates(self, xml_text)
def update_ext_conf(self, goal_state):
if goal_state.ext_uri is None:
logger.info("ExtensionsConfig.xml uri is empty")
self.ext_conf = ExtensionsConfig(None)
return
incarnation = goal_state.incarnation
local_file = os.path.join(conf.get_lib_dir(),
EXT_CONF_FILE_NAME.format(incarnation))
xml_text = self.fetch_config(goal_state.ext_uri, self.get_header())
self.save_cache(local_file, xml_text)
self.ext_conf = ExtensionsConfig(xml_text)
def update_goal_state(self, forced=False, max_retry=3):
uri = GOAL_STATE_URI.format(self.endpoint)
xml_text = self.fetch_config(uri, self.get_header())
goal_state = GoalState(xml_text)
incarnation_file = os.path.join(conf.get_lib_dir(),
INCARNATION_FILE_NAME)
if not forced:
last_incarnation = None
if(os.path.isfile(incarnation_file)):
last_incarnation = fileutil.read_file(incarnation_file)
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
file_name = GOAL_STATE_FILE_NAME.format(goal_state.incarnation)
goal_state_file = os.path.join(conf.get_lib_dir(), file_name)
self.save_cache(goal_state_file, xml_text)
self.save_cache(incarnation_file, 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 = self.fetch_config(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_file = os.path.join(conf.get_lib_dir(),
INCARNATION_FILE_NAME)
incarnation = self.fetch_cache(incarnation_file)
file_name = GOAL_STATE_FILE_NAME.format(incarnation)
goal_state_file = os.path.join(conf.get_lib_dir(), file_name)
xml_text = self.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):
local_file = os.path.join(conf.get_lib_dir(), HOSTING_ENV_FILE_NAME)
xml_text = self.fetch_cache(local_file)
self.hosting_env = HostingEnv(xml_text)
return self.hosting_env
def get_shared_conf(self):
if(self.shared_conf is None):
local_file = os.path.join(conf.get_lib_dir(), SHARED_CONF_FILE_NAME)
xml_text = self.fetch_cache(local_file)
self.shared_conf = SharedConfig(xml_text)
return self.shared_conf
def get_certs(self):
if(self.certs is None):
local_file = os.path.join(conf.get_lib_dir(), CERTS_FILE_NAME)
xml_text = self.fetch_cache(local_file)
self.certs = Certificates(self, 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()
if goal_state.ext_uri is None:
self.ext_conf = ExtensionsConfig(None)
else:
local_file = EXT_CONF_FILE_NAME.format(goal_state.incarnation)
local_file = os.path.join(conf.get_lib_dir(), local_file)
xml_text = self.fetch_cache(local_file)
self.ext_conf = ExtensionsConfig(xml_text)
return self.ext_conf
def get_ext_manifest(self, ext_handler, goal_state):
local_file = MANIFEST_FILE_NAME.format(ext_handler.name,
goal_state.incarnation)
local_file = os.path.join(conf.get_lib_dir(), local_file)
xml_text = self.fetch_manifest(ext_handler.versionUris)
self.save_cache(local_file, xml_text)
return ExtensionManifest(xml_text)
def check_wire_protocol_version(self):
uri = VERSION_INFO_URI.format(self.endpoint)
version_info_xml = self.fetch_config(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 ProtocolNotFoundError(error)
def upload_status_blob(self):
ext_conf = self.get_ext_conf()
if ext_conf.status_upload_blob is not None:
self.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 = role_prop.encode("utf-8")
role_prop_uri = ROLE_PROP_URI.format(self.endpoint)
headers = self.get_header_for_xml_content()
try:
resp = self.call_wireserver(restutil.http_post, role_prop_uri,
role_prop, headers = headers)
except HttpError as e:
raise ProtocolError((u"Failed to send role properties: {0}"
u"").format(e))
if resp.status != httpclient.ACCEPTED:
raise ProtocolError((u"Failed to send role properties: {0}"
u", {1}").format(resp.status, resp.read()))
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 = health_report.encode("utf-8")
health_report_uri = HEALTH_REPORT_URI.format(self.endpoint)
headers = self.get_header_for_xml_content()
try:
resp = self.call_wireserver(restutil.http_post, health_report_uri,
health_report, headers = headers)
except HttpError as e:
raise ProtocolError((u"Failed to send provision status: {0}"
u"").format(e))
if resp.status != httpclient.OK:
raise ProtocolError((u"Failed to send provision status: {0}"
u", {1}").format(resp.status, resp.read()))
def send_event(self, provider_id, event_str):
uri = TELEMETRY_URI.format(self.endpoint)
data_format = (''
''
'{1}'
''
'')
data = data_format.format(provider_id, event_str)
try:
header = self.get_header_for_xml_content()
resp = self.call_wireserver(restutil.http_post, uri, data, header)
except 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):
trans_cert_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_CERT_FILE_NAME)
content = self.fetch_cache(trans_cert_file)
cert = get_bytes_from_pem(content)
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, client, xml_text):
logger.verb("Load Certificates.xml")
self.client = client
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
cryptutil = CryptUtil(conf.get_openssl_cmd())
p7m_file = os.path.join(conf.get_lib_dir(), P7M_FILE_NAME)
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, p7m_file, data)
self.client.save_cache(p7m_file, p7m)
trans_prv_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_PRV_FILE_NAME)
trans_cert_file = os.path.join(conf.get_lib_dir(),
TRANSPORT_CERT_FILE_NAME)
pem_file = os.path.join(conf.get_lib_dir(), PEM_FILE_NAME)
#decrypt certificates
cryptutil.decrypt_p7m(p7m_file, trans_prv_file, trans_cert_file,
pem_file)
#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) 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 = cryptutil.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 = cryptutil.get_pubkey_from_crt(tmp_file)
thumbprint = cryptutil.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(conf.get_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(conf.get_lib_dir(), prv))
for v1_cert in v1_cert_list:
cert = Cert()
set_properties("certs", cert, v1_cert)
self.cert_list.certificates.append(cert)
def write_to_tmp_file(self, index, suffix, buf):
file_name = os.path.join(conf.get_lib_dir(),
"{0}.{1}".format(index, suffix))
self.client.save_cache(file_name, "".join(buf))
return file_name
class ExtensionsConfig(object):
"""
parse ExtensionsConfig, downloading and unpacking them to /var/lib/waagent.
Install if true, remove if it is set to false.
"""
def __init__(self, xml_text):
logger.verb("Load ExtensionsConfig.xml")
self.ext_handlers = ExtHandlerList()
self.status_upload_blob = None
if xml_text is not 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_handler = self.parse_plugin(plugin)
self.ext_handlers.extHandlers.append(ext_handler)
self.parse_plugin_settings(ext_handler, plugin_settings)
self.status_upload_blob = findtext(xml_doc, "StatusUploadBlob")
def parse_plugin(self, plugin):
ext_handler = ExtHandler()
ext_handler.name = getattrib(plugin, "name")
ext_handler.properties.version = getattrib(plugin, "version")
ext_handler.properties.state = getattrib(plugin, "state")
auto_upgrade = getattrib(plugin, "autoUpgrade")
if auto_upgrade is not None and auto_upgrade.lower() == "true":
ext_handler.properties.upgradePolicy = "auto"
else:
ext_handler.properties.upgradePolicy = "manual"
location = getattrib(plugin, "location")
failover_location = getattrib(plugin, "failoverlocation")
for uri in [location, failover_location]:
version_uri = ExtHandlerVersionUri()
version_uri.uri = uri
ext_handler.versionUris.append(version_uri)
return ext_handler
def parse_plugin_settings(self, ext_handler, plugin_settings):
if plugin_settings is None:
return
name = ext_handler.name
version = ext_handler.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 = Extension()
#There is no "extension name" in wire protocol.
#Put
ext.name = ext_handler.name
ext.sequenceNumber = seqNo
ext.publicSettings = handler_settings.get("publicSettings")
ext.protectedSettings = handler_settings.get("protectedSettings")
thumbprint = handler_settings.get("protectedSettingsCertThumbprint")
ext.certificateThumbprint = thumbprint
ext_handler.properties.extensions.append(ext)
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 = ExtHandlerPackageList()
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 = ExtHandlerPackage()
package.version = version
for uri in uri_list:
pkg_uri = ExtHandlerVersionUri()
pkg_uri.uri = uri
package.uris.append(pkg_uri)
self.pkg_list.versions.append(package)
WALinuxAgent-2.1.3/azurelinuxagent/utils/ 0000775 0000000 0000000 00000000000 12651710031 0020404 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/azurelinuxagent/utils/__init__.py 0000664 0000000 0000000 00000001226 12651710031 0022516 0 ustar 00root root 0000000 0000000 # Microsoft 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+
#
WALinuxAgent-2.1.3/azurelinuxagent/utils/cryptutil.py 0000664 0000000 0000000 00000011142 12651710031 0023014 0 ustar 00root root 0000000 0000000 # Microsoft 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 base64
import struct
from azurelinuxagent.future import ustr, bytebuffer
from azurelinuxagent.exception import CryptError
import azurelinuxagent.utils.shellutil as shellutil
class CryptUtil(object):
def __init__(self, openssl_cmd):
self.openssl_cmd = openssl_cmd
def gen_transport_cert(self, prv_file, crt_file):
"""
Create ssl certificate for https communication with endpoint server.
"""
cmd = ("{0} req -x509 -nodes -subj /CN=LinuxTransport -days 32768 "
"-newkey rsa:2048 -keyout {1} "
"-out {2}").format(self.openssl_cmd, prv_file, crt_file)
shellutil.run(cmd)
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 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 decrypt_p7m(self, p7m_file, trans_prv_file, trans_cert_file, pem_file):
cmd = ("{0} cms -decrypt -in {1} -inkey {2} -recip {3} "
"| {4} pkcs12 -nodes -password pass: -out {5}"
"").format(self.openssl_cmd, p7m_file, trans_prv_file,
trans_cert_file, self.openssl_cmd, pem_file)
shellutil.run(cmd)
def crt_to_ssh(self, input_file, output_file):
shellutil.run("ssh-keygen -i -m PKCS8 -f {0} >> {1}".format(input_file,
output_file))
def asn1_to_ssh(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 ustr(b"ssh-rsa " + keydata_base64 + b"\n",
encoding='utf-8')
except ImportError as e:
raise CryptError("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)
WALinuxAgent-2.1.3/azurelinuxagent/utils/fileutil.py 0000664 0000000 0000000 00000012403 12651710031 0022573 0 ustar 00root root 0000000 0000000 # Microsoft 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 ustr
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 = ustr(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 IOError("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
WALinuxAgent-2.1.3/azurelinuxagent/utils/restutil.py 0000664 0000000 0000000 00000013316 12651710031 0022635 0 ustar 00root root 0000000 0000000 # Microsoft 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.conf as conf
import azurelinuxagent.logger as logger
from azurelinuxagent.exception import HttpError
from azurelinuxagent.future import httpclient, urlparse
"""
REST api util functions
"""
RETRY_WAITING_INTERVAL = 10
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()
port = conf.get_httpproxy_port()
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, timeout=10)
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, timeout=10)
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, timeout=10)
#If proxy is used, full url is needed.
url = "http://{0}:{1}{2}".format(host, port, rel_uri)
else:
conn = httpclient.HTTPConnection(host, port, timeout=10)
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)
if url is not None and len(url) > 100:
url_log = url[0: 100] #In case the url is too long
else:
url_log = url
raise HttpError("HTTP Err: {0} {1}".format(method, url_log))
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
WALinuxAgent-2.1.3/azurelinuxagent/utils/shellutil.py 0000664 0000000 0000000 00000006212 12651710031 0022764 0 ustar 00root root 0000000 0000000 # Microsoft 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 ustr
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, log_cmd=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
"""
if log_cmd:
logger.verb(u"run cmd '{0}'", cmd)
try:
output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
output = ustr(output, encoding='utf-8', errors="backslashreplace")
except subprocess.CalledProcessError as e :
output = ustr(e.output, encoding='utf-8', errors="backslashreplace")
if chk_err:
if log_cmd:
logger.error(u"run cmd '{0}' failed", e.cmd)
logger.error(u"Error Code:{0}", e.returncode)
logger.error(u"Result:{0}", output)
return e.returncode, output
return 0, output
#End shell command util functions
WALinuxAgent-2.1.3/azurelinuxagent/utils/textutil.py 0000664 0000000 0000000 00000014466 12651710031 0022653 0 ustar 00root root 0000000 0000000 # Microsoft 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
from distutils.version import LooseVersion
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, crypt_id, salt_len):
collection = string.ascii_letters + string.digits
salt = ''.join(random.choice(collection) for _ in range(salt_len))
salt = "${0}${1}".format(crypt_id, salt)
return crypt.crypt(password, salt)
def get_bytes_from_pem(pem_str):
base64_bytes = ""
for line in pem_str.split('\n'):
if "----" not in line:
base64_bytes += line
return base64_bytes
Version = LooseVersion
WALinuxAgent-2.1.3/bin/ 0000775 0000000 0000000 00000000000 12651710031 0014567 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/bin/waagent 0000775 0000000 0000000 00000002752 12651710031 0016151 0 ustar 00root root 0000000 0000000 #!/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)
WALinuxAgent-2.1.3/bin/waagent2.0 0000664 0000000 0000000 00000756741 12651710031 0016404 0 ustar 00root root 0000000 0000000 #!/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 crypt
import random
import array
import base64
import httplib
import os
import os.path
import platform
import pwd
import re
import shutil
import socket
import SocketServer
import struct
import string
import subprocess
import sys
import tempfile
import textwrap
import threading
import time
import traceback
import xml.dom.minidom
import fcntl
import inspect
import zipfile
import json
import datetime
import xml.sax.saxutils
from distutils.version import LooseVersion
if not hasattr(subprocess,'check_output'):
def check_output(*popenargs, **kwargs):
r"""Backport from subprocess module from python 2.7"""
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
output, unused_err = process.communicate()
retcode = process.poll()
if retcode:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise subprocess.CalledProcessError(retcode, cmd, output=output)
return output
# Exception classes used by this module.
class CalledProcessError(Exception):
def __init__(self, returncode, cmd, output=None):
self.returncode = returncode
self.cmd = cmd
self.output = output
def __str__(self):
return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
subprocess.check_output=check_output
subprocess.CalledProcessError=CalledProcessError
GuestAgentName = "WALinuxAgent"
GuestAgentLongName = "Azure Linux Agent"
GuestAgentVersion = "WALinuxAgent-2.0.16"
ProtocolVersion = "2012-11-30" #WARNING this value is used to confirm the correct fabric protocol.
Config = None
WaAgent = None
DiskActivated = False
Openssl = "openssl"
Children = []
ExtensionChildren = []
VMM_STARTUP_SCRIPT_NAME='install'
VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml'
global RulesFiles
RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules",
"/etc/udev/rules.d/70-persistent-net.rules" ]
VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"]
global LibDir
LibDir = "/var/lib/waagent"
global provisioned
provisioned=False
global provisionError
provisionError=None
HandlerStatusToAggStatus = {"installed":"Installing", "enabled":"Ready", "unintalled":"NotReady", "disabled":"NotReady"}
WaagentConf = """\
#
# Azure Linux Agent Configuration
#
Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status
# to the endpoint server.
Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
Provisioning.Enabled=y #
Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Filesystem=ext4 # Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
ResourceDisk.MountPoint=/mnt/resource #
ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
LBProbeResponder=y # Respond to load balancer probes if requested by Azure.
Logs.Verbose=n # Enable verbose logs
OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
OS.OpensslPath=None # If "None", the system default version is used.
"""
README_FILENAME="DATALOSS_WARNING_README.txt"
README_FILECONTENT="""\
WARNING: THIS IS A TEMPORARY DISK.
Any data stored on this drive is SUBJECT TO LOSS and THERE IS NO WAY TO RECOVER IT.
Please do not use this disk for storing any personal or application data.
For additional details to please refer to the MSDN documentation at : http://msdn.microsoft.com/en-us/library/windowsazure/jj672979.aspx
"""
############################################################
# BEGIN DISTRO CLASS DEFS
############################################################
############################################################
# AbstractDistro
############################################################
class AbstractDistro(object):
"""
AbstractDistro defines a skeleton neccesary for a concrete Distro class.
Generic methods and attributes are kept here, distribution specific attributes
and behavior are to be placed in the concrete child named distroDistro, where
distro is the string returned by calling python platform.linux_distribution()[0].
So for CentOS the derived class is called 'centosDistro'.
"""
def __init__(self):
"""
Generic Attributes go here. These are based on 'majority rules'.
This __init__() may be called or overriden by the child.
"""
self.agent_service_name = os.path.basename(sys.argv[0])
self.selinux=None
self.service_cmd='/usr/sbin/service'
self.ssh_service_restart_option='restart'
self.ssh_service_name='ssh'
self.ssh_config_file='/etc/ssh/sshd_config'
self.hostname_file_path='/etc/hostname'
self.dhcp_client_name='dhclient'
self.requiredDeps = [ 'route', 'shutdown', 'ssh-keygen', 'useradd', 'usermod',
'openssl', 'sfdisk', 'fdisk', 'mkfs',
'sed', 'grep', 'sudo', 'parted' ]
self.init_script_file='/etc/init.d/waagent'
self.agent_package_name='WALinuxAgent'
self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log",'/etc/resolv.conf' ]
self.agent_files_to_uninstall = ["/etc/waagent.conf", "/etc/logrotate.d/waagent"]
self.grubKernelBootOptionsFile = '/etc/default/grub'
self.grubKernelBootOptionsLine = 'GRUB_CMDLINE_LINUX_DEFAULT='
self.getpidcmd = 'pidof'
self.mount_dvd_cmd = 'mount'
self.sudoers_dir_base = '/etc'
self.waagent_conf_file = WaagentConf
self.shadow_file_mode=0600
self.shadow_file_path="/etc/shadow"
self.dhcp_enabled = False
def isSelinuxSystem(self):
"""
Checks and sets self.selinux = True if SELinux is available on system.
"""
if self.selinux == None:
if Run("which getenforce",chk_err=False):
self.selinux = False
else:
self.selinux = True
return self.selinux
def isSelinuxRunning(self):
"""
Calls shell command 'getenforce' and returns True if 'Enforcing'.
"""
if self.isSelinuxSystem():
return RunGetOutput("getenforce")[1].startswith("Enforcing")
else:
return False
def setSelinuxEnforce(self,state):
"""
Calls shell command 'setenforce' with 'state' and returns resulting exit code.
"""
if self.isSelinuxSystem():
if state: s = '1'
else: s='0'
return Run("setenforce "+s)
def setSelinuxContext(self,path,cn):
"""
Calls shell 'chcon' with 'path' and 'cn' context.
Returns exit result.
"""
if self.isSelinuxSystem():
return Run('chcon ' + cn + ' ' + path)
def setHostname(self,name):
"""
Shell call to hostname.
Returns resulting exit code.
"""
return Run('hostname ' + name)
def publishHostname(self,name):
"""
Set the contents of the hostname file to 'name'.
Return 1 on failure.
"""
try:
r=SetFileContents(self.hostname_file_path, name)
for f in EtcDhcpClientConfFiles:
if os.path.exists(f) and FindStringInFile(f,r'^[^#]*?send\s*host-name.*?(|gethostname[(,)])') == None :
r=ReplaceFileContentsAtomic('/etc/dhcp/dhclient.conf', "send host-name \"" + name + "\";\n"
+ "\n".join(filter(lambda a: not a.startswith("send host-name"), GetFileContents('/etc/dhcp/dhclient.conf').split('\n'))))
except:
return 1
return r
def installAgentServiceScriptFiles(self):
"""
Create the waagent support files for service installation.
Called by registerAgentService()
Abstract Virtual Function. Over-ridden in concrete Distro classes.
"""
pass
def registerAgentService(self):
"""
Calls installAgentService to create service files.
Shell exec service registration commands. (e.g. chkconfig --add waagent)
Abstract Virtual Function. Over-ridden in concrete Distro classes.
"""
pass
def uninstallAgentService(self):
"""
Call service subsystem to remove waagent script.
Abstract Virtual Function. Over-ridden in concrete Distro classes.
"""
pass
def unregisterAgentService(self):
"""
Calls self.stopAgentService and call self.uninstallAgentService()
"""
self.stopAgentService()
self.uninstallAgentService()
def startAgentService(self):
"""
Service call to start the Agent service
"""
return Run(self.service_cmd + ' ' + self.agent_service_name + ' start')
def stopAgentService(self):
"""
Service call to stop the Agent service
"""
return Run(self.service_cmd + ' ' + self.agent_service_name + ' stop',False)
def restartSshService(self):
"""
Service call to re(start) the SSH service
"""
sshRestartCmd = self.service_cmd + " " + self.ssh_service_name + " " + self.ssh_service_restart_option
retcode = Run(sshRestartCmd)
if retcode > 0:
Error("Failed to restart SSH service with return code:" + str(retcode))
return retcode
def sshDeployPublicKey(self,fprint,path):
"""
Generic sshDeployPublicKey - over-ridden in some concrete Distro classes due to minor differences in openssl packages deployed
"""
error=0
SshPubKey = OvfEnv().OpensslToSsh(fprint)
if SshPubKey != None:
AppendFileContents(path, SshPubKey)
else:
Error("Failed: " + fprint + ".crt -> " + path)
error = 1
return error
def checkPackageInstalled(self,p):
"""
Query package database for prescence of an installed package.
Abstract Virtual Function. Over-ridden in concrete Distro classes.
"""
pass
def checkPackageUpdateable(self,p):
"""
Online check if updated package of walinuxagent is available.
Abstract Virtual Function. Over-ridden in concrete Distro classes.
"""
pass
def deleteRootPassword(self):
"""
Generic root password removal.
"""
filepath="/etc/shadow"
ReplaceFileContentsAtomic(filepath,"root:*LOCK*:14600::::::\n"
+ "\n".join(filter(lambda a: not a.startswith("root:"),GetFileContents(filepath).split('\n'))))
os.chmod(filepath,self.shadow_file_mode)
if self.isSelinuxSystem():
self.setSelinuxContext(filepath,'system_u:object_r:shadow_t:s0')
Log("Root password deleted.")
return 0
def changePass(self,user,password):
Log("Change user password")
crypt_id = Config.get("Provisioning.PasswordCryptId")
if crypt_id is None:
crypt_id = "6"
salt_len = Config.get("Provisioning.PasswordCryptSaltLength")
try:
salt_len = int(salt_len)
if salt_len < 0 or salt_len > 10:
salt_len = 10
except (ValueError, TypeError):
salt_len = 10
return self.chpasswd(user, password, crypt_id=crypt_id,
salt_len=salt_len)
def chpasswd(self, username, password, crypt_id=6, salt_len=10):
passwd_hash = self.gen_password_hash(password, crypt_id, salt_len)
cmd = "usermod -p '{0}' {1}".format(passwd_hash, username)
ret, output = RunGetOutput(cmd, log_cmd=False)
if ret != 0:
return "Failed to set password for {0}: {1}".format(username, output)
def gen_password_hash(self, password, crypt_id, salt_len):
collection = string.ascii_letters + string.digits
salt = ''.join(random.choice(collection) for _ in range(salt_len))
salt = "${0}${1}".format(crypt_id, salt)
return crypt.crypt(password, salt)
def load_ata_piix(self):
return WaAgent.TryLoadAtapiix()
def unload_ata_piix(self):
"""
Generic function to remove ata_piix.ko.
"""
return WaAgent.TryUnloadAtapiix()
def deprovisionWarnUser(self):
"""
Generic user warnings used at deprovision.
"""
print("WARNING! Nameserver configuration in /etc/resolv.conf will be deleted.")
def deprovisionDeleteFiles(self):
"""
Files to delete when VM is deprovisioned
"""
for a in VarLibDhcpDirectories:
Run("rm -f " + a + "/*")
# Clear LibDir, remove nameserver and root bash history
for f in os.listdir(LibDir) + self.fileBlackList:
try:
os.remove(f)
except:
pass
return 0
def uninstallDeleteFiles(self):
"""
Files to delete when agent is uninstalled.
"""
for f in self.agent_files_to_uninstall:
try:
os.remove(f)
except:
pass
return 0
def checkDependencies(self):
"""
Generic dependency check.
Return 1 unless all dependencies are satisfied.
"""
if self.checkPackageInstalled('NetworkManager'):
Error(GuestAgentLongName + " is not compatible with network-manager.")
return 1
try:
m= __import__('pyasn1')
except ImportError:
Error(GuestAgentLongName + " requires python-pyasn1 for your Linux distribution.")
return 1
for a in self.requiredDeps:
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
Error("Missing required dependency: " + a)
return 1
return 0
def packagedInstall(self,buildroot):
"""
Called from setup.py for use by RPM.
Copies generated files waagent.conf, under the buildroot.
"""
if not os.path.exists(buildroot+'/etc'):
os.mkdir(buildroot+'/etc')
SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
if not os.path.exists(buildroot+'/etc/logrotate.d'):
os.mkdir(buildroot+'/etc/logrotate.d')
SetFileContents(buildroot+'/etc/logrotate.d/waagent', WaagentLogrotate)
self.init_script_file=buildroot+self.init_script_file
# this allows us to call installAgentServiceScriptFiles()
if not os.path.exists(os.path.dirname(self.init_script_file)):
os.mkdir(os.path.dirname(self.init_script_file))
self.installAgentServiceScriptFiles()
def GetIpv4Address(self):
"""
Return the ip of the
first active non-loopback interface.
"""
addr=''
iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
return addr
def GetMacAddress(self):
return GetMacAddress()
def GetInterfaceName(self):
return GetFirstActiveNetworkInterfaceNonLoopback()[0]
def RestartInterface(self, iface, max_retry=3):
for retry in range(1, max_retry + 1):
ret = Run("ifdown " + iface + " && ifup " + iface)
if ret == 0:
return
Log("Failed to restart interface: {0}, ret={1}".format(iface, ret))
if retry < max_retry:
Log("Retry restart interface in 5 seconds")
time.sleep(5)
def CreateAccount(self,user, password, expiration, thumbprint):
return CreateAccount(user, password, expiration, thumbprint)
def DeleteAccount(self,user):
return DeleteAccount(user)
def ActivateResourceDisk(self):
"""
Format, mount, and if specified in the configuration
set resource disk as swap.
"""
global DiskActivated
format = Config.get("ResourceDisk.Format")
if format == None or format.lower().startswith("n"):
DiskActivated = True
return
device = DeviceForIdePort(1)
if device == None:
Error("ActivateResourceDisk: Unable to detect disk topology.")
return
device = "/dev/" + device
mountlist = RunGetOutput("mount")[1]
mountpoint = GetMountPoint(mountlist, device)
if(mountpoint):
Log("ActivateResourceDisk: " + device + "1 is already mounted.")
else:
mountpoint = Config.get("ResourceDisk.MountPoint")
if mountpoint == None:
mountpoint = "/mnt/resource"
CreateDir(mountpoint, "root", 0755)
fs = Config.get("ResourceDisk.Filesystem")
if fs == None:
fs = "ext3"
partition = device + "1"
#Check partition type
Log("Detect GPT...")
ret = RunGetOutput("parted {0} print".format(device))
if ret[0] == 0 and "gpt" in ret[1]:
Log("GPT detected.")
#GPT(Guid Partition Table) is used.
#Get partitions.
parts = filter(lambda x : re.match("^\s*[0-9]+", x), ret[1].split("\n"))
#If there are more than 1 partitions, remove all partitions
#and create a new one using the entire disk space.
if len(parts) > 1:
for i in range(1, len(parts) + 1):
Run("parted {0} rm {1}".format(device, i))
Run("parted {0} mkpart primary 0% 100%".format(device))
Run("mkfs." + fs + " " + partition + " -F")
else:
existingFS = RunGetOutput("sfdisk -q -c " + device + " 1", chk_err=False)[1].rstrip()
if existingFS == "7" and fs != "ntfs":
Run("sfdisk -c " + device + " 1 83")
Run("mkfs." + fs + " " + partition)
if Run("mount " + partition + " " + mountpoint, chk_err=False):
#If mount failed, try to format the partition and mount again
Warn("Failed to mount resource disk. Retry mounting.")
Run("mkfs." + fs + " " + partition + " -F")
if Run("mount " + partition + " " + mountpoint):
Error("ActivateResourceDisk: Failed to mount resource disk (" + partition + ").")
return
Log("Resource disk (" + partition + ") is mounted at " + mountpoint + " with fstype " + fs)
#Create README file under the root of resource disk
SetFileContents(os.path.join(mountpoint,README_FILENAME), README_FILECONTENT)
DiskActivated = True
#Create swap space
swap = Config.get("ResourceDisk.EnableSwap")
if swap == None or swap.lower().startswith("n"):
return
sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
os.remove(mountpoint + "/swapfile")
if not os.path.isfile(mountpoint + "/swapfile"):
Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
Run("mkswap " + mountpoint + "/swapfile")
Run("chmod 600 " + mountpoint + "/swapfile")
if not Run("swapon " + mountpoint + "/swapfile"):
Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
else:
Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
def Install(self):
return Install()
def mediaHasFilesystem(self,dsk):
if len(dsk) == 0 :
return False
if Run("LC_ALL=C fdisk -l " + dsk + " | grep Disk"):
return False
return True
def mountDVD(self,dvd,location):
return RunGetOutput(self.mount_dvd_cmd + ' ' + dvd + ' ' + location)
def GetHome(self):
return GetHome()
def getDhcpClientName(self):
return self.dhcp_client_name
def initScsiDiskTimeout(self):
"""
Set the SCSI disk timeout when the agent starts running
"""
self.setScsiDiskTimeout()
def setScsiDiskTimeout(self):
"""
Iterate all SCSI disks(include hot-add) and set their timeout if their value are different from the OS.RootDeviceScsiTimeout
"""
try:
scsiTimeout = Config.get("OS.RootDeviceScsiTimeout")
for diskName in [disk for disk in os.listdir("/sys/block") if disk.startswith("sd")]:
self.setBlockDeviceTimeout(diskName, scsiTimeout)
except:
pass
def setBlockDeviceTimeout(self, device, timeout):
"""
Set SCSI disk timeout by set /sys/block/sd*/device/timeout
"""
if timeout != None and device:
filePath = "/sys/block/" + device + "/device/timeout"
if(GetFileContents(filePath).splitlines()[0].rstrip() != timeout):
SetFileContents(filePath,timeout)
Log("SetBlockDeviceTimeout: Update the device " + device + " with timeout " + timeout)
def waitForSshHostKey(self, path):
"""
Provide a dummy waiting, since by default, ssh host key is created by waagent and the key
should already been created.
"""
if(os.path.isfile(path)):
return True
else:
Error("Can't find host key: {0}".format(path))
return False
def isDHCPEnabled(self):
return self.dhcp_enabled
def stopDHCP(self):
"""
Stop the system DHCP client so that the agent can bind on its port. If
the distro has set dhcp_enabled to True, it will need to provide an
implementation of this method.
"""
raise NotImplementedError('stopDHCP method missing')
def startDHCP(self):
"""
Start the system DHCP client. If the distro has set dhcp_enabled to
True, it will need to provide an implementation of this method.
"""
raise NotImplementedError('startDHCP method missing')
def translateCustomData(self, data):
"""
Translate the custom data from a Base64 encoding. Default to no-op.
"""
decodeCustomData = Config.get("Provisioning.DecodeCustomData")
if decodeCustomData != None and decodeCustomData.lower().startswith("y"):
return base64.b64decode(data)
return data
def getConfigurationPath(self):
return "/etc/waagent.conf"
def getProcessorCores(self):
return int(RunGetOutput("grep 'processor.*:' /proc/cpuinfo |wc -l")[1])
def getTotalMemory(self):
return int(RunGetOutput("grep MemTotal /proc/meminfo |awk '{print $2}'")[1])/1024
def getInterfaceNameByMac(self, mac):
ret, output = RunGetOutput("ifconfig -a")
if ret != 0:
raise Exception("Failed to get network interface info")
output = output.replace('\n', '')
match = re.search(r"(eth\d).*(HWaddr|ether) {0}".format(mac),
output, re.IGNORECASE)
if match is None:
raise Exception("Failed to get ifname with mac: {0}".format(mac))
output = match.group(0)
eths = re.findall(r"eth\d", output)
if eths is None or len(eths) == 0:
raise Exception("Failed to get ifname with mac: {0}".format(mac))
return eths[-1]
def configIpV4(self, ifName, addr, netmask=24):
ret, output = RunGetOutput("ifconfig {0} up".format(ifName))
if ret != 0:
raise Exception("Failed to bring up {0}: {1}".format(ifName,
output))
ret, output = RunGetOutput("ifconfig {0} {1}/{2}".format(ifName, addr,
netmask))
if ret != 0:
raise Exception("Failed to config ipv4 for {0}: {1}".format(ifName,
output))
def setDefaultGateway(self, gateway):
Run("/sbin/route add default gw" + gateway, chk_err=False)
def routeAdd(self, net, mask, gateway):
Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway,
chk_err=False)
############################################################
# GentooDistro
############################################################
gentoo_init_file = """\
#!/sbin/runscript
command=/usr/sbin/waagent
pidfile=/var/run/waagent.pid
command_args=-daemon
command_background=true
name="Azure Linux Agent"
depend()
{
need localmount
use logger network
after bootmisc modules
}
"""
class gentooDistro(AbstractDistro):
"""
Gentoo distro concrete class
"""
def __init__(self): #
super(gentooDistro,self).__init__()
self.service_cmd='/sbin/service'
self.ssh_service_name='sshd'
self.hostname_file_path='/etc/conf.d/hostname'
self.dhcp_client_name='dhcpcd'
self.shadow_file_mode=0640
self.init_file=gentoo_init_file
def publishHostname(self,name):
try:
if (os.path.isfile(self.hostname_file_path)):
r=ReplaceFileContentsAtomic(self.hostname_file_path, "hostname=\"" + name + "\"\n"
+ "\n".join(filter(lambda a: not a.startswith("hostname="), GetFileContents(self.hostname_file_path).split("\n"))))
except:
return 1
return r
def installAgentServiceScriptFiles(self):
SetFileContents(self.init_script_file, self.init_file)
os.chmod(self.init_script_file, 0755)
def registerAgentService(self):
self.installAgentServiceScriptFiles()
return Run('rc-update add ' + self.agent_service_name + ' default')
def uninstallAgentService(self):
return Run('rc-update del ' + self.agent_service_name + ' default')
def unregisterAgentService(self):
self.stopAgentService()
return self.uninstallAgentService()
def checkPackageInstalled(self,p):
if Run('eix -I ^' + p + '$',chk_err=False):
return 0
else:
return 1
def checkPackageUpdateable(self,p):
if Run('eix -u ^' + p + '$',chk_err=False):
return 0
else:
return 1
def RestartInterface(self, iface):
Run("/etc/init.d/net." + iface + " restart")
############################################################
# SuSEDistro
############################################################
suse_init_file = """\
#! /bin/sh
#
# 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: AzureLinuxAgent
# Required-Start: $network sshd
# Required-Stop: $network sshd
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Description: Start the AzureLinuxAgent
### 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 AzureLinuxAgent"
## 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 AzureLinuxAgent"
## 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 AzureLinuxAgent "
## Check status with checkproc(8), if process is running
## checkproc will return with exit status 0.
checkproc -p ${WAZD_PIDFILE} ${PYTHON} ${WAZD_BIN}
rc_status -v
;;
probe)
;;
*)
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload}"
exit 1
;;
esac
rc_exit
"""
class SuSEDistro(AbstractDistro):
"""
SuSE Distro concrete class
Put SuSE specific behavior here...
"""
def __init__(self):
super(SuSEDistro,self).__init__()
self.service_cmd='/sbin/service'
self.ssh_service_name='sshd'
self.kernel_boot_options_file='/boot/grub/menu.lst'
self.hostname_file_path='/etc/HOSTNAME'
self.requiredDeps += [ "/sbin/insserv" ]
self.init_file=suse_init_file
self.dhcp_client_name='dhcpcd'
if ((DistInfo(fullname=1)[0] == 'SUSE Linux Enterprise Server' and DistInfo()[1] >= '12') or \
(DistInfo(fullname=1)[0] == 'openSUSE' and DistInfo()[1] >= '13.2')):
self.dhcp_client_name='wickedd-dhcp4'
self.grubKernelBootOptionsFile = '/boot/grub/menu.lst'
self.grubKernelBootOptionsLine = 'kernel'
self.getpidcmd='pidof '
self.dhcp_enabled=True
def checkPackageInstalled(self,p):
if Run("rpm -q " + p,chk_err=False):
return 0
else:
return 1
def checkPackageUpdateable(self,p):
if Run("zypper list-updates | grep " + p,chk_err=False):
return 1
else:
return 0
def installAgentServiceScriptFiles(self):
try:
SetFileContents(self.init_script_file, self.init_file)
os.chmod(self.init_script_file, 0744)
except:
pass
def registerAgentService(self):
self.installAgentServiceScriptFiles()
return Run('insserv ' + self.agent_service_name)
def uninstallAgentService(self):
return Run('insserv -r ' + self.agent_service_name)
def unregisterAgentService(self):
self.stopAgentService()
return self.uninstallAgentService()
def startDHCP(self):
Run("service " + self.dhcp_client_name + " start", chk_err=False)
def stopDHCP(self):
Run("service " + self.dhcp_client_name + " stop", chk_err=False)
############################################################
# redhatDistro
############################################################
redhat_init_file= """\
#!/bin/bash
#
# Init file for AzureLinuxAgent.
#
# chkconfig: 2345 60 80
# description: AzureLinuxAgent
#
# source function library
. /etc/rc.d/init.d/functions
RETVAL=0
FriendlyName="AzureLinuxAgent"
WAZD_BIN=/usr/sbin/waagent
start()
{
echo -n $"Starting $FriendlyName: "
$WAZD_BIN -daemon &
}
stop()
{
echo -n $"Stopping $FriendlyName: "
killproc -p /var/run/waagent.pid $WAZD_BIN
RETVAL=$?
echo
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
reload)
;;
report)
;;
status)
status $WAZD_BIN
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|restart|status}"
RETVAL=1
esac
exit $RETVAL
"""
class redhatDistro(AbstractDistro):
"""
Redhat Distro concrete class
Put Redhat specific behavior here...
"""
def __init__(self):
super(redhatDistro,self).__init__()
self.service_cmd='/sbin/service'
self.ssh_service_restart_option='condrestart'
self.ssh_service_name='sshd'
self.hostname_file_path= None if DistInfo()[1] < '7.0' else '/etc/hostname'
self.init_file=redhat_init_file
self.grubKernelBootOptionsFile = '/boot/grub/menu.lst'
self.grubKernelBootOptionsLine = 'kernel'
def publishHostname(self,name):
super(redhatDistro,self).publishHostname(name)
if DistInfo()[1] < '7.0' :
filepath = "/etc/sysconfig/network"
if os.path.isfile(filepath):
ReplaceFileContentsAtomic(filepath, "HOSTNAME=" + name + "\n"
+ "\n".join(filter(lambda a: not a.startswith("HOSTNAME"), GetFileContents(filepath).split('\n'))))
ethernetInterface = MyDistro.GetInterfaceName()
filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
if os.path.isfile(filepath):
ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
+ "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
return 0
def installAgentServiceScriptFiles(self):
SetFileContents(self.init_script_file, self.init_file)
os.chmod(self.init_script_file, 0744)
return 0
def registerAgentService(self):
self.installAgentServiceScriptFiles()
return Run('chkconfig --add waagent')
def uninstallAgentService(self):
return Run('chkconfig --del ' + self.agent_service_name)
def unregisterAgentService(self):
self.stopAgentService()
return self.uninstallAgentService()
def checkPackageInstalled(self,p):
if Run("yum list installed " + p,chk_err=False):
return 0
else:
return 1
def checkPackageUpdateable(self,p):
if Run("yum check-update | grep "+ p,chk_err=False):
return 1
else:
return 0
def checkDependencies(self):
"""
Generic dependency check.
Return 1 unless all dependencies are satisfied.
"""
if DistInfo()[1] < '7.0' and self.checkPackageInstalled('NetworkManager'):
Error(GuestAgentLongName + " is not compatible with network-manager.")
return 1
try:
m= __import__('pyasn1')
except ImportError:
Error(GuestAgentLongName + " requires python-pyasn1 for your Linux distribution.")
return 1
for a in self.requiredDeps:
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
Error("Missing required dependency: " + a)
return 1
return 0
############################################################
# centosDistro
############################################################
class centosDistro(redhatDistro):
"""
CentOS Distro concrete class
Put CentOS specific behavior here...
"""
def __init__(self):
super(centosDistro,self).__init__()
############################################################
# oracleDistro
############################################################
class oracleDistro(redhatDistro):
"""
Oracle Distro concrete class
Put Oracle specific behavior here...
"""
def __init__(self):
super(oracleDistro, self).__init__()
############################################################
# asianuxDistro
############################################################
class asianuxDistro(redhatDistro):
"""
Asianux Distro concrete class
Put Asianux specific behavior here...
"""
def __init__(self):
super(asianuxDistro,self).__init__()
############################################################
# CoreOSDistro
############################################################
class CoreOSDistro(AbstractDistro):
"""
CoreOS Distro concrete class
Put CoreOS specific behavior here...
"""
CORE_UID = 500
def __init__(self):
super(CoreOSDistro,self).__init__()
self.requiredDeps += [ "/usr/bin/systemctl" ]
self.agent_service_name = 'waagent'
self.init_script_file='/etc/systemd/system/waagent.service'
self.fileBlackList.append("/etc/machine-id")
self.dhcp_client_name='systemd-networkd'
self.getpidcmd='pidof '
self.shadow_file_mode=0640
self.waagent_path='/usr/share/oem/bin'
self.python_path='/usr/share/oem/python/bin'
self.dhcp_enabled=True
if 'PATH' in os.environ:
os.environ['PATH'] = "{0}:{1}".format(os.environ['PATH'], self.python_path)
else:
os.environ['PATH'] = self.python_path
if 'PYTHONPATH' in os.environ:
os.environ['PYTHONPATH'] = "{0}:{1}".format(os.environ['PYTHONPATH'], self.waagent_path)
else:
os.environ['PYTHONPATH'] = self.waagent_path
def checkPackageInstalled(self,p):
"""
There is no package manager in CoreOS. Return 1 since it must be preinstalled.
"""
return 1
def checkDependencies(self):
for a in self.requiredDeps:
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
Error("Missing required dependency: " + a)
return 1
return 0
def checkPackageUpdateable(self,p):
"""
There is no package manager in CoreOS. Return 0 since it can't be updated via package.
"""
return 0
def startAgentService(self):
return Run('systemctl start ' + self.agent_service_name)
def stopAgentService(self):
return Run('systemctl stop ' + self.agent_service_name)
def restartSshService(self):
"""
SSH is socket activated on CoreOS. No need to restart it.
"""
return 0
def sshDeployPublicKey(self,fprint,path):
"""
We support PKCS8.
"""
if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
return 1
else :
return 0
def RestartInterface(self, iface):
Run("systemctl restart systemd-networkd")
def CreateAccount(self, user, password, expiration, thumbprint):
"""
Create a user account, with 'user', 'password', 'expiration', ssh keys
and sudo permissions.
Returns None if successful, error string on failure.
"""
userentry = None
try:
userentry = pwd.getpwnam(user)
except:
pass
uidmin = None
try:
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
except:
pass
if uidmin == None:
uidmin = 100
if userentry != None and userentry[2] < uidmin and userentry[2] != self.CORE_UID:
Error("CreateAccount: " + user + " is a system user. Will not set password.")
return "Failed to set password for system user: " + user + " (0x06)."
if userentry == None:
command = "useradd --create-home --password '*' " + user
if expiration != None:
command += " --expiredate " + expiration.split('.')[0]
if Run(command):
Error("Failed to create user account: " + user)
return "Failed to create user account: " + user + " (0x07)."
else:
Log("CreateAccount: " + user + " already exists. Will update password.")
if password != None:
self.changePass(user, password)
try:
if password == None:
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
else:
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
os.chmod("/etc/sudoers.d/waagent", 0440)
except:
Error("CreateAccount: Failed to configure sudo access for user.")
return "Failed to configure sudo privileges (0x08)."
home = MyDistro.GetHome()
if thumbprint != None:
dir = home + "/" + user + "/.ssh"
CreateDir(dir, user, 0700)
pub = dir + "/id_rsa.pub"
prv = dir + "/id_rsa"
Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
for f in [pub, prv]:
os.chmod(f, 0600)
ChangeOwner(f, user)
SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
ChangeOwner(dir + "/authorized_keys", user)
Log("Created user account: " + user)
return None
def startDHCP(self):
Run("systemctl start " + self.dhcp_client_name, chk_err=False)
def stopDHCP(self):
Run("systemctl stop " + self.dhcp_client_name, chk_err=False)
def translateCustomData(self, data):
return base64.b64decode(data)
def getConfigurationPath(self):
return "/usr/share/oem/waagent.conf"
############################################################
# debianDistro
############################################################
debian_init_file = """\
#!/bin/sh
### BEGIN INIT INFO
# Provides: AzureLinuxAgent
# Required-Start: $network $syslog
# Required-Stop: $network $syslog
# Should-Start: $network $syslog
# Should-Stop: $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: AzureLinuxAgent
# Description: AzureLinuxAgent
### END INIT INFO
. /lib/lsb/init-functions
OPTIONS="-daemon"
WAZD_BIN=/usr/sbin/waagent
WAZD_PID=/var/run/waagent.pid
case "$1" in
start)
log_begin_msg "Starting AzureLinuxAgent..."
pid=$( pidofproc $WAZD_BIN )
if [ -n "$pid" ] ; then
log_begin_msg "Already running."
log_end_msg 0
exit 0
fi
start-stop-daemon --start --quiet --oknodo --background --exec $WAZD_BIN -- $OPTIONS
log_end_msg $?
;;
stop)
log_begin_msg "Stopping AzureLinuxAgent..."
start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID
ret=$?
rm -f $WAZD_PID
log_end_msg $ret
;;
force-reload)
$0 restart
;;
restart)
$0 stop
$0 start
;;
status)
status_of_proc $WAZD_BIN && exit 0 || exit $?
;;
*)
log_success_msg "Usage: /etc/init.d/waagent {start|stop|force-reload|restart|status}"
exit 1
;;
esac
exit 0
"""
class debianDistro(AbstractDistro):
"""
debian Distro concrete class
Put debian specific behavior here...
"""
def __init__(self):
super(debianDistro,self).__init__()
self.requiredDeps += [ "/usr/sbin/update-rc.d" ]
self.init_file=debian_init_file
self.agent_package_name='walinuxagent'
self.dhcp_client_name='dhclient'
self.getpidcmd='pidof '
self.shadow_file_mode=0640
def checkPackageInstalled(self,p):
"""
Check that the package is installed.
Return 1 if installed, 0 if not installed.
This method of using dpkg-query
allows wildcards to be present in the
package name.
"""
if not Run("dpkg-query -W -f='${Status}\n' '" + p + "' | grep ' installed' 2>&1",chk_err=False):
return 1
else:
return 0
def checkDependencies(self):
"""
Debian dependency check. python-pyasn1 is NOT needed.
Return 1 unless all dependencies are satisfied.
NOTE: using network*manager will catch either package name in Ubuntu or debian.
"""
if self.checkPackageInstalled('network*manager'):
Error(GuestAgentLongName + " is not compatible with network-manager.")
return 1
for a in self.requiredDeps:
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
Error("Missing required dependency: " + a)
return 1
return 0
def checkPackageUpdateable(self,p):
if Run("apt-get update ; apt-get upgrade -us | grep " + p,chk_err=False):
return 1
else:
return 0
def installAgentServiceScriptFiles(self):
"""
If we are packaged - the service name is walinuxagent, do nothing.
"""
if self.agent_service_name == 'walinuxagent':
return 0
try:
SetFileContents(self.init_script_file, self.init_file)
os.chmod(self.init_script_file, 0744)
except OSError, e:
ErrorWithPrefix('installAgentServiceScriptFiles','Exception: '+str(e)+' occured creating ' + self.init_script_file)
return 1
return 0
def registerAgentService(self):
if self.installAgentServiceScriptFiles() == 0:
return Run('update-rc.d waagent defaults')
else :
return 1
def uninstallAgentService(self):
return Run('update-rc.d -f ' + self.agent_service_name + ' remove')
def unregisterAgentService(self):
self.stopAgentService()
return self.uninstallAgentService()
def sshDeployPublicKey(self,fprint,path):
"""
We support PKCS8.
"""
if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
return 1
else :
return 0
############################################################
# KaliDistro - WIP
# Functioning on Kali 1.1.0a so far
############################################################
class KaliDistro(debianDistro):
"""
Kali Distro concrete class
Put Kali specific behavior here...
"""
def __init__(self):
super(KaliDistro,self).__init__()
############################################################
# UbuntuDistro
############################################################
ubuntu_upstart_file = """\
#walinuxagent - start Azure agent
description "walinuxagent"
author "Ben Howard "
start on (filesystem and started rsyslog)
pre-start script
WALINUXAGENT_ENABLED=1
[ -r /etc/default/walinuxagent ] && . /etc/default/walinuxagent
if [ "$WALINUXAGENT_ENABLED" != "1" ]; then
exit 1
fi
if [ ! -x /usr/sbin/waagent ]; then
exit 1
fi
#Load the udf module
modprobe -b udf
end script
exec /usr/sbin/waagent -daemon
"""
class UbuntuDistro(debianDistro):
"""
Ubuntu Distro concrete class
Put Ubuntu specific behavior here...
"""
def __init__(self):
super(UbuntuDistro,self).__init__()
self.init_script_file='/etc/init/waagent.conf'
self.init_file=ubuntu_upstart_file
self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log"]
self.dhcp_client_name=None
self.getpidcmd='pidof '
def registerAgentService(self):
return self.installAgentServiceScriptFiles()
def uninstallAgentService(self):
"""
If we are packaged - the service name is walinuxagent, do nothing.
"""
if self.agent_service_name == 'walinuxagent':
return 0
os.remove('/etc/init/' + self.agent_service_name + '.conf')
def unregisterAgentService(self):
"""
If we are packaged - the service name is walinuxagent, do nothing.
"""
if self.agent_service_name == 'walinuxagent':
return
self.stopAgentService()
return self.uninstallAgentService()
def deprovisionWarnUser(self):
"""
Ubuntu specific warning string from Deprovision.
"""
print("WARNING! Nameserver configuration in /etc/resolvconf/resolv.conf.d/{tail,original} will be deleted.")
def deprovisionDeleteFiles(self):
"""
Ubuntu uses resolv.conf by default, so removing /etc/resolv.conf will
break resolvconf. Therefore, we check to see if resolvconf is in use,
and if so, we remove the resolvconf artifacts.
"""
if os.path.realpath('/etc/resolv.conf') != '/run/resolvconf/resolv.conf':
Log("resolvconf is not configured. Removing /etc/resolv.conf")
self.fileBlackList.append('/etc/resolv.conf')
else:
Log("resolvconf is enabled; leaving /etc/resolv.conf intact")
resolvConfD = '/etc/resolvconf/resolv.conf.d/'
self.fileBlackList.extend([resolvConfD + 'tail', resolvConfD + 'original'])
for f in os.listdir(LibDir)+self.fileBlackList:
try:
os.remove(f)
except:
pass
return 0
def getDhcpClientName(self):
if self.dhcp_client_name != None :
return self.dhcp_client_name
if DistInfo()[1] == '12.04' :
self.dhcp_client_name='dhclient3'
else :
self.dhcp_client_name='dhclient'
return self.dhcp_client_name
def waitForSshHostKey(self, path):
"""
Wait until the ssh host key is generated by cloud init.
"""
for retry in range(0, 10):
if(os.path.isfile(path)):
return True
time.sleep(1)
Error("Can't find host key: {0}".format(path))
return False
############################################################
# LinuxMintDistro
############################################################
class LinuxMintDistro(UbuntuDistro):
"""
LinuxMint Distro concrete class
Put LinuxMint specific behavior here...
"""
def __init__(self):
super(LinuxMintDistro,self).__init__()
############################################################
# fedoraDistro
############################################################
fedora_systemd_service = """\
[Unit]
Description=Azure Linux Agent
After=network.target
After=sshd.service
ConditionFileIsExecutable=/usr/sbin/waagent
ConditionPathExists=/etc/waagent.conf
[Service]
Type=simple
ExecStart=/usr/sbin/waagent -daemon
[Install]
WantedBy=multi-user.target
"""
class fedoraDistro(redhatDistro):
"""
FedoraDistro concrete class
Put Fedora specific behavior here...
"""
def __init__(self):
super(fedoraDistro,self).__init__()
self.service_cmd = '/usr/bin/systemctl'
self.hostname_file_path = '/etc/hostname'
self.init_script_file = '/usr/lib/systemd/system/' + self.agent_service_name + '.service'
self.init_file = fedora_systemd_service
self.grubKernelBootOptionsFile = '/etc/default/grub'
self.grubKernelBootOptionsLine = 'GRUB_CMDLINE_LINUX='
def publishHostname(self, name):
SetFileContents(self.hostname_file_path, name + '\n')
ethernetInterface = MyDistro.GetInterfaceName()
filepath = "/etc/sysconfig/network-scripts/ifcfg-" + ethernetInterface
if os.path.isfile(filepath):
ReplaceFileContentsAtomic(filepath, "DHCP_HOSTNAME=" + name + "\n"
+ "\n".join(filter(lambda a: not a.startswith("DHCP_HOSTNAME"), GetFileContents(filepath).split('\n'))))
return 0
def installAgentServiceScriptFiles(self):
SetFileContents(self.init_script_file, self.init_file)
os.chmod(self.init_script_file, 0644)
return Run(self.service_cmd + ' daemon-reload')
def registerAgentService(self):
self.installAgentServiceScriptFiles()
return Run(self.service_cmd + ' enable ' + self.agent_service_name)
def uninstallAgentService(self):
"""
Call service subsystem to remove waagent script.
"""
return Run(self.service_cmd + ' disable ' + self.agent_service_name)
def unregisterAgentService(self):
"""
Calls self.stopAgentService and call self.uninstallAgentService()
"""
self.stopAgentService()
self.uninstallAgentService()
def startAgentService(self):
"""
Service call to start the Agent service
"""
return Run(self.service_cmd + ' start ' + self.agent_service_name)
def stopAgentService(self):
"""
Service call to stop the Agent service
"""
return Run(self.service_cmd + ' stop ' + self.agent_service_name, False)
def restartSshService(self):
"""
Service call to re(start) the SSH service
"""
sshRestartCmd = self.service_cmd + " " + self.ssh_service_restart_option + " " + self.ssh_service_name
retcode = Run(sshRestartCmd)
if retcode > 0:
Error("Failed to restart SSH service with return code:" + str(retcode))
return retcode
def checkPackageInstalled(self, p):
"""
Query package database for prescence of an installed package.
"""
import rpm
ts = rpm.TransactionSet()
rpms = ts.dbMatch(rpm.RPMTAG_PROVIDES, p)
return bool(len(rpms) > 0)
def deleteRootPassword(self):
return Run("/sbin/usermod root -p '!!'")
def packagedInstall(self,buildroot):
"""
Called from setup.py for use by RPM.
Copies generated files waagent.conf, under the buildroot.
"""
if not os.path.exists(buildroot+'/etc'):
os.mkdir(buildroot+'/etc')
SetFileContents(buildroot+'/etc/waagent.conf', MyDistro.waagent_conf_file)
if not os.path.exists(buildroot+'/etc/logrotate.d'):
os.mkdir(buildroot+'/etc/logrotate.d')
SetFileContents(buildroot+'/etc/logrotate.d/WALinuxAgent', WaagentLogrotate)
self.init_script_file=buildroot+self.init_script_file
# this allows us to call installAgentServiceScriptFiles()
if not os.path.exists(os.path.dirname(self.init_script_file)):
os.mkdir(os.path.dirname(self.init_script_file))
self.installAgentServiceScriptFiles()
def CreateAccount(self, user, password, expiration, thumbprint):
super(fedoraDistro, self).CreateAccount(user, password, expiration, thumbprint)
Run('/sbin/usermod ' + user + ' -G wheel')
def DeleteAccount(self, user):
Run('/sbin/usermod ' + user + ' -G ""')
super(fedoraDistro, self).DeleteAccount(user)
############################################################
# FreeBSD
############################################################
FreeBSDWaagentConf = """\
#
# Azure Linux Agent Configuration
#
Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status
# to the endpoint server.
Role.ConfigurationConsumer=None # Specified program is invoked with XML file argument specifying role configuration.
Role.TopologyConsumer=None # Specified program is invoked with XML file argument specifying role topology.
Provisioning.Enabled=y #
Provisioning.DeleteRootPassword=y # Password authentication for root account will be unavailable.
Provisioning.RegenerateSshHostKeyPair=y # Generate fresh host key pair.
Provisioning.SshHostKeyPairType=rsa # Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.MonitorHostName=y # Monitor host name changes and publish changes via DHCP requests.
ResourceDisk.Format=y # Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Filesystem=ufs2 #
ResourceDisk.MountPoint=/mnt/resource #
ResourceDisk.EnableSwap=n # Create and use swapfile on resource disk.
ResourceDisk.SwapSizeMB=0 # Size of the swapfile.
LBProbeResponder=y # Respond to load balancer probes if requested by Azure.
Logs.Verbose=n # Enable verbose logs
OS.RootDeviceScsiTimeout=300 # Root device timeout in seconds.
OS.OpensslPath=None # If "None", the system default version is used.
"""
bsd_init_file="""\
#! /bin/sh
# PROVIDE: waagent
# REQUIRE: DAEMON cleanvar sshd
# BEFORE: LOGIN
# KEYWORD: nojail
. /etc/rc.subr
export PATH=$PATH:/usr/local/bin
name="waagent"
rcvar="waagent_enable"
command="/usr/sbin/${name}"
command_interpreter="/usr/local/bin/python"
waagent_flags=" daemon &"
pidfile="/var/run/waagent.pid"
load_rc_config $name
run_rc_command "$1"
"""
bsd_activate_resource_disk_txt="""\
#!/usr/bin/env python
import os
import sys
import imp
# waagent has no '.py' therefore create waagent module import manually.
__name__='setupmain' #prevent waagent.__main__ from executing
waagent=imp.load_source('waagent','/tmp/waagent')
waagent.LoggerInit('/var/log/waagent.log','/dev/console')
from waagent import RunGetOutput,Run
Config=waagent.ConfigurationProvider(None)
format = Config.get("ResourceDisk.Format")
if format == None or format.lower().startswith("n"):
sys.exit(0)
device_base = 'da1'
device = "/dev/" + device_base
for entry in RunGetOutput("mount")[1].split():
if entry.startswith(device + "s1"):
waagent.Log("ActivateResourceDisk: " + device + "s1 is already mounted.")
sys.exit(0)
mountpoint = Config.get("ResourceDisk.MountPoint")
if mountpoint == None:
mountpoint = "/mnt/resource"
waagent.CreateDir(mountpoint, "root", 0755)
fs = Config.get("ResourceDisk.Filesystem")
if waagent.FreeBSDDistro().mediaHasFilesystem(device) == False :
Run("newfs " + device + "s1")
if Run("mount " + device + "s1 " + mountpoint):
waagent.Error("ActivateResourceDisk: Failed to mount resource disk (" + device + "s1).")
sys.exit(0)
waagent.Log("Resource disk (" + device + "s1) is mounted at " + mountpoint + " with fstype " + fs)
waagent.SetFileContents(os.path.join(mountpoint,waagent.README_FILENAME), waagent.README_FILECONTENT)
swap = Config.get("ResourceDisk.EnableSwap")
if swap == None or swap.lower().startswith("n"):
sys.exit(0)
sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
os.remove(mountpoint + "/swapfile")
if not os.path.isfile(mountpoint + "/swapfile"):
Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
if Run("mdconfig -a -t vnode -f " + mountpoint + "/swapfile -u 0"):
waagent.Error("ActivateResourceDisk: Configuring swap - Failed to create md0")
if not Run("swapon /dev/md0"):
waagent.Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
else:
waagent.Error("ActivateResourceDisk: Failed to activate swap at " + mountpoint + "/swapfile")
"""
class FreeBSDDistro(AbstractDistro):
"""
"""
def __init__(self):
"""
Generic Attributes go here. These are based on 'majority rules'.
This __init__() may be called or overriden by the child.
"""
super(FreeBSDDistro,self).__init__()
self.agent_service_name = os.path.basename(sys.argv[0])
self.selinux=False
self.ssh_service_name='sshd'
self.ssh_config_file='/etc/ssh/sshd_config'
self.hostname_file_path='/etc/hostname'
self.dhcp_client_name='dhclient'
self.requiredDeps = [ 'route', 'shutdown', 'ssh-keygen', 'pw'
, 'openssl', 'fdisk', 'sed', 'grep' , 'sudo']
self.init_script_file='/etc/rc.d/waagent'
self.init_file=bsd_init_file
self.agent_package_name='WALinuxAgent'
self.fileBlackList = [ "/root/.bash_history", "/var/log/waagent.log",'/etc/resolv.conf' ]
self.agent_files_to_uninstall = ["/etc/waagent.conf"]
self.grubKernelBootOptionsFile = '/boot/loader.conf'
self.grubKernelBootOptionsLine = ''
self.getpidcmd = 'pgrep -n'
self.mount_dvd_cmd = 'dd bs=2048 count=33 skip=295 if=' # custom data max len is 64k
self.sudoers_dir_base = '/usr/local/etc'
self.waagent_conf_file = FreeBSDWaagentConf
def installAgentServiceScriptFiles(self):
SetFileContents(self.init_script_file, self.init_file)
os.chmod(self.init_script_file, 0777)
AppendFileContents("/etc/rc.conf","waagent_enable='YES'\n")
return 0
def registerAgentService(self):
self.installAgentServiceScriptFiles()
return Run("services_mkdb " + self.init_script_file)
def sshDeployPublicKey(self,fprint,path):
"""
We support PKCS8.
"""
if Run("ssh-keygen -i -m PKCS8 -f " + fprint + " >> " + path):
return 1
else :
return 0
def deleteRootPassword(self):
"""
BSD root password removal.
"""
filepath="/etc/master.passwd"
ReplaceStringInFile(filepath,r'root:.*?:','root::')
#ReplaceFileContentsAtomic(filepath,"root:*LOCK*:14600::::::\n"
# + "\n".join(filter(lambda a: not a.startswith("root:"),GetFileContents(filepath).split('\n'))))
os.chmod(filepath,self.shadow_file_mode)
if self.isSelinuxSystem():
self.setSelinuxContext(filepath,'system_u:object_r:shadow_t:s0')
RunGetOutput("pwd_mkdb -u root /etc/master.passwd")
Log("Root password deleted.")
return 0
def changePass(self,user,password):
return RunSendStdin("pw usermod " + user + " -h 0 ",password, log_cmd=False)
def load_ata_piix(self):
return 0
def unload_ata_piix(self):
return 0
def checkDependencies(self):
"""
FreeBSD dependency check.
Return 1 unless all dependencies are satisfied.
"""
for a in self.requiredDeps:
if Run("which " + a + " > /dev/null 2>&1",chk_err=False):
Error("Missing required dependency: " + a)
return 1
return 0
def packagedInstall(self,buildroot):
pass
def GetInterfaceName(self):
"""
Return the ip of the
active ethernet interface.
"""
iface,inet,mac=self.GetFreeBSDEthernetInfo()
return iface
def RestartInterface(self, iface):
Run("service netif restart")
def GetIpv4Address(self):
"""
Return the ip of the
active ethernet interface.
"""
iface,inet,mac=self.GetFreeBSDEthernetInfo()
return inet
def GetMacAddress(self):
"""
Return the ip of the
active ethernet interface.
"""
iface,inet,mac=self.GetFreeBSDEthernetInfo()
l=mac.split(':')
r=[]
for i in l:
r.append(string.atoi(i,16))
return r
def GetFreeBSDEthernetInfo(self):
"""
There is no SIOCGIFCONF
on freeBSD - just parse ifconfig.
Returns strings: iface, inet4_addr, and mac
or 'None,None,None' if unable to parse.
We will sleep and retry as the network must be up.
"""
code,output=RunGetOutput("ifconfig",chk_err=False)
Log(output)
retries=10
cmd='ifconfig | grep -A2 -B2 ether | grep -B3 inet | grep -A4 UP '
code=1
while code > 0 :
if code > 0 and retries == 0:
Error("GetFreeBSDEthernetInfo - Failed to detect ethernet interface")
return None, None, None
code,output=RunGetOutput(cmd,chk_err=False)
retries-=1
if code > 0 and retries > 0 :
Log("GetFreeBSDEthernetInfo - Error: retry ethernet detection " + str(retries))
if retries == 9 :
c,o=RunGetOutput("ifconfig | grep -A1 -B2 ether",chk_err=False)
if c == 0:
t=o.replace('\n',' ')
t=t.split()
i=t[0][:-1]
Log(RunGetOutput('id')[1])
Run('dhclient '+i)
time.sleep(10)
j=output.replace('\n',' ')
j=j.split()
iface=j[0][:-1]
for i in range(len(j)):
if j[i] == 'inet' :
inet=j[i+1]
elif j[i] == 'ether' :
mac=j[i+1]
return iface, inet, mac
def CreateAccount(self,user, password, expiration, thumbprint):
"""
Create a user account, with 'user', 'password', 'expiration', ssh keys
and sudo permissions.
Returns None if successful, error string on failure.
"""
userentry = None
try:
userentry = pwd.getpwnam(user)
except:
pass
uidmin = None
try:
if os.path.isfile("/etc/login.defs"):
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
except:
pass
if uidmin == None:
uidmin = 100
if userentry != None and userentry[2] < uidmin:
Error("CreateAccount: " + user + " is a system user. Will not set password.")
return "Failed to set password for system user: " + user + " (0x06)."
if userentry == None:
command = "pw useradd " + user + " -m"
if expiration != None:
command += " -e " + expiration.split('.')[0]
if Run(command):
Error("Failed to create user account: " + user)
return "Failed to create user account: " + user + " (0x07)."
else:
Log("CreateAccount: " + user + " already exists. Will update password.")
if password != None:
self.changePass(user,password)
try:
# for older distros create sudoers.d
if not os.path.isdir(MyDistro.sudoers_dir_base+'/sudoers.d/'):
# create the /etc/sudoers.d/ directory
os.mkdir(MyDistro.sudoers_dir_base+'/sudoers.d')
# add the include of sudoers.d to the /etc/sudoers
SetFileContents(MyDistro.sudoers_dir_base+'/sudoers',GetFileContents(MyDistro.sudoers_dir_base+'/sudoers')+'\n#includedir ' + MyDistro.sudoers_dir_base + '/sudoers.d\n')
if password == None:
SetFileContents(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
else:
SetFileContents(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
os.chmod(MyDistro.sudoers_dir_base+"/sudoers.d/waagent", 0440)
except:
Error("CreateAccount: Failed to configure sudo access for user.")
return "Failed to configure sudo privileges (0x08)."
home = MyDistro.GetHome()
if thumbprint != None:
dir = home + "/" + user + "/.ssh"
CreateDir(dir, user, 0700)
pub = dir + "/id_rsa.pub"
prv = dir + "/id_rsa"
Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
for f in [pub, prv]:
os.chmod(f, 0600)
ChangeOwner(f, user)
SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
ChangeOwner(dir + "/authorized_keys", user)
Log("Created user account: " + user)
return None
def DeleteAccount(self,user):
"""
Delete the 'user'.
Clear utmp first, to avoid error.
Removes the /etc/sudoers.d/waagent file.
"""
userentry = None
try:
userentry = pwd.getpwnam(user)
except:
pass
if userentry == None:
Error("DeleteAccount: " + user + " not found.")
return
uidmin = None
try:
if os.path.isfile("/etc/login.defs"):
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
except:
pass
if uidmin == None:
uidmin = 100
if userentry[2] < uidmin:
Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
return
Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
pid = subprocess.Popen(['rmuser', '-y', user], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE).pid
try:
os.remove(MyDistro.sudoers_dir_base+"/sudoers.d/waagent")
except:
pass
return
def ActivateResourceDiskNoThread(self):
"""
Format, mount, and if specified in the configuration
set resource disk as swap.
"""
global DiskActivated
Run('cp /usr/sbin/waagent /tmp/')
SetFileContents('/tmp/bsd_activate_resource_disk.py',bsd_activate_resource_disk_txt)
Run('chmod +x /tmp/bsd_activate_resource_disk.py')
pid = subprocess.Popen(["/tmp/bsd_activate_resource_disk.py", ""]).pid
Log("Spawning bsd_activate_resource_disk.py")
DiskActivated = True
return
def Install(self):
"""
Install the agent service.
Check dependencies.
Create /etc/waagent.conf and move old version to
/etc/waagent.conf.old
Copy RulesFiles to /var/lib/waagent
Create /etc/logrotate.d/waagent
Set /etc/ssh/sshd_config ClientAliveInterval to 180
Call ApplyVNUMAWorkaround()
"""
if MyDistro.checkDependencies():
return 1
os.chmod(sys.argv[0], 0755)
SwitchCwd()
for a in RulesFiles:
if os.path.isfile(a):
if os.path.isfile(GetLastPathElement(a)):
os.remove(GetLastPathElement(a))
shutil.move(a, ".")
Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
MyDistro.registerAgentService()
if os.path.isfile("/etc/waagent.conf"):
try:
os.remove("/etc/waagent.conf.old")
except:
pass
try:
os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
except:
pass
SetFileContents("/etc/waagent.conf", self.waagent_conf_file)
if os.path.exists('/usr/local/etc/logrotate.d/'):
SetFileContents("/usr/local/etc/logrotate.d/waagent", WaagentLogrotate)
filepath = "/etc/ssh/sshd_config"
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
a.startswith("ClientAliveInterval"),
GetFileContents(filepath).split('\n'))) + "\nClientAliveInterval 180\n")
Log("Configured SSH client probing to keep connections alive.")
#ApplyVNUMAWorkaround()
return 0
def mediaHasFilesystem(self,dsk):
if Run('LC_ALL=C fdisk -p ' + dsk + ' | grep "invalid fdisk partition table found" ',False):
return False
return True
def mountDVD(self,dvd,location):
#At this point we cannot read a joliet option udf DVD in freebsd10 - so we 'dd' it into our location
retcode,out = RunGetOutput(self.mount_dvd_cmd + dvd + ' of=' + location + '/ovf-env.xml')
if retcode != 0:
return retcode,out
ovfxml = (GetFileContents(location+"/ovf-env.xml",asbin=False))
if ord(ovfxml[0]) > 128 and ord(ovfxml[1]) > 128 and ord(ovfxml[2]) > 128 :
ovfxml = ovfxml[3:] # BOM is not stripped. First three bytes are > 128 and not unicode chars so we ignore them.
ovfxml = ovfxml.strip(chr(0x00))
ovfxml = "".join(filter(lambda x: ord(x)<128, ovfxml))
ovfxml = re.sub(r'.*\Z','',ovfxml,0,re.DOTALL)
ovfxml += ''
SetFileContents(location+"/ovf-env.xml", ovfxml)
return retcode,out
def GetHome(self):
return '/home'
def initScsiDiskTimeout(self):
"""
Set the SCSI disk timeout by updating the kernal config
"""
timeout = Config.get("OS.RootDeviceScsiTimeout")
if timeout:
Run("sysctl kern.cam.da.default_timeout=" + timeout)
def setScsiDiskTimeout(self):
return
def setBlockDeviceTimeout(self, device, timeout):
return
def getProcessorCores(self):
return int(RunGetOutput("sysctl hw.ncpu | awk '{print $2}'")[1])
def getTotalMemory(self):
return int(RunGetOutput("sysctl hw.realmem | awk '{print $2}'")[1])/1024
def setDefaultGateway(self, gateway):
Run("/sbin/route add default " + gateway, chk_err=False)
def routeAdd(self, net, mask, gateway):
Run("/sbin/route add -net " + net + " " + mask + " " + gateway, chk_err=False)
############################################################
# END DISTRO CLASS DEFS
############################################################
# This lets us index into a string or an array of integers transparently.
def Ord(a):
"""
Allows indexing into a string or an array of integers transparently.
Generic utility function.
"""
if type(a) == type("a"):
a = ord(a)
return a
def IsLinux():
"""
Returns True if platform is Linux.
Generic utility function.
"""
return (platform.uname()[0] == "Linux")
def GetLastPathElement(path):
"""
Similar to basename.
Generic utility function.
"""
return path.rsplit('/', 1)[1]
def GetFileContents(filepath,asbin=False):
"""
Read and return contents of 'filepath'.
"""
mode='r'
if asbin:
mode+='b'
c=None
try:
with open(filepath, mode) as F :
c=F.read()
except IOError, e:
ErrorWithPrefix('GetFileContents','Reading from file ' + filepath + ' Exception is ' + str(e))
return None
return c
def SetFileContents(filepath, contents):
"""
Write 'contents' to 'filepath'.
"""
if type(contents) == str :
contents=contents.encode('latin-1', 'ignore')
try:
with open(filepath, "wb+") as F :
F.write(contents)
except IOError, e:
ErrorWithPrefix('SetFileContents','Writing to file ' + filepath + ' Exception is ' + str(e))
return None
return 0
def AppendFileContents(filepath, contents):
"""
Append 'contents' to 'filepath'.
"""
if type(contents) == str :
contents=contents.encode('latin-1')
try:
with open(filepath, "a+") as F :
F.write(contents)
except IOError, e:
ErrorWithPrefix('AppendFileContents','Appending to file ' + filepath + ' Exception is ' + str(e))
return None
return 0
def ReplaceFileContentsAtomic(filepath, contents):
"""
Write 'contents' to 'filepath' by creating a temp file, and replacing original.
"""
handle, temp = tempfile.mkstemp(dir = os.path.dirname(filepath))
if type(contents) == str :
contents=contents.encode('latin-1')
try:
os.write(handle, contents)
except IOError, e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Writing to file ' + filepath + ' Exception is ' + str(e))
return None
finally:
os.close(handle)
try:
os.rename(temp, filepath)
return None
except IOError, e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Renaming ' + temp+ ' to ' + filepath + ' Exception is ' + str(e))
try:
os.remove(filepath)
except IOError, e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
try:
os.rename(temp,filepath)
except IOError, e:
ErrorWithPrefix('ReplaceFileContentsAtomic','Removing '+ filepath + ' Exception is ' + str(e))
return 1
return 0
def GetLineStartingWith(prefix, filepath):
"""
Return line from 'filepath' if the line startswith 'prefix'
"""
for line in GetFileContents(filepath).split('\n'):
if line.startswith(prefix):
return line
return None
def Run(cmd,chk_err=True):
"""
Calls RunGetOutput on 'cmd', returning only the return code.
If chk_err=True then errors will be reported in the log.
If chk_err=False then errors will be suppressed from the log.
"""
retcode,out=RunGetOutput(cmd,chk_err)
return retcode
def RunGetOutput(cmd, chk_err=True, log_cmd=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
"""
if log_cmd:
LogIfVerbose(cmd)
try:
output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
except subprocess.CalledProcessError,e :
if chk_err and log_cmd:
Error('CalledProcessError. Error Code is ' + str(e.returncode) )
Error('CalledProcessError. Command string was ' + e.cmd )
Error('CalledProcessError. Command result was ' + (e.output[:-1]).decode('latin-1'))
return e.returncode,e.output.decode('latin-1')
return 0,output.decode('latin-1')
def RunSendStdin(cmd, input, chk_err=True, log_cmd=True):
"""
Wrapper for subprocess.Popen.
Execute 'cmd', sending 'input' to STDIN of 'cmd'.
Returns return code and STDOUT, trapping expected exceptions.
Reports exceptions to Error if chk_err parameter is True
"""
if log_cmd:
LogIfVerbose(cmd+input)
try:
me=subprocess.Popen([cmd], shell=True, stdin=subprocess.PIPE,stderr=subprocess.STDOUT,stdout=subprocess.PIPE)
output=me.communicate(input)
except OSError , e :
if chk_err and log_cmd:
Error('CalledProcessError. Error Code is ' + str(me.returncode) )
Error('CalledProcessError. Command string was ' + cmd )
Error('CalledProcessError. Command result was ' + output[0].decode('latin-1'))
return 1,output[0].decode('latin-1')
if me.returncode is not 0 and chk_err is True and log_cmd:
Error('CalledProcessError. Error Code is ' + str(me.returncode) )
Error('CalledProcessError. Command string was ' + cmd )
Error('CalledProcessError. Command result was ' + output[0].decode('latin-1'))
return me.returncode,output[0].decode('latin-1')
def GetNodeTextData(a):
"""
Filter non-text nodes from DOM tree
"""
for b in a.childNodes:
if b.nodeType == b.TEXT_NODE:
return b.data
def GetHome():
"""
Attempt to guess the $HOME location.
Return the path string.
"""
home = None
try:
home = GetLineStartingWith("HOME", "/etc/default/useradd").split('=')[1].strip()
except:
pass
if (home == None) or (home.startswith("/") == False):
home = "/home"
return home
def ChangeOwner(filepath, user):
"""
Lookup user. Attempt chown 'filepath' to 'user'.
"""
p = None
try:
p = pwd.getpwnam(user)
except:
pass
if p != None:
os.chown(filepath, p[2], p[3])
def CreateDir(dirpath, user, mode):
"""
Attempt os.makedirs, catch all exceptions.
Call ChangeOwner afterwards.
"""
try:
os.makedirs(dirpath, mode)
except:
pass
ChangeOwner(dirpath, user)
def CreateAccount(user, password, expiration, thumbprint):
"""
Create a user account, with 'user', 'password', 'expiration', ssh keys
and sudo permissions.
Returns None if successful, error string on failure.
"""
userentry = None
try:
userentry = pwd.getpwnam(user)
except:
pass
uidmin = None
try:
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
except:
pass
if uidmin == None:
uidmin = 100
if userentry != None and userentry[2] < uidmin:
Error("CreateAccount: " + user + " is a system user. Will not set password.")
return "Failed to set password for system user: " + user + " (0x06)."
if userentry == None:
command = "useradd -m " + user
if expiration != None:
command += " -e " + expiration.split('.')[0]
if Run(command):
Error("Failed to create user account: " + user)
return "Failed to create user account: " + user + " (0x07)."
else:
Log("CreateAccount: " + user + " already exists. Will update password.")
if password != None:
MyDistro.changePass(user, password)
try:
# for older distros create sudoers.d
if not os.path.isdir('/etc/sudoers.d/'):
# create the /etc/sudoers.d/ directory
os.mkdir('/etc/sudoers.d/')
# add the include of sudoers.d to the /etc/sudoers
SetFileContents('/etc/sudoers',GetFileContents('/etc/sudoers')+'\n#includedir /etc/sudoers.d\n')
if password == None:
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n")
else:
SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) ALL\n")
os.chmod("/etc/sudoers.d/waagent", 0440)
except:
Error("CreateAccount: Failed to configure sudo access for user.")
return "Failed to configure sudo privileges (0x08)."
home = MyDistro.GetHome()
if thumbprint != None:
dir = home + "/" + user + "/.ssh"
CreateDir(dir, user, 0700)
pub = dir + "/id_rsa.pub"
prv = dir + "/id_rsa"
Run("ssh-keygen -y -f " + thumbprint + ".prv > " + pub)
SetFileContents(prv, GetFileContents(thumbprint + ".prv"))
for f in [pub, prv]:
os.chmod(f, 0600)
ChangeOwner(f, user)
SetFileContents(dir + "/authorized_keys", GetFileContents(pub))
ChangeOwner(dir + "/authorized_keys", user)
Log("Created user account: " + user)
return None
def DeleteAccount(user):
"""
Delete the 'user'.
Clear utmp first, to avoid error.
Removes the /etc/sudoers.d/waagent file.
"""
userentry = None
try:
userentry = pwd.getpwnam(user)
except:
pass
if userentry == None:
Error("DeleteAccount: " + user + " not found.")
return
uidmin = None
try:
uidmin = int(GetLineStartingWith("UID_MIN", "/etc/login.defs").split()[1])
except:
pass
if uidmin == None:
uidmin = 100
if userentry[2] < uidmin:
Error("DeleteAccount: " + user + " is a system user. Will not delete account.")
return
Run("> /var/run/utmp") #Delete utmp to prevent error if we are the 'user' deleted
Run("userdel -f -r " + user)
try:
os.remove("/etc/sudoers.d/waagent")
except:
pass
return
def IsInRangeInclusive(a, low, high):
"""
Return True if 'a' in 'low' <= a >= 'high'
"""
return (a >= low and a <= high)
def IsPrintable(ch):
"""
Return True if character is displayable.
"""
return IsInRangeInclusive(ch, Ord('A'), Ord('Z')) or IsInRangeInclusive(ch, Ord('a'), Ord('z')) or IsInRangeInclusive(ch, Ord('0'), Ord('9'))
def HexDump(buffer, size):
"""
Return Hex formated dump of a 'buffer' of 'size'.
"""
if size < 0:
size = len(buffer)
result = ""
for i in range(0, size):
if (i % 16) == 0:
result += "%06X: " % i
byte = buffer[i]
if type(byte) == str:
byte = ord(byte.decode('latin1'))
result += "%02X " % byte
if (i & 15) == 7:
result += " "
if ((i + 1) % 16) == 0 or (i + 1) == size:
j = i
while ((j + 1) % 16) != 0:
result += " "
if (j & 7) == 7:
result += " "
j += 1
result += " "
for j in range(i - (i % 16), i + 1):
byte=buffer[j]
if type(byte) == str:
byte = ord(byte.decode('latin1'))
k = '.'
if IsPrintable(byte):
k = chr(byte)
result += k
if (i + 1) != size:
result += "\n"
return result
def SimpleLog(file_path,message):
if not file_path or len(message) < 1:
return
t = time.localtime()
t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
lines=re.sub(re.compile(r'^(.)',re.MULTILINE),t+r'\1',message)
with open(file_path, "a") as F :
lines = filter(lambda x : x in string.printable, lines)
F.write(lines.encode('ascii','ignore') + "\n")
class Logger(object):
"""
The Agent's logging assumptions are:
For Log, and LogWithPrefix all messages are logged to the
self.file_path and to the self.con_path. Setting either path
parameter to None skips that log. If Verbose is enabled, messages
calling the LogIfVerbose method will be logged to file_path yet
not to con_path. Error and Warn messages are normal log messages
with the 'ERROR:' or 'WARNING:' prefix added.
"""
def __init__(self,filepath,conpath,verbose=False):
"""
Construct an instance of Logger.
"""
self.file_path=filepath
self.con_path=conpath
self.verbose=verbose
def ThrottleLog(self,counter):
"""
Log everything up to 10, every 10 up to 100, then every 100.
"""
return (counter < 10) or ((counter < 100) and ((counter % 10) == 0)) or ((counter % 100) == 0)
def LogToFile(self,message):
"""
Write 'message' to logfile.
"""
if self.file_path:
try:
with open(self.file_path, "a") as F :
message = filter(lambda x : x in string.printable, message)
F.write(message.encode('ascii','ignore') + "\n")
except IOError, e:
print e
pass
def LogToCon(self,message):
"""
Write 'message' to /dev/console.
This supports serial port logging if the /dev/console
is redirected to ttys0 in kernel boot options.
"""
if self.con_path:
try:
with open(self.con_path, "w") as C :
message = filter(lambda x : x in string.printable, message)
C.write(message.encode('ascii','ignore') + "\n")
except IOError, e:
pass
def Log(self,message):
"""
Standard Log function.
Logs to self.file_path, and con_path
"""
self.LogWithPrefix("", message)
def LogWithPrefix(self,prefix, message):
"""
Prefix each line of 'message' with current time+'prefix'.
"""
t = time.localtime()
t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
t += prefix
for line in message.split('\n'):
line = t + line
self.LogToFile(line)
self.LogToCon(line)
def NoLog(self,message):
"""
Don't Log.
"""
pass
def LogIfVerbose(self,message):
"""
Only log 'message' if global Verbose is True.
"""
self.LogWithPrefixIfVerbose('',message)
def LogWithPrefixIfVerbose(self,prefix, message):
"""
Only log 'message' if global Verbose is True.
Prefix each line of 'message' with current time+'prefix'.
"""
if self.verbose == True:
t = time.localtime()
t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)
t += prefix
for line in message.split('\n'):
line = t + line
self.LogToFile(line)
self.LogToCon(line)
def Warn(self,message):
"""
Prepend the text "WARNING:" to the prefix for each line in 'message'.
"""
self.LogWithPrefix("WARNING:", message)
def Error(self,message):
"""
Call ErrorWithPrefix(message).
"""
ErrorWithPrefix("", message)
def ErrorWithPrefix(self,prefix, message):
"""
Prepend the text "ERROR:" to the prefix for each line in 'message'.
Errors written to logfile, and /dev/console
"""
self.LogWithPrefix("ERROR:", message)
def LoggerInit(log_file_path,log_con_path,verbose=False):
"""
Create log object and export its methods to global scope.
"""
global Log,LogWithPrefix,LogIfVerbose,LogWithPrefixIfVerbose,Error,ErrorWithPrefix,Warn,NoLog,ThrottleLog,myLogger
l=Logger(log_file_path,log_con_path,verbose)
Log,LogWithPrefix,LogIfVerbose,LogWithPrefixIfVerbose,Error,ErrorWithPrefix,Warn,NoLog,ThrottleLog,myLogger = l.Log,l.LogWithPrefix,l.LogIfVerbose,l.LogWithPrefixIfVerbose,l.Error,l.ErrorWithPrefix,l.Warn,l.NoLog,l.ThrottleLog,l
def Linux_ioctl_GetInterfaceMac(ifname):
"""
Return the mac-address bound to the socket.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', (ifname[:15]+('\0'*241)).encode('latin-1')))
return ''.join(['%02X' % Ord(char) for char in info[18:24]])
def GetFirstActiveNetworkInterfaceNonLoopback():
"""
Return the interface name, and ip addr of the
first active non-loopback interface.
"""
iface=''
expected=16 # how many devices should I expect...
is_64bits = sys.maxsize > 2**32
struct_size=40 if is_64bits else 32 # for 64bit the size is 40 bytes, for 32bits it is 32 bytes.
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
buff=array.array('B', b'\0' * (expected*struct_size))
retsize=(struct.unpack('iL', fcntl.ioctl(s.fileno(), 0x8912, struct.pack('iL',expected*struct_size,buff.buffer_info()[0]))))[0]
if retsize == (expected*struct_size) :
Warn('SIOCGIFCONF returned more than ' + str(expected) + ' up network interfaces.')
s=buff.tostring()
preferred_nic = Config.get("Network.Interface")
for i in range(0,struct_size*expected,struct_size):
iface=s[i:i+16].split(b'\0', 1)[0]
if iface == b'lo':
continue
elif preferred_nic is None:
break
elif iface == preferred_nic:
break
return iface.decode('latin-1'), socket.inet_ntoa(s[i+20:i+24])
def GetIpv4Address():
"""
Return the ip of the
first active non-loopback interface.
"""
iface,addr=GetFirstActiveNetworkInterfaceNonLoopback()
return addr
def HexStringToByteArray(a):
"""
Return hex string packed into a binary struct.
"""
b = b""
for c in range(0, len(a) // 2):
b += struct.pack("B", int(a[c * 2:c * 2 + 2], 16))
return b
def GetMacAddress():
"""
Convienience function, returns mac addr bound to
first non-loobback interface.
"""
ifname=''
while len(ifname) < 2 :
ifname=GetFirstActiveNetworkInterfaceNonLoopback()[0]
a = Linux_ioctl_GetInterfaceMac(ifname)
return HexStringToByteArray(a)
def DeviceForIdePort(n):
"""
Return device name attached to ide port 'n'.
"""
if n > 3:
return None
g0 = "00000000"
if n > 1:
g0 = "00000001"
n = n - 2
device = None
path = "/sys/bus/vmbus/devices/"
for vmbus in os.listdir(path):
guid = GetFileContents(path + vmbus + "/device_id").lstrip('{').split('-')
if guid[0] == g0 and guid[1] == "000" + str(n):
for root, dirs, files in os.walk(path + vmbus):
if root.endswith("/block"):
device = dirs[0]
break
else : #older distros
for d in dirs:
if ':' in d and "block" == d.split(':')[0]:
device = d.split(':')[1]
break
break
return device
class HttpResourceGoneError(Exception):
pass
def DoInstallRHUIRPM():
"""
Install RHUI RPM according to VM region
"""
rhuiRPMinstalled = os.path.exists(LibDir + "/rhuirpminstalled")
if rhuiRPMinstalled:
return
else:
SetFileContents(LibDir + "/rhuirpminstalled", "")
Log("Begin to install RHUI RPM")
cmd = "grep '' /var/lib/waagent/ExtensionsConfig* --no-filename | sed 's///g' | sed 's/<\/Location>//g' | sed 's/ //g' | tr 'A-Z' 'a-z' | uniq"
retcode,out = RunGetOutput(cmd, True)
region = out.rstrip("\n")
#try a few times at most to get the region info
retry = 0
for i in range(0, 8):
if (region != ""):
break
Log("region info is empty, now wait 15 seconds...")
time.sleep(15)
retcode,out = RunGetOutput(cmd, True)
region = out.rstrip("\n")
if region == "":
Log("could not detect region info, now use the default region: eastus2")
region = "eastus2"
scriptFilePath = "/tmp/install-rhui-rpm.sh"
if not os.path.exists(scriptFilePath):
Error(scriptFilePath + " does not exist, now quit RHUI RPM installation.");
return
#chmod a+x script file
os.chmod(scriptFilePath, 0100)
Log("begin to run " + scriptFilePath)
#execute the downloaded script file
retcode,out = RunGetOutput(scriptFilePath, True)
if retcode != 0:
Error("execute script " + scriptFilePath + " failed, return code: " + str(retcode) + ", now exit RHUI RPM installation.");
return
Log("install RHUI RPM completed")
class Util(object):
"""
Http communication class.
Base of GoalState, and Agent classes.
"""
RetryWaitingInterval=10
def __init__(self):
self.Endpoint = None
def _ParseUrl(self, url):
secure = False
host = self.Endpoint
path = url
port = None
#"http[s]://hostname[:port][/]"
if url.startswith("http://"):
url = url[7:]
if "/" in url:
host = url[0: url.index("/")]
path = url[url.index("/"):]
else:
host = url
path = "/"
elif url.startswith("https://"):
secure = True
url = url[8:]
if "/" in url:
host = url[0: url.index("/")]
path = url[url.index("/"):]
else:
host = url
path = "/"
if host is None:
raise ValueError("Host is invalid:{0}".format(url))
if(":" in host):
pos = host.rfind(":")
port = int(host[pos + 1:])
host = host[0:pos]
return host, port, secure, path
def GetHttpProxy(self, secure):
"""
Get http_proxy and https_proxy from environment variables.
Username and password is not supported now.
"""
host = Config.get("HttpProxy.Host")
port = Config.get("HttpProxy.Port")
return (host, port)
def _HttpRequest(self, method, host, path, port=None, data=None, secure=False,
headers=None, proxyHost=None, proxyPort=None):
resp = None
conn = None
try:
if secure:
port = 443 if port is None else port
if proxyHost is not None and proxyPort is not None:
conn = httplib.HTTPSConnection(proxyHost, proxyPort, timeout=10)
conn.set_tunnel(host, port)
#If proxy is used, full url is needed.
path = "https://{0}:{1}{2}".format(host, port, path)
else:
conn = httplib.HTTPSConnection(host, port, timeout=10)
else:
port = 80 if port is None else port
if proxyHost is not None and proxyPort is not None:
conn = httplib.HTTPConnection(proxyHost, proxyPort, timeout=10)
#If proxy is used, full url is needed.
path = "http://{0}:{1}{2}".format(host, port, path)
else:
conn = httplib.HTTPConnection(host, port, timeout=10)
if headers == None:
conn.request(method, path, data)
else:
conn.request(method, path, data, headers)
resp = conn.getresponse()
except httplib.HTTPException, e:
Error('HTTPException {0}, args:{1}'.format(e, repr(e.args)))
except IOError, e:
Error('Socket IOError {0}, args:{1}'.format(e, repr(e.args)))
return resp
def HttpRequest(self, method, url, data=None,
headers=None, maxRetry=3, chkProxy=False):
"""
Sending http request to server
On error, sleep 10 and maxRetry times.
Return the output buffer or None.
"""
LogIfVerbose("HTTP Req: {0} {1}".format(method, url))
LogIfVerbose("HTTP Req: Data={0}".format(data))
LogIfVerbose("HTTP Req: Header={0}".format(headers))
try:
host, port, secure, path = self._ParseUrl(url)
except ValueError, e:
Error("Failed to parse url:{0}".format(url))
return None
#Check proxy
proxyHost, proxyPort = (None, None)
if chkProxy:
proxyHost, proxyPort = self.GetHttpProxy(secure)
#If httplib module is not built with ssl support. Fallback to http
if secure and not hasattr(httplib, "HTTPSConnection"):
Warn("httplib is not built with ssl support")
secure = False
proxyHost, proxyPort = self.GetHttpProxy(secure)
#If httplib module doesn't support https tunnelling. Fallback to http
if secure and \
proxyHost is not None and \
proxyPort is not None and \
not hasattr(httplib.HTTPSConnection, "set_tunnel"):
Warn("httplib doesn't support https tunnelling(new in python 2.7)")
secure = False
proxyHost, proxyPort = self.GetHttpProxy(secure)
resp = self._HttpRequest(method, host, path, port=port, data=data,
secure=secure, headers=headers,
proxyHost=proxyHost, proxyPort=proxyPort)
for retry in range(0, maxRetry):
if resp is not None and \
(resp.status == httplib.OK or \
resp.status == httplib.CREATED or \
resp.status == httplib.ACCEPTED):
return resp;
if resp is not None and resp.status == httplib.GONE:
raise HttpResourceGoneError("Http resource gone.")
Error("Retry={0}".format(retry))
Error("HTTP Req: {0} {1}".format(method, url))
Error("HTTP Req: Data={0}".format(data))
Error("HTTP Req: Header={0}".format(headers))
if resp is None:
Error("HTTP Err: response is empty.".format(retry))
else:
Error("HTTP Err: Status={0}".format(resp.status))
Error("HTTP Err: Reason={0}".format(resp.reason))
Error("HTTP Err: Header={0}".format(resp.getheaders()))
Error("HTTP Err: Body={0}".format(resp.read()))
time.sleep(self.__class__.RetryWaitingInterval)
resp = self._HttpRequest(method, host, path, port=port, data=data,
secure=secure, headers=headers,
proxyHost=proxyHost, proxyPort=proxyPort)
return None
def HttpGet(self, url, headers=None, maxRetry=3, chkProxy=False):
return self.HttpRequest("GET", url, headers=headers,
maxRetry=maxRetry, chkProxy=chkProxy)
def HttpHead(self, url, headers=None, maxRetry=3, chkProxy=False):
return self.HttpRequest("HEAD", url, headers=headers,
maxRetry=maxRetry, chkProxy=chkProxy)
def HttpPost(self, url, data, headers=None, maxRetry=3, chkProxy=False):
return self.HttpRequest("POST", url, data=data, headers=headers,
maxRetry=maxRetry, chkProxy=chkProxy)
def HttpPut(self, url, data, headers=None, maxRetry=3, chkProxy=False):
return self.HttpRequest("PUT", url, data=data, headers=headers,
maxRetry=maxRetry, chkProxy=chkProxy)
def HttpDelete(self, url, headers=None, maxRetry=3, chkProxy=False):
return self.HttpRequest("DELETE", url, headers=headers,
maxRetry=maxRetry, chkProxy=chkProxy)
def HttpGetWithoutHeaders(self, url, maxRetry=3, chkProxy=False):
"""
Return data from an HTTP get on 'url'.
"""
resp = self.HttpGet(url, headers=None, maxRetry=maxRetry,
chkProxy=chkProxy)
return resp.read() if resp is not None else None
def HttpGetWithHeaders(self, url, maxRetry=3, chkProxy=False):
"""
Return data from an HTTP get on 'url' with
x-ms-agent-name and x-ms-version
headers.
"""
resp = self.HttpGet(url, headers={
"x-ms-agent-name": GuestAgentName,
"x-ms-version": ProtocolVersion
}, maxRetry=maxRetry, chkProxy=chkProxy)
return resp.read() if resp is not None else None
def HttpSecureGetWithHeaders(self, url, transportCert, maxRetry=3,
chkProxy=False):
"""
Return output of get using ssl cert.
"""
resp = self.HttpGet(url, headers={
"x-ms-agent-name": GuestAgentName,
"x-ms-version": ProtocolVersion,
"x-ms-cipher-name": "DES_EDE3_CBC",
"x-ms-guest-agent-public-x509-cert": transportCert
}, maxRetry=maxRetry, chkProxy=chkProxy)
return resp.read() if resp is not None else None
def HttpPostWithHeaders(self, url, data, maxRetry=3, chkProxy=False):
headers = {
"x-ms-agent-name": GuestAgentName,
"Content-Type": "text/xml; charset=utf-8",
"x-ms-version": ProtocolVersion
}
try:
return self.HttpPost(url, data=data, headers=headers,
maxRetry=maxRetry, chkProxy=chkProxy)
except HttpResourceGoneError as e:
Error("Failed to post: {0} {1}".format(url, e))
return None
__StorageVersion="2014-02-14"
def GetBlobType(url):
restutil = Util()
#Check blob type
LogIfVerbose("Check blob type.")
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
blobPropResp = restutil.HttpHead(url, {
"x-ms-date" : timestamp,
'x-ms-version' : __StorageVersion
}, chkProxy=True);
blobType = None
if blobPropResp is None:
Error("Can't get status blob type.")
return None
blobType = blobPropResp.getheader("x-ms-blob-type")
LogIfVerbose("Blob type={0}".format(blobType))
return blobType
def PutBlockBlob(url, data):
restutil = Util()
LogIfVerbose("Upload block blob")
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
ret = restutil.HttpPut(url, data, {
"x-ms-date" : timestamp,
"x-ms-blob-type" : "BlockBlob",
"Content-Length": str(len(data)),
"x-ms-version" : __StorageVersion
}, chkProxy=True)
if ret is None:
Error("Failed to upload block blob for status.")
return -1
return 0
def PutPageBlob(url, data):
restutil = Util()
LogIfVerbose("Replace old page blob")
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
#Align to 512 bytes
pageBlobSize = ((len(data) + 511) / 512) * 512
ret = restutil.HttpPut(url, "", {
"x-ms-date" : timestamp,
"x-ms-blob-type" : "PageBlob",
"Content-Length": "0",
"x-ms-blob-content-length" : str(pageBlobSize),
"x-ms-version" : __StorageVersion
}, chkProxy=True)
if ret is None:
Error("Failed to clean up page blob for status")
return -1
if url.index('?') < 0:
url = "{0}?comp=page".format(url)
else:
url = "{0}&comp=page".format(url)
LogIfVerbose("Upload page blob")
pageMax = 4 * 1024 * 1024 #Max page size: 4MB
start = 0
end = 0
while end < len(data):
end = min(len(data), start + pageMax)
contentSize = end - start
#Align to 512 bytes
pageEnd = ((end + 511) / 512) * 512
bufSize = pageEnd - start
buf = bytearray(bufSize)
buf[0 : contentSize] = data[start : end]
ret = restutil.HttpPut(url, buffer(buf), {
"x-ms-date" : timestamp,
"x-ms-range" : "bytes={0}-{1}".format(start, pageEnd - 1),
"x-ms-page-write" : "update",
"x-ms-version" : __StorageVersion,
"Content-Length": str(pageEnd - start)
}, chkProxy=True)
if ret is None:
Error("Failed to upload page blob for status")
return -1
start = end
return 0
def UploadStatusBlob(url, data):
LogIfVerbose("Upload status blob")
LogIfVerbose("Status={0}".format(data))
blobType = GetBlobType(url)
if blobType == "BlockBlob":
return PutBlockBlob(url, data)
elif blobType == "PageBlob":
return PutPageBlob(url, data)
else:
Error("Unknown blob type: {0}".format(blobType))
return -1
class TCPHandler(SocketServer.BaseRequestHandler):
"""
Callback object for LoadBalancerProbeServer.
Recv and send LB probe messages.
"""
def __init__(self,lb_probe):
super(TCPHandler,self).__init__()
self.lb_probe=lb_probe
def GetHttpDateTimeNow(self):
"""
Return formatted gmtime "Date: Fri, 25 Mar 2011 04:53:10 GMT"
"""
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
def handle(self):
"""
Log LB probe messages, read the socket buffer,
send LB probe response back to server.
"""
self.lb_probe.ProbeCounter = (self.lb_probe.ProbeCounter + 1) % 1000000
log = [NoLog, LogIfVerbose][ThrottleLog(self.lb_probe.ProbeCounter)]
strCounter = str(self.lb_probe.ProbeCounter)
if self.lb_probe.ProbeCounter == 1:
Log("Receiving LB probes.")
log("Received LB probe # " + strCounter)
self.request.recv(1024)
self.request.send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: text/html\r\nDate: " + self.GetHttpDateTimeNow() + "\r\n\r\nOK")
class LoadBalancerProbeServer(object):
"""
Threaded object to receive and send LB probe messages.
Load Balancer messages but be recv'd by
the load balancing server, or this node may be shut-down.
"""
def __init__(self, port):
self.ProbeCounter = 0
self.server = SocketServer.TCPServer((self.get_ip(), port), TCPHandler)
self.server_thread = threading.Thread(target = self.server.serve_forever)
self.server_thread.setDaemon(True)
self.server_thread.start()
def shutdown(self):
self.server.shutdown()
def get_ip(self):
for retry in range(1,6):
ip = MyDistro.GetIpv4Address()
if ip == None :
Log("LoadBalancerProbeServer: GetIpv4Address() returned None, sleeping 10 before retry " + str(retry+1) )
time.sleep(10)
else:
return ip
class ConfigurationProvider(object):
"""
Parse amd store key:values in waagent.conf
"""
def __init__(self, walaConfigFile):
self.values = dict()
if 'MyDistro' not in globals():
global MyDistro
MyDistro = GetMyDistro()
if walaConfigFile is None:
walaConfigFile = MyDistro.getConfigurationPath()
if os.path.isfile(walaConfigFile) == False:
raise Exception("Missing configuration in {0}".format(walaConfigFile))
try:
for line in GetFileContents(walaConfigFile).split('\n'):
if not line.startswith("#") and "=" in line:
parts = line.split()[0].split('=')
value = parts[1].strip("\" ")
if value != "None":
self.values[parts[0]] = value
else:
self.values[parts[0]] = None
except:
Error("Unable to parse {0}".format(walaConfigFile))
raise
return
def get(self, key):
return self.values.get(key)
class EnvMonitor(object):
"""
Montor changes to dhcp and hostname.
If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
"""
def __init__(self):
self.shutdown = False
self.HostName = socket.gethostname()
self.server_thread = threading.Thread(target = self.monitor)
self.server_thread.setDaemon(True)
self.server_thread.start()
self.published = False
def monitor(self):
"""
Monitor dhcp client pid and hostname.
If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
"""
publish = Config.get("Provisioning.MonitorHostName")
dhcpcmd = MyDistro.getpidcmd+ ' ' + MyDistro.getDhcpClientName()
dhcppid = RunGetOutput(dhcpcmd)[1]
while not self.shutdown:
for a in RulesFiles:
if os.path.isfile(a):
if os.path.isfile(GetLastPathElement(a)):
os.remove(GetLastPathElement(a))
shutil.move(a, ".")
Log("EnvMonitor: Moved " + a + " -> " + LibDir)
MyDistro.setScsiDiskTimeout()
if publish != None and publish.lower().startswith("y"):
try:
if socket.gethostname() != self.HostName:
Log("EnvMonitor: Detected host name change: " + self.HostName + " -> " + socket.gethostname())
self.HostName = socket.gethostname()
WaAgent.UpdateAndPublishHostName(self.HostName)
dhcppid = RunGetOutput(dhcpcmd)[1]
self.published = True
except:
pass
else:
self.published = True
pid = ""
if not os.path.isdir("/proc/" + dhcppid.strip()):
pid = RunGetOutput(dhcpcmd)[1]
if pid != "" and pid != dhcppid:
Log("EnvMonitor: Detected dhcp client restart. Restoring routing table.")
WaAgent.RestoreRoutes()
dhcppid = pid
for child in Children:
if child.poll() != None:
Children.remove(child)
time.sleep(5)
def SetHostName(self, name):
"""
Generic call to MyDistro.setHostname(name).
Complian to Log on error.
"""
if socket.gethostname() == name:
self.published = True
elif MyDistro.setHostname(name):
Error("Error: SetHostName: Cannot set hostname to " + name)
return ("Error: SetHostName: Cannot set hostname to " + name)
def IsHostnamePublished(self):
"""
Return self.published
"""
return self.published
def ShutdownService(self):
"""
Stop server comminucation and join the thread to main thread.
"""
self.shutdown = True
self.server_thread.join()
class Certificates(object):
"""
Object containing certificates of host and provisioned user.
Parses and splits certificates into files.
"""
#
# 2010-12-15
# 2
# Pkcs7BlobWithPfxContents
# MIILTAY...
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
"""
Reset the Role, Incarnation
"""
self.Incarnation = None
self.Role = None
def Parse(self, xmlText):
"""
Parse multiple certificates into seperate files.
"""
self.reinitialize()
SetFileContents("Certificates.xml", xmlText)
dom = xml.dom.minidom.parseString(xmlText)
for a in [ "CertificateFile", "Version", "Incarnation",
"Format", "Data", ]:
if not dom.getElementsByTagName(a):
Error("Certificates.Parse: Missing " + a)
return None
node = dom.childNodes[0]
if node.localName != "CertificateFile":
Error("Certificates.Parse: root not CertificateFile")
return None
SetFileContents("Certificates.p7m",
"MIME-Version: 1.0\n"
+ "Content-Disposition: attachment; filename=\"Certificates.p7m\"\n"
+ "Content-Type: application/x-pkcs7-mime; name=\"Certificates.p7m\"\n"
+ "Content-Transfer-Encoding: base64\n\n"
+ GetNodeTextData(dom.getElementsByTagName("Data")[0]))
if Run(Openssl + " cms -decrypt -in Certificates.p7m -inkey TransportPrivate.pem -recip TransportCert.pem | " + Openssl + " pkcs12 -nodes -password pass: -out Certificates.pem"):
Error("Certificates.Parse: Failed to extract certificates from CMS message.")
return self
# There may be multiple certificates in this package. Split them.
file = open("Certificates.pem")
pindex = 1
cindex = 1
output = open("temp.pem", "w")
for line in file.readlines():
output.write(line)
if re.match(r'[-]+END .*?(KEY|CERTIFICATE)[-]+$',line):
output.close()
if re.match(r'[-]+END .*?KEY[-]+$',line):
os.rename("temp.pem", str(pindex) + ".prv")
pindex += 1
else:
os.rename("temp.pem", str(cindex) + ".crt")
cindex += 1
output = open("temp.pem", "w")
output.close()
os.remove("temp.pem")
keys = dict()
index = 1
filename = str(index) + ".crt"
while os.path.isfile(filename):
thumbprint = (RunGetOutput(Openssl + " x509 -in " + filename + " -fingerprint -noout")[1]).rstrip().split('=')[1].replace(':', '').upper()
pubkey=RunGetOutput(Openssl + " x509 -in " + filename + " -pubkey -noout")[1]
keys[pubkey] = thumbprint
os.rename(filename, thumbprint + ".crt")
os.chmod(thumbprint + ".crt", 0600)
MyDistro.setSelinuxContext(thumbprint + '.crt','unconfined_u:object_r:ssh_home_t:s0')
index += 1
filename = str(index) + ".crt"
index = 1
filename = str(index) + ".prv"
while os.path.isfile(filename):
pubkey = RunGetOutput(Openssl + " rsa -in " + filename + " -pubout 2> /dev/null ")[1]
os.rename(filename, keys[pubkey] + ".prv")
os.chmod(keys[pubkey] + ".prv", 0600)
MyDistro.setSelinuxContext( keys[pubkey] + '.prv','unconfined_u:object_r:ssh_home_t:s0')
index += 1
filename = str(index) + ".prv"
return self
class SharedConfig(object):
"""
Parse role endpoint server and goal state config.
"""
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
"""
Reset members.
"""
self.RdmaMacAddress = None
self.RdmaIPv4Address = None
self.xmlText = None
def Parse(self, xmlText):
"""
Parse and write configuration to file SharedConfig.xml.
"""
LogIfVerbose(xmlText)
self.reinitialize()
self.xmlText = xmlText
dom = xml.dom.minidom.parseString(xmlText)
for a in [ "SharedConfig", "Deployment", "Service",
"ServiceInstance", "Incarnation", "Role", ]:
if not dom.getElementsByTagName(a):
Error("SharedConfig.Parse: Missing " + a)
node = dom.childNodes[0]
if node.localName != "SharedConfig":
Error("SharedConfig.Parse: root not SharedConfig")
nodes = dom.getElementsByTagName("Instance")
if nodes is not None and len(nodes) != 0:
node = nodes[0]
if node.hasAttribute("rdmaMacAddress"):
addr = node.getAttribute("rdmaMacAddress")
self.RdmaMacAddress = addr[0:2]
for i in range(1, 6):
self.RdmaMacAddress += ":" + addr[2 * i : 2 *i + 2]
if node.hasAttribute("rdmaIPv4Address"):
self.RdmaIPv4Address = node.getAttribute("rdmaIPv4Address")
return self
def Save(self):
LogIfVerbose("Save SharedConfig.xml")
SetFileContents("SharedConfig.xml", self.xmlText)
def InvokeTopologyConsumer(self):
program = Config.get("Role.TopologyConsumer")
if program != None:
try:
Children.append(subprocess.Popen([program, LibDir + "/SharedConfig.xml"]))
except OSError, e :
ErrorWithPrefix('Agent.Run','Exception: '+ str(e) +' occured launching ' + program )
def Process(self):
global rdma_configured
if not rdma_configured and self.RdmaMacAddress is not None and self.RdmaIPv4Address is not None:
handler = RdmaHandler(self.RdmaMacAddress, self.RdmaIPv4Address)
handler.start()
rdma_configured = True
self.InvokeTopologyConsumer()
rdma_configured = False
class RdmaError(Exception):
pass
class RdmaHandler(object):
"""
Handle rdma configuration.
"""
def __init__(self, mac, ip_addr, dev="/dev/hvnd_rdma",
dat_conf_files=['/etc/dat.conf', '/etc/rdma/dat.conf',
'/usr/local/etc/dat.conf']):
self.mac = mac
self.ip_addr = ip_addr
self.dev = dev
self.dat_conf_files = dat_conf_files
self.data = ('rdmaMacAddress="{0}" rdmaIPv4Address="{1}"'
'').format(self.mac, self.ip_addr)
def start(self):
"""
Start a new thread to process rdma
"""
threading.Thread(target=self.process).start()
def process(self):
try:
self.set_dat_conf()
self.set_rdma_dev()
self.set_rdma_ip()
except RdmaError as e:
Error("Failed to config rdma device: {0}".format(e))
def set_dat_conf(self):
"""
Agent needs to search all possible locations for dat.conf
"""
Log("Set dat.conf")
for dat_conf_file in self.dat_conf_files:
if not os.path.isfile(dat_conf_file):
continue
try:
self.write_dat_conf(dat_conf_file)
except IOError as e:
raise RdmaError("Failed to write to dat.conf: {0}".format(e))
def write_dat_conf(self, dat_conf_file):
Log("Write config to {0}".format(dat_conf_file))
old = ("ofa-v2-ib0 u2.0 nonthreadsafe default libdaplofa.so.2 "
"dapl.2.0 \"\S+ 0\"")
new = ("ofa-v2-ib0 u2.0 nonthreadsafe default libdaplofa.so.2 "
"dapl.2.0 \"{0} 0\"").format(self.ip_addr)
lines = GetFileContents(dat_conf_file)
lines = re.sub(old, new, lines)
SetFileContents(dat_conf_file, lines)
def set_rdma_dev(self):
"""
Write config string to /dev/hvnd_rdma
"""
Log("Set /dev/hvnd_rdma")
self.wait_rdma_dev()
self.write_rdma_dev_conf()
def write_rdma_dev_conf(self):
Log("Write rdma config to {0}: {1}".format(self.dev, self.data))
try:
with open(self.dev, "w") as c:
c.write(self.data)
except IOError, e:
raise RdmaError("Error writing {0}, {1}".format(self.dev, e))
def wait_rdma_dev(self):
Log("Wait for /dev/hvnd_rdma")
retry = 0
while retry < 120:
if os.path.exists(self.dev):
return
time.sleep(1)
retry += 1
raise RdmaError("The device doesn't show up in 120 seconds")
def set_rdma_ip(self):
Log("Set ip addr for rdma")
try:
if_name = MyDistro.getInterfaceNameByMac(self.mac)
#Azure is using 12 bits network mask for infiniband.
MyDistro.configIpV4(if_name, self.ip_addr, 12)
except Exception as e:
raise RdmaError("Failed to config rdma device: {0}".format(e))
class ExtensionsConfig(object):
"""
Parse ExtensionsConfig, downloading and unpacking them to /var/lib/waagent.
Install if true, remove if it is set to false.
"""
#
#
#
#
#
#
# {"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"1BE9A13AA1321C7C515EF109746998BAB6D86FD1",
#"protectedSettings":"MIIByAYJKoZIhvcNAQcDoIIBuTCCAbUCAQAxggFxMIIBbQIBADBVMEExPzA9BgoJkiaJk/IsZAEZFi9XaW5kb3dzIEF6dXJlIFNlcnZpY2UgTWFuYWdlbWVudCBmb3IgR
#Xh0ZW5zaW9ucwIQZi7dw+nhc6VHQTQpCiiV2zANBgkqhkiG9w0BAQEFAASCAQCKr09QKMGhwYe+O4/a8td+vpB4eTR+BQso84cV5KCAnD6iUIMcSYTrn9aveY6v6ykRLEw8GRKfri2d6
#tvVDggUrBqDwIgzejGTlCstcMJItWa8Je8gHZVSDfoN80AEOTws9Fp+wNXAbSuMJNb8EnpkpvigAWU2v6pGLEFvSKC0MCjDTkjpjqciGMcbe/r85RG3Zo21HLl0xNOpjDs/qqikc/ri43Y76E/X
#v1vBSHEGMFprPy/Hwo3PqZCnulcbVzNnaXN3qi/kxV897xGMPPC3IrO7Nc++AT9qRLFI0841JLcLTlnoVG1okPzK9w6ttksDQmKBSHt3mfYV+skqs+EOMDsGCSqGSIb3DQEHATAUBggqh
#kiG9w0DBwQITgu0Nu3iFPuAGD6/QzKdtrnCI5425fIUy7LtpXJGmpWDUA==","publicSettings":{"port":"3000"}}}]}
#
#
#https://ostcextensions.blob.core.test-cint.azure-test.net/vhds/eg-plugin7-vm.eg-plugin7-vm.eg-plugin7-vm.status?sr=b&sp=rw&
#se=9999-01-01&sk=key1&sv=2012-02-12&sig=wRUIDN1x2GC06FWaetBP9sjjifOWvRzS2y2XBB4qoBU%3D
def __init__(self):
self.reinitialize()
def reinitialize(self):
"""
Reset members.
"""
self.Extensions = None
self.Plugins = None
self.Util = None
def Parse(self, xmlText):
"""
Write configuration to file ExtensionsConfig.xml.
Log plugin specific activity to /var/log/azure/.//CommandExecution.log.
If state is enabled:
if the plugin is installed:
if the new plugin's version is higher
if DisallowMajorVersionUpgrade is false or if true, the version is a minor version do upgrade:
download the new archive
do the updateCommand.
disable the old plugin and remove
enable the new plugin
if the new plugin's version is the same or lower:
create the new .settings file from the configuration received
do the enableCommand
if the plugin is not installed:
download/unpack archive and call the installCommand/Enable
if state is disabled:
call disableCommand
if state is uninstall:
call uninstallCommand
remove old plugin directory.
"""
self.reinitialize()
self.Util=Util()
dom = xml.dom.minidom.parseString(xmlText)
LogIfVerbose(xmlText)
self.plugin_log_dir='/var/log/azure'
if not os.path.exists(self.plugin_log_dir):
os.mkdir(self.plugin_log_dir)
try:
self.Extensions=dom.getElementsByTagName("Extensions")
pg = dom.getElementsByTagName("Plugins")
if len(pg) > 0:
self.Plugins = pg[0].getElementsByTagName("Plugin")
else:
self.Plugins = []
incarnation=self.Extensions[0].getAttribute("goalStateIncarnation")
SetFileContents('ExtensionsConfig.'+incarnation+'.xml', xmlText)
except Exception, e:
Error('ERROR: Error parsing ExtensionsConfig: {0}.'.format(e))
return None
for p in self.Plugins:
if len(p.getAttribute("location"))<1: # this plugin is inside the PluginSettings
continue
p.setAttribute('restricted','false')
previous_version = None
version=p.getAttribute("version")
name=p.getAttribute("name")
plog_dir=self.plugin_log_dir+'/'+name +'/'+ version
if not os.path.exists(plog_dir):
os.makedirs(plog_dir)
p.plugin_log=plog_dir+'/CommandExecution.log'
handler=name + '-' + version
if p.getAttribute("isJson") != 'true':
Error("Plugin " + name+" version: " +version+" is not a JSON Extension. Skipping.")
continue
Log("Found Plugin: " + name + ' version: ' + version)
if p.getAttribute("state") == 'disabled' or p.getAttribute("state") == 'uninstall':
#disable
zip_dir=LibDir+"/" + name + '-' + version
mfile=None
for root, dirs, files in os.walk(zip_dir):
for f in files:
if f in ('HandlerManifest.json'):
mfile=os.path.join(root,f)
if mfile != None:
break
if mfile == None :
Error('HandlerManifest.json not found.')
continue
manifest = GetFileContents(mfile)
p.setAttribute('manifestdata',manifest)
if self.launchCommand(p.plugin_log,name,version,'disableCommand') == None :
self.SetHandlerState(handler, 'Enabled')
Error('Unable to disable '+name)
SimpleLog(p.plugin_log,'ERROR: Unable to disable '+name)
else :
self.SetHandlerState(handler, 'Disabled')
Log(name+' is disabled')
SimpleLog(p.plugin_log,name+' is disabled')
# uninstall if needed
if p.getAttribute("state") == 'uninstall':
if self.launchCommand(p.plugin_log,name,version,'uninstallCommand') == None :
self.SetHandlerState(handler, 'Installed')
Error('Unable to uninstall '+name)
SimpleLog(p.plugin_log,'Unable to uninstall '+name)
else :
self.SetHandlerState(handler, 'NotInstalled')
Log(name+' uninstallCommand completed .')
# remove the plugin
Run('rm -rf ' + LibDir + '/' + name +'-'+ version + '*')
Log(name +'-'+ version + ' extension files deleted.')
SimpleLog(p.plugin_log,name +'-'+ version + ' extension files deleted.')
continue
# state is enabled
# if the same plugin exists and the version is newer or
# does not exist then download and unzip the new plugin
plg_dir=None
latest_version_installed = LooseVersion("0.0")
for item in os.listdir(LibDir):
itemPath = os.path.join(LibDir, item)
if os.path.isdir(itemPath) and name in item:
try:
#Split plugin dir name with '-' to get intalled plugin name and version
sperator = item.rfind('-')
if sperator < 0:
continue
installed_plg_name = item[0:sperator]
installed_plg_version = LooseVersion(item[sperator + 1:])
#Check installed plugin name and compare installed version to get the latest version installed
if installed_plg_name == name and installed_plg_version > latest_version_installed:
plg_dir = itemPath
previous_version = str(installed_plg_version)
latest_version_installed = installed_plg_version
except Exception as e:
Warn("Invalid plugin dir name: {0} {1}".format(item, e))
continue
if plg_dir == None or LooseVersion(version) > LooseVersion(previous_version) :
location=p.getAttribute("location")
Log("Downloading plugin manifest: " + name + " from " + location)
SimpleLog(p.plugin_log,"Downloading plugin manifest: " + name + " from " + location)
self.Util.Endpoint=location.split('/')[2]
Log("Plugin server is: " + self.Util.Endpoint)
SimpleLog(p.plugin_log,"Plugin server is: " + self.Util.Endpoint)
manifest=self.Util.HttpGetWithoutHeaders(location, chkProxy=True)
if manifest == None:
Error("Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.")
SimpleLog(p.plugin_log,"Unable to download plugin manifest" + name + " from primary location. Attempting with failover location.")
failoverlocation=p.getAttribute("failoverlocation")
self.Util.Endpoint=failoverlocation.split('/')[2]
Log("Plugin failover server is: " + self.Util.Endpoint)
SimpleLog(p.plugin_log,"Plugin failover server is: " + self.Util.Endpoint)
manifest=self.Util.HttpGetWithoutHeaders(failoverlocation, chkProxy=True)
#if failoverlocation also fail what to do then?
if manifest == None:
AddExtensionEvent(name,WALAEventOperation.Download,False,0,version,"Download mainfest fail "+failoverlocation)
Log("Plugin manifest " + name + " downloading failed from failover location.")
SimpleLog(p.plugin_log,"Plugin manifest " + name + " downloading failed from failover location.")
filepath=LibDir+"/" + name + '.' + incarnation + '.manifest'
if os.path.splitext(location)[-1] == '.xml' : #if this is an xml file we may have a BOM
if ord(manifest[0]) > 128 and ord(manifest[1]) > 128 and ord(manifest[2]) > 128:
manifest=manifest[3:]
SetFileContents(filepath,manifest)
#Get the bundle url from the manifest
p.setAttribute('manifestdata',manifest)
man_dom = xml.dom.minidom.parseString(manifest)
bundle_uri = ""
for mp in man_dom.getElementsByTagName("Plugin"):
if GetNodeTextData(mp.getElementsByTagName("Version")[0]) == version:
bundle_uri = GetNodeTextData(mp.getElementsByTagName("Uri")[0])
break
if len(mp.getElementsByTagName("DisallowMajorVersionUpgrade")):
if GetNodeTextData(mp.getElementsByTagName("DisallowMajorVersionUpgrade")[0]) == 'true' and previous_version !=None and previous_version.split('.')[0] != version.split('.')[0] :
Log('DisallowMajorVersionUpgrade is true, this major version is restricted from upgrade.')
SimpleLog(p.plugin_log,'DisallowMajorVersionUpgrade is true, this major version is restricted from upgrade.')
p.setAttribute('restricted','true')
continue
if len(bundle_uri) < 1 :
Error("Unable to fetch Bundle URI from manifest for " + name + " v " + version)
SimpleLog(p.plugin_log,"Unable to fetch Bundle URI from manifest for " + name + " v " + version)
continue
Log("Bundle URI = " + bundle_uri)
SimpleLog(p.plugin_log,"Bundle URI = " + bundle_uri)
# Download the zipfile archive and save as '.zip'
bundle=self.Util.HttpGetWithoutHeaders(bundle_uri, chkProxy=True)
if bundle == None:
AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download zip fail "+bundle_uri)
Error("Unable to download plugin bundle" + bundle_uri )
SimpleLog(p.plugin_log,"Unable to download plugin bundle" + bundle_uri )
continue
AddExtensionEvent(name,WALAEventOperation.Download,True,0,version,"Download Success")
b=bytearray(bundle)
filepath=LibDir+"/" + os.path.basename(bundle_uri) + '.zip'
SetFileContents(filepath,b)
Log("Plugin bundle" + bundle_uri + "downloaded successfully length = " + str(len(bundle)))
SimpleLog(p.plugin_log,"Plugin bundle" + bundle_uri + "downloaded successfully length = " + str(len(bundle)))
# unpack the archive
z=zipfile.ZipFile(filepath)
zip_dir=LibDir+"/" + name + '-' + version
z.extractall(zip_dir)
Log('Extracted ' + bundle_uri + ' to ' + zip_dir)
SimpleLog(p.plugin_log,'Extracted ' + bundle_uri + ' to ' + zip_dir)
# zip no file perms in .zip so set all the scripts to +x
Run( "find " + zip_dir +" -type f | xargs chmod u+x ")
#write out the base64 config data so the plugin can process it.
mfile=None
for root, dirs, files in os.walk(zip_dir):
for f in files:
if f in ('HandlerManifest.json'):
mfile=os.path.join(root,f)
if mfile != None:
break
if mfile == None :
Error('HandlerManifest.json not found.')
SimpleLog(p.plugin_log,'HandlerManifest.json not found.')
continue
manifest = GetFileContents(mfile)
p.setAttribute('manifestdata',manifest)
# create the status and config dirs
Run('mkdir -p ' + root + '/status')
Run('mkdir -p ' + root + '/config')
# write out the configuration data to goalStateIncarnation.settings file in the config path.
config=''
seqNo='0'
if len(dom.getElementsByTagName("PluginSettings")) != 0 :
pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin")
for ps in pslist:
if name == ps.getAttribute("name") and version == ps.getAttribute("version"):
Log("Found RuntimeSettings for " + name + " V " + version)
SimpleLog(p.plugin_log,"Found RuntimeSettings for " + name + " V " + version)
config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0])
seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo")
break
if config == '':
Log("No RuntimeSettings for " + name + " V " + version)
SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version)
SetFileContents(root +"/config/" + seqNo +".settings", config )
#create HandlerEnvironment.json
handler_env='[{ "name": "'+name+'", "seqNo": "'+seqNo+'", "version": 1.0, "handlerEnvironment": { "logFolder": "'+os.path.dirname(p.plugin_log)+'", "configFolder": "' + root + '/config", "statusFolder": "' + root + '/status", "heartbeatFile": "'+ root + '/heartbeat.log"}}]'
SetFileContents(root+'/HandlerEnvironment.json',handler_env)
self.SetHandlerState(handler, 'NotInstalled')
cmd = ''
getcmd='installCommand'
if plg_dir != None and previous_version != None and LooseVersion(version) > LooseVersion(previous_version):
previous_handler=name+'-'+previous_version
if self.GetHandlerState(previous_handler) != 'NotInstalled':
getcmd='updateCommand'
# disable the old plugin if it exists
if self.launchCommand(p.plugin_log,name,previous_version,'disableCommand') == None :
self.SetHandlerState(previous_handler, 'Enabled')
Error('Unable to disable old plugin '+name+' version ' + previous_version)
SimpleLog(p.plugin_log,'Unable to disable old plugin '+name+' version ' + previous_version)
else :
self.SetHandlerState(previous_handler, 'Disabled')
Log(name+' version ' + previous_version + ' is disabled')
SimpleLog(p.plugin_log,name+' version ' + previous_version + ' is disabled')
try:
Log("Copy status file from old plugin dir to new")
old_plg_dir = plg_dir
new_plg_dir = os.path.join(LibDir, "{0}-{1}".format(name, version))
old_ext_status_dir = os.path.join(old_plg_dir, "status")
new_ext_status_dir = os.path.join(new_plg_dir, "status")
if os.path.isdir(old_ext_status_dir):
for status_file in os.listdir(old_ext_status_dir):
status_file_path = os.path.join(old_ext_status_dir, status_file)
if os.path.isfile(status_file_path):
shutil.copy2(status_file_path, new_ext_status_dir)
mrseq_file = os.path.join(old_plg_dir, "mrseq")
if os.path.isfile(mrseq_file):
shutil.copy(mrseq_file, new_plg_dir)
except Exception as e:
Error("Failed to copy status file.")
isupgradeSuccess = True
if getcmd=='updateCommand':
if self.launchCommand(p.plugin_log,name,version,getcmd,previous_version) == None :
Error('Update failed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Update failed for '+name+'-'+version)
isupgradeSuccess=False
else :
Log('Update complete'+name+'-'+version)
SimpleLog(p.plugin_log,'Update complete'+name+'-'+version)
# if we updated - call unistall for the old plugin
if self.launchCommand(p.plugin_log,name,previous_version,'uninstallCommand') == None :
self.SetHandlerState(previous_handler, 'Installed')
Error('Uninstall failed for '+name+'-'+previous_version)
SimpleLog(p.plugin_log,'Uninstall failed for '+name+'-'+previous_version)
isupgradeSuccess=False
else :
self.SetHandlerState(previous_handler, 'NotInstalled')
Log('Uninstall complete'+ previous_handler )
SimpleLog(p.plugin_log,'Uninstall complete'+ name +'-' + previous_version)
try:
#rm old plugin dir
if os.path.isdir(plg_dir):
shutil.rmtree(plg_dir)
Log(name +'-'+ previous_version + ' extension files deleted.')
SimpleLog(p.plugin_log,name +'-'+ previous_version + ' extension files deleted.')
except Exception as e:
Error("Failed to remove old plugin directory")
AddExtensionEvent(name,WALAEventOperation.Upgrade,isupgradeSuccess,0,previous_version)
else : # run install
if self.launchCommand(p.plugin_log,name,version,getcmd) == None :
self.SetHandlerState(handler, 'NotInstalled')
Error('Installation failed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Installation failed for '+name+'-'+version)
else :
self.SetHandlerState(handler, 'Installed')
Log('Installation completed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version)
#end if plg_dir == none or version > = prev
# change incarnation of settings file so it knows how to name status...
zip_dir=LibDir+"/" + name + '-' + version
mfile=None
for root, dirs, files in os.walk(zip_dir):
for f in files:
if f in ('HandlerManifest.json'):
mfile=os.path.join(root,f)
if mfile != None:
break
if mfile == None :
Error('HandlerManifest.json not found.')
SimpleLog(p.plugin_log,'HandlerManifest.json not found.')
continue
manifest = GetFileContents(mfile)
p.setAttribute('manifestdata',manifest)
config=''
seqNo='0'
if len(dom.getElementsByTagName("PluginSettings")) != 0 :
try:
pslist=dom.getElementsByTagName("PluginSettings")[0].getElementsByTagName("Plugin")
except:
Error('Error parsing ExtensionsConfig.')
SimpleLog(p.plugin_log,'Error parsing ExtensionsConfig.')
continue
for ps in pslist:
if name == ps.getAttribute("name") and version == ps.getAttribute("version"):
Log("Found RuntimeSettings for " + name + " V " + version)
SimpleLog(p.plugin_log,"Found RuntimeSettings for " + name + " V " + version)
config=GetNodeTextData(ps.getElementsByTagName("RuntimeSettings")[0])
seqNo=ps.getElementsByTagName("RuntimeSettings")[0].getAttribute("seqNo")
break
if config == '':
Error("No RuntimeSettings for " + name + " V " + version)
SimpleLog(p.plugin_log,"No RuntimeSettings for " + name + " V " + version)
SetFileContents(root +"/config/" + seqNo +".settings", config )
# state is still enable
if (self.GetHandlerState(handler) == 'NotInstalled'): # run install first if true
if self.launchCommand(p.plugin_log,name,version,'installCommand') == None :
self.SetHandlerState(handler, 'NotInstalled')
Error('Installation failed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Installation failed for '+name+'-'+version)
else :
self.SetHandlerState(handler, 'Installed')
Log('Installation completed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Installation completed for '+name+'-'+version)
if (self.GetHandlerState(handler) != 'NotInstalled'):
if self.launchCommand(p.plugin_log,name,version,'enableCommand') == None :
self.SetHandlerState(handler, 'Installed')
Error('Enable failed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Enable failed for '+name+'-'+version)
else :
self.SetHandlerState(handler, 'Enabled')
Log('Enable completed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Enable completed for '+name+'-'+version)
# this plugin processing is complete
Log('Processing completed for '+name+'-'+version)
SimpleLog(p.plugin_log,'Processing completed for '+name+'-'+version)
#end plugin processing loop
Log('Finished processing ExtensionsConfig.xml')
try:
SimpleLog(p.plugin_log,'Finished processing ExtensionsConfig.xml')
except:
pass
return self
def launchCommand(self,plugin_log,name,version,command,prev_version=None):
commandToEventOperation={
"installCommand":WALAEventOperation.Install,
"uninstallCommand":WALAEventOperation.UnIsntall,
"updateCommand": WALAEventOperation.Upgrade,
"enableCommand": WALAEventOperation.Enable,
"disableCommand": WALAEventOperation.Disable,
}
isSuccess=True
start = datetime.datetime.now()
r=self.__launchCommandWithoutEventLog(plugin_log,name,version,command,prev_version)
if r==None:
isSuccess=False
Duration = int((datetime.datetime.now() - start).seconds)
if commandToEventOperation.get(command):
AddExtensionEvent(name,commandToEventOperation[command],isSuccess,Duration,version)
return r
def __launchCommandWithoutEventLog(self,plugin_log,name,version,command,prev_version=None):
# get the manifest and read the command
mfile=None
zip_dir=LibDir+"/" + name + '-' + version
for root, dirs, files in os.walk(zip_dir):
for f in files:
if f in ('HandlerManifest.json'):
mfile=os.path.join(root,f)
if mfile != None:
break
if mfile == None :
Error('HandlerManifest.json not found.')
SimpleLog(plugin_log,'HandlerManifest.json not found.')
return None
manifest = GetFileContents(mfile)
try:
jsn = json.loads(manifest)
except:
Error('Error parsing HandlerManifest.json.')
SimpleLog(plugin_log,'Error parsing HandlerManifest.json.')
return None
if type(jsn)==list:
jsn=jsn[0]
if jsn.has_key('handlerManifest') :
cmd = jsn['handlerManifest'][command]
else :
Error('Key handlerManifest not found. Handler cannot be installed.')
SimpleLog(plugin_log,'Key handlerManifest not found. Handler cannot be installed.')
if len(cmd) == 0 :
Error('Unable to read ' + command )
SimpleLog(plugin_log,'Unable to read ' + command )
return None
# for update we send the path of the old installation
arg=''
if prev_version != None :
arg=' ' + LibDir+'/' + name + '-' + prev_version
dirpath=os.path.dirname(mfile)
LogIfVerbose('Command is '+ dirpath+'/'+ cmd)
# launch
pid=None
try:
child = subprocess.Popen(dirpath+'/'+cmd+arg,shell=True,cwd=dirpath,stdout=subprocess.PIPE)
except Exception as e:
Error('Exception launching ' + cmd + str(e))
SimpleLog(plugin_log,'Exception launching ' + cmd + str(e))
pid = child.pid
if pid == None or pid < 1 :
ExtensionChildren.append((-1,root))
Error('Error launching ' + cmd + '.')
SimpleLog(plugin_log,'Error launching ' + cmd + '.')
else :
ExtensionChildren.append((pid,root))
Log("Spawned "+ cmd + " PID " + str(pid))
SimpleLog(plugin_log,"Spawned "+ cmd + " PID " + str(pid))
# wait until install/upgrade is finished
timeout = 300 # 5 minutes
retry = timeout/5
while retry > 0 and child.poll() == None:
LogIfVerbose(cmd + ' still running with PID ' + str(pid))
time.sleep(5)
retry-=1
if retry==0:
Error('Process exceeded timeout of ' + str(timeout) + ' seconds. Terminating process ' + str(pid))
SimpleLog(plugin_log,'Process exceeded timeout of ' + str(timeout) + ' seconds. Terminating process ' + str(pid))
os.kill(pid,9)
return None
code = child.wait()
if code == None or code != 0:
Error('Process ' + str(pid) + ' returned non-zero exit code (' + str(code) + ')')
SimpleLog(plugin_log,'Process ' + str(pid) + ' returned non-zero exit code (' + str(code) + ')')
return None
Log(command + ' completed.')
SimpleLog(plugin_log,command + ' completed.')
return 0
def ReportHandlerStatus(self):
"""
Collect all status reports.
"""
# { "version": "1.0", "timestampUTC": "2014-03-31T21:28:58Z",
# "aggregateStatus": {
# "guestAgentStatus": { "version": "2.0.4PRE", "status": "Ready", "formattedMessage": { "lang": "en-US", "message": "GuestAgent is running and accepting new configurations." } },
# "handlerAggregateStatus": [{
# "handlerName": "ExampleHandlerLinux", "handlerVersion": "1.0", "status": "Ready", "runtimeSettingsStatus": {
# "sequenceNumber": "2", "settingsStatus": { "timestampUTC": "2014-03-31T23:46:00Z", "status": { "name": "ExampleHandlerLinux", "operation": "Command Execution Finished", "configurationAppliedTime": "2014-03-31T23:46:00Z", "status": "success", "formattedMessage": { "lang": "en-US", "message": "Finished executing command" },
# "substatus": [
# { "name": "StdOut", "status": "success", "formattedMessage": { "lang": "en-US", "message": "Goodbye world!" } },
# { "name": "StdErr", "status": "success", "formattedMessage": { "lang": "en-US", "message": "" } }
# ]
# } } } }
# ]
# }}
try:
incarnation=self.Extensions[0].getAttribute("goalStateIncarnation")
except:
Error('Error parsing ExtensionsConfig. Unable to send status reports')
return -1
status=''
statuses=''
for p in self.Plugins:
if p.getAttribute("state") == 'uninstall' or p.getAttribute("restricted") == 'true' :
continue
version=p.getAttribute("version")
name=p.getAttribute("name")
if p.getAttribute("isJson") != 'true':
LogIfVerbose("Plugin " + name+" version: " +version+" is not a JSON Extension. Skipping.")
continue
reportHeartbeat = False
if len(p.getAttribute("manifestdata"))<1:
Error("Failed to get manifestdata.")
else:
reportHeartbeat = json.loads(p.getAttribute("manifestdata"))[0]['handlerManifest']['reportHeartbeat']
if len(statuses)>0:
statuses+=','
statuses+=self.GenerateAggStatus(name, version, reportHeartbeat)
tstamp=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
#header
#agent state
if provisioned == False:
if provisionError == None :
agent_state='Provisioning'
agent_msg='Guest Agent is starting.'
else:
agent_state='Provisioning Error.'
agent_msg=provisionError
else:
agent_state='Ready'
agent_msg='GuestAgent is running and accepting new configurations.'
status='{"version":"1.0","timestampUTC":"'+tstamp+'","aggregateStatus":{"guestAgentStatus":{"version":"'+GuestAgentVersion+'","status":"'+agent_state+'","formattedMessage":{"lang":"en-US","message":"'+agent_msg+'"}},"handlerAggregateStatus":['+statuses+']}}'
try:
uri=GetNodeTextData(self.Extensions[0].getElementsByTagName("StatusUploadBlob")[0]).replace('&','&')
except:
Error('Error parsing ExtensionsConfig. Unable to send status reports')
return -1
LogIfVerbose('Status report '+status+' sent to ' + uri)
return UploadStatusBlob(uri, status.encode("utf-8"))
def GetCurrentSequenceNumber(self, plugin_base_dir):
"""
Get the settings file with biggest file number in config folder
"""
config_dir = os.path.join(plugin_base_dir, 'config')
seq_no = 0
for subdir, dirs, files in os.walk(config_dir):
for file in files:
try:
cur_seq_no = int(os.path.basename(file).split('.')[0])
if cur_seq_no > seq_no:
seq_no = cur_seq_no
except ValueError:
continue
return str(seq_no)
def GenerateAggStatus(self, name, version, reportHeartbeat = False):
"""
Generate the status which Azure can understand by the status and heartbeat reported by extension
"""
plugin_base_dir = LibDir+'/'+name+'-'+version+'/'
current_seq_no = self.GetCurrentSequenceNumber(plugin_base_dir)
status_file=os.path.join(plugin_base_dir, 'status/', current_seq_no +'.status')
heartbeat_file = os.path.join(plugin_base_dir, 'heartbeat.log')
handler_state_file = os.path.join(plugin_base_dir, 'config', 'HandlerState')
agg_state = 'NotReady'
handler_state = None
status_obj = None
status_code = None
formatted_message = None
localized_message = None
if os.path.exists(handler_state_file):
handler_state = GetFileContents(handler_state_file).lower()
if HandlerStatusToAggStatus.has_key(handler_state):
agg_state = HandlerStatusToAggStatus[handler_state]
if reportHeartbeat:
if os.path.exists(heartbeat_file):
d=int(time.time()-os.stat(heartbeat_file).st_mtime)
if d > 600 : # not updated for more than 10 min
agg_state = 'Unresponsive'
else:
try:
heartbeat = json.loads(GetFileContents(heartbeat_file))[0]["heartbeat"]
agg_state = heartbeat.get("status")
status_code = heartbeat.get("code")
formatted_message = heartbeat.get("formattedMessage")
localized_message = heartbeat.get("message")
except:
Error("Incorrect heartbeat file. Ignore it. ")
else:
agg_state = 'Unresponsive'
#get status file reported by extension
if os.path.exists(status_file):
# raw status generated by extension is an array, get the first item and remove the unnecessary element
try:
status_obj = json.loads(GetFileContents(status_file))[0]
del status_obj["version"]
except:
Error("Incorrect status file. Will NOT settingsStatus in settings. ")
agg_status_obj = {"handlerName": name, "handlerVersion": version, "status": agg_state, "runtimeSettingsStatus" :
{"sequenceNumber": current_seq_no}}
if status_obj:
agg_status_obj["runtimeSettingsStatus"]["settingsStatus"] = status_obj
if status_code != None:
agg_status_obj["code"] = status_code
if formatted_message:
agg_status_obj["formattedMessage"] = formatted_message
if localized_message:
agg_status_obj["message"] = localized_message
agg_status_string = json.dumps(agg_status_obj)
LogIfVerbose("Handler Aggregated Status:" + agg_status_string)
return agg_status_string
def SetHandlerState(self, handler, state=''):
zip_dir=LibDir+"/" + handler
mfile=None
for root, dirs, files in os.walk(zip_dir):
for f in files:
if f in ('HandlerManifest.json'):
mfile=os.path.join(root,f)
if mfile != None:
break
if mfile == None :
Error('SetHandlerState(): HandlerManifest.json not found, cannot set HandlerState.')
return None
Log("SetHandlerState: "+handler+", "+state)
return SetFileContents(os.path.dirname(mfile)+'/config/HandlerState', state)
def GetHandlerState(self, handler):
handlerState = GetFileContents(handler+'/config/HandlerState')
if (handlerState):
return handlerState.rstrip('\r\n')
else:
return 'NotInstalled'
class HostingEnvironmentConfig(object):
"""
Parse Hosting enviromnet config and store in
HostingEnvironmentConfig.xml
"""
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
"""
Reset Members.
"""
self.StoredCertificates = None
self.Deployment = None
self.Incarnation = None
self.Role = None
self.HostingEnvironmentSettings = None
self.ApplicationSettings = None
self.Certificates = None
self.ResourceReferences = None
def Parse(self, xmlText):
"""
Parse and create HostingEnvironmentConfig.xml.
"""
self.reinitialize()
SetFileContents("HostingEnvironmentConfig.xml", xmlText)
dom = xml.dom.minidom.parseString(xmlText)
for a in [ "HostingEnvironmentConfig", "Deployment", "Service",
"ServiceInstance", "Incarnation", "Role", ]:
if not dom.getElementsByTagName(a):
Error("HostingEnvironmentConfig.Parse: Missing " + a)
return None
node = dom.childNodes[0]
if node.localName != "HostingEnvironmentConfig":
Error("HostingEnvironmentConfig.Parse: root not HostingEnvironmentConfig")
return None
self.ApplicationSettings = dom.getElementsByTagName("Setting")
self.Certificates = dom.getElementsByTagName("StoredCertificate")
return self
def DecryptPassword(self, e):
"""
Return decrypted password.
"""
SetFileContents("password.p7m",
"MIME-Version: 1.0\n"
+ "Content-Disposition: attachment; filename=\"password.p7m\"\n"
+ "Content-Type: application/x-pkcs7-mime; name=\"password.p7m\"\n"
+ "Content-Transfer-Encoding: base64\n\n"
+ textwrap.fill(e, 64))
return RunGetOutput(Openssl + " cms -decrypt -in password.p7m -inkey Certificates.pem -recip Certificates.pem")[1]
def ActivateResourceDisk(self):
return MyDistro.ActivateResourceDisk()
def Process(self):
"""
Execute ActivateResourceDisk in separate thread.
Create the user account.
Launch ConfigurationConsumer if specified in the config.
"""
no_thread = False
if DiskActivated == False:
for m in inspect.getmembers(MyDistro):
if 'ActivateResourceDiskNoThread' in m:
no_thread = True
break
if no_thread == True :
MyDistro.ActivateResourceDiskNoThread()
else :
diskThread = threading.Thread(target = self.ActivateResourceDisk)
diskThread.start()
User = None
Pass = None
Expiration = None
Thumbprint = None
for b in self.ApplicationSettings:
sname = b.getAttribute("name")
svalue = b.getAttribute("value")
if User != None and Pass != None:
if User != "root" and User != "" and Pass != "":
CreateAccount(User, Pass, Expiration, Thumbprint)
else:
Error("Not creating user account: " + User)
for c in self.Certificates:
csha1 = c.getAttribute("certificateId").split(':')[1].upper()
if os.path.isfile(csha1 + ".prv"):
Log("Private key with thumbprint: " + csha1 + " was retrieved.")
if os.path.isfile(csha1 + ".crt"):
Log("Public cert with thumbprint: " + csha1 + " was retrieved.")
program = Config.get("Role.ConfigurationConsumer")
if program != None:
try:
Children.append(subprocess.Popen([program, LibDir + "/HostingEnvironmentConfig.xml"]))
except OSError, e :
ErrorWithPrefix('HostingEnvironmentConfig.Process','Exception: '+ str(e) +' occured launching ' + program )
class GoalState(Util):
"""
Primary container for all configuration except OvfXml.
Encapsulates http communication with endpoint server.
Initializes and populates:
self.HostingEnvironmentConfig
self.SharedConfig
self.ExtensionsConfig
self.Certificates
"""
#
#
# 2010-12-15
# 1
#
# Started
#
# 16001
#
#
#
# c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2
#
#
# MachineRole_IN_0
# Started
#
# http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=hostingEnvironmentConfig&incarnation=1
# http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=config&type=sharedConfig&incarnation=1
# http://10.115.153.40:80/machine/c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2/MachineRole%5FIN%5F0?comp=certificates&incarnation=1
# http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&type=extensionsConfig&incarnation=2
# http://100.67.238.230:80/machine/9c87aa94-3bda-45e3-b2b7-0eb0fca7baff/1552dd64dc254e6884f8d5b8b68aa18f.eg%2Dplug%2Dvm?comp=config&type=fullConfig&incarnation=2
#
#
#
#
#
#
# There is only one Role for VM images.
#
# Of primary interest is:
# LBProbePorts -- an http server needs to run here
# We also note Container/ContainerID and RoleInstance/InstanceId to form the health report.
# And of course, Incarnation
#
def __init__(self, Agent):
self.Agent = Agent
self.Endpoint = Agent.Endpoint
self.TransportCert = Agent.TransportCert
self.reinitialize()
def reinitialize(self):
self.Incarnation = None # integer
self.ExpectedState = None # "Started"
self.HostingEnvironmentConfigUrl = None
self.HostingEnvironmentConfigXml = None
self.HostingEnvironmentConfig = None
self.SharedConfigUrl = None
self.SharedConfigXml = None
self.SharedConfig = None
self.CertificatesUrl = None
self.CertificatesXml = None
self.Certificates = None
self.ExtensionsConfigUrl = None
self.ExtensionsConfigXml = None
self.ExtensionsConfig = None
self.RoleInstanceId = None
self.ContainerId = None
self.LoadBalancerProbePort = None # integer, ?list of integers
def Parse(self, xmlText):
"""
Request configuration data from endpoint server.
Parse and populate contained configuration objects.
Calls Certificates().Parse()
Calls SharedConfig().Parse
Calls ExtensionsConfig().Parse
Calls HostingEnvironmentConfig().Parse
"""
self.reinitialize()
LogIfVerbose(xmlText)
node = xml.dom.minidom.parseString(xmlText).childNodes[0]
if node.localName != "GoalState":
Error("GoalState.Parse: root not GoalState")
return None
for a in node.childNodes:
if a.nodeType == node.ELEMENT_NODE:
if a.localName == "Incarnation":
self.Incarnation = GetNodeTextData(a)
elif a.localName == "Machine":
for b in a.childNodes:
if b.nodeType == node.ELEMENT_NODE:
if b.localName == "ExpectedState":
self.ExpectedState = GetNodeTextData(b)
Log("ExpectedState: " + self.ExpectedState)
elif b.localName == "LBProbePorts":
for c in b.childNodes:
if c.nodeType == node.ELEMENT_NODE and c.localName == "Port":
self.LoadBalancerProbePort = int(GetNodeTextData(c))
elif a.localName == "Container":
for b in a.childNodes:
if b.nodeType == node.ELEMENT_NODE:
if b.localName == "ContainerId":
self.ContainerId = GetNodeTextData(b)
Log("ContainerId: " + self.ContainerId)
elif b.localName == "RoleInstanceList":
for c in b.childNodes:
if c.localName == "RoleInstance":
for d in c.childNodes:
if d.nodeType == node.ELEMENT_NODE:
if d.localName == "InstanceId":
self.RoleInstanceId = GetNodeTextData(d)
Log("RoleInstanceId: " + self.RoleInstanceId)
elif d.localName == "State":
pass
elif d.localName == "Configuration":
for e in d.childNodes:
if e.nodeType == node.ELEMENT_NODE:
LogIfVerbose(e.localName)
if e.localName == "HostingEnvironmentConfig":
self.HostingEnvironmentConfigUrl = GetNodeTextData(e)
LogIfVerbose("HostingEnvironmentConfigUrl:" + self.HostingEnvironmentConfigUrl)
self.HostingEnvironmentConfigXml = self.HttpGetWithHeaders(self.HostingEnvironmentConfigUrl)
self.HostingEnvironmentConfig = HostingEnvironmentConfig().Parse(self.HostingEnvironmentConfigXml)
elif e.localName == "SharedConfig":
self.SharedConfigUrl = GetNodeTextData(e)
LogIfVerbose("SharedConfigUrl:" + self.SharedConfigUrl)
self.SharedConfigXml = self.HttpGetWithHeaders(self.SharedConfigUrl)
self.SharedConfig = SharedConfig().Parse(self.SharedConfigXml)
self.SharedConfig.Save()
elif e.localName == "ExtensionsConfig":
self.ExtensionsConfigUrl = GetNodeTextData(e)
LogIfVerbose("ExtensionsConfigUrl:" + self.ExtensionsConfigUrl)
self.ExtensionsConfigXml = self.HttpGetWithHeaders(self.ExtensionsConfigUrl)
elif e.localName == "Certificates":
self.CertificatesUrl = GetNodeTextData(e)
LogIfVerbose("CertificatesUrl:" + self.CertificatesUrl)
self.CertificatesXml = self.HttpSecureGetWithHeaders(self.CertificatesUrl, self.TransportCert)
self.Certificates = Certificates().Parse(self.CertificatesXml)
if self.Incarnation == None:
Error("GoalState.Parse: Incarnation missing")
return None
if self.ExpectedState == None:
Error("GoalState.Parse: ExpectedState missing")
return None
if self.RoleInstanceId == None:
Error("GoalState.Parse: RoleInstanceId missing")
return None
if self.ContainerId == None:
Error("GoalState.Parse: ContainerId missing")
return None
SetFileContents("GoalState." + self.Incarnation + ".xml", xmlText)
return self
def Process(self):
"""
Calls HostingEnvironmentConfig.Process()
"""
LogIfVerbose("Process goalstate")
self.HostingEnvironmentConfig.Process()
self.SharedConfig.Process()
class OvfEnv(object):
"""
Read, and process provisioning info from provisioning file OvfEnv.xml
"""
#
#
#
#
# 1.0
#
# LinuxProvisioningConfiguration
# HostName
# UserName
# UserPassword
# false
#
#
#
# EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62
# $HOME/UserName/.ssh/authorized_keys
#
#
#
#
# EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62
# $HOME/UserName/.ssh/id_rsa
#
#
#
#
#
#
#
def __init__(self):
self.reinitialize()
def reinitialize(self):
"""
Reset members.
"""
self.WaNs = "http://schemas.microsoft.com/windowsazure"
self.OvfNs = "http://schemas.dmtf.org/ovf/environment/1"
self.MajorVersion = 1
self.MinorVersion = 0
self.ComputerName = None
self.AdminPassword = None
self.UserName = None
self.UserPassword = None
self.CustomData = None
self.DisableSshPasswordAuthentication = True
self.SshPublicKeys = []
self.SshKeyPairs = []
def Parse(self, xmlText, isDeprovision = False):
"""
Parse xml tree, retreiving user and ssh key information.
Return self.
"""
self.reinitialize()
LogIfVerbose(re.sub(".*?<", "*<", xmlText))
dom = xml.dom.minidom.parseString(xmlText)
if len(dom.getElementsByTagNameNS(self.OvfNs, "Environment")) != 1:
Error("Unable to parse OVF XML.")
section = None
newer = False
for p in dom.getElementsByTagNameNS(self.WaNs, "ProvisioningSection"):
for n in p.childNodes:
if n.localName == "Version":
verparts = GetNodeTextData(n).split('.')
major = int(verparts[0])
minor = int(verparts[1])
if major > self.MajorVersion:
newer = True
if major != self.MajorVersion:
break
if minor > self.MinorVersion:
newer = True
section = p
if newer == True:
Warn("Newer provisioning configuration detected. Please consider updating waagent.")
if section == None:
Error("Could not find ProvisioningSection with major version=" + str(self.MajorVersion))
return None
self.ComputerName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "HostName")[0])
self.UserName = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserName")[0])
if isDeprovision == True:
return self
try:
self.UserPassword = GetNodeTextData(section.getElementsByTagNameNS(self.WaNs, "UserPassword")[0])
except:
pass
CDSection=None
try:
CDSection=section.getElementsByTagNameNS(self.WaNs, "CustomData")
if len(CDSection) > 0 :
self.CustomData=GetNodeTextData(CDSection[0])
if len(self.CustomData)>0:
SetFileContents(LibDir + '/CustomData', bytearray(MyDistro.translateCustomData(self.CustomData)))
Log('Wrote ' + LibDir + '/CustomData')
else :
Error(' contains no data!')
except Exception, e:
Error( str(e)+' occured creating ' + LibDir + '/CustomData')
disableSshPass = section.getElementsByTagNameNS(self.WaNs, "DisableSshPasswordAuthentication")
if len(disableSshPass) != 0:
self.DisableSshPasswordAuthentication = (GetNodeTextData(disableSshPass[0]).lower() == "true")
for pkey in section.getElementsByTagNameNS(self.WaNs, "PublicKey"):
LogIfVerbose(repr(pkey))
fp = None
path = None
for c in pkey.childNodes:
if c.localName == "Fingerprint":
fp = GetNodeTextData(c).upper()
LogIfVerbose(fp)
if c.localName == "Path":
path = GetNodeTextData(c)
LogIfVerbose(path)
self.SshPublicKeys += [[fp, path]]
for keyp in section.getElementsByTagNameNS(self.WaNs, "KeyPair"):
fp = None
path = None
LogIfVerbose(repr(keyp))
for c in keyp.childNodes:
if c.localName == "Fingerprint":
fp = GetNodeTextData(c).upper()
LogIfVerbose(fp)
if c.localName == "Path":
path = GetNodeTextData(c)
LogIfVerbose(path)
self.SshKeyPairs += [[fp, path]]
return self
def PrepareDir(self, filepath):
"""
Create home dir for self.UserName
Change owner and return path.
"""
home = MyDistro.GetHome()
# Expand HOME variable if present in path
path = os.path.normpath(filepath.replace("$HOME", home))
if (path.startswith("/") == False) or (path.endswith("/") == True):
return None
dir = path.rsplit('/', 1)[0]
if dir != "":
CreateDir(dir, "root", 0700)
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
ChangeOwner(dir, self.UserName)
return path
def NumberToBytes(self, i):
"""
Pack number into bytes. Retun as string.
"""
result = []
while i:
result.append(chr(i & 0xFF))
i >>= 8
result.reverse()
return ''.join(result)
def BitsToString(self, a):
"""
Return string representation of bits in a.
"""
index=7
s = ""
c = 0
for bit in a:
c = c | (bit << index)
index = index - 1
if index == -1:
s = s + struct.pack('>B', c)
c = 0
index = 7
return s
def OpensslToSsh(self, file):
"""
Return base-64 encoded key appropriate for ssh.
"""
from pyasn1.codec.der import decoder as der_decoder
try:
f = open(file).read().replace('\n','').split("KEY-----")[1].split('-')[0]
k=der_decoder.decode(self.BitsToString(der_decoder.decode(base64.b64decode(f))[0][1]))[0]
n=k[0]
e=k[1]
keydata=""
keydata += struct.pack('>I',len("ssh-rsa"))
keydata += "ssh-rsa"
keydata += struct.pack('>I',len(self.NumberToBytes(e)))
keydata += self.NumberToBytes(e)
keydata += struct.pack('>I',len(self.NumberToBytes(n)) + 1)
keydata += "\0"
keydata += self.NumberToBytes(n)
except Exception, e:
print("OpensslToSsh: Exception " + str(e))
return None
return "ssh-rsa " + base64.b64encode(keydata) + "\n"
def Process(self):
"""
Process all certificate and key info.
DisableSshPasswordAuthentication if configured.
CreateAccount(user)
Wait for WaAgent.EnvMonitor.IsHostnamePublished().
Restart ssh service.
"""
error = None
if self.ComputerName == None :
return "Error: Hostname missing"
error=WaAgent.EnvMonitor.SetHostName(self.ComputerName)
if error: return error
if self.DisableSshPasswordAuthentication:
filepath = "/etc/ssh/sshd_config"
# Disable RFC 4252 and RFC 4256 authentication schemes.
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
(a.startswith("PasswordAuthentication") or a.startswith("ChallengeResponseAuthentication")),
GetFileContents(filepath).split('\n'))) + "\nPasswordAuthentication no\nChallengeResponseAuthentication no\n")
Log("Disabled SSH password-based authentication methods.")
if self.AdminPassword != None:
MyDistro.changePass('root',self.AdminPassword)
if self.UserName != None:
error = MyDistro.CreateAccount(self.UserName, self.UserPassword, None, None)
sel = MyDistro.isSelinuxRunning()
if sel :
MyDistro.setSelinuxEnforce(0)
home = MyDistro.GetHome()
for pkey in self.SshPublicKeys:
Log("Deploy public key:{0}".format(pkey[0]))
if not os.path.isfile(pkey[0] + ".crt"):
Error("PublicKey not found: " + pkey[0])
error = "Failed to deploy public key (0x09)."
continue
path = self.PrepareDir(pkey[1])
if path == None:
Error("Invalid path: " + pkey[1] + " for PublicKey: " + pkey[0])
error = "Invalid path for public key (0x03)."
continue
Run(Openssl + " x509 -in " + pkey[0] + ".crt -noout -pubkey > " + pkey[0] + ".pub")
MyDistro.setSelinuxContext(pkey[0] + '.pub','unconfined_u:object_r:ssh_home_t:s0')
MyDistro.sshDeployPublicKey(pkey[0] + '.pub',path)
MyDistro.setSelinuxContext(path,'unconfined_u:object_r:ssh_home_t:s0')
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
ChangeOwner(path, self.UserName)
for keyp in self.SshKeyPairs:
Log("Deploy key pair:{0}".format(keyp[0]))
if not os.path.isfile(keyp[0] + ".prv"):
Error("KeyPair not found: " + keyp[0])
error = "Failed to deploy key pair (0x0A)."
continue
path = self.PrepareDir(keyp[1])
if path == None:
Error("Invalid path: " + keyp[1] + " for KeyPair: " + keyp[0])
error = "Invalid path for key pair (0x05)."
continue
SetFileContents(path, GetFileContents(keyp[0] + ".prv"))
os.chmod(path, 0600)
Run("ssh-keygen -y -f " + keyp[0] + ".prv > " + path + ".pub")
MyDistro.setSelinuxContext(path,'unconfined_u:object_r:ssh_home_t:s0')
MyDistro.setSelinuxContext(path + '.pub','unconfined_u:object_r:ssh_home_t:s0')
if path.startswith(os.path.normpath(home + "/" + self.UserName + "/")):
ChangeOwner(path, self.UserName)
ChangeOwner(path + ".pub", self.UserName)
if sel :
MyDistro.setSelinuxEnforce(1)
while not WaAgent.EnvMonitor.IsHostnamePublished():
time.sleep(1)
MyDistro.restartSshService()
return error
class WALAEvent(object):
def __init__(self):
self.providerId=""
self.eventId=1
self.OpcodeName=""
self.KeywordName=""
self.TaskName=""
self.TenantName=""
self.RoleName=""
self.RoleInstanceName=""
self.ContainerId=""
self.ExecutionMode="IAAS"
self.OSVersion=""
self.GAVersion=""
self.RAM=0
self.Processors=0
def ToXml(self):
strEventid=u''.format(self.eventId)
strProviderid=u''.format(self.providerId)
strRecordFormat = u''
strRecordNoQuoteFormat = u''
strMtStr=u'mt:wstr'
strMtUInt64=u'mt:uint64'
strMtBool=u'mt:bool'
strMtFloat=u'mt:float64'
strEventsData=u""
for attName in self.__dict__:
if attName in ["eventId","filedCount","providerId"]:
continue
attValue = self.__dict__[attName]
if type(attValue) is int:
strEventsData+=strRecordFormat.format(attName,attValue,strMtUInt64)
continue
if type(attValue) is str:
attValue = xml.sax.saxutils.quoteattr(attValue)
strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
continue
if str(type(attValue)).count("'unicode'") >0 :
attValue = xml.sax.saxutils.quoteattr(attValue)
strEventsData+=strRecordNoQuoteFormat.format(attName,attValue,strMtStr)
continue
if type(attValue) is bool:
strEventsData+=strRecordFormat.format(attName,attValue,strMtBool)
continue
if type(attValue) is float:
strEventsData+=strRecordFormat.format(attName,attValue,strMtFloat)
continue
Log("Warning: property "+attName+":"+str(type(attValue))+":type"+str(type(attValue))+"Can't convert to events data:"+":type not supported")
return u"{0}{1}{2}".format(strProviderid,strEventid,strEventsData)
def Save(self):
eventfolder = LibDir+"/events"
if not os.path.exists(eventfolder):
os.mkdir(eventfolder)
os.chmod(eventfolder,0700)
if len(os.listdir(eventfolder)) > 1000:
raise Exception("WriteToFolder:Too many file under "+eventfolder+" exit")
filename = os.path.join(eventfolder,str(int(time.time()*1000000)))
with open(filename+".tmp",'wb+') as hfile:
hfile.write(self.ToXml().encode("utf-8"))
os.rename(filename+".tmp",filename+".tld")
class WALAEventOperation:
HeartBeat="HeartBeat"
Provision = "Provision"
Install = "Install"
UnIsntall = "UnInstall"
Disable = "Disable"
Enable = "Enable"
Download = "Download"
Upgrade = "Upgrade"
Update = "Update"
def AddExtensionEvent(name,op,isSuccess,duration=0,version="1.0",message="",type="",isInternal=False):
event = ExtensionEvent()
event.Name=name
event.Version=version
event.IsInternal=isInternal
event.Operation=op
event.OperationSuccess=isSuccess
event.Message=message
event.Duration=duration
event.ExtensionType=type
try:
event.Save()
except:
Error("Error "+traceback.format_exc())
class ExtensionEvent(WALAEvent):
def __init__(self):
WALAEvent.__init__(self)
self.eventId=1
self.providerId="69B669B9-4AF8-4C50-BDC4-6006FA76E975"
self.Name=""
self.Version=""
self.IsInternal=False
self.Operation=""
self.OperationSuccess=True
self.ExtensionType=""
self.Message=""
self.Duration=0
class WALAEventMonitor(WALAEvent):
def __init__(self,postMethod):
WALAEvent.__init__(self)
self.post = postMethod
self.sysInfo={}
self.eventdir = LibDir+"/events"
self.issysteminfoinitilized = False
def StartEventsLoop(self):
eventThread = threading.Thread(target = self.EventsLoop)
eventThread.setDaemon(True)
eventThread.start()
def EventsLoop(self):
LastReportHeartBeatTime = datetime.datetime.min
try:
while(True):
if (datetime.datetime.now()-LastReportHeartBeatTime) > datetime.timedelta(hours=12):
LastReportHeartBeatTime = datetime.datetime.now()
AddExtensionEvent(op=WALAEventOperation.HeartBeat,name="WALA",isSuccess=True)
self.postNumbersInOneLoop=0
self.CollectAndSendWALAEvents()
time.sleep(60)
except:
Error("Exception in events loop:"+traceback.format_exc())
def SendEvent(self,providerid,events):
dataFormat = u'{1}'\
''
data = dataFormat.format(providerid,events)
self.post("/machine/?comp=telemetrydata", data)
def CollectAndSendWALAEvents(self):
if not os.path.exists(self.eventdir):
return
#Throtting, can't send more than 3 events in 15 seconds
eventSendNumber=0
eventFiles = os.listdir(self.eventdir)
events = {}
for file in eventFiles:
if not file.endswith(".tld"):
continue
with open(os.path.join(self.eventdir,file),"rb") as hfile:
#if fail to open or delete the file, throw exception
xmlStr = hfile.read().decode("utf-8",'ignore')
os.remove(os.path.join(self.eventdir,file))
params=""
eventid=""
providerid=""
#if exception happen during process an event, catch it and continue
try:
xmlStr = self.AddSystemInfo(xmlStr)
for node in xml.dom.minidom.parseString(xmlStr.encode("utf-8")).childNodes[0].childNodes:
if node.tagName == "Param":
params+=node.toxml()
if node.tagName == "Event":
eventid=node.getAttribute("id")
if node.tagName == "Provider":
providerid = node.getAttribute("id")
except:
Error(traceback.format_exc())
continue
if len(params)==0 or len(eventid)==0 or len(providerid)==0:
Error("Empty filed in params:"+params+" event id:"+eventid+" provider id:"+providerid)
continue
eventstr = u''.format(eventid,params)
if not events.get(providerid):
events[providerid]=""
if len(events[providerid]) >0 and len(events.get(providerid)+eventstr)>= 63*1024:
eventSendNumber+=1
self.SendEvent(providerid,events.get(providerid))
if eventSendNumber %3 ==0:
time.sleep(15)
events[providerid]=""
if len(eventstr) >= 63*1024:
Error("Signle event too large abort "+eventstr[:300])
continue
events[providerid]=events.get(providerid)+eventstr
for key in events.keys():
if len(events[key]) > 0:
eventSendNumber+=1
self.SendEvent(key,events[key])
if eventSendNumber%3 == 0:
time.sleep(15)
def AddSystemInfo(self,eventData):
if not self.issysteminfoinitilized:
self.issysteminfoinitilized=True
try:
self.sysInfo["OSVersion"]=platform.system()+":"+"-".join(DistInfo(1))+":"+platform.release()
self.sysInfo["GAVersion"]=GuestAgentVersion
self.sysInfo["RAM"]=MyDistro.getTotalMemory()
self.sysInfo["Processors"]=MyDistro.getProcessorCores()
sharedConfig = xml.dom.minidom.parse("/var/lib/waagent/SharedConfig.xml").childNodes[0]
hostEnvConfig= xml.dom.minidom.parse("/var/lib/waagent/HostingEnvironmentConfig.xml").childNodes[0]
gfiles = RunGetOutput("ls -t /var/lib/waagent/GoalState.*.xml")[1]
goalStateConfi = xml.dom.minidom.parse(gfiles.split("\n")[0]).childNodes[0]
self.sysInfo["TenantName"]=hostEnvConfig.getElementsByTagName("Deployment")[0].getAttribute("name")
self.sysInfo["RoleName"]=hostEnvConfig.getElementsByTagName("Role")[0].getAttribute("name")
self.sysInfo["RoleInstanceName"]=sharedConfig.getElementsByTagName("Instance")[0].getAttribute("id")
self.sysInfo["ContainerId"]=goalStateConfi.getElementsByTagName("ContainerId")[0].childNodes[0].nodeValue
except:
Error(traceback.format_exc())
eventObject = xml.dom.minidom.parseString(eventData.encode("utf-8")).childNodes[0]
for node in eventObject.childNodes:
if node.tagName == "Param":
name = node.getAttribute("Name")
if self.sysInfo.get(name):
node.setAttribute("Value",xml.sax.saxutils.escape(str(self.sysInfo[name])))
return eventObject.toxml()
class Agent(Util):
"""
Primary object container for the provisioning process.
"""
def __init__(self):
self.GoalState = None
self.Endpoint = None
self.LoadBalancerProbeServer = None
self.HealthReportCounter = 0
self.TransportCert = ""
self.EnvMonitor = None
self.SendData = None
self.DhcpResponse = None
def CheckVersions(self):
"""
Query endpoint server for wire protocol version.
Fail if our desired protocol version is not seen.
"""
#
#
#
# 2010-12-15
#
#
# 2010-12-15
# 2010-28-10
#
#
global ProtocolVersion
protocolVersionSeen = False
node = xml.dom.minidom.parseString(self.HttpGetWithoutHeaders("/?comp=versions")).childNodes[0]
if node.localName != "Versions":
Error("CheckVersions: root not Versions")
return False
for a in node.childNodes:
if a.nodeType == node.ELEMENT_NODE and a.localName == "Supported":
for b in a.childNodes:
if b.nodeType == node.ELEMENT_NODE and b.localName == "Version":
v = GetNodeTextData(b)
LogIfVerbose("Fabric supported wire protocol version: " + v)
if v == ProtocolVersion:
protocolVersionSeen = True
if a.nodeType == node.ELEMENT_NODE and a.localName == "Preferred":
v = GetNodeTextData(a.getElementsByTagName("Version")[0])
Log("Fabric preferred wire protocol version: " + v)
if not protocolVersionSeen:
Warn("Agent supported wire protocol version: " + ProtocolVersion + " was not advertised by Fabric.")
else:
Log("Negotiated wire protocol version: " + ProtocolVersion)
return True
def Unpack(self, buffer, offset, range):
"""
Unpack bytes into python values.
"""
result = 0
for i in range:
result = (result << 8) | Ord(buffer[offset + i])
return result
def UnpackLittleEndian(self, buffer, offset, length):
"""
Unpack little endian bytes into python values.
"""
return self.Unpack(buffer, offset, list(range(length - 1, -1, -1)))
def UnpackBigEndian(self, buffer, offset, length):
"""
Unpack big endian bytes into python values.
"""
return self.Unpack(buffer, offset, list(range(0, length)))
def HexDump3(self, buffer, offset, length):
"""
Dump range of buffer in formatted hex.
"""
return ''.join(['%02X' % Ord(char) for char in buffer[offset:offset + length]])
def HexDump2(self, buffer):
"""
Dump buffer in formatted hex.
"""
return self.HexDump3(buffer, 0, len(buffer))
def BuildDhcpRequest(self):
"""
Build DHCP request string.
"""
#
# typedef struct _DHCP {
# UINT8 Opcode; /* op: BOOTREQUEST or BOOTREPLY */
# UINT8 HardwareAddressType; /* htype: ethernet */
# UINT8 HardwareAddressLength; /* hlen: 6 (48 bit mac address) */
# UINT8 Hops; /* hops: 0 */
# UINT8 TransactionID[4]; /* xid: random */
# UINT8 Seconds[2]; /* secs: 0 */
# UINT8 Flags[2]; /* flags: 0 or 0x8000 for broadcast */
# UINT8 ClientIpAddress[4]; /* ciaddr: 0 */
# UINT8 YourIpAddress[4]; /* yiaddr: 0 */
# UINT8 ServerIpAddress[4]; /* siaddr: 0 */
# UINT8 RelayAgentIpAddress[4]; /* giaddr: 0 */
# UINT8 ClientHardwareAddress[16]; /* chaddr: 6 byte ethernet MAC address */
# UINT8 ServerName[64]; /* sname: 0 */
# UINT8 BootFileName[128]; /* file: 0 */
# UINT8 MagicCookie[4]; /* 99 130 83 99 */
# /* 0x63 0x82 0x53 0x63 */
# /* options -- hard code ours */
#
# UINT8 MessageTypeCode; /* 53 */
# UINT8 MessageTypeLength; /* 1 */
# UINT8 MessageType; /* 1 for DISCOVER */
# UINT8 End; /* 255 */
# } DHCP;
#
# tuple of 244 zeros
# (struct.pack_into would be good here, but requires Python 2.5)
sendData = [0] * 244
transactionID = os.urandom(4)
macAddress = MyDistro.GetMacAddress()
# Opcode = 1
# HardwareAddressType = 1 (ethernet/MAC)
# HardwareAddressLength = 6 (ethernet/MAC/48 bits)
for a in range(0, 3):
sendData[a] = [1, 1, 6][a]
# fill in transaction id (random number to ensure response matches request)
for a in range(0, 4):
sendData[4 + a] = Ord(transactionID[a])
LogIfVerbose("BuildDhcpRequest: transactionId:%s,%04X" % (self.HexDump2(transactionID), self.UnpackBigEndian(sendData, 4, 4)))
# fill in ClientHardwareAddress
for a in range(0, 6):
sendData[0x1C + a] = Ord(macAddress[a])
# DHCP Magic Cookie: 99, 130, 83, 99
# MessageTypeCode = 53 DHCP Message Type
# MessageTypeLength = 1
# MessageType = DHCPDISCOVER
# End = 255 DHCP_END
for a in range(0, 8):
sendData[0xEC + a] = [99, 130, 83, 99, 53, 1, 1, 255][a]
return array.array("B", sendData)
def IntegerToIpAddressV4String(self, a):
"""
Build DHCP request string.
"""
return "%u.%u.%u.%u" % ((a >> 24) & 0xFF, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF)
def RouteAdd(self, net, mask, gateway):
"""
Add specified route using /sbin/route add -net.
"""
net = self.IntegerToIpAddressV4String(net)
mask = self.IntegerToIpAddressV4String(mask)
gateway = self.IntegerToIpAddressV4String(gateway)
Log("Route add: net={0}, mask={1}, gateway={2}".format(net, mask, gateway))
MyDistro.routeAdd(net, mask, gateway)
def SetDefaultGateway(self, gateway):
"""
Set default gateway
"""
gateway = self.IntegerToIpAddressV4String(gateway)
Log("Set default gateway: {0}".format(gateway))
MyDistro.setDefaultGateway(gateway)
def HandleDhcpResponse(self, sendData, receiveBuffer):
"""
Parse DHCP response:
Set default gateway.
Set default routes.
Retrieve endpoint server.
Returns endpoint server or None on error.
"""
LogIfVerbose("HandleDhcpResponse")
bytesReceived = len(receiveBuffer)
if bytesReceived < 0xF6:
Error("HandleDhcpResponse: Too few bytes received " + str(bytesReceived))
return None
LogIfVerbose("BytesReceived: " + hex(bytesReceived))
LogWithPrefixIfVerbose("DHCP response:", HexDump(receiveBuffer, bytesReceived))
# check transactionId, cookie, MAC address
# cookie should never mismatch
# transactionId and MAC address may mismatch if we see a response meant from another machine
for offsets in [list(range(4, 4 + 4)), list(range(0x1C, 0x1C + 6)), list(range(0xEC, 0xEC + 4))]:
for offset in offsets:
sentByte = Ord(sendData[offset])
receivedByte = Ord(receiveBuffer[offset])
if sentByte != receivedByte:
LogIfVerbose("HandleDhcpResponse: sent cookie:" + self.HexDump3(sendData, 0xEC, 4))
LogIfVerbose("HandleDhcpResponse: rcvd cookie:" + self.HexDump3(receiveBuffer, 0xEC, 4))
LogIfVerbose("HandleDhcpResponse: sent transactionID:" + self.HexDump3(sendData, 4, 4))
LogIfVerbose("HandleDhcpResponse: rcvd transactionID:" + self.HexDump3(receiveBuffer, 4, 4))
LogIfVerbose("HandleDhcpResponse: sent ClientHardwareAddress:" + self.HexDump3(sendData, 0x1C, 6))
LogIfVerbose("HandleDhcpResponse: rcvd ClientHardwareAddress:" + self.HexDump3(receiveBuffer, 0x1C, 6))
LogIfVerbose("HandleDhcpResponse: transactionId, cookie, or MAC address mismatch")
return None
endpoint = None
#
# Walk all the returned options, parsing out what we need, ignoring the others.
# We need the custom option 245 to find the the endpoint we talk to,
# as well as, to handle some Linux DHCP client incompatibilities,
# options 3 for default gateway and 249 for routes. And 255 is end.
#
i = 0xF0 # offset to first option
while i < bytesReceived:
option = Ord(receiveBuffer[i])
length = 0
if (i + 1) < bytesReceived:
length = Ord(receiveBuffer[i + 1])
LogIfVerbose("DHCP option " + hex(option) + " at offset:" + hex(i) + " with length:" + hex(length))
if option == 255:
LogIfVerbose("DHCP packet ended at offset " + hex(i))
break
elif option == 249:
# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx
LogIfVerbose("Routes at offset:" + hex(i) + " with length:" + hex(length))
if length < 5:
Error("Data too small for option " + str(option))
j = i + 2
while j < (i + length + 2):
maskLengthBits = Ord(receiveBuffer[j])
maskLengthBytes = (((maskLengthBits + 7) & ~7) >> 3)
mask = 0xFFFFFFFF & (0xFFFFFFFF << (32 - maskLengthBits))
j += 1
net = self.UnpackBigEndian(receiveBuffer, j, maskLengthBytes)
net <<= (32 - maskLengthBytes * 8)
net &= mask
j += maskLengthBytes
gateway = self.UnpackBigEndian(receiveBuffer, j, 4)
j += 4
self.RouteAdd(net, mask, gateway)
if j != (i + length + 2):
Error("HandleDhcpResponse: Unable to parse routes")
elif option == 3 or option == 245:
if i + 5 < bytesReceived:
if length != 4:
Error("HandleDhcpResponse: Endpoint or Default Gateway not 4 bytes")
return None
gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4)
IpAddress = self.IntegerToIpAddressV4String(gateway)
if option == 3:
self.SetDefaultGateway(gateway)
name = "DefaultGateway"
else:
endpoint = IpAddress
name = "Azure wire protocol endpoint"
LogIfVerbose(name + ": " + IpAddress + " at " + hex(i))
else:
Error("HandleDhcpResponse: Data too small for option " + str(option))
else:
LogIfVerbose("Skipping DHCP option " + hex(option) + " at " + hex(i) + " with length " + hex(length))
i += length + 2
return endpoint
def DoDhcpWork(self):
"""
Discover the wire server via DHCP option 245.
And workaround incompatibility with Azure DHCP servers.
"""
ShortSleep = False # Sleep 1 second before retrying DHCP queries.
ifname=None
sleepDurations = [0, 10, 30, 60, 60]
maxRetry = len(sleepDurations)
lastTry = (maxRetry - 1)
for retry in range(0, maxRetry):
try:
#Open DHCP port if iptables is enabled.
Run("iptables -D INPUT -p udp --dport 68 -j ACCEPT",chk_err=False) # We supress error logging on error.
Run("iptables -I INPUT -p udp --dport 68 -j ACCEPT",chk_err=False) # We supress error logging on error.
strRetry = str(retry)
prefix = "DoDhcpWork: try=" + strRetry
LogIfVerbose(prefix)
sendData = self.BuildDhcpRequest()
LogWithPrefixIfVerbose("DHCP request:", HexDump(sendData, len(sendData)))
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
missingDefaultRoute = True
try:
if DistInfo()[0] == 'FreeBSD':
missingDefaultRoute = True
else:
routes = RunGetOutput("route -n")[1]
for line in routes.split('\n'):
if line.startswith("0.0.0.0 ") or line.startswith("default "):
missingDefaultRoute = False
except:
pass
if missingDefaultRoute:
# This is required because sending after binding to 0.0.0.0 fails with
# network unreachable when the default gateway is not set up.
ifname=MyDistro.GetInterfaceName()
Log("DoDhcpWork: Missing default route - adding broadcast route for DHCP.")
if DistInfo()[0] == 'FreeBSD':
Run("route add -net 255.255.255.255 -iface " + ifname,chk_err=False)
else:
Run("route add 255.255.255.255 dev " + ifname,chk_err=False)
if MyDistro.isDHCPEnabled():
MyDistro.stopDHCP()
sock.bind(("0.0.0.0", 68))
sock.sendto(sendData, ("", 67))
sock.settimeout(10)
Log("DoDhcpWork: Setting socket.timeout=10, entering recv")
receiveBuffer = sock.recv(1024)
endpoint = self.HandleDhcpResponse(sendData, receiveBuffer)
if endpoint == None:
LogIfVerbose("DoDhcpWork: No endpoint found")
if endpoint != None or retry == lastTry:
if endpoint != None:
self.SendData = sendData
self.DhcpResponse = receiveBuffer
if retry == lastTry:
LogIfVerbose("DoDhcpWork: try=" + strRetry)
return endpoint
sleepDuration = [sleepDurations[retry % len(sleepDurations)], 1][ShortSleep]
LogIfVerbose("DoDhcpWork: sleep=" + str(sleepDuration))
time.sleep(sleepDuration)
except Exception, e:
ErrorWithPrefix(prefix, str(e))
ErrorWithPrefix(prefix, traceback.format_exc())
finally:
sock.close()
if missingDefaultRoute:
#We added this route - delete it
Log("DoDhcpWork: Removing broadcast route for DHCP.")
if DistInfo()[0] == 'FreeBSD':
Run("route del -net 255.255.255.255 -iface " + ifname,chk_err=False)
else:
Run("route del 255.255.255.255 dev " + ifname,chk_err=False) # We supress error logging on error.
if MyDistro.isDHCPEnabled():
MyDistro.startDHCP()
return None
def UpdateAndPublishHostName(self, name):
"""
Set hostname locally and publish to iDNS
"""
Log("Setting host name: " + name)
MyDistro.publishHostname(name)
ethernetInterface = MyDistro.GetInterfaceName()
MyDistro.RestartInterface(ethernetInterface)
self.RestoreRoutes()
def RestoreRoutes(self):
"""
If there is a DHCP response, then call HandleDhcpResponse.
"""
if self.SendData != None and self.DhcpResponse != None:
self.HandleDhcpResponse(self.SendData, self.DhcpResponse)
def UpdateGoalState(self):
"""
Retreive goal state information from endpoint server.
Parse xml and initialize Agent.GoalState object.
Return object or None on error.
"""
goalStateXml = None
maxRetry = 9
log = NoLog
for retry in range(1, maxRetry + 1):
strRetry = str(retry)
log("retry UpdateGoalState,retry=" + strRetry)
goalStateXml = self.HttpGetWithHeaders("/machine/?comp=goalstate")
if goalStateXml != None:
break
log = Log
time.sleep(retry)
if not goalStateXml:
Error("UpdateGoalState failed.")
return
Log("Retrieved GoalState from Azure Fabric.")
self.GoalState = GoalState(self).Parse(goalStateXml)
return self.GoalState
def ReportReady(self):
"""
Send health report 'Ready' to server.
This signals the fabric that our provosion is completed,
and the host is ready for operation.
"""
counter = (self.HealthReportCounter + 1) % 1000000
self.HealthReportCounter = counter
healthReport = (""
+ self.GoalState.Incarnation
+ ""
+ self.GoalState.ContainerId
+ ""
+ self.GoalState.RoleInstanceId
+ "Ready")
a = self.HttpPostWithHeaders("/machine?comp=health", healthReport)
if a != None:
return a.getheader("x-ms-latest-goal-state-incarnation-number")
return None
def ReportNotReady(self, status, desc):
"""
Send health report 'Provisioning' to server.
This signals the fabric that our provosion is starting.
"""
healthReport = (""
+ self.GoalState.Incarnation
+ ""
+ self.GoalState.ContainerId
+ ""
+ self.GoalState.RoleInstanceId
+ "NotReady"
+ "" + status + "" + desc + " "
+ "")
a = self.HttpPostWithHeaders("/machine?comp=health", healthReport)
if a != None:
return a.getheader("x-ms-latest-goal-state-incarnation-number")
return None
def ReportRoleProperties(self, thumbprint):
"""
Send roleProperties and thumbprint to server.
"""
roleProperties = (""
+ "" + self.GoalState.ContainerId + ""
+ ""
+ "" + self.GoalState.RoleInstanceId + ""
+ ""
+ "")
a = self.HttpPostWithHeaders("/machine?comp=roleProperties",
roleProperties)
Log("Posted Role Properties. CertificateThumbprint=" + thumbprint)
return a
def LoadBalancerProbeServer_Shutdown(self):
"""
Shutdown the LoadBalancerProbeServer.
"""
if self.LoadBalancerProbeServer != None:
self.LoadBalancerProbeServer.shutdown()
self.LoadBalancerProbeServer = None
def GenerateTransportCert(self):
"""
Create ssl certificate for https communication with endpoint server.
"""
Run(Openssl + " req -x509 -nodes -subj /CN=LinuxTransport -days 32768 -newkey rsa:2048 -keyout TransportPrivate.pem -out TransportCert.pem")
cert = ""
for line in GetFileContents("TransportCert.pem").split('\n'):
if not "CERTIFICATE" in line:
cert += line.rstrip()
return cert
def DoVmmStartup(self):
"""
Spawn the VMM startup script.
"""
Log("Starting Microsoft System Center VMM Initialization Process")
pid = subprocess.Popen(["/bin/bash","/mnt/cdrom/secure/"+VMM_STARTUP_SCRIPT_NAME,"-p /mnt/cdrom/secure/ "]).pid
time.sleep(5)
sys.exit(0)
def TryUnloadAtapiix(self):
"""
If global modloaded is True, then we loaded the ata_piix kernel module, unload it.
"""
if modloaded:
Run("rmmod ata_piix.ko",chk_err=False)
Log("Unloaded ata_piix.ko driver for ATAPI CD-ROM")
def TryLoadAtapiix(self):
"""
Load the ata_piix kernel module if it exists.
If successful, set global modloaded to True.
If unable to load module leave modloaded False.
"""
global modloaded
modloaded=False
retcode,krn=RunGetOutput('uname -r')
krn_pth='/lib/modules/'+krn.strip('\n')+'/kernel/drivers/ata/ata_piix.ko'
if Run("lsmod | grep ata_piix",chk_err=False) == 0 :
Log("Module " + krn_pth + " driver for ATAPI CD-ROM is already present.")
return 0
if retcode:
Error("Unable to provision: Failed to call uname -r")
return "Unable to provision: Failed to call uname"
if os.path.isfile(krn_pth):
retcode,output=RunGetOutput("insmod " + krn_pth,chk_err=False)
else:
Log("Module " + krn_pth + " driver for ATAPI CD-ROM does not exist.")
return 1
if retcode != 0:
Error('Error calling insmod for '+ krn_pth + ' driver for ATAPI CD-ROM')
return retcode
time.sleep(1)
# check 3 times if the mod is loaded
for i in range(3):
if Run('lsmod | grep ata_piix'):
continue
else :
modloaded=True
break
if not modloaded:
Error('Unable to load '+ krn_pth + ' driver for ATAPI CD-ROM')
return 1
Log("Loaded " + krn_pth + " driver for ATAPI CD-ROM")
# we have succeeded loading the ata_piix mod if it can be done.
def SearchForVMMStartup(self):
"""
Search for a DVD/CDROM containing VMM's VMM_CONFIG_FILE_NAME.
Call TryLoadAtapiix in case we must load the ata_piix module first.
If VMM_CONFIG_FILE_NAME is found, call DoVmmStartup.
Else, return to Azure Provisioning process.
"""
self.TryLoadAtapiix()
if os.path.exists('/mnt/cdrom/secure') == False:
CreateDir("/mnt/cdrom/secure", "root", 0700)
mounted=False
for dvds in [re.match(r'(sr[0-9]|hd[c-z]|cdrom[0-9]|cd[0-9]?)',x) for x in os.listdir('/dev/')]:
if dvds == None:
continue
dvd = '/dev/'+dvds.group(0)
if Run("LC_ALL=C fdisk -l " + dvd + " | grep Disk",chk_err=False):
continue # Not mountable
else:
for retry in range(1,6):
retcode,output=RunGetOutput("mount -v " + dvd + " /mnt/cdrom/secure")
Log(output[:-1])
if retcode == 0:
Log("mount succeeded on attempt #" + str(retry) )
mounted=True
break
if 'is already mounted on /mnt/cdrom/secure' in output:
Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
mounted=True
break
Log("mount failed on attempt #" + str(retry) )
Log("mount loop sleeping 5...")
time.sleep(5)
if not mounted:
# unable to mount
continue
if not os.path.isfile("/mnt/cdrom/secure/"+VMM_CONFIG_FILE_NAME):
#nope - mount the next drive
if mounted:
Run("umount "+dvd,chk_err=False)
mounted=False
continue
else : # it is the vmm startup
self.DoVmmStartup()
Log("VMM Init script not found. Provisioning for Azure")
return
def Provision(self):
"""
Responible for:
Regenerate ssh keys,
Mount, read, and parse ovfenv.xml from provisioning dvd rom
Process the ovfenv.xml info
Call ReportRoleProperties
If configured, delete root password.
Return None on success, error string on error.
"""
enabled = Config.get("Provisioning.Enabled")
if enabled != None and enabled.lower().startswith("n"):
return
Log("Provisioning image started.")
type = Config.get("Provisioning.SshHostKeyPairType")
if type == None:
type = "rsa"
regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
Run("rm -f /etc/ssh/ssh_host_*key*")
Run("ssh-keygen -N '' -t " + type + " -f /etc/ssh/ssh_host_" + type + "_key")
MyDistro.restartSshService()
#SetFileContents(LibDir + "/provisioned", "")
dvd = None
for dvds in [re.match(r'(sr[0-9]|hd[c-z]|cdrom[0-9]|cd[0-9]?)',x) for x in os.listdir('/dev/')]:
if dvds == None :
continue
dvd = '/dev/'+dvds.group(0)
if dvd == None:
# No DVD device detected
Error("No DVD device detected, unable to provision.")
return "No DVD device detected, unable to provision."
if MyDistro.mediaHasFilesystem(dvd) is False :
out=MyDistro.load_ata_piix()
if out:
return out
for i in range(10): # we may have to wait
if os.path.exists(dvd):
break
Log("Waiting for DVD - sleeping 1 - "+str(i+1)+" try...")
time.sleep(1)
if os.path.exists('/mnt/cdrom/secure') == False:
CreateDir("/mnt/cdrom/secure", "root", 0700)
#begin mount loop - 5 tries - 5 sec wait between
for retry in range(1,6):
location='/mnt/cdrom/secure'
retcode,output=MyDistro.mountDVD(dvd,location)
Log(output[:-1])
if retcode == 0:
Log("mount succeeded on attempt #" + str(retry) )
break
if 'is already mounted on /mnt/cdrom/secure' in output:
Log("Device " + dvd + " is already mounted on /mnt/cdrom/secure." + str(retry) )
break
Log("mount failed on attempt #" + str(retry) )
Log("mount loop sleeping 5...")
time.sleep(5)
if not os.path.isfile("/mnt/cdrom/secure/ovf-env.xml"):
Error("Unable to provision: Missing ovf-env.xml on DVD.")
return "Failed to retrieve provisioning data (0x02)."
ovfxml = (GetFileContents(u"/mnt/cdrom/secure/ovf-env.xml",asbin=False)) # use unicode here to ensure correct codec gets used.
if ord(ovfxml[0]) > 128 and ord(ovfxml[1]) > 128 and ord(ovfxml[2]) > 128 :
ovfxml = ovfxml[3:] # BOM is not stripped. First three bytes are > 128 and not unicode chars so we ignore them.
ovfxml=ovfxml.strip(chr(0x00)) # we may have NULLs.
ovfxml=ovfxml[ovfxml.find(''):] # chop leading text if present
SetFileContents("ovf-env.xml", re.sub(".*?<", "*<", ovfxml))
Run("umount " + dvd,chk_err=False)
MyDistro.unload_ata_piix()
error = None
if ovfxml != None:
Log("Provisioning image using OVF settings in the DVD.")
ovfobj = OvfEnv().Parse(ovfxml)
if ovfobj != None:
error = ovfobj.Process()
if error :
Error ("Provisioning image FAILED " + error)
return ("Provisioning image FAILED " + error)
Log("Ovf XML process finished")
# This is done here because regenerated SSH host key pairs may be potentially overwritten when processing the ovfxml
fingerprint = RunGetOutput("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub")[1].rstrip().split()[1].replace(':','')
self.ReportRoleProperties(fingerprint)
delRootPass = Config.get("Provisioning.DeleteRootPassword")
if delRootPass != None and delRootPass.lower().startswith("y"):
MyDistro.deleteRootPassword()
Log("Provisioning image completed.")
return error
def Run(self):
"""
Called by 'waagent -daemon.'
Main loop to process the goal state. State is posted every 25 seconds
when provisioning has been completed.
Search for VMM enviroment, start VMM script if found.
Perform DHCP and endpoint server discovery by calling DoDhcpWork().
Check wire protocol versions.
Set SCSI timeout on root device.
Call GenerateTransportCert() to create ssl certs for server communication.
Call UpdateGoalState().
If not provisioned, call ReportNotReady("Provisioning", "Starting")
Call Provision(), set global provisioned = True if successful.
Call goalState.Process()
Start LBProbeServer if indicated in waagent.conf.
Start the StateConsumer if indicated in waagent.conf.
ReportReady if provisioning is complete.
If provisioning failed, call ReportNotReady("ProvisioningFailed", provisionError)
"""
SetFileContents("/var/run/waagent.pid", str(os.getpid()) + "\n")
reportHandlerStatusCount = 0
# Determine if we are in VMM. Spawn VMM_STARTUP_SCRIPT_NAME if found.
self.SearchForVMMStartup()
ipv4=''
while ipv4 == '' or ipv4 == '0.0.0.0' :
ipv4=MyDistro.GetIpv4Address()
if ipv4 == '' or ipv4 == '0.0.0.0' :
Log("Waiting for network.")
time.sleep(10)
Log("IPv4 address: " + ipv4)
mac=''
mac=MyDistro.GetMacAddress()
if len(mac)>0 :
Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in mac]))
# Consume Entropy in ACPI table provided by Hyper-V
try:
SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
except:
pass
Log("Probing for Azure environment.")
self.Endpoint = self.DoDhcpWork()
while self.Endpoint == None:
Log("Azure environment not detected.")
Log("Retry environment detection in 60 seconds")
time.sleep(60)
self.Endpoint = self.DoDhcpWork()
Log("Discovered Azure endpoint: " + self.Endpoint)
if not self.CheckVersions():
Error("Agent.CheckVersions failed")
sys.exit(1)
self.EnvMonitor = EnvMonitor()
# Set SCSI timeout on SCSI disks
MyDistro.initScsiDiskTimeout()
global provisioned
global provisionError
global Openssl
Openssl = Config.get("OS.OpensslPath")
if Openssl == None:
Openssl = "openssl"
self.TransportCert = self.GenerateTransportCert()
eventMonitor = None
incarnation = None # goalStateIncarnationFromHealthReport
currentPort = None # loadBalancerProbePort
goalState = None # self.GoalState, instance of GoalState
provisioned = os.path.exists(LibDir + "/provisioned")
program = Config.get("Role.StateConsumer")
provisionError = None
lbProbeResponder = True
setting = Config.get("LBProbeResponder")
if setting != None and setting.lower().startswith("n"):
lbProbeResponder = False
while True:
if (goalState == None) or (incarnation == None) or (goalState.Incarnation != incarnation):
try:
goalState = self.UpdateGoalState()
except HttpResourceGoneError as e:
Warn("Incarnation is out of date:{0}".format(e))
incarnation = None
continue
if goalState == None :
Warn("Failed to fetch goalstate")
continue
if provisioned == False:
self.ReportNotReady("Provisioning", "Starting")
goalState.Process()
if provisioned == False:
provisionError = self.Provision()
if provisionError == None :
provisioned = True
SetFileContents(LibDir + "/provisioned", "")
lastCtime = "NOTFIND"
try:
walaConfigFile = MyDistro.getConfigurationPath()
lastCtime = time.ctime(os.path.getctime(walaConfigFile))
except:
pass
#Get Ctime of wala config, can help identify the base image of this VM
AddExtensionEvent(name="WALA",op=WALAEventOperation.Provision,isSuccess=True,
message="WALA Config Ctime:"+lastCtime)
executeCustomData = Config.get("Provisioning.ExecuteCustomData")
if executeCustomData != None and executeCustomData.lower().startswith("y"):
if os.path.exists(LibDir + '/CustomData'):
Run('chmod +x ' + LibDir + '/CustomData')
Run(LibDir + '/CustomData')
else:
Error(LibDir + '/CustomData does not exist.')
#
# only one port supported
# restart server if new port is different than old port
# stop server if no longer a port
#
goalPort = goalState.LoadBalancerProbePort
if currentPort != goalPort:
try:
self.LoadBalancerProbeServer_Shutdown()
currentPort = goalPort
if currentPort != None and lbProbeResponder == True:
self.LoadBalancerProbeServer = LoadBalancerProbeServer(currentPort)
if self.LoadBalancerProbeServer == None :
lbProbeResponder = False
Log("Unable to create LBProbeResponder.")
except Exception, e:
Error("Failed to launch LBProbeResponder: {0}".format(e))
currentPort = None
# Report SSH key fingerprint
type = Config.get("Provisioning.SshHostKeyPairType")
if type == None:
type = "rsa"
host_key_path = "/etc/ssh/ssh_host_" + type + "_key.pub"
if(MyDistro.waitForSshHostKey(host_key_path)):
fingerprint = RunGetOutput("ssh-keygen -lf /etc/ssh/ssh_host_" + type + "_key.pub")[1].rstrip().split()[1].replace(':','')
self.ReportRoleProperties(fingerprint)
if program != None and DiskActivated == True:
try:
Children.append(subprocess.Popen([program, "Ready"]))
except OSError, e :
ErrorWithPrefix('SharedConfig.Parse','Exception: '+ str(e) +' occured launching ' + program )
program = None
sleepToReduceAccessDenied = 3
time.sleep(sleepToReduceAccessDenied)
if provisionError != None:
incarnation = self.ReportNotReady("ProvisioningFailed", provisionError)
else:
incarnation = self.ReportReady()
# Process our extensions.
if goalState.ExtensionsConfig == None and goalState.ExtensionsConfigXml != None :
reportHandlerStatusCount = 0 #Reset count when new goal state comes
goalState.ExtensionsConfig = ExtensionsConfig().Parse(goalState.ExtensionsConfigXml)
# report the status/heartbeat results of extension processing
if goalState.ExtensionsConfig != None :
ret = goalState.ExtensionsConfig.ReportHandlerStatus()
if ret != 0:
Error("Failed to report handler status")
elif reportHandlerStatusCount % 1000 == 0:
#Agent report handler status every 25 seconds. Reduce the log entries by adding a count
Log("Successfully reported handler status")
reportHandlerStatusCount += 1
global LinuxDistro
if LinuxDistro == "redhat":
DoInstallRHUIRPM()
if not eventMonitor:
eventMonitor = WALAEventMonitor(self.HttpPostWithHeaders)
eventMonitor.StartEventsLoop()
time.sleep(25 - sleepToReduceAccessDenied)
WaagentLogrotate = """\
/var/log/waagent.log {
monthly
rotate 6
notifempty
missingok
}
"""
def GetMountPoint(mountlist, device):
"""
Example of mountlist:
/dev/sda1 on / type ext4 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
/dev/sdb1 on /mnt/resource type ext4 (rw)
"""
if (mountlist and device):
for entry in mountlist.split('\n'):
if(re.search(device, entry)):
tokens = entry.split()
#Return the 3rd column of this line
return tokens[2] if len(tokens) > 2 else None
return None
def FindInLinuxKernelCmdline(option):
"""
Return match object if 'option' is present in the kernel boot options
of the grub configuration.
"""
m=None
matchs=r'^.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?'+option+r'.*$'
try:
m=FindStringInFile(MyDistro.grubKernelBootOptionsFile,matchs)
except IOError, e:
Error('FindInLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
return m
def AppendToLinuxKernelCmdline(option):
"""
Add 'option' to the kernel boot options of the grub configuration.
"""
if not FindInLinuxKernelCmdline(option):
src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r')(.*?)("?)$'
rep=r'\1\2 '+ option + r'\3'
try:
ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
except IOError, e :
Error('AppendToLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
return 1
Run("update-grub",chk_err=False)
return 0
def RemoveFromLinuxKernelCmdline(option):
"""
Remove 'option' to the kernel boot options of the grub configuration.
"""
if FindInLinuxKernelCmdline(option):
src=r'^(.*?'+MyDistro.grubKernelBootOptionsLine+r'.*?)('+option+r')(.*?)("?)$'
rep=r'\1\3\4'
try:
ReplaceStringInFile(MyDistro.grubKernelBootOptionsFile,src,rep)
except IOError, e :
Error('RemoveFromLinuxKernelCmdline: Exception opening ' + MyDistro.grubKernelBootOptionsFile + 'Exception:' + str(e))
return 1
Run("update-grub",chk_err=False)
return 0
def FindStringInFile(fname,matchs):
"""
Return match object if found in file.
"""
try:
ms=re.compile(matchs)
for l in (open(fname,'r')).readlines():
m=re.search(ms,l)
if m:
return m
except:
raise
return None
def ReplaceStringInFile(fname,src,repl):
"""
Replace 'src' with 'repl' in file.
"""
try:
sr=re.compile(src)
if FindStringInFile(fname,src):
updated=''
for l in (open(fname,'r')).readlines():
n=re.sub(sr,repl,l)
updated+=n
ReplaceFileContentsAtomic(fname,updated)
except :
raise
return
def ApplyVNUMAWorkaround():
"""
If kernel version has NUMA bug, add 'numa=off' to
kernel boot options.
"""
VersionParts = platform.release().replace('-', '.').split('.')
if int(VersionParts[0]) > 2:
return
if int(VersionParts[1]) > 6:
return
if int(VersionParts[2]) > 37:
return
if AppendToLinuxKernelCmdline("numa=off") == 0 :
Log("Your kernel version " + platform.release() + " has a NUMA-related bug: NUMA has been disabled.")
else :
"Error adding 'numa=off'. NUMA has not been disabled."
def RevertVNUMAWorkaround():
"""
Remove 'numa=off' from kernel boot options.
"""
if RemoveFromLinuxKernelCmdline("numa=off") == 0 :
Log('NUMA has been re-enabled')
else :
Log('NUMA has not been re-enabled')
def Install():
"""
Install the agent service.
Check dependencies.
Create /etc/waagent.conf and move old version to
/etc/waagent.conf.old
Copy RulesFiles to /var/lib/waagent
Create /etc/logrotate.d/waagent
Set /etc/ssh/sshd_config ClientAliveInterval to 180
Call ApplyVNUMAWorkaround()
"""
if MyDistro.checkDependencies():
return 1
os.chmod(sys.argv[0], 0755)
SwitchCwd()
for a in RulesFiles:
if os.path.isfile(a):
if os.path.isfile(GetLastPathElement(a)):
os.remove(GetLastPathElement(a))
shutil.move(a, ".")
Warn("Moved " + a + " -> " + LibDir + "/" + GetLastPathElement(a) )
MyDistro.registerAgentService()
if os.path.isfile("/etc/waagent.conf"):
try:
os.remove("/etc/waagent.conf.old")
except:
pass
try:
os.rename("/etc/waagent.conf", "/etc/waagent.conf.old")
Warn("Existing /etc/waagent.conf has been renamed to /etc/waagent.conf.old")
except:
pass
SetFileContents("/etc/waagent.conf", MyDistro.waagent_conf_file)
SetFileContents("/etc/logrotate.d/waagent", WaagentLogrotate)
filepath = "/etc/ssh/sshd_config"
ReplaceFileContentsAtomic(filepath, "\n".join(filter(lambda a: not
a.startswith("ClientAliveInterval"),
GetFileContents(filepath).split('\n'))) + "\nClientAliveInterval 180\n")
Log("Configured SSH client probing to keep connections alive.")
ApplyVNUMAWorkaround()
return 0
def GetMyDistro(dist_class_name=''):
"""
Return MyDistro object.
NOTE: Logging is not initialized at this point.
"""
if dist_class_name == '':
if 'Linux' in platform.system():
Distro=DistInfo()[0]
else : # I know this is not Linux!
if 'FreeBSD' in platform.system():
Distro=platform.system()
Distro=Distro.strip('"')
Distro=Distro.strip(' ')
dist_class_name=Distro+'Distro'
else:
Distro=dist_class_name
if not globals().has_key(dist_class_name):
print Distro+' is not a supported distribution.'
return None
return globals()[dist_class_name]() # the distro class inside this module.
def DistInfo(fullname=0):
if 'FreeBSD' in platform.system():
release = re.sub('\-.*\Z', '', str(platform.release()))
distinfo = ['FreeBSD', release]
return distinfo
if 'linux_distribution' in dir(platform):
distinfo = list(platform.linux_distribution(full_distribution_name=fullname))
distinfo[0] = distinfo[0].strip() # remove trailing whitespace in distro name
return distinfo
else:
return platform.dist()
def PackagedInstall(buildroot):
"""
Called from setup.py for use by RPM.
Generic implementation Creates directories and
files /etc/waagent.conf, /etc/init.d/waagent, /usr/sbin/waagent,
/etc/logrotate.d/waagent, /etc/sudoers.d/waagent under buildroot.
Copies generated files waagent.conf, into place and exits.
"""
MyDistro=GetMyDistro()
if MyDistro == None :
sys.exit(1)
MyDistro.packagedInstall(buildroot)
def LibraryInstall(buildroot):
pass
def Uninstall():
"""
Uninstall the agent service.
Copy RulesFiles back to original locations.
Delete agent-related files.
Call RevertVNUMAWorkaround().
"""
SwitchCwd()
for a in RulesFiles:
if os.path.isfile(GetLastPathElement(a)):
try:
shutil.move(GetLastPathElement(a), a)
Warn("Moved " + LibDir + "/" + GetLastPathElement(a) + " -> " + a )
except:
pass
MyDistro.unregisterAgentService()
MyDistro.uninstallDeleteFiles()
RevertVNUMAWorkaround()
return 0
def Deprovision(force, deluser):
"""
Remove user accounts created by provisioning.
Disables root password if Provisioning.DeleteRootPassword = 'y'
Stop agent service.
Remove SSH host keys if they were generated by the provision.
Set hostname to 'localhost.localdomain'.
Delete cached system configuration files in /var/lib and /var/lib/waagent.
"""
#Append blank line at the end of file, so the ctime of this file is changed every time
Run("echo ''>>"+ MyDistro.getConfigurationPath())
SwitchCwd()
ovfxml = GetFileContents(LibDir+"/ovf-env.xml")
ovfobj = None
if ovfxml != None:
ovfobj = OvfEnv().Parse(ovfxml, True)
print("WARNING! The waagent service will be stopped.")
print("WARNING! All SSH host key pairs will be deleted.")
print("WARNING! Cached DHCP leases will be deleted.")
MyDistro.deprovisionWarnUser()
delRootPass = Config.get("Provisioning.DeleteRootPassword")
if delRootPass != None and delRootPass.lower().startswith("y"):
print("WARNING! root password will be disabled. You will not be able to login as root.")
if ovfobj != None and deluser == True:
print("WARNING! " + ovfobj.UserName + " account and entire home directory will be deleted.")
if force == False and not raw_input('Do you want to proceed (y/n)? ').startswith('y'):
return 1
MyDistro.stopAgentService()
# Remove SSH host keys
regenerateKeys = Config.get("Provisioning.RegenerateSshHostKeyPair")
if regenerateKeys == None or regenerateKeys.lower().startswith("y"):
Run("rm -f /etc/ssh/ssh_host_*key*")
# Remove root password
if delRootPass != None and delRootPass.lower().startswith("y"):
MyDistro.deleteRootPassword()
# Remove distribution specific networking configuration
MyDistro.publishHostname('localhost.localdomain')
MyDistro.deprovisionDeleteFiles()
if deluser == True:
MyDistro.DeleteAccount(ovfobj.UserName)
return 0
def SwitchCwd():
"""
Switch to cwd to /var/lib/waagent.
Create if not present.
"""
CreateDir(LibDir, "root", 0700)
os.chdir(LibDir)
def Usage():
"""
Print the arguments to waagent.
"""
print("usage: " + sys.argv[0] + " [-verbose] [-force] [-help|-install|-uninstall|-deprovision[+user]|-version|-serialconsole|-daemon]")
return 0
def main():
"""
Instantiate MyDistro, exit if distro class is not defined.
Parse command-line arguments, exit with usage() on error.
Instantiate ConfigurationProvider.
Call appropriate non-daemon methods and exit.
If daemon mode, enter Agent.Run() loop.
"""
if GuestAgentVersion == "":
print("WARNING! This is a non-standard agent that does not include a valid version string.")
if len(sys.argv) == 1:
sys.exit(Usage())
LoggerInit('/var/log/waagent.log','/dev/console')
global LinuxDistro
LinuxDistro=DistInfo()[0]
global MyDistro
MyDistro=GetMyDistro()
if MyDistro == None :
sys.exit(1)
args = []
conf_file = None
global force
force = False
for a in sys.argv[1:]:
if re.match("^([-/]*)(help|usage|\?)", a):
sys.exit(Usage())
elif re.match("^([-/]*)version", a):
print(GuestAgentVersion + " running on " + LinuxDistro)
sys.exit(0)
elif re.match("^([-/]*)verbose", a):
myLogger.verbose = True
elif re.match("^([-/]*)force", a):
force = True
elif re.match("^(?:[-/]*)conf=.+", a):
conf_file = re.match("^(?:[-/]*)conf=(.+)", a).groups()[0]
elif re.match("^([-/]*)(setup|install)", a):
sys.exit(MyDistro.Install())
elif re.match("^([-/]*)(uninstall)", a):
sys.exit(Uninstall())
else:
args.append(a)
global Config
Config = ConfigurationProvider(conf_file)
logfile = Config.get("Logs.File")
if logfile is not None:
myLogger.file_path = logfile
logconsole = Config.get("Logs.Console")
if logconsole is not None and logconsole.lower().startswith("n"):
myLogger.con_path = None
verbose = Config.get("Logs.Verbose")
if verbose != None and verbose.lower().startswith("y"):
myLogger.verbose=True
global daemon
daemon = False
for a in args:
if re.match("^([-/]*)deprovision\+user", a):
sys.exit(Deprovision(force, True))
elif re.match("^([-/]*)deprovision", a):
sys.exit(Deprovision(force, False))
elif re.match("^([-/]*)daemon", a):
daemon = True
elif re.match("^([-/]*)serialconsole", a):
AppendToLinuxKernelCmdline("console=ttyS0 earlyprintk=ttyS0")
Log("Configured kernel to use ttyS0 as the boot console.")
sys.exit(0)
else:
print("Invalid command line parameter:" + a)
sys.exit(1)
if daemon == False:
sys.exit(Usage())
global modloaded
modloaded = False
while True:
try:
SwitchCwd()
Log(GuestAgentLongName + " Version: " + GuestAgentVersion)
if IsLinux():
Log("Linux Distribution Detected : " + LinuxDistro)
global WaAgent
WaAgent = Agent()
WaAgent.Run()
except Exception, e:
Error(traceback.format_exc())
Error("Exception: " + str(e))
Log("Restart agent in 15 seconds")
time.sleep(15)
if __name__ == '__main__' :
main()
WALinuxAgent-2.1.3/config/ 0000775 0000000 0000000 00000000000 12651710031 0015264 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/config/coreos/ 0000775 0000000 0000000 00000000000 12651710031 0016556 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/config/coreos/waagent.conf 0000664 0000000 0000000 00000004137 12651710031 0021060 0 ustar 00root root 0000000 0000000 #
# Microsoft 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=y
# Password authentication for root account will be unavailable.
Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
# Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
Provisioning.MonitorHostName=y
# Decode CustomData from Base64.
Provisioning.DecodeCustomData=y
# Execute CustomData after provisioning.
Provisioning.ExecuteCustomData=n
# Algorithm used by crypt when generating password hash.
#Provisioning.PasswordCryptId=6
# Length of random salt used when generating password hash.
#Provisioning.PasswordCryptSaltLength=10
# Allow reset password of sys user
Provisioning.AllowResetSysUser=n
# Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Format=y
# 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
# 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
#
# Lib.Dir=/var/lib/waagent
#
# DVD.MountPoint=/mnt/cdrom/secure
#
# Pid.File=/var/run/waagent.pid
#
# Extension.LogDir=/var/log/azure
#
# Home.Dir=/home
WALinuxAgent-2.1.3/config/suse/ 0000775 0000000 0000000 00000000000 12651710031 0016243 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/config/suse/waagent.conf 0000664 0000000 0000000 00000004026 12651710031 0020542 0 ustar 00root root 0000000 0000000 #
# Microsoft 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=y
# Password authentication for root account will be unavailable.
Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
# Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
Provisioning.MonitorHostName=y
# Decode CustomData from Base64.
Provisioning.DecodeCustomData=n
# Execute CustomData after provisioning.
Provisioning.ExecuteCustomData=n
# Algorithm used by crypt when generating password hash.
#Provisioning.PasswordCryptId=6
# Length of random salt used when generating password hash.
#Provisioning.PasswordCryptSaltLength=10
# Allow reset password of sys user
Provisioning.AllowResetSysUser=n
# Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Format=y
# File system on the resource disk
# Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
ResourceDisk.Filesystem=ext3
# 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 Microsoft 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
WALinuxAgent-2.1.3/config/ubuntu/ 0000775 0000000 0000000 00000000000 12651710031 0016606 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/config/ubuntu/waagent.conf 0000664 0000000 0000000 00000004015 12651710031 0021103 0 ustar 00root root 0000000 0000000 #
# Microsoft 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
# Algorithm used by crypt when generating password hash.
#Provisioning.PasswordCryptId=6
# Length of random salt used when generating password hash.
#Provisioning.PasswordCryptSaltLength=10
# Allow reset password of sys user
Provisioning.AllowResetSysUser=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
# 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 Microsoft 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
WALinuxAgent-2.1.3/config/waagent.conf 0000664 0000000 0000000 00000004137 12651710031 0017566 0 ustar 00root root 0000000 0000000 #
# Microsoft 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=y
# Password authentication for root account will be unavailable.
Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
# Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
Provisioning.MonitorHostName=y
# Decode CustomData from Base64.
Provisioning.DecodeCustomData=n
# Execute CustomData after provisioning.
Provisioning.ExecuteCustomData=n
# Algorithm used by crypt when generating password hash.
#Provisioning.PasswordCryptId=6
# Length of random salt used when generating password hash.
#Provisioning.PasswordCryptSaltLength=10
# Allow reset password of sys user
Provisioning.AllowResetSysUser=n
# Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Format=y
# 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
# 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
#
# Lib.Dir=/var/lib/waagent
#
# DVD.MountPoint=/mnt/cdrom/secure
#
# Pid.File=/var/run/waagent.pid
#
# Extension.LogDir=/var/log/azure
#
# Home.Dir=/home
WALinuxAgent-2.1.3/config/waagent.logrotate 0000664 0000000 0000000 00000000134 12651710031 0020632 0 ustar 00root root 0000000 0000000 /var/log/waagent.log {
compress
monthly
rotate 6
notifempty
missingok
}
WALinuxAgent-2.1.3/debian/ 0000775 0000000 0000000 00000000000 12651710031 0015241 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/debian/README.debian 0000664 0000000 0000000 00000001641 12651710031 0017344 0 ustar 00root root 0000000 0000000 The preferred method of installing WALinuxAgent for Ubuntu is to use Debian
Packaging. Ubuntu supports the official Ubuntu WALinuxAgent version, which
can be found here: https://launchpad.net/ubuntu/+source/walinuxagent . The
Debian packaging is derived from the source here.
These instructions tell you build the package with out having to install
too many extra packages, and keep your source directory clean.
Run once:
1. Install required packages:
sudo apt-get -y install ubuntu-dev-tools pbuilder
2. Create the pbuilder environment:
sudo pbuilder create --debootstrapopts --variant=buildd
To compile the package:
From the top-most directory, (i.e. not debian), run:
1. Build the source package:
dpkg-buildpackage -S
2. Build the package:
sudo pbuilder build ../walinuxagent_1.3-0ubuntu1.dsc
3. Fetch the built package, usually from /var/cache/pbuilder/results
WALinuxAgent-2.1.3/debian/README.source 0000664 0000000 0000000 00000000012 12651710031 0017411 0 ustar 00root root 0000000 0000000 version=3
WALinuxAgent-2.1.3/debian/changelog 0000664 0000000 0000000 00000006747 12651710031 0017131 0 ustar 00root root 0000000 0000000 walinuxagent (1.3-0ubuntu1) raring; urgency=low
* New upstream version (LP: #1101371)
- 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
* Modified walinuxagent to recognize when its been packaged.
-- Ben Howard Fri, 18 Jan 2013 15:43:08 -0700
walinuxagent (1.2-0ubuntu1) raring; urgency=low
* New upstream version (LP: #1077148)
* Upstream features:
- Added - load ata_piix.ko module loaded if needed for CDROM device support
- Additional logging for DoDhcpWork()
- Update sock.recv timeout from 30 to 10 seconds
- Fix: Linux waagent deprovision, user is not deleted properly
- Fix: Make LBProbeResponder construction more robust
- Fix: Agent fails to provision user with public/private key pairs
- Fix: DHCP broadcast response not received
- Fix: Linux agent fails to delete root user password
- Fix: Linux agent should report error messages to Fabric when
passed an invalid hostname.
* Dropped Ubuntu specific patches
- Removed debian/patches/000_resolv-conf.patch as upstream now supports
resolvconf properly.
- Removed debian/patches/001-strip-init-d.patch as redundant now that
upstream understands Ubuntu upstart. Upstream script does not
handle the removal of the upstart job anyway.
* Added debian/patches/000_use_package_upstart.patch to use packaged
upstart job over in-script upstart.
-- Ben Howard Fri, 07 Dec 2012 16:52:42 -0700
walinuxagent (1.1-0ubuntu2) raring; urgency=low
* Stop upgrades purging walinuxagent meta-data and configuration files
(LP: #1079897):
- d/{control,walinuxagent-data-saver.preinst}: Added
walinuxagent-data-saver package to ensure that agent generated data is
not lost on upgrade by diverting /usr/sbin/waagent during the upgrade
process.
- d/walinuxagent-data-saver.lintian-overrides: Override errors about use
of dpkg-divert in this package.
- d/control: Added Pre-Depends to walinuxagent on walinuxagent-data-saver.
- d/prerm: Stop calling waagent --uninstall during reconfiguration
and upgrade, specify files to remove manually for purge.
- d/postinst: Remove divert of /usr/sbin/waagent prior to completion of
package install.
* d/preinst: If upgrading from package version with unmanaged waagent upstart
configuration stop the agent and remove the upstart configuration.
* d/upstart: Tidied description in upstart job.
-- James Page Fri, 23 Nov 2012 16:07:41 +0000
walinuxagent (1.1-0ubuntu1) raring; urgency=low
* New upstream version (LP: #1078074, #1077147).
* Moved upstart job to be managed by packaging.
-- Ben Howard Wed, 14 Nov 2012 10:59:37 -0700
walinuxagent (1.0~git20120606.c16f5e9-0ubuntu2) quantal; urgency=low
* Restrict target architectures to i386 and amd64.
-- James Page Mon, 06 Aug 2012 10:24:07 +0100
walinuxagent (1.0~git20120606.c16f5e9-0ubuntu1) quantal; urgency=low
* Initial package import (LP: #1014864).
* Ubuntu specific modifications:
- Made resolvconf aware during deprovisioning
- Added Ubuntu upstart job
- Added ability to prevent agent startup.
-- Ben Howard Fri, 22 Jun 2012 09:10:22 -0600
WALinuxAgent-2.1.3/debian/compat 0000664 0000000 0000000 00000000002 12651710031 0016437 0 ustar 00root root 0000000 0000000 8
WALinuxAgent-2.1.3/debian/control 0000664 0000000 0000000 00000002405 12651710031 0016645 0 ustar 00root root 0000000 0000000 Source: walinuxagent
Section: python
Priority: extra
Maintainer: Ben Howard
XSBC-Original-Maintainer: Microsoft Corporation
Build-Depends: debhelper (>= 8), python-all
Standards-Version: 3.9.3
XS-Python-Version: all
Homepage: http://go.microsoft.com/fwlink/?LinkId=250998
Package: walinuxagent
Architecture: i386 amd64
Pre-Depends: walinuxagent-data-saver (= ${binary:Version})
Depends: python (>= 2.4),
openssl (>=1.0),
openssh-server (>=1:5.9p1),
passwd (>=4.1.4.2),
util-linux (>=2.0),
linux-image-extra-virtual,
${misc:Depends},
${python:Depends}
Conflicts: network-manager
Description: Microsoft Azure Linux Agent
The Microsoft Azure Linux Agent supports the provisioning and running of Linux
VMs in the Microsoft Azure cloud. This package should be installed on Linux
disk images that are built to run in the Microsoft Azure environment.
Package: walinuxagent-data-saver
Architecture: i386 amd64
Depends: ${misc:Depends}
Description: Helper package which ensures safe upgrade for walinuxagent
Early versions of walinuxagent contained a bug the deleted configuration and
data on reconfiguration or upgrade.
.
This package is used to ensure safe upgrades.
WALinuxAgent-2.1.3/debian/copyright 0000664 0000000 0000000 00000001316 12651710031 0017175 0 ustar 00root root 0000000 0000000 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
Upstream-Name: walinuxagent
Upstream-Contact: Microsoft Corporation
Source: https://github.com/Azure/WALinuxAgent/
Files: *
Copyright: 2012, Microsoft Corporation
License: Apache-2.0
Files: waaagent
Copyright: 2012, Microsoft Corporation
2012, Ben Howard
License: Apache-2.0
Files: debian/*
Copyright: 2012, Ben Howard
License: Apache-2.0
License: Apache-2.0
On Debian systems, the complete text of the Apache version 2.0 license
can be found in "/usr/share/common-licenses/Apache-2.0".
WALinuxAgent-2.1.3/debian/default 0000664 0000000 0000000 00000000363 12651710031 0016612 0 ustar 00root root 0000000 0000000 # To disable the Microsoft Azure Agent, set WALINUXAGENT_ENABLED=0
WALINUXAGENT_ENABLED=1
# Setting this variable to anything other than 1, runs the script
# in stand-alone mode. This is heavily, heavily discouraged.
WAXLINUXAGENT_PACKAGED=1
WALinuxAgent-2.1.3/debian/dirs 0000664 0000000 0000000 00000000034 12651710031 0016122 0 ustar 00root root 0000000 0000000 /usr/share/doc/walinuxagent
WALinuxAgent-2.1.3/debian/docs 0000664 0000000 0000000 00000000036 12651710031 0016113 0 ustar 00root root 0000000 0000000 NOTICE
LICENSE-2.0.txt
README
WALinuxAgent-2.1.3/debian/install 0000664 0000000 0000000 00000000021 12651710031 0016623 0 ustar 00root root 0000000 0000000 waagent usr/sbin
WALinuxAgent-2.1.3/debian/postinst 0000664 0000000 0000000 00000000742 12651710031 0017052 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -e
if [ -f /usr/sbin/waagent.save ]; then
dpkg-divert --package walinuxagent-data-saver --rename --remove /usr/sbin/waagent
fi
case "$1" in
configure)
waagent --setup --force
;;
abort-upgrade|abort-remove|abort-deconfigure)
if [ -f /etc/init.d/waagent ]; then
rm /etc/init.d/waagent
fi
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
#DEBHELPER#
exit 0
WALinuxAgent-2.1.3/debian/preinst 0000664 0000000 0000000 00000000453 12651710031 0016652 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -e
# If upgrading from package version
# with unmanaged upstart configuration
# and agent stop the agent and remove
# the upstart configuration.
if [ -f /etc/init/waagent.conf ]; then
stop waagent 2>&1 > /dev/null || true
rm -f /etc/init/waagent.conf
fi
#DEBHELPER#
exit 0
WALinuxAgent-2.1.3/debian/prerm 0000664 0000000 0000000 00000000765 12651710031 0016321 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -e
case "$1" in
purge)
rm /etc/waagent.conf > /dev/null || true
rm -rf /var/lib/waagent > /dev/null || true
rm /etc/ssh/*waagent > /dev/null || true
;;
remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0
WALinuxAgent-2.1.3/debian/rules 0000775 0000000 0000000 00000000054 12651710031 0016320 0 ustar 00root root 0000000 0000000 #!/usr/bin/make -f
%:
dh $@ --with python2
WALinuxAgent-2.1.3/debian/source/ 0000775 0000000 0000000 00000000000 12651710031 0016541 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/debian/source/format 0000664 0000000 0000000 00000000015 12651710031 0017750 0 ustar 00root root 0000000 0000000 3.0 (native)
WALinuxAgent-2.1.3/debian/upstart 0000664 0000000 0000000 00000000726 12651710031 0016673 0 ustar 00root root 0000000 0000000 description "Microsoft Azure Linux agent"
author "Ben Howard "
start on mounted MOUNTPOINT=/
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
WALinuxAgent-2.1.3/debian/walinuxagent-data-saver.lintian-overrides 0000664 0000000 0000000 00000000744 12651710031 0025361 0 ustar 00root root 0000000 0000000 # This package exists purely to stop older version of
# walinuxagent purging all local meta-data, sudo config
# and configuration file on upgrade.
# It does this by diverting the waagent binary during the
# upgrade process; the diversion is removed by the walinuxagent
# postinst script.
walinuxagent-data-saver: orphaned-diversion usr/sbin/waagent preinst
walinuxagent-data-saver: diversion-for-unknown-file usr/sbin/waagent preinst:11
walinuxagent-data-saver: empty-binary-package
WALinuxAgent-2.1.3/debian/walinuxagent-data-saver.preinst 0000664 0000000 0000000 00000000547 12651710031 0023410 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -e
if [ -f /usr/sbin/waagent ]; then
# Divert the waagent binary so that the prerm script
# in earlier versions of walinuxagent can't purge its
# user data
dpkg-divert --add --rename \
--package walinuxagent-data-saver \
--divert /usr/sbin/waagent.save /usr/sbin/waagent
fi
#DEBHELPER#
exit 0
WALinuxAgent-2.1.3/init/ 0000775 0000000 0000000 00000000000 12651710031 0014762 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/init/coreos/ 0000775 0000000 0000000 00000000000 12651710031 0016254 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/init/coreos/cloud-config.yml 0000664 0000000 0000000 00000002351 12651710031 0021351 0 ustar 00root root 0000000 0000000 #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
WALinuxAgent-2.1.3/init/suse/ 0000775 0000000 0000000 00000000000 12651710031 0015741 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/init/suse/waagent 0000775 0000000 0000000 00000006201 12651710031 0017314 0 ustar 00root root 0000000 0000000 #! /bin/sh
#
# Microsoft 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: MicrosoftAzureLinuxAgent
# Required-Start: $network sshd
# Required-Stop: $network sshd
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Description: Start the MicrosoftAzureLinuxAgent
### 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 MicrosoftAzureLinuxAgent"
## Start daemon with startproc(8). If this fails
## the echo return value is set appropriate.
startproc -f ${PYTHON} ${WAZD_BIN} -start
rc_status -v
;;
stop)
echo -n "Shutting down MicrosoftAzureLinuxAgent"
## 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 MicrosoftAzureLinuxAgent "
## 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
WALinuxAgent-2.1.3/init/ubuntu/ 0000775 0000000 0000000 00000000000 12651710031 0016304 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/init/ubuntu/walinuxagent 0000664 0000000 0000000 00000000132 12651710031 0020731 0 ustar 00root root 0000000 0000000 # To disable the Microsoft Azure Agent, set WALINUXAGENT_ENABLED=0
WALINUXAGENT_ENABLED=1
WALinuxAgent-2.1.3/init/ubuntu/walinuxagent.conf 0000664 0000000 0000000 00000000732 12651710031 0021663 0 ustar 00root root 0000000 0000000 description "Microsoft Azure Linux agent"
author "Ben Howard "
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
WALinuxAgent-2.1.3/init/ubuntu/walinuxagent.service 0000775 0000000 0000000 00000000574 12651710031 0022405 0 ustar 00root root 0000000 0000000 [Unit]
Description=Azure Linux Agent
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
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/sbin/waagent -daemon
Restart=always
[Install]
WantedBy=multi-user.target
WALinuxAgent-2.1.3/init/waagent 0000775 0000000 0000000 00000001476 12651710031 0016346 0 ustar 00root root 0000000 0000000 #!/bin/bash
#
# Init file for AzureLinuxAgent.
#
# chkconfig: 2345 60 80
# description: AzureLinuxAgent
#
# source function library
. /etc/rc.d/init.d/functions
RETVAL=0
FriendlyName="AzureLinuxAgent"
WAZD_BIN=/usr/sbin/waagent
start()
{
echo -n $"Starting $FriendlyName: "
$WAZD_BIN -start
RETVAL=$?
echo
return $RETVAL
}
stop()
{
echo -n $"Stopping $FriendlyName: "
killproc -p /var/run/waagent.pid $WAZD_BIN
RETVAL=$?
echo
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
reload)
;;
report)
;;
status)
status $WAZD_BIN
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|restart|status}"
RETVAL=1
esac
exit $RETVAL
WALinuxAgent-2.1.3/init/waagent.service 0000775 0000000 0000000 00000000417 12651710031 0017777 0 ustar 00root root 0000000 0000000 [Unit]
Description=Azure Linux Agent
After=network.target
After=sshd.service
ConditionFileIsExecutable=/usr/sbin/waagent
ConditionPathExists=/etc/waagent.conf
[Service]
Type=simple
ExecStart=/usr/sbin/waagent -daemon
Restart=always
[Install]
WantedBy=multi-user.target
WALinuxAgent-2.1.3/rpm/ 0000775 0000000 0000000 00000000000 12651710031 0014615 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/rpm/README 0000664 0000000 0000000 00000001157 12651710031 0015501 0 ustar 00root root 0000000 0000000 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.
The instructions below will describe how you can build your own RPM
package.
1. Install setuptools
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!
WALinuxAgent-2.1.3/rpm/post-inst 0000664 0000000 0000000 00000000031 12651710031 0016472 0 ustar 00root root 0000000 0000000 waagent register-service
WALinuxAgent-2.1.3/setup.py 0000775 0000000 0000000 00000014765 12651710031 0015551 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
#
# Microsoft Azure Linux Agent setup.py
#
# Copyright 2013 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
from azurelinuxagent.metadata import AGENT_NAME, AGENT_VERSION, \
AGENT_DESCRIPTION, \
DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME
from azurelinuxagent.agent import Agent
import setuptools
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))
def set_conf_files(data_files, dest="/etc", src=["config/waagent.conf"]):
data_files.append((dest, src))
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):
"""
Determine data_files according to distro name, version and init system type
"""
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.startswith("6"):
set_sysv_files(data_files)
else:
#redhat7.0+ use systemd
set_systemd_files(data_files, dest="/usr/lib/systemd/system")
if version.startswith("7.1"):
#TODO this is a mitigation to systemctl bug on 7.1
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",
src=["config/coreos/waagent.conf"])
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.startswith("12") or version.startswith("14"):
#Ubuntu12.04/14.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="",
src=["init/ubuntu/snappy/walinuxagent.yml"])
else:
#Ubuntu15.04+ uses systemd
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.startswith('11') or \
fullname == 'openSUSE' and version.startswith('13.1'):
set_sysv_files(data_files, dest='/etc/init.d',
src=["init/suse/waagent"])
else:
#sles 12+ and openSUSE 13.2+ use systemd
set_systemd_files(data_files, dest='/usr/lib/systemd/system')
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, '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=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 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(False).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
})
WALinuxAgent-2.1.3/snappy/ 0000775 0000000 0000000 00000000000 12651710031 0015331 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/snappy/bin/ 0000775 0000000 0000000 00000000000 12651710031 0016101 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/snappy/bin/waagent 0000775 0000000 0000000 00000002752 12651710031 0017463 0 ustar 00root root 0000000 0000000 #!/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)
WALinuxAgent-2.1.3/snappy/bin/waagent.start 0000775 0000000 0000000 00000000154 12651710031 0020611 0 ustar 00root root 0000000 0000000 #!/bin/bash
export PYTHONPATH="${PATH}:${SNAP_APP_PATH}/lib"
python3 ${SNAP_APP_PATH}/bin/waagent -daemon
WALinuxAgent-2.1.3/snappy/lib/ 0000775 0000000 0000000 00000000000 12651710031 0016077 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/snappy/lib/readme.md 0000664 0000000 0000000 00000000045 12651710031 0017655 0 ustar 00root root 0000000 0000000 Copy the azurelinuxagent folder here
WALinuxAgent-2.1.3/snappy/meta/ 0000775 0000000 0000000 00000000000 12651710031 0016257 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/snappy/meta/package.yaml 0000664 0000000 0000000 00000000414 12651710031 0020535 0 ustar 00root root 0000000 0000000 name: walinuxagent
version: 2.1.1
vendor: Microsoft Corporation
type: framework
services:
- name: walinuxagent
start: bin/waagent.start
security-policy:
apparmor: meta/walinuxagent.apparmor
seccomp: meta/walinuxagent.seccomp
WALinuxAgent-2.1.3/snappy/meta/readme.md 0000664 0000000 0000000 00000000054 12651710031 0020035 0 ustar 00root root 0000000 0000000 Microsoft Azure Linux Agent Snap Framework
WALinuxAgent-2.1.3/snappy/meta/walinuxagent.apparmor 0000664 0000000 0000000 00000005325 12651710031 0022535 0 ustar 00root root 0000000 0000000 # AppArmor confinement for waagent
#include
# Specified profile variables
###VAR###
###PROFILEATTACH### flags=(attach_disconnected) {
#include
#include
#include
#include
# Executable binaries
/usr/{,s}bin/* ixr,
/{,s}bin/* ixr,
# Capabilities
capability net_bind_service,
capability net_raw,
capability net_admin,
capability dac_override,
capability sys_module,
capability sys_admin,
capability sys_ptrace,
ptrace (read),
ptrace (trace),
mount,
umount,
network,
# Log path
/var/log/waagent.log rw,
/var/log/azure/ rw,
/var/log/azure/** rw,
# Lib path
/var/lib/waagent/ rw,
/var/lib/waagent/** mrwlk,
# Enable VM extensions to execute unconfined
/var/lib/waagent/** PUx,
/{,usr/}lib/ r,
/{,usr/}lib/** r,
/etc/ r,
/etc/** r,
/etc/udev/rules.d/** w,
/usr/share/ r,
/usr/share/** r,
/usr/local/{,s}bin/ r,
/usr/{,s}bin/ r,
/{,s}bin/ r,
/dev/ r,
/dev/sr0 r,
/dev/null w,
/dev/console rw,
/dev/tty rw,
/run/ r,
/run/** r,
/run/mount/utab w,
/run/waagent.pid w,
@{PROC}/ r,
@{PROC}/** r,
/sys/module/ r,
/sys/module/** r,
/sys/firmware/acpi/tables/** r,
/sys/block/ r,
/sys/block/sd*/device/timeout rw,
/sys/devices/** rw,
/mnt/cdrom/ rw,
/mnt/cdrom/secure/ rw,
# Writable for the install directory
@{CLICK_DIR}/@{APP_PKGNAME}/ r,
@{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/ r,
@{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/** mrwklix,
}
WALinuxAgent-2.1.3/snappy/meta/walinuxagent.seccomp 0000664 0000000 0000000 00000000016 12651710031 0022335 0 ustar 00root root 0000000 0000000 @unrestricted
WALinuxAgent-2.1.3/snappy/readme.md 0000664 0000000 0000000 00000000732 12651710031 0017112 0 ustar 00root root 0000000 0000000 ### Building the walinuxagent snap package
1. You will need the snappy developer tools on your Ubuntu Developer Desktop:
$ sudo add-apt-repository ppa:snappy-dev/tools
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install snappy-tools
2. Copy the azurelinuxagent folder to snappy/lib
$ cp -rf azurelinuxagent snappy/lib
3. Build the snap package under the snappy folder
$ cd snappy
$ snappy build WALinuxAgent-2.1.3/snappy/waagent.conf 0000664 0000000 0000000 00000003421 12651710031 0017626 0 ustar 00root root 0000000 0000000 #
# Microsoft 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 Microsoft 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
WALinuxAgent-2.1.3/tests/ 0000775 0000000 0000000 00000000000 12651710031 0015161 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/tests/__init__.py 0000664 0000000 0000000 00000001456 12651710031 0017300 0 ustar 00root root 0000000 0000000 # 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
WALinuxAgent-2.1.3/tests/data/ 0000775 0000000 0000000 00000000000 12651710031 0016072 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/tests/data/dhcp 0000664 0000000 0000000 00000000510 12651710031 0016727 0 ustar 00root root 0000000 0000000 ƪ]
>`
>*
> ]88 RD008CFA06B61C cSc56
>*
>
>"test-cs12.h1.internal.cloudapp.net :;3
> WALinuxAgent-2.1.3/tests/data/ext/ 0000775 0000000 0000000 00000000000 12651710031 0016672 5 ustar 00root root 0000000 0000000 WALinuxAgent-2.1.3/tests/data/ext/event.xml 0000775 0000000 0000000 00000002220 12651710031 0020534 0 ustar 00root root 0000000 0000000