diff options
author | Ben Howard <ben.howard@ubuntu.com> | 2016-02-08 16:33:07 -0700 |
---|---|---|
committer | usd-importer <ubuntu-server@lists.ubuntu.com> | 2016-02-09 00:59:05 +0000 |
commit | a00729ff7421b3661e8b1a1e0fa46393379f2e96 (patch) | |
tree | 4563b927e3a57446a4a928a72a92d72c9ad4f6e6 | |
parent | 53f54030cae2de3d5fa474a61fe51f16c7a07c79 (diff) | |
download | vyos-walinuxagent-a00729ff7421b3661e8b1a1e0fa46393379f2e96.tar.gz vyos-walinuxagent-a00729ff7421b3661e8b1a1e0fa46393379f2e96.zip |
Import patches-unapplied version 2.1.3-0ubuntu1 to ubuntu/xenial-proposed
Imported using git-ubuntu import.
Changelog parent: 53f54030cae2de3d5fa474a61fe51f16c7a07c79
New changelog entries:
* New upstream release (LP: #1543359):
- Bug fixes for extension handling
- Feature enablement for AzureStack.
137 files changed, 4093 insertions, 4268 deletions
@@ -60,3 +60,6 @@ waagentc waagentc bin/waagent2.0c + +# rope project +.ropeproject/ @@ -1,5 +1,12 @@ 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 @@ -1,6 +1,6 @@ -Microsoft Azure Linux Agent README +## Microsoft Azure Linux Agent README -INTRODUCTION +### INTRODUCTION The Microsoft Azure Linux Agent (waagent) manages Linux & FreeBSD provisioning, and VM interaction with the Azure Fabric Controller. It provides the following @@ -39,7 +39,7 @@ functionality for Linux and FreeBSD IaaS deployments: - VM Extension reference implementation on https://github.com/Azure/azure-linux-extensions -COMMUNICATION +### COMMUNICATION The information flow from the platform to the agent occurs via two channels: @@ -51,7 +51,7 @@ The information flow from the platform to the agent occurs via two channels: configuration. -REQUIREMENTS +### 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 @@ -81,14 +81,14 @@ Waagent depends on some system packages in order to function properly: * Network tools: ip-route -INSTALLATION +### 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: +or prefixes, you can use ***setuptools*** to install from source by running: #sudo python setup.py install --register-service @@ -98,7 +98,7 @@ You can view more installation options by running: The agent's log file is kept at /var/log/waagent.log. -UPGRADE +### UPGRADE Upgrading via your distribution's package repository is preferred. @@ -121,7 +121,7 @@ For CoreOS, use: The agent's log file is kept at /var/log/waagent.log. -COMMAND LINE OPTIONS +### COMMAND LINE OPTIONS Flags: @@ -133,25 +133,6 @@ Commands: -help: Lists the supported commands and flags. - -install: Manual installation of the agent - * Checks the system for required dependencies - * Creates the SysV init script (/etc/init.d/waagent), the logrotate - configuration file (/etc/logrotate.d/waagent) and configures the image - to run the init script on boot - * Writes sample configuration file to /etc/waagent.conf - * Any existing configuration file is moved to /etc/waagent.conf.old - * Detects kernel version and applies the VNUMA workaround if necessary - * Moves udev rules that may interfere with networking - (/lib/udev/rules.d/75-persistent-net-generator.rules, - /etc/udev/rules.d/70-persistent-net.rules) to /var/lib/waagent/ - - -uninstall: Unregisters the init script from the system and deletes it. - Deletes the logrotate configuration and the waagent config file in - /etc/waagent.conf. Automatic reverting of the VNUMA workaround is not - supported, please edit the GRUB configuration files by hand to re-enable - NUMA if required. Restores any moved udev rules that were moved during - installation. - -deprovision: Attempt to clean the system and make it suitable for re-provisioning. Deletes the following: * All SSH host keys @@ -180,15 +161,12 @@ Commands: -start: Run waagent as a background process -CONFIGURATION +### CONFIGURATION A configuration file (/etc/waagent.conf) controls the actions of waagent. A sample configuration file is shown below: -# -# Azure Linux Agent Configuration -# - +``` Role.StateConsumer=None Role.ConfigurationConsumer=None Role.TopologyConsumer=None @@ -212,6 +190,7 @@ 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 @@ -379,10 +358,12 @@ Type: String Default: None If set, agent will use proxy server to access internet -APPENDIX +### APPENDIX Sample Role Configuration File: +``` + <?xml version="1.0" encoding="utf-8"?> <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1"> <StoredCertificates> @@ -440,9 +421,11 @@ version="1.0.0.0" goalStateIncarnation="1"> disableQuota="false" /> </ResourceReferences> </HostingEnvironmentConfig> +``` Sample Role Topology File: +``` <?xml version="1.0" encoding="utf-8"?> <SharedConfig version="1.0.0.0" goalStateIncarnation="2"> <Deployment name="a99549a92e38498f98cf2989330cd2f1" @@ -544,3 +527,4 @@ version="1.0.0.0" goalStateIncarnation="2"> </Instance> </Instances> </SharedConfig> +``` diff --git a/azurelinuxagent/agent.py b/azurelinuxagent/agent.py index 849a192..93e9c16 100644 --- a/azurelinuxagent/agent.py +++ b/azurelinuxagent/agent.py @@ -29,27 +29,60 @@ from azurelinuxagent.metadata import AGENT_NAME, AGENT_LONG_VERSION, \ DISTRO_NAME, DISTRO_VERSION, \ PY_VERSION_MAJOR, PY_VERSION_MINOR, \ PY_VERSION_MICRO -from azurelinuxagent.utils.osutil import OSUTIL -from azurelinuxagent.handler import HANDLERS +from azurelinuxagent.distro.loader import get_distro -def init(verbose): - """ - Initialize agent running environment. - """ - HANDLERS.init_handler.init(verbose) +class Agent(object): + def __init__(self, verbose): + """ + Initialize agent running environment. + """ + self.distro = get_distro(); + self.distro.init_handler.run(verbose) -def run(): - """ - Run agent daemon - """ - HANDLERS.main_handler.run() + 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 deprovision(force=False, deluser=False): + 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(): """ - Run deprovision command + Parse command line arguments, exit with usage() on error. + Invoke different methods according to different command """ - HANDLERS.deprovision_handler.deprovision(force=force, deluser=deluser) + 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): """ @@ -108,34 +141,3 @@ def start(): devnull = open(os.devnull, 'w') subprocess.Popen([sys.argv[0], '-daemon'], stdout=devnull, stderr=devnull) -def register_service(): - """ - Register agent as a service - """ - print("Register {0} service".format(AGENT_NAME)) - OSUTIL.register_agent_service() - print("Start {0} service".format(AGENT_NAME)) - OSUTIL.start_agent_service() - -def main(): - """ - Parse command line arguments, exit with usage() on error. - Invoke different methods according to different command - """ - command, force, verbose = parse_args(sys.argv[1:]) - if command == "version": - version() - elif command == "help": - usage() - else: - init(verbose) - if command == "deprovision+user": - deprovision(force, deluser=True) - elif command == "deprovision": - deprovision(force, deluser=False) - elif command == "start": - start() - elif command == "register-service": - register_service() - elif command == "daemon": - run() diff --git a/azurelinuxagent/conf.py b/azurelinuxagent/conf.py index 2b0eb01..7921e79 100644 --- a/azurelinuxagent/conf.py +++ b/azurelinuxagent/conf.py @@ -43,11 +43,11 @@ class ConfigurationProvider(object): else: self.values[parts[0]] = None - def get(self, key, default_val=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=False): + def get_switch(self, key, default_val): val = self.values.get(key) if val is not None and val.lower() == 'y': return True @@ -55,7 +55,7 @@ class ConfigurationProvider(object): return False return default_val - def get_int(self, key, default_val=-1): + def get_int(self, key, default_val): try: return int(self.values.get(key)) except TypeError: @@ -64,9 +64,9 @@ class ConfigurationProvider(object): return default_val -__config__ = ConfigurationProvider() +__conf__ = ConfigurationProvider() -def load_conf(conf_file_path, conf=__config__): +def load_conf_from_file(conf_file_path, conf=__conf__): """ Load conf file from: conf_file_path """ @@ -80,30 +80,87 @@ def load_conf(conf_file_path, conf=__config__): raise AgentConfigError(("Failed to load conf file:{0}, {1}" "").format(conf_file_path, err)) -def get(key, default_val=None, conf=__config__): - """ - Get option value by key, return default_val if not found - """ - if conf is not None: - return conf.get(key, default_val) - else: - return default_val +def get_logs_verbose(conf=__conf__): + return conf.get_switch("Logs.Verbose", False) -def get_switch(key, default_val=None, conf=__config__): - """ - Get bool option value by key, return default_val if not found - """ - if conf is not None: - return conf.get_switch(key, default_val) - else: - return default_val +def get_lib_dir(conf=__conf__): + return conf.get("Lib.Dir", "/var/lib/waagent") -def get_int(key, default_val=None, conf=__config__): - """ - Get int option value by key, return default_val if not found - """ - if conf is not None: - return conf.get_int(key, default_val) - else: - return default_val +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) diff --git a/azurelinuxagent/distro/centos/loader.py b/azurelinuxagent/distro/centos/loader.py deleted file mode 100644 index 9dc428f..0000000 --- a/azurelinuxagent/distro/centos/loader.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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.metadata import DISTRO_NAME, DISTRO_VERSION -import azurelinuxagent.distro.redhat.loader as redhat - -def get_osutil(): - return redhat.get_osutil() - diff --git a/azurelinuxagent/distro/coreos/deprovision.py b/azurelinuxagent/distro/coreos/deprovision.py index 99d3a40..9642579 100644 --- a/azurelinuxagent/distro/coreos/deprovision.py +++ b/azurelinuxagent/distro/coreos/deprovision.py @@ -21,6 +21,9 @@ 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.") diff --git a/azurelinuxagent/distro/coreos/handlerFactory.py b/azurelinuxagent/distro/coreos/distro.py index 58f476c..04c7bff 100644 --- a/azurelinuxagent/distro/coreos/handlerFactory.py +++ b/azurelinuxagent/distro/coreos/distro.py @@ -17,11 +17,13 @@ # Requires Python 2.4+ and Openssl 1.0+ # -from .deprovision import CoreOSDeprovisionHandler -from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory +from azurelinuxagent.distro.default.distro import DefaultDistro +from azurelinuxagent.distro.coreos.osutil import CoreOSUtil +from azurelinuxagent.distro.coreos.deprovision import CoreOSDeprovisionHandler -class CoreOSHandlerFactory(DefaultHandlerFactory): +class CoreOSDistro(DefaultDistro): def __init__(self): - super(CoreOSHandlerFactory, self).__init__() - self.deprovision_handler = CoreOSDeprovisionHandler() + super(CoreOSDistro, self).__init__() + self.osutil = CoreOSUtil() + self.deprovision_handler = CoreOSDeprovisionHandler(self) diff --git a/azurelinuxagent/distro/coreos/osutil.py b/azurelinuxagent/distro/coreos/osutil.py index c244311..ffc83e3 100644 --- a/azurelinuxagent/distro/coreos/osutil.py +++ b/azurelinuxagent/distro/coreos/osutil.py @@ -35,9 +35,9 @@ 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' - self.conf_file_path = '/usr/share/oem/waagent.conf' if 'PATH' in os.environ: path = "{0}:{1}".format(os.environ['PATH'], self.python_path) else: @@ -85,9 +85,6 @@ class CoreOSUtil(DefaultOSUtil): ret= shellutil.run_get_output("pidof systemd-networkd") return ret[1] if ret[0] == 0 else None - def decode_customdata(self, data): - return base64.b64decode(data) - def set_ssh_client_alive_interval(self): #In CoreOS, /etc/sshd_config is mount readonly. Skip the setting pass diff --git a/azurelinuxagent/distro/coreos/loader.py b/azurelinuxagent/distro/debian/distro.py index 802f276..01f4e3e 100644 --- a/azurelinuxagent/distro/coreos/loader.py +++ b/azurelinuxagent/distro/debian/distro.py @@ -17,12 +17,11 @@ # Requires Python 2.4+ and Openssl 1.0+ # +from azurelinuxagent.distro.default.distro import DefaultDistro +from azurelinuxagent.distro.debian.osutil import DebianOSUtil -def get_osutil(): - from azurelinuxagent.distro.coreos.osutil import CoreOSUtil - return CoreOSUtil() - -def get_handlers(): - from azurelinuxagent.distro.coreos.handlerFactory import CoreOSHandlerFactory - return CoreOSHandlerFactory() +class DebianDistro(DefaultDistro): + def __init__(self): + super(DebianDistro, self).__init__() + self.osutil = DebianOSUtil() diff --git a/azurelinuxagent/distro/default/daemon.py b/azurelinuxagent/distro/default/daemon.py new file mode 100644 index 0000000..cf9eb16 --- /dev/null +++ b/azurelinuxagent/distro/default/daemon.py @@ -0,0 +1,103 @@ +# 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) + diff --git a/azurelinuxagent/distro/default/deprovision.py b/azurelinuxagent/distro/default/deprovision.py index b62c5f6..4db4cdc 100644 --- a/azurelinuxagent/distro/default/deprovision.py +++ b/azurelinuxagent/distro/default/deprovision.py @@ -18,10 +18,8 @@ # import azurelinuxagent.conf as conf -from azurelinuxagent.utils.osutil import OSUTIL +from azurelinuxagent.exception import ProtocolError from azurelinuxagent.future import read_input -import azurelinuxagent.protocol as prot -import azurelinuxagent.protocol.ovfenv as ovf import azurelinuxagent.utils.fileutil as fileutil import azurelinuxagent.utils.shellutil as shellutil @@ -35,18 +33,20 @@ class DeprovisionAction(object): 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(OSUTIL.del_root_password)) + actions.append(DeprovisionAction(self.distro.osutil.del_root_password)) def del_user(self, warnings, actions): try: - ovfenv = ovf.get_ovf_env() - except prot.ProtocolError: + 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 @@ -54,7 +54,8 @@ class DeprovisionHandler(object): username = ovfenv.username warnings.append(("WARNING! {0} account and entire home directory " "will be deleted.").format(username)) - actions.append(DeprovisionAction(OSUTIL.del_account, [username])) + actions.append(DeprovisionAction(self.distro.osutil.del_account, + [username])) def regen_ssh_host_key(self, warnings, actions): @@ -64,7 +65,7 @@ class DeprovisionHandler(object): def stop_agent_service(self, warnings, actions): warnings.append("WARNING! The waagent service will be stopped.") - actions.append(DeprovisionAction(OSUTIL.stop_agent_service)) + 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'] @@ -76,26 +77,28 @@ class DeprovisionHandler(object): actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del)) def del_lib_dir(self, warnings, actions): - dirs_to_del = [OSUTIL.get_lib_dir()] + 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(OSUTIL.set_hostname, localhost)) - actions.append(DeprovisionAction(OSUTIL.set_dhcp_hostname, localhost)) + 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_switch("Provisioning.RegenerateSshHostkey", False): + 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_switch("Provisioning.DeleteRootPassword", False): + if conf.get_delete_root_password(): self.del_root_password(warnings, actions) self.del_lib_dir(warnings, actions) @@ -106,7 +109,7 @@ class DeprovisionHandler(object): return warnings, actions - def deprovision(self, force=False, deluser=False): + def run(self, force=False, deluser=False): warnings, actions = self.setup(deluser) for warning in warnings: print(warning) diff --git a/azurelinuxagent/distro/default/dhcp.py b/azurelinuxagent/distro/default/dhcp.py index 4fd23ef..fc439d2 100644 --- a/azurelinuxagent/distro/default/dhcp.py +++ b/azurelinuxagent/distro/default/dhcp.py @@ -19,61 +19,106 @@ import os import socket import array import time +import threading import azurelinuxagent.logger as logger -from azurelinuxagent.utils.osutil import OSUTIL -from azurelinuxagent.exception import AgentNetworkError +import azurelinuxagent.conf as conf import azurelinuxagent.utils.fileutil as fileutil import azurelinuxagent.utils.shellutil as shellutil -from azurelinuxagent.utils.textutil import * +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 -WIRE_SERVER_ADDR_FILE_NAME="WireServer" class DhcpHandler(object): - def __init__(self): + """ + 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): - ipv4 = OSUTIL.get_ip4_addr() + """ + 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) - OSUTIL.start_network() - ipv4 = OSUTIL.get_ip4_addr() - - def probe(self): - logger.info("Send dhcp request") - self.wait_for_network() - mac_addr = OSUTIL.get_mac_addr() - req = build_dhcp_request(mac_addr) - resp = send_dhcp_request(req) - if resp is None: - logger.warn("Failed to detect wire server.") - return - endpoint, gateway, routes = parse_dhcp_resp(resp) - self.endpoint = endpoint - logger.info("Wire server endpoint:{0}", endpoint) - logger.info("Gateway:{0}", gateway) - logger.info("Routes:{0}", routes) - if endpoint is not None: - path = os.path.join(OSUTIL.get_lib_dir(), WIRE_SERVER_ADDR_FILE_NAME) - fileutil.write_file(path, endpoint) - self.gateway = gateway - self.routes = routes - self.conf_routes() - - def get_endpoint(self): - return self.endpoint + 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: - OSUTIL.route_add(0 , 0, self.gateway) + self.distro.osutil.route_add(0 , 0, self.gateway) if self.routes is not None: for route in self.routes: - OSUTIL.route_add(route[0], route[1], route[2]) + 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) @@ -92,28 +137,25 @@ def validate_dhcp_resp(request, response): logger.verb("Cookie not match:\nsend={0},\nreceive={1}", hex_dump3(request, 0xEC, 4), hex_dump3(response, 0xEC, 4)) - raise AgentNetworkError("Cookie in dhcp respones " - "doesn't match the request") + 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 AgentNetworkError("TransactionID in dhcp respones " - "doesn't match the request") + 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 AgentNetworkError("Mac Addr in dhcp respones " - "doesn't match the request") + 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)) + 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) @@ -169,9 +211,7 @@ def parse_dhcp_resp(response): 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)) + hex(option), hex(i), hex(length)) if option == 255: logger.verb("DHCP packet ended at offset:{0}", hex(i)) break @@ -179,69 +219,17 @@ def parse_dhcp_resp(response): 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)) + 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)) + 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)) + hex(option), hex(i), hex(length)) i += length + 2 return endpoint, gateway, routes - -def allow_dhcp_broadcast(func): - """ - Temporary allow broadcase for dhcp. Remove the route when done. - """ - def wrapper(*args, **kwargs): - missing_default_route = OSUTIL.is_missing_default_route() - ifname = OSUTIL.get_if_name() - if missing_default_route: - OSUTIL.set_route_for_dhcp_broadcast(ifname) - result = func(*args, **kwargs) - if missing_default_route: - OSUTIL.remove_route_for_dhcp_broadcast(ifname) - return result - return wrapper - -def disable_dhcp_service(func): - """ - In some distros, dhcp service needs to be shutdown before agent probe - endpoint through dhcp. - """ - def wrapper(*args, **kwargs): - if OSUTIL.is_dhcp_enabled(): - OSUTIL.stop_dhcp_service() - result = func(*args, **kwargs) - OSUTIL.start_dhcp_service() - return result - else: - return func(*args, **kwargs) - return wrapper - - -@allow_dhcp_broadcast -@disable_dhcp_service -def send_dhcp_request(request): - __waiting_duration__ = [0, 10, 30, 60, 60] - for duration in __waiting_duration__: - try: - OSUTIL.allow_dhcp_broadcast() - response = socket_send(request) - validate_dhcp_resp(request, response) - return response - except AgentNetworkError as e: - logger.warn("Failed to send DHCP request: {0}", e) - time.sleep(duration) - return None - def socket_send(request): sock = None try: @@ -257,7 +245,7 @@ def socket_send(request): response = sock.recv(1024) return response except IOError as e: - raise AgentNetworkError("{0}".format(e)) + raise DhcpError("{0}".format(e)) finally: if sock is not None: sock.close() diff --git a/azurelinuxagent/distro/default/distro.py b/azurelinuxagent/distro/default/distro.py new file mode 100644 index 0000000..ca0d77e --- /dev/null +++ b/azurelinuxagent/distro/default/distro.py @@ -0,0 +1,51 @@ +# 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) + diff --git a/azurelinuxagent/distro/default/env.py b/azurelinuxagent/distro/default/env.py index 28bf718..7878cff 100644 --- a/azurelinuxagent/distro/default/env.py +++ b/azurelinuxagent/distro/default/env.py @@ -23,7 +23,6 @@ import threading import time import azurelinuxagent.logger as logger import azurelinuxagent.conf as conf -from azurelinuxagent.utils.osutil import OSUTIL class EnvHandler(object): """ @@ -31,35 +30,25 @@ class EnvHandler(object): If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric. Monitor scsi disk. - If new scsi disk found, set + If new scsi disk found, set timeout """ - def __init__(self, handlers): - self.monitor = EnvMonitor(handlers.dhcp_handler) - - def start(self): - self.monitor.start() - - def stop(self): - self.monitor.stop() - -class EnvMonitor(object): - - def __init__(self, dhcp_handler): - self.dhcp_handler = dhcp_handler + def __init__(self, distro): + self.distro = distro self.stopped = True self.hostname = None self.dhcpid = None self.server_thread=None - def start(self): + 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 = OSUTIL.get_dhcp_pid() + 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() @@ -70,11 +59,11 @@ class EnvMonitor(object): If dhcp clinet process re-start has occurred, reset routes. """ while not self.stopped: - OSUTIL.remove_rules_files() - timeout = conf.get("OS.RootDeviceScsiTimeout", None) + self.distro.osutil.remove_rules_files() + timeout = conf.get_root_device_scsi_timeout() if timeout is not None: - OSUTIL.set_scsi_disks_timeout(timeout) - if conf.get_switch("Provisioning.MonitorHostName", False): + self.distro.osutil.set_scsi_disks_timeout(timeout) + if conf.get_monitor_hostname(): self.handle_hostname_update() self.handle_dhclient_restart() time.sleep(5) @@ -84,25 +73,25 @@ class EnvMonitor(object): if curr_hostname != self.hostname: logger.info("EnvMonitor: Detected host name change: {0} -> {1}", self.hostname, curr_hostname) - OSUTIL.set_hostname(curr_hostname) - OSUTIL.publish_hostname(curr_hostname) + self.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 = OSUTIL.get_dhcp_pid() + 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 = OSUTIL.get_dhcp_pid() + 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.dhcp_handler.conf_routes() + self.distro.dhcp_handler.conf_routes() self.dhcpid = newpid def stop(self): diff --git a/azurelinuxagent/distro/default/extension.py b/azurelinuxagent/distro/default/extension.py index f6c02aa..82cdfed 100644 --- a/azurelinuxagent/distro/default/extension.py +++ b/azurelinuxagent/distro/default/extension.py @@ -22,13 +22,16 @@ import time import json import subprocess import shutil +import azurelinuxagent.conf as conf import azurelinuxagent.logger as logger -from azurelinuxagent.future import text -from azurelinuxagent.utils.osutil import OSUTIL -import azurelinuxagent.protocol as prot -from azurelinuxagent.metadata import AGENT_VERSION from azurelinuxagent.event import add_event, WALAEventOperation -from azurelinuxagent.exception import ExtensionError +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 @@ -41,15 +44,6 @@ VALID_EXTENSION_STATUS = ['transitioning', 'error', 'success', 'warning'] VALID_HANDLER_STATUS = ['Ready', 'NotReady', "Installing", "Unresponsive"] -def handler_state_to_status(handler_state): - if handler_state == "Enabled": - return "Ready" - elif handler_state in VALID_HANDLER_STATUS: - return handler_state - else: - return "NotReady" - - def validate_has_key(obj, key, fullname): if key not in obj: raise ExtensionError("Missing: {0}".format(fullname)) @@ -64,14 +58,13 @@ def parse_formatted_message(formatted_message): 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 = prot.ExtensionSubStatus() + status = ExtensionSubStatus() status.name = substatus.get('name') status.status = substatus.get('status') status.code = substatus.get('code', 0) @@ -105,333 +98,330 @@ def parse_ext_status(ext_status, data): for substatus in substatus_list: ext_status.substatusList.append(parse_ext_substatus(substatus)) -def parse_extension_dirname(dirname): - """ - Parse installed extension dir name. Sample: ExtensionName-Version/ - """ - seprator = dirname.rfind('-') - if seprator < 0: - raise ExtensionError("Invalid extenation dir name") - return dirname[0:seprator], dirname[seprator + 1:] - -def get_installed_version(target_name): - """ - Return the highest version instance with the same name - """ - installed_version = None - lib_dir = OSUTIL.get_lib_dir() - for dir_name in os.listdir(lib_dir): - path = os.path.join(lib_dir, dir_name) - if os.path.isdir(path) and dir_name.startswith(target_name): - name, version = parse_extension_dirname(dir_name) - #Here we need to ensure names are exactly the same. - if name == target_name: - if installed_version is None or \ - Version(installed_version) < Version(version): - installed_version = version - return installed_version - class ExtHandlerState(object): + NotInstalled = "NotInstalled" + Installed = "Installed" Enabled = "Enabled" - Disabled = "Disabled" - Failed = "Failed" - class ExtHandlersHandler(object): - - def process(self): + 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: - protocol = prot.FACTORY.get_default_protocol() - ext_handlers = protocol.get_ext_handlers() - except prot.ProtocolError as e: - add_event(name="WALA", is_success=False, message = text(e)) + 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 - - vm_status = prot.VMStatus() + 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.extHandlers is None or \ - len(ext_handlers.extHandlers) == 0: - logger.verb("No extensions to handle") - else: + if ext_handlers is not None: for ext_handler in ext_handlers.extHandlers: - #TODO handle extension in parallel try: - pkg_list = protocol.get_ext_handler_pkgs(ext_handler) - except prot.ProtocolError as e: - add_event(name="WALA", is_success=False, message=text(e)) - continue - - handler_status = self.process_extension(ext_handler, pkg_list) - if handler_status is not None: - vm_status.vmAgent.extensionHandlers.append(handler_status) - + 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: - logger.verb("Report vm agent status") - protocol.report_vm_status(vm_status) - except prot.ProtocolError as e: - add_event(name="WALA", is_success=False, message = text(e)) - - def process_extension(self, ext_handler, pkg_list): - installed_version = get_installed_version(ext_handler.name) - if installed_version is not None: - handler = ExtHandlerInstance(ext_handler, pkg_list, - installed_version, installed=True) - else: - handler = ExtHandlerInstance(ext_handler, pkg_list, - ext_handler.properties.version) - handler.handle() + 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) - if handler.ext_status is not None: + 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: - protocol = prot.FACTORY.get_default_protocol() - protocol.report_ext_status(handler.name, handler.ext.name, - handler.ext_status) - except prot.ProtocolError as e: - add_event(name="WALA", is_success=False, message=text(e)) - - return handler.handler_status + 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, pkg_list, curr_version, installed=False): + def __init__(self, ext_handler, protocol): self.ext_handler = ext_handler - self.name = ext_handler.name - self.version = ext_handler.properties.version - self.pkg_list = pkg_list - self.state = ext_handler.properties.state - self.update_policy = ext_handler.properties.upgradePolicy - - self.curr_version = curr_version - self.installed = installed - self.handler_state = None - self.lib_dir = OSUTIL.get_lib_dir() - - self.ext_status = prot.ExtensionStatus() - self.handler_status = prot.ExtHandlerStatus() - self.handler_status.name = self.name - self.handler_status.version = self.curr_version - - #Currently, extension settings will have no more than 1 instance - if len(ext_handler.properties.extensions) > 0: - self.ext = ext_handler.properties.extensions[0] - self.handler_status.extensions = [self.ext.name] - else: - #When no extension settings, set sequenceNumber to 0 - self.ext = prot.Extension(sequenceNumber=0) - self.ext_status.sequenceNumber = self.ext.sequenceNumber + 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) - def init_logger(self): - #Init logger appender for extension - fileutil.mkdir(self.get_log_dir(), mode=0o644) log_file = os.path.join(self.get_log_dir(), "CommandExecution.log") self.logger.add_appender(logger.AppenderType.FILE, logger.LogLevel.INFO, log_file) - def handle(self): - self.init_logger() - self.logger.verb("Start processing extension handler") - - try: - self.handle_state() - except ExtensionError as e: - self.set_state_err(text(e)) - self.report_event(is_success=False, message=text(e)) - self.logger.error("Failed to process extension handler") - return - - try: - if self.installed: - self.collect_ext_status() - self.collect_handler_status() - except ExtensionError as e: - self.report_event(is_success=False, message=text(e)) - self.logger.error("Failed to get extension handler status") - return - - self.logger.verb("Finished processing extension handler") - - def handle_state(self): - if self.installed: - self.handler_state = self.get_state() - - self.handler_status.status = handler_state_to_status(self.handler_state) - self.logger.verb("Handler state: {0}", self.handler_state) - self.logger.verb("Sequence number: {0}", self.ext.sequenceNumber) - - if self.state == 'enabled': - if self.handler_state == ExtHandlerState.Failed: - self.logger.verb("Found previous failure, quit handle_enable") - return - - if self.handler_state == ExtHandlerState.Enabled: - self.logger.verb("Already enabled with sequenceNumber: {0}", - self.ext.sequenceNumber) - self.logger.verb("Quit handle_enable") - return + def decide_version(self): + """ + If auto-upgrade, get the largest public extension version under + the requested major version family of currently installed plugin version - try: - new = self.handle_enable() - if new is not None: - #Upgrade happened - new.set_state(ExtHandlerState.Enabled) - else: - self.set_state(ExtHandlerState.Enabled) + 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) - except ExtensionError as e: - self.set_state(ExtHandlerState.Failed) - raise e - elif self.state == 'disabled': - if self.handler_state == ExtHandlerState.Failed: - self.logger.verb("Found previous failure, quit handle_disable") - return - - if self.handler_state == ExtHandlerState.Disabled: - self.logger.verb("Already disabled with sequenceNumber: {0}", - self.ext.sequenceNumber) - self.logger.verb("Quit handle_disable") - return + 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)) - try: - self.handle_disable() - self.set_state(ExtHandlerState.Disabled) - except ExtensionError as e: - self.set_state(ExtHandlerState.Failed) - raise e - elif self.state == 'uninstall': - try: - self.handle_uninstall() - except ExtensionError as e: - self.set_state(ExtHandlerState.Failed) - raise e + version_prefix = None + if update_policy is not None and update_policy == 'auto': + version_prefix = "{0}.".format(version_frag[0]) else: - raise ExtensionError("Unknown state:{0}".format(self.state)) - - def handle_enable(self): - target_version = self.get_target_version() - self.logger.info("Target version: {0}", target_version) - if self.installed: - if Version(target_version) > Version(self.curr_version): - return self.upgrade(target_version) - elif Version(target_version) == Version(self.curr_version): - self.enable() - else: - raise ExtensionError("A newer version is already installed") - else: - if Version(target_version) > Version(self.version): - #This will happen when auto upgrade policy is enabled - self.logger.info("Auto upgrade to new version:{0}", - target_version) - self.curr_version = target_version - self.download() - self.init_dir() - self.install() - self.enable() + 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) - def handle_disable(self): - if not self.installed: - self.logger.verb("Not installed, quit disable") - return + 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 - self.disable() + 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 handle_uninstall(self): - if not self.installed: - self.logger.verb("Not installed, quit unistall") - self.handler_status = None - self.ext_status = None - return - self.disable() - self.uninstall() - - def report_event(self, is_success=True, message=""): - if self.ext_status is not None: - if not is_success: - self.ext_status.status = "error" - self.ext_status.code = -1 - if self.handler_status is not None: - self.handler_status.message = message - if not is_success: - self.handler_status.status = "NotReady" - add_event(name=self.name, op=self.ext_status.operation, - is_success=is_success, message=message) - - def set_operation(self, operation): - if self.ext_status.operation != WALAEventOperation.Upgrade: - self.ext_status.operation = operation - - def upgrade(self, target_version): - self.logger.info("Upgrade from: {0} to {1}", self.curr_version, - target_version) - self.set_operation(WALAEventOperation.Upgrade) - - old = self - new = ExtHandlerInstance(self.ext_handler, self.pkg_list, - target_version) - self.logger.info("Download new extension package") - new.init_logger() - new.download() - self.logger.info("Initialize new extension directory") - new.init_dir() - - old.disable() - self.logger.info("Update new extension") - new.update() - old.uninstall() - man = new.load_manifest() - if man.is_update_with_install(): - self.logger.info("Install new extension") - new.install() - self.logger.info("Enable new extension") - new.enable() - return new + 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) - - uris = self.get_package_uris() + if self.pkg is None: + raise ExtensionError("No package uri found") + package = None - for uri in uris: + for uri in self.pkg.uris: try: - resp = restutil.http_get(uri.uri, chk_proxy=True) - if resp.status == restutil.httpclient.OK: - package = resp.read() - break - except restutil.HttpError as e: - self.logger.warn("Failed download extension from: {0}", uri.uri) - + 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("Download extension failed") + raise ExtensionError("Failed to download extension") self.logger.info("Unpack extension package") - pkg_file = os.path.join(self.lib_dir, os.path.basename(uri.uri) + ".zip") - fileutil.write_file(pkg_file, bytearray(package), asbin=True) - zipfile.ZipFile(pkg_file).extractall(self.get_base_dir()) + 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") - def init_dir(self): self.logger.info("Initialize extension directory") #Save HandlerManifest.json man_file = fileutil.search_file(self.get_base_dir(), 'HandlerManifest.json') - man = fileutil.read_file(man_file, remove_bom=True) - fileutil.write_file(self.get_manifest_file(), man) - #Create status and config dir - status_dir = self.get_status_dir() - fileutil.mkdir(status_dir, mode=0o700) - conf_dir = self.get_conf_dir() - fileutil.mkdir(conf_dir, mode=0o700) + if man_file is None: + raise ExtensionError("HandlerManifest.json not found") - self.make_handler_state_dir() + 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() @@ -442,6 +432,8 @@ class ExtHandlerInstance(object): 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.") @@ -449,6 +441,8 @@ class ExtHandlerInstance(object): 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.") @@ -456,24 +450,31 @@ class ExtHandlerInstance(object): man = self.load_manifest() self.launch_command(man.get_install_command(), timeout=900) - self.installed = True + self.set_handler_state(ExtHandlerState.Installed) def uninstall(self): self.logger.info("Uninstall extension.") self.set_operation(WALAEventOperation.UnInstall) - man = self.load_manifest() - self.launch_command(man.get_uninstall_command()) - - self.logger.info("Remove ext handler dir: {0}", self.get_base_dir()) try: - shutil.rmtree(self.get_base_dir()) + 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: - raise ExtensionError("Failed to rm ext handler dir: {0}".format(e)) - - self.installed = False - self.handler_status = None - self.ext_status = None + 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.") @@ -481,95 +482,82 @@ class ExtHandlerInstance(object): man = self.load_manifest() self.launch_command(man.get_update_command(), timeout=900) - - def collect_handler_status(self): - self.logger.verb("Collect extension handler status") - if self.handler_status is None: - return - - handler_state = self.get_state() - self.handler_status.status = handler_state_to_status(handler_state) - self.handler_status.message = self.get_state_err() + + def update_with_install(self): man = self.load_manifest() - if man.is_report_heartbeat(): - heartbeat = self.collect_heartbeat() - if heartbeat is not None: - self.handler_status.status = heartbeat['status'] + 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 collect_ext_status(self): + 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") - if self.handler_status is None: - return - if self.ext is None: - return + 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_file = self.get_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(self.ext_status, data) + parse_ext_status(ext_status, data) except IOError as e: - raise ExtensionError("Failed to get status file: {0}".format(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: - raise ExtensionError("Malformed status file: {0}".format(e)) - - def make_handler_state_dir(self): - handler_state_dir = self.get_handler_state_dir() - fileutil.mkdir(handler_state_dir, 0o600) - if not os.path.exists(handler_state_dir): - os.makedirs(handler_state_dir) - - def get_state(self): - handler_state_file = self.get_handler_state_file() - if not os.path.isfile(handler_state_file): - return None - try: - handler_state = fileutil.read_file(handler_state_file) - if handler_state is not None: - handler_state = handler_state.rstrip() - return handler_state - except IOError as e: - err = "Failed to get handler state: {0}".format(e) - add_event(name=self.name, is_success=False, message=err) - - def set_state(self, state): - handler_state_file = self.get_handler_state_file() - if not os.path.isfile(handler_state_file): - self.make_handler_state_dir() - try: - fileutil.write_file(handler_state_file, state) - except IOError as e: - err = "Failed to set handler state: {0}".format(e) - add_event(name=self.name, is_success=False, message=err) - - def get_state_err(self): - """Get handler error message""" - handler_state_err_file= self.get_handler_state_err_file() - if not os.path.isfile(handler_state_err_file): - return None - try: - message = fileutil.read_file(handler_state_err_file) - return message - except IOError as e: - err = "Failed to get handler state message: {0}".format(e) - add_event(name=self.name, is_success=False, message=err) - - def set_state_err(self, message): - """Set handler error message""" - handler_state_err_file = self.get_handler_state_err_file() - if not os.path.isfile(handler_state_err_file): - self.make_handler_state_dir() - try: - fileutil.write_file(handler_state_err_file, message) - except IOError as e: - err = "Failed to set handler state message: {0}".format(e) - add_event(name=self.name, is_success=False, message=err) + 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): - self.logger.info("Collect heart beat") - heartbeat_file = os.path.join(OSUTIL.get_lib_dir(), + 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): @@ -586,15 +574,14 @@ class ExtHandlerInstance(object): except ValueError as e: raise ExtensionError("Malformed heartbeat file: {0}".format(e)) return heartbeat - + def is_responsive(self, heartbeat_file): last_update=int(time.time() - os.stat(heartbeat_file).st_mtime) return last_update > 600 # not updated for more than 10 min - + def launch_command(self, cmd, timeout=300): self.logger.info("Launch command:{0}", cmd) base_dir = self.get_base_dir() - self.update_settings() try: devnull = open(os.devnull, 'w') child = subprocess.Popen(base_dir + "/" + cmd, shell=True, @@ -614,6 +601,7 @@ class ExtHandlerInstance(object): 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): @@ -627,26 +615,40 @@ class ExtHandlerInstance(object): 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 is None: - self.logger.verb("Extension has no settings") + 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 - - settings = { - 'publicSettings': self.ext.publicSettings, - 'protectedSettings': self.ext.privateSettings, - 'protectedSettingsCertThumbprint': self.ext.certificateThumbprint - } - ext_settings = { - "runtimeSettings":[{ - "handlerSettings": settings - }] - } - fileutil.write_file(self.get_settings_file(), json.dumps(ext_settings)) + + 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.name, + "name": self.ext_handler.name, "version" : HANDLER_ENVIRONMENT_VERSION, "handlerEnvironment" : { "logFolder" : self.get_log_dir(), @@ -655,73 +657,91 @@ class ExtHandlerInstance(object): "heartbeatFile" : self.get_heartbeat_file() } }] - fileutil.write_file(self.get_env_file(), - json.dumps(env)) - - def get_target_version(self): - version = self.version - update_policy = self.update_policy - if update_policy is None or update_policy.lower() != 'auto': - return version - - major = version.split('.')[0] - if major is None: - raise ExtensionError("Wrong version format: {0}".format(version)) - - packages = [x for x in self.pkg_list.versions \ - if x.version.startswith(major + ".")] - packages = sorted(packages, key=lambda x: Version(x.version), - reverse=True) - if len(packages) <= 0: - raise ExtensionError("Can't find version: {0}.*".format(major)) + 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()) - return packages[0].version + 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 - def get_package_uris(self): - version = self.curr_version - packages = self.pkg_list.versions - if packages is None: - raise ExtensionError("Package uris is None.") + 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") - for package in packages: - if Version(package.version) == Version(version): - return package.uris + 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) - raise ExtensionError("Can't get package uris for {0}.".format(version)) - def get_full_name(self): - return "{0}-{1}".format(self.name, self.curr_version) - + return "{0}-{1}".format(self.ext_handler.name, + self.ext_handler.properties.version) + def get_base_dir(self): - return os.path.join(OSUTIL.get_lib_dir(), self.get_full_name()) + 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_status_file(self): - return os.path.join(self.get_status_dir(), - "{0}.status".format(self.ext.sequenceNumber)) - def get_conf_dir(self): return os.path.join(self.get_base_dir(), 'config') - def get_settings_file(self): - return os.path.join(self.get_conf_dir(), - "{0}.settings".format(self.ext.sequenceNumber)) - - def get_handler_state_dir(self): - return os.path.join(OSUTIL.get_lib_dir(), "handler_state", - self.get_full_name()) - - def get_handler_state_file(self): - return os.path.join(self.get_handler_state_dir(), - '{0}.state'.format(self.ext.sequenceNumber)) - - def get_handler_state_err_file(self): - return os.path.join(self.get_handler_state_dir(), - '{0}.error'.format(self.ext.sequenceNumber)) - - def get_heartbeat_file(self): return os.path.join(self.get_base_dir(), 'heartbeat.log') @@ -732,8 +752,8 @@ class ExtHandlerInstance(object): return os.path.join(self.get_base_dir(), 'HandlerEnvironment.json') def get_log_dir(self): - return os.path.join(OSUTIL.get_ext_log_dir(), self.name, - self.curr_version) + 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): @@ -782,19 +802,16 @@ class HandlerManifest(object): return self.data['handlerManifest']["disableCommand"] def is_reboot_after_install(self): - #TODO handle reboot after install - if "rebootAfterInstall" not in self.data['handlerManifest']: - return False - return self.data['handlerManifest']["rebootAfterInstall"] + """ + Deprecated + """ + return False def is_report_heartbeat(self): - if "reportHeartbeat" not in self.data['handlerManifest']: - return False - return self.data['handlerManifest']["reportHeartbeat"] + return self.data['handlerManifest'].get('reportHeartbeat', False) def is_update_with_install(self): - if "updateMode" not in self.data['handlerManifest']: - return False - if "updateMode" in self.data: - return self.data['handlerManifest']["updateMode"].lower() == "updatewithinstall" - return False + update_mode = self.data['handlerManifest'].get('updateMode') + if update_mode is None: + return True + return update_mode.low() == "updatewithinstall" diff --git a/azurelinuxagent/distro/default/handlerFactory.py b/azurelinuxagent/distro/default/handlerFactory.py deleted file mode 100644 index dceb2a3..0000000 --- a/azurelinuxagent/distro/default/handlerFactory.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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 .init import InitHandler -from .run import MainHandler -from .scvmm import ScvmmHandler -from .dhcp import DhcpHandler -from .env import EnvHandler -from .provision import ProvisionHandler -from .resourceDisk import ResourceDiskHandler -from .extension import ExtHandlersHandler -from .deprovision import DeprovisionHandler - -class DefaultHandlerFactory(object): - def __init__(self): - self.init_handler = InitHandler() - self.main_handler = MainHandler(self) - self.scvmm_handler = ScvmmHandler() - self.dhcp_handler = DhcpHandler() - self.env_handler = EnvHandler(self) - self.provision_handler = ProvisionHandler() - self.resource_disk_handler = ResourceDiskHandler() - self.ext_handlers_handler = ExtHandlersHandler() - self.deprovision_handler = DeprovisionHandler() - diff --git a/azurelinuxagent/distro/default/init.py b/azurelinuxagent/distro/default/init.py index db74fef..c703e87 100644 --- a/azurelinuxagent/distro/default/init.py +++ b/azurelinuxagent/distro/default/init.py @@ -20,30 +20,34 @@ import os import azurelinuxagent.conf as conf import azurelinuxagent.logger as logger -from azurelinuxagent.utils.osutil import OSUTIL -import azurelinuxagent.utils.fileutil as fileutil +import azurelinuxagent.event as event class InitHandler(object): - def init(self, verbose): + 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 = OSUTIL.get_conf_file_path() - conf.load_conf(conf_file_path) + 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_switch("Logs.Verbose", False) + 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") - #Create lib dir - fileutil.mkdir(OSUTIL.get_lib_dir(), mode=0o700) - os.chdir(OSUTIL.get_lib_dir()) + #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") + diff --git a/azurelinuxagent/distro/default/monitor.py b/azurelinuxagent/distro/default/monitor.py new file mode 100644 index 0000000..3b26c9a --- /dev/null +++ b/azurelinuxagent/distro/default/monitor.py @@ -0,0 +1,182 @@ +# 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) diff --git a/azurelinuxagent/distro/default/osutil.py b/azurelinuxagent/distro/default/osutil.py index 00a57cc..18ab2ba 100644 --- a/azurelinuxagent/distro/default/osutil.py +++ b/azurelinuxagent/distro/default/osutil.py @@ -25,11 +25,15 @@ import struct import time import pwd import fcntl +import base64 import azurelinuxagent.logger as logger -from azurelinuxagent.future import text +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" ] @@ -40,44 +44,14 @@ for all distros. Each concrete distro classes could overwrite default behavior if needed. """ -class OSUtilError(Exception): - pass - class DefaultOSUtil(object): def __init__(self): - self.lib_dir = "/var/lib/waagent" - self.ext_log_dir = "/var/log/azure" - self.dvd_mount_point = "/mnt/cdrom/secure" - self.ovf_env_file_path = "/mnt/cdrom/secure/ovf-env.xml" - self.agent_pid_file_path = "/var/run/waagent.pid" - self.passwd_file_path = "/etc/shadow" - self.home = '/home' - self.sshd_conf_file_path = '/etc/ssh/sshd_config' - self.openssl_cmd = '/usr/bin/openssl' - self.conf_file_path = '/etc/waagent.conf' + self.agent_conf_file_path = '/etc/waagent.conf' self.selinux=None - def get_lib_dir(self): - return self.lib_dir - - def get_ext_log_dir(self): - return self.ext_log_dir - - def get_dvd_mount_point(self): - return self.dvd_mount_point - - def get_conf_file_path(self): - return self.conf_file_path - - def get_ovf_env_file_path_on_dvd(self): - return self.ovf_env_file_path - - def get_agent_pid_file_path(self): - return self.agent_pid_file_path - - def get_openssl_cmd(self): - return self.openssl_cmd + def get_agent_conf_file_path(self): + return self.agent_conf_file_path def get_userentry(self, username): try: @@ -86,6 +60,14 @@ class DefaultOSUtil(object): 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: @@ -104,9 +86,13 @@ class DefaultOSUtil(object): def useradd(self, username, expiration=None): """ - Update password and ssh key for user account. - New account will be created if not exists. + 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: @@ -146,42 +132,21 @@ class DefaultOSUtil(object): def del_root_password(self): try: - passwd_content = fileutil.read_file(self.passwd_file_path) + 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(self.passwd_file_path, "\n".join(new_passwd)) + 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 get_home(self): - return self.home - - def get_pubkey_from_prv(self, file_name): - cmd = "{0} rsa -in {1} -pubout 2>/dev/null".format(self.openssl_cmd, - file_name) - pub = shellutil.run_get_output(cmd)[1] - return pub - - def get_pubkey_from_crt(self, file_name): - cmd = "{0} x509 -in {1} -pubkey -noout".format(self.openssl_cmd, - file_name) - pub = shellutil.run_get_output(cmd)[1] - return pub - def _norm_path(self, filepath): - home = self.get_home() + home = conf.get_home_dir() # Expand HOME variable if present in path path = os.path.normpath(filepath.replace("$HOME", home)) return path - def get_thumbprint_from_crt(self, file_name): - cmd="{0} x509 -in {1} -fingerprint -noout".format(self.openssl_cmd, - file_name) - thumbprint = shellutil.run_get_output(cmd)[1] - thumbprint = thumbprint.rstrip().split('=')[1].replace(':', '').upper() - return thumbprint - def deploy_ssh_keypair(self, username, keypair): """ Deploy id_rsa and id_rsa.pub @@ -190,13 +155,14 @@ class DefaultOSUtil(object): path = self._norm_path(path) dir_path = os.path.dirname(path) fileutil.mkdir(dir_path, mode=0o700, owner=username) - lib_dir = self.get_lib_dir() + 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' - pub = self.get_pubkey_from_prv(prv_path) + 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') @@ -204,8 +170,8 @@ class DefaultOSUtil(object): os.chmod(pub_path, 0o600) def openssl_to_openssh(self, input_file, output_file): - shellutil.run("ssh-keygen -i -m PKCS8 -f {0} >> {1}".format(input_file, - output_file)) + cryptutil = CryptUtil(conf.get_openssl_cmd()) + cryptutil.crt_to_ssh(input_file, output_file) def deploy_ssh_pubkey(self, username, pubkey): """ @@ -215,6 +181,8 @@ class DefaultOSUtil(object): 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) @@ -223,12 +191,12 @@ class DefaultOSUtil(object): raise OSUtilError("Bad public key: {0}".format(value)) fileutil.write_file(path, value) elif thumbprint is not None: - lib_dir = self.get_lib_dir() + 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 = self.get_pubkey_from_crt(crt_path) + 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') @@ -280,23 +248,21 @@ class DefaultOSUtil(object): if self.is_selinux_system(): return shellutil.run('chcon ' + con + ' ' + path) - def get_sshd_conf_file_path(self): - return self.sshd_conf_file_path - def set_ssh_client_alive_interval(self): - conf_file_path = self.get_sshd_conf_file_path() - conf = fileutil.read_file(conf_file_path).split("\n") - textutil.set_ssh_config(conf, "ClientAliveInterval", "180") - fileutil.write_file(conf_file_path, '\n'.join(conf)) + 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 = self.get_sshd_conf_file_path() - conf = fileutil.read_file(conf_file_path).split("\n") - textutil.set_ssh_config(conf, "PasswordAuthentication", option) - textutil.set_ssh_config(conf, "ChallengeResponseAuthentication", option) - fileutil.write_file(conf_file_path, "\n".join(conf)) + 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.") @@ -309,7 +275,7 @@ class DefaultOSUtil(object): def mount_dvd(self, max_retry=6, chk_err=True): dvd = self.get_dvd_device() - mount_point = self.get_dvd_mount_point() + 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 @@ -332,7 +298,7 @@ class DefaultOSUtil(object): raise OSUtilError("Failed to mount dvd.") def umount_dvd(self, chk_err=True): - mount_point = self.get_dvd_mount_point() + 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.") @@ -386,17 +352,9 @@ class DefaultOSUtil(object): shellutil.run("iptables -I INPUT -p udp --dport 68 -j ACCEPT", chk_err=False) - def gen_transport_cert(self): - """ - Create ssl certificate for https communication with endpoint server. - """ - cmd = ("{0} req -x509 -nodes -subj /CN=LinuxTransport -days 32768 " - "-newkey rsa:2048 -keyout TransportPrivate.pem " - "-out TransportCert.pem").format(self.openssl_cmd) - shellutil.run(cmd) def remove_rules_files(self, rules_files=__RULES_FILES__): - lib_dir = self.get_lib_dir() + 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) @@ -407,7 +365,7 @@ class DefaultOSUtil(object): shutil.move(src, dest) def restore_rules_files(self, rules_files=__RULES_FILES__): - lib_dir = self.get_lib_dir() + lib_dir = conf.get_lib_dir() for dest in rules_files: filename = fileutil.base_name(dest) src = os.path.join(lib_dir, filename) @@ -603,7 +561,7 @@ class DefaultOSUtil(object): for vmbus in os.listdir(path): deviceid = fileutil.read_file(os.path.join(path, vmbus, "device_id")) guid = deviceid.lstrip('{').split('-') - if guid[0] == g0 and guid[1] == "000" + text(port_id): + 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] @@ -633,7 +591,7 @@ class DefaultOSUtil(object): raise OSUtilError("Failed to remove sudoer: {0}".format(e)) def decode_customdata(self, data): - return data + return base64.b64decode(data) def get_total_mem(self): cmd = "grep MemTotal /proc/meminfo |awk '{print $2}'" @@ -649,4 +607,17 @@ class DefaultOSUtil(object): 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)) diff --git a/azurelinuxagent/distro/default/protocolUtil.py b/azurelinuxagent/distro/default/protocolUtil.py new file mode 100644 index 0000000..34466cf --- /dev/null +++ b/azurelinuxagent/distro/default/protocolUtil.py @@ -0,0 +1,243 @@ +# 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("<UserPassword>.*?<", "<UserPassword>*<", 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 + diff --git a/azurelinuxagent/distro/default/provision.py b/azurelinuxagent/distro/default/provision.py index 424f083..695b82a 100644 --- a/azurelinuxagent/distro/default/provision.py +++ b/azurelinuxagent/distro/default/provision.py @@ -21,13 +21,11 @@ Provision handler import os import azurelinuxagent.logger as logger -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr import azurelinuxagent.conf as conf from azurelinuxagent.event import add_event, WALAEventOperation -from azurelinuxagent.exception import * -from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError -import azurelinuxagent.protocol as prot -import azurelinuxagent.protocol.ovfenv as ovf +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 @@ -35,61 +33,49 @@ CUSTOM_DATA_FILE="CustomData" class ProvisionHandler(object): - def process(self): + def __init__(self, distro): + self.distro = distro + + def run(self): #If provision is not enabled, return - if not conf.get_switch("Provisioning.Enabled", True): + if not conf.get_provision_enabled(): logger.info("Provisioning is disabled. Skip.") - return + return - provisioned = os.path.join(OSUTIL.get_lib_dir(), "provisioned") + provisioned = os.path.join(conf.get_lib_dir(), "provisioned") if os.path.isfile(provisioned): return - logger.info("run provision handler.") - protocol = prot.FACTORY.get_default_protocol() + 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: - status = prot.ProvisionStatus(status="NotReady", - subStatus="Provisioning", - description="Starting") - try: - protocol.report_provision_status(status) - except prot.ProtocolError as e: - add_event(name="WALA", is_success=False, message=text(e), - op=WALAEventOperation.Provision) - - self.provision() + logger.info("Start provisioning") + self.provision(ovfenv) fileutil.write_file(provisioned, "") thumbprint = self.reg_ssh_host_key() - logger.info("Finished provisioning") - status = prot.ProvisionStatus(status="Ready") - status.properties.certificateThumbprint = thumbprint - - try: - protocol.report_provision_status(status) - except prot.ProtocolError as pe: - add_event(name="WALA", is_success=False, message=text(pe), - op=WALAEventOperation.Provision) - - add_event(name="WALA", is_success=True, message="", - op=WALAEventOperation.Provision) except ProvisionError as e: logger.error("Provision failed: {0}", e) - status = prot.ProvisionStatus(status="NotReady", - subStatus="ProvisioningFailed", - description= text(e)) - try: - protocol.report_provision_status(status) - except prot.ProtocolError as pe: - add_event(name="WALA", is_success=False, message=text(pe), - op=WALAEventOperation.Provision) - - add_event(name="WALA", is_success=False, message=text(e), - op=WALAEventOperation.Provision) + 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("Provisioning.SshHostKeyPairType", "rsa") - if conf.get_switch("Provisioning.RegenerateSshHostKeyPair"): + 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)) @@ -105,77 +91,101 @@ class ProvisionHandler(object): raise ProvisionError(("Failed to generate ssh host key: " "ret={0}, out= {1}").format(ret[0], ret[1])) - - def provision(self): - logger.info("Copy ovf-env.xml.") - try: - ovfenv = ovf.copy_ovf_env() - except prot.ProtocolError as e: - raise ProvisionError("Failed to copy ovf-env.xml: {0}".format(e)) - + def provision(self, ovfenv): logger.info("Handle ovf-env.xml.") try: logger.info("Set host name.") - OSUTIL.set_hostname(ovfenv.hostname) + self.distro.osutil.set_hostname(ovfenv.hostname) logger.info("Publish host name.") - OSUTIL.publish_hostname(ovfenv.hostname) + 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() - if conf.get_switch("Provisioning.DeleteRootPassword"): - OSUTIL.del_root_password() except OSUtilError as e: raise ProvisionError("Failed to handle ovf-env.xml: {0}".format(e)) def config_user_account(self, ovfenv): logger.info("Create user account if not exists") - OSUTIL.useradd(ovfenv.username) + self.distro.osutil.useradd(ovfenv.username) if ovfenv.user_password is not None: logger.info("Set user password.") - crypt_id = conf.get("Provision.PasswordCryptId", "6") - salt_len = conf.get_int("Provision.PasswordCryptSaltLength", 10) - OSUTIL.chpasswd(ovfenv.username, ovfenv.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") - OSUTIL.conf_sudoer(ovfenv.username, ovfenv.user_password is None) + self.distro.osutil.conf_sudoer(ovfenv.username, ovfenv.user_password is None) logger.info("Configure sshd") - OSUTIL.conf_sshd(ovfenv.disable_ssh_password_auth) + self.distro.osutil.conf_sshd(ovfenv.disable_ssh_password_auth) #Disable selinux temporary - sel = OSUTIL.is_selinux_enforcing() + sel = self.distro.osutil.is_selinux_enforcing() if sel: - OSUTIL.set_selinux_enforce(0) + self.distro.osutil.set_selinux_enforce(0) self.deploy_ssh_pubkeys(ovfenv) self.deploy_ssh_keypairs(ovfenv) if sel: - OSUTIL.set_selinux_enforce(1) + self.distro.osutil.set_selinux_enforce(1) - OSUTIL.restart_ssh_service() + self.distro.osutil.restart_ssh_service() def save_customdata(self, ovfenv): - logger.info("Save custom data") customdata = ovfenv.customdata if customdata is None: return - lib_dir = OSUTIL.get_lib_dir() - fileutil.write_file(os.path.join(lib_dir, CUSTOM_DATA_FILE), - OSUTIL.decode_customdata(customdata)) + + 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.") - OSUTIL.deploy_ssh_pubkey(ovfenv.username, pubkey) + 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.") - OSUTIL.deploy_ssh_keypair(ovfenv.username, keypair) + 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)) diff --git a/azurelinuxagent/distro/default/resourceDisk.py b/azurelinuxagent/distro/default/resourceDisk.py index 734863c..a6c5232 100644 --- a/azurelinuxagent/distro/default/resourceDisk.py +++ b/azurelinuxagent/distro/default/resourceDisk.py @@ -21,9 +21,8 @@ import os import re import threading import azurelinuxagent.logger as logger -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr import azurelinuxagent.conf as conf -from azurelinuxagent.utils.osutil import OSUTIL from azurelinuxagent.event import add_event, WALAEventOperation import azurelinuxagent.utils.fileutil as fileutil import azurelinuxagent.utils.shellutil as shellutil @@ -41,6 +40,8 @@ For additional details to please refer to the MSDN documentation at : http://msd """ class ResourceDiskHandler(object): + def __init__(self, distro): + self.distro = distro def start_activate_resource_disk(self): disk_thread = threading.Thread(target = self.run) @@ -48,17 +49,17 @@ class ResourceDiskHandler(object): def run(self): mount_point = None - if conf.get_switch("ResourceDisk.Format", False): + if conf.get_resourcedisk_format(): mount_point = self.activate_resource_disk() if mount_point is not None and \ - conf.get_switch("ResourceDisk.EnableSwap", False): + 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", "/mnt/resource") - fs = conf.get("ResourceDisk.Filesystem", "ext3") + 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: @@ -68,25 +69,25 @@ class ResourceDiskHandler(object): return mount_point except ResourceDiskError as e: logger.error("Failed to mount resource disk {0}", e) - add_event(name="WALA", is_success=False, message=text(e), + 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_int("ResourceDisk.SwapSizeMB", 0) + 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 = OSUTIL.device_for_ide_port(1) + 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 = OSUTIL.get_mount_point(mountlist, device) + existing = self.distro.osutil.get_mount_point(mountlist, device) if(existing): logger.info("Resource disk {0}1 is already mounted", device) diff --git a/azurelinuxagent/distro/default/run.py b/azurelinuxagent/distro/default/run.py deleted file mode 100644 index dfd3b03..0000000 --- a/azurelinuxagent/distro/default/run.py +++ /dev/null @@ -1,71 +0,0 @@ -# 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 azurelinuxagent.logger as logger -from azurelinuxagent.future import text -import azurelinuxagent.conf as conf -from azurelinuxagent.metadata import AGENT_LONG_NAME, AGENT_VERSION, \ - DISTRO_NAME, DISTRO_VERSION, \ - DISTRO_FULL_NAME, PY_VERSION_MAJOR, \ - PY_VERSION_MINOR, PY_VERSION_MICRO -import azurelinuxagent.event as event -import azurelinuxagent.protocol as prot -from azurelinuxagent.utils.osutil import OSUTIL -import azurelinuxagent.utils.fileutil as fileutil - - -class MainHandler(object): - def __init__(self, handlers): - self.handlers = handlers - - def run(self): - logger.info("{0} Version:{1}", AGENT_LONG_NAME, AGENT_VERSION) - logger.info("OS: {0} {1}", DISTRO_NAME, DISTRO_VERSION) - logger.info("Python: {0}.{1}.{2}", PY_VERSION_MAJOR, PY_VERSION_MINOR, - PY_VERSION_MICRO) - - event.enable_unhandled_err_dump("Azure Linux Agent") - fileutil.write_file(OSUTIL.get_agent_pid_file_path(), text(os.getpid())) - - if conf.get_switch("DetectScvmmEnv", False): - if self.handlers.scvmm_handler.detect_scvmm_env(): - return - - self.handlers.dhcp_handler.probe() - - prot.detect_default_protocol() - - event.EventMonitor().start() - - self.handlers.provision_handler.process() - - if conf.get_switch("ResourceDisk.Format", False): - self.handlers.resource_disk_handler.start_activate_resource_disk() - - self.handlers.env_handler.start() - - protocol = prot.FACTORY.get_default_protocol() - while True: - #Handle extensions - self.handlers.ext_handlers_handler.process() - time.sleep(25) - diff --git a/azurelinuxagent/distro/default/scvmm.py b/azurelinuxagent/distro/default/scvmm.py index 680c04b..4d083b4 100644 --- a/azurelinuxagent/distro/default/scvmm.py +++ b/azurelinuxagent/distro/default/scvmm.py @@ -20,28 +20,29 @@ import os import subprocess import azurelinuxagent.logger as logger -from azurelinuxagent.utils.osutil import OSUTIL VMM_CONF_FILE_NAME = "linuxosconfiguration.xml" VMM_STARTUP_SCRIPT_NAME= "install" class ScvmmHandler(object): + def __init__(self, distro): + self.distro = distro def detect_scvmm_env(self): logger.info("Detecting Microsoft System Center VMM Environment") - OSUTIL.mount_dvd(max_retry=1, chk_err=False) - mount_point = OSUTIL.get_dvd_mount_point() + 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: - OSUTIL.umount_dvd(chk_err=False) + 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 = OSUTIL.get_dvd_mount_point() + 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]) diff --git a/azurelinuxagent/distro/loader.py b/azurelinuxagent/distro/loader.py index 375abd2..74ea9e7 100644 --- a/azurelinuxagent/distro/loader.py +++ b/azurelinuxagent/distro/loader.py @@ -16,31 +16,52 @@ # import azurelinuxagent.logger as logger -from azurelinuxagent.metadata import DISTRO_NAME -import azurelinuxagent.distro.default.loader as default_loader +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_loader(): - try: - logger.verb("Loading distro implemetation from: {0}", DISTRO_NAME) - pkg_name = "azurelinuxagent.distro.{0}.loader".format(DISTRO_NAME) - return __import__(pkg_name, fromlist="loader") - except (ImportError, ValueError): - logger.warn("Unable to load distro implemetation for {0}.", DISTRO_NAME) +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 default_loader - -DISTRO_LOADER = get_distro_loader() - -def get_osutil(): - try: - return DISTRO_LOADER.get_osutil() - except AttributeError: - return default_loader.get_osutil() - -def get_handlers(): - try: - return DISTRO_LOADER.get_handlers() - except AttributeError: - return default_loader.get_handlers() + return DefaultDistro() diff --git a/azurelinuxagent/distro/oracle/loader.py b/azurelinuxagent/distro/oracle/loader.py deleted file mode 100644 index 9dc428f..0000000 --- a/azurelinuxagent/distro/oracle/loader.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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.metadata import DISTRO_NAME, DISTRO_VERSION -import azurelinuxagent.distro.redhat.loader as redhat - -def get_osutil(): - return redhat.get_osutil() - diff --git a/azurelinuxagent/distro/ubuntu/handlerFactory.py b/azurelinuxagent/distro/redhat/distro.py index 11f7f04..2f128d7 100644 --- a/azurelinuxagent/distro/ubuntu/handlerFactory.py +++ b/azurelinuxagent/distro/redhat/distro.py @@ -17,13 +17,16 @@ # Requires Python 2.4+ and Openssl 1.0+ # -from azurelinuxagent.distro.ubuntu.provision import UbuntuProvisionHandler -from azurelinuxagent.distro.ubuntu.deprovision import UbuntuDeprovisionHandler -from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory +from azurelinuxagent.distro.default.distro import DefaultDistro +from azurelinuxagent.distro.redhat.osutil import RedhatOSUtil, Redhat6xOSUtil +from azurelinuxagent.distro.coreos.deprovision import CoreOSDeprovisionHandler -class UbuntuHandlerFactory(DefaultHandlerFactory): +class Redhat6xDistro(DefaultDistro): def __init__(self): - super(UbuntuHandlerFactory, self).__init__() - self.provision_handler = UbuntuProvisionHandler() - self.deprovision_handler = UbuntuDeprovisionHandler() + super(Redhat6xDistro, self).__init__() + self.osutil = Redhat6xOSUtil() +class RedhatDistro(DefaultDistro): + def __init__(self): + super(RedhatDistro, self).__init__() + self.osutil = RedhatOSUtil() diff --git a/azurelinuxagent/distro/redhat/loader.py b/azurelinuxagent/distro/redhat/loader.py deleted file mode 100644 index 8d3c75b..0000000 --- a/azurelinuxagent/distro/redhat/loader.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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.metadata import DISTRO_NAME, DISTRO_VERSION - -def get_osutil(): - from azurelinuxagent.distro.redhat.osutil import Redhat6xOSUtil, RedhatOSUtil - if DISTRO_VERSION < "7": - return Redhat6xOSUtil() - else: - return RedhatOSUtil() - diff --git a/azurelinuxagent/distro/redhat/osutil.py b/azurelinuxagent/distro/redhat/osutil.py index 7478867..7f769a5 100644 --- a/azurelinuxagent/distro/redhat/osutil.py +++ b/azurelinuxagent/distro/redhat/osutil.py @@ -26,20 +26,19 @@ import struct import fcntl import time import base64 +import azurelinuxagent.conf as conf import azurelinuxagent.logger as logger -from azurelinuxagent.future import text, bytebuffer +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.distro.default.osutil import DefaultOSUtil, OSUtilError +from azurelinuxagent.utils.cryptutil import CryptUtil +from azurelinuxagent.distro.default.osutil import DefaultOSUtil class Redhat6xOSUtil(DefaultOSUtil): def __init__(self): super(Redhat6xOSUtil, self).__init__() - self.sshd_conf_file_path = '/etc/ssh/sshd_config' - self.openssl_cmd = '/usr/bin/openssl' - self.conf_file_path = '/etc/waagent.conf' - self.selinux=None def start_network(self): return shellutil.run("/sbin/service networking start", chk_err=False) @@ -58,63 +57,14 @@ class Redhat6xOSUtil(DefaultOSUtil): def unregister_agent_service(self): return shellutil.run("chkconfig --del waagent", chk_err=False) - - def asn1_to_ssh_rsa(self, pubkey): - lines = pubkey.split("\n") - lines = [x for x in lines if not x.startswith("----")] - base64_encoded = "".join(lines) - try: - #TODO remove pyasn1 dependency - from pyasn1.codec.der import decoder as der_decoder - der_encoded = base64.b64decode(base64_encoded) - der_encoded = der_decoder.decode(der_encoded)[0][1] - key = der_decoder.decode(self.bits_to_bytes(der_encoded))[0] - n=key[0] - e=key[1] - keydata = bytearray() - keydata.extend(struct.pack('>I', len("ssh-rsa"))) - keydata.extend(b"ssh-rsa") - keydata.extend(struct.pack('>I', len(self.num_to_bytes(e)))) - keydata.extend(self.num_to_bytes(e)) - keydata.extend(struct.pack('>I', len(self.num_to_bytes(n)) + 1)) - keydata.extend(b"\0") - keydata.extend(self.num_to_bytes(n)) - keydata_base64 = base64.b64encode(bytebuffer(keydata)) - return text(b"ssh-rsa " + keydata_base64 + b"\n", - encoding='utf-8') - except ImportError as e: - raise OSUtilError("Failed to load pyasn1.codec.der") - - def num_to_bytes(self, num): - """ - Pack number into bytes. Retun as string. - """ - result = bytearray() - while num: - result.append(num & 0xFF) - num >>= 8 - result.reverse() - return result - - def bits_to_bytes(self, bits): - """ - Convert an array contains bits, [0,1] to a byte array - """ - index = 7 - byte_array = bytearray() - curr = 0 - for bit in bits: - curr = curr | (bit << index) - index = index - 1 - if index == -1: - byte_array.append(curr) - curr = 0 - index = 7 - return bytes(byte_array) - + def openssl_to_openssh(self, input_file, output_file): pubkey = fileutil.read_file(input_file) - ssh_rsa_pubkey = self.asn1_to_ssh_rsa(pubkey) + 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 @@ -134,8 +84,7 @@ class Redhat6xOSUtil(DefaultOSUtil): 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', + fileutil.update_conf_file(filepath, 'DHCP_HOSTNAME', 'DHCP_HOSTNAME={0}'.format(hostname)) class RedhatOSUtil(Redhat6xOSUtil): @@ -162,4 +111,5 @@ class RedhatOSUtil(Redhat6xOSUtil): 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) diff --git a/azurelinuxagent/distro/default/loader.py b/azurelinuxagent/distro/suse/distro.py index 55a51e0..5b39369 100644 --- a/azurelinuxagent/distro/default/loader.py +++ b/azurelinuxagent/distro/suse/distro.py @@ -17,12 +17,16 @@ # Requires Python 2.4+ and Openssl 1.0+ # -def get_osutil(): - from azurelinuxagent.distro.default.osutil import DefaultOSUtil - return DefaultOSUtil() +from azurelinuxagent.distro.default.distro import DefaultDistro +from azurelinuxagent.distro.suse.osutil import SUSE11OSUtil, SUSEOSUtil -def get_handlers(): - from azurelinuxagent.distro.default.handlerFactory import DefaultHandlerFactory - return DefaultHandlerFactory() +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() diff --git a/azurelinuxagent/distro/suse/loader.py b/azurelinuxagent/distro/suse/loader.py deleted file mode 100644 index b01384b..0000000 --- a/azurelinuxagent/distro/suse/loader.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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.metadata import DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME - -def get_osutil(): - from azurelinuxagent.distro.suse.osutil import SUSE11OSUtil, SUSEOSUtil - if DISTRO_FULL_NAME=='SUSE Linux Enterprise Server' and DISTRO_VERSION < '12' \ - or DISTRO_FULL_NAME == 'openSUSE' and DISTRO_VERSION < '13.2': - return SUSE11OSUtil() - else: - return SUSEOSUtil() - diff --git a/azurelinuxagent/distro/ubuntu/deprovision.py b/azurelinuxagent/distro/ubuntu/deprovision.py index 0c3c4e5..da6e834 100644 --- a/azurelinuxagent/distro/ubuntu/deprovision.py +++ b/azurelinuxagent/distro/ubuntu/deprovision.py @@ -33,6 +33,9 @@ def del_resolv(): 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 " diff --git a/azurelinuxagent/distro/ubuntu/distro.py b/azurelinuxagent/distro/ubuntu/distro.py new file mode 100644 index 0000000..f380f6c --- /dev/null +++ b/azurelinuxagent/distro/ubuntu/distro.py @@ -0,0 +1,55 @@ +# 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) diff --git a/azurelinuxagent/distro/ubuntu/loader.py b/azurelinuxagent/distro/ubuntu/loader.py deleted file mode 100644 index 3fe2239..0000000 --- a/azurelinuxagent/distro/ubuntu/loader.py +++ /dev/null @@ -1,40 +0,0 @@ -# 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.metadata import DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME - -def get_osutil(): - from azurelinuxagent.distro.ubuntu.osutil import Ubuntu1204OSUtil, \ - UbuntuOSUtil, \ - Ubuntu14xOSUtil, \ - UbuntuSnappyOSUtil - - if DISTRO_VERSION == "12.04": - return Ubuntu1204OSUtil() - elif DISTRO_VERSION == "14.04" or DISTRO_VERSION == "14.10": - return Ubuntu14xOSUtil() - elif DISTRO_FULL_NAME == "Snappy Ubuntu Core": - return UbuntuSnappyOSUtil() - else: - return UbuntuOSUtil() - -def get_handlers(): - from azurelinuxagent.distro.ubuntu.handlerFactory import UbuntuHandlerFactory - return UbuntuHandlerFactory() - diff --git a/azurelinuxagent/distro/ubuntu/osutil.py b/azurelinuxagent/distro/ubuntu/osutil.py index adf7660..cc4b8ef 100644 --- a/azurelinuxagent/distro/ubuntu/osutil.py +++ b/azurelinuxagent/distro/ubuntu/osutil.py @@ -31,9 +31,9 @@ import azurelinuxagent.utils.shellutil as shellutil import azurelinuxagent.utils.textutil as textutil from azurelinuxagent.distro.default.osutil import DefaultOSUtil -class Ubuntu14xOSUtil(DefaultOSUtil): +class Ubuntu14OSUtil(DefaultOSUtil): def __init__(self): - super(Ubuntu14xOSUtil, self).__init__() + super(Ubuntu14OSUtil, self).__init__() def start_network(self): return shellutil.run("service networking start", chk_err=False) @@ -44,16 +44,16 @@ class Ubuntu14xOSUtil(DefaultOSUtil): def start_agent_service(self): return shellutil.run("service walinuxagent start", chk_err=False) -class Ubuntu1204OSUtil(Ubuntu14xOSUtil): +class Ubuntu12OSUtil(Ubuntu14OSUtil): def __init__(self): - super(Ubuntu1204OSUtil, self).__init__() + 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(Ubuntu14xOSUtil): +class UbuntuOSUtil(Ubuntu14OSUtil): def __init__(self): super(UbuntuOSUtil, self).__init__() @@ -63,7 +63,7 @@ class UbuntuOSUtil(Ubuntu14xOSUtil): def unregister_agent_service(self): return shellutil.run("systemctl mask walinuxagent", chk_err=False) -class UbuntuSnappyOSUtil(Ubuntu14xOSUtil): +class UbuntuSnappyOSUtil(Ubuntu14OSUtil): def __init__(self): super(UbuntuSnappyOSUtil, self).__init__() self.conf_file_path = '/apps/walinuxagent/current/waagent.conf' diff --git a/azurelinuxagent/distro/ubuntu/provision.py b/azurelinuxagent/distro/ubuntu/provision.py index a68fe4d..330e057 100644 --- a/azurelinuxagent/distro/ubuntu/provision.py +++ b/azurelinuxagent/distro/ubuntu/provision.py @@ -20,12 +20,11 @@ import os import time import azurelinuxagent.logger as logger -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr import azurelinuxagent.conf as conf -import azurelinuxagent.protocol as prot +import azurelinuxagent.protocol.ovfenv as ovfenv from azurelinuxagent.event import add_event, WALAEventOperation -from azurelinuxagent.exception import * -from azurelinuxagent.utils.osutil import OSUTIL +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 @@ -34,49 +33,61 @@ from azurelinuxagent.distro.default.provision import ProvisionHandler On ubuntu image, provision could be disabled. """ class UbuntuProvisionHandler(ProvisionHandler): - def process(self): + def __init__(self, distro): + self.distro = distro + + def run(self): #If provision is enabled, run default provision handler - if conf.get_switch("Provisioning.Enabled", False): - super(UbuntuProvisionHandler, self).process() + if conf.get_provision_enabled(): + super(UbuntuProvisionHandler, self).run() return logger.info("run Ubuntu provision handler") - provisioned = os.path.join(OSUTIL.get_lib_dir(), "provisioned") + provisioned = os.path.join(conf.get_lib_dir(), "provisioned") if os.path.isfile(provisioned): return - logger.info("Waiting cloud-init to finish provisioning.") - protocol = prot.FACTORY.get_default_protocol() + 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") - status = prot.ProvisionStatus(status="Ready") - status.properties.certificateThumbprint = thumbprint - try: - protocol.report_provision_status(status) - except prot.ProtocolError as pe: - add_event(name="WALA", is_success=False, message=text(pe), - op=WALAEventOperation.Provision) - + except ProvisionError as e: logger.error("Provision failed: {0}", e) - status = prot.ProvisionStatus(status="NotReady", - subStatus="ProvisioningFailed", - description= text(e)) - try: - protocol.report_provision_status(status) - except prot.ProtocolError as pe: - add_event(name="WALA", is_success=False, message=text(pe), - op=WALAEventOperation.Provision) + 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) - add_event(name="WALA", is_success=False, message=text(e), - op=WALAEventOperation.Provision) + 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): - kepair_type = conf.get("Provisioning.SshHostKeyPairType", "rsa") + """ + 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): diff --git a/azurelinuxagent/event.py b/azurelinuxagent/event.py index 02e8017..f38b242 100644 --- a/azurelinuxagent/event.py +++ b/azurelinuxagent/event.py @@ -25,14 +25,15 @@ import datetime import threading import platform import azurelinuxagent.logger as logger -from azurelinuxagent.future import text -import azurelinuxagent.protocol as prot +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 -from azurelinuxagent.utils.osutil import OSUTIL -class EventError(Exception): - pass class WALAEventOperation: HeartBeat="HeartBeat" @@ -47,132 +48,65 @@ class WALAEventOperation: ActivateResourceDisk="ActivateResourceDisk" UnhandledError="UnhandledError" -class EventMonitor(object): +class EventLogger(object): def __init__(self): - self.sysinfo = [] - self.event_dir = os.path.join(OSUTIL.get_lib_dir(), "events") + self.event_dir = None - def init_sysinfo(self): - osversion = "{0}:{1}-{2}-{3}:{4}".format(platform.system(), - DISTRO_NAME, - DISTRO_VERSION, - DISTRO_CODE_NAME, - platform.release()) + def save_event(self, data): + if self.event_dir is None: + logger.warn("Event reporter is not initialized.") + return - self.sysinfo.append(prot.TelemetryEventParam("OSVersion", osversion)) - self.sysinfo.append(prot.TelemetryEventParam("GAVersion", - AGENT_VERSION)) - self.sysinfo.append(prot.TelemetryEventParam("RAM", - OSUTIL.get_total_mem())) - self.sysinfo.append(prot.TelemetryEventParam("Processors", - OSUTIL.get_processor_cores())) - try: - protocol = prot.FACTORY.get_default_protocol() - vminfo = protocol.get_vminfo() - self.sysinfo.append(prot.TelemetryEventParam("VMName", - vminfo.vmName)) - #TODO add other system info like, subscription id, etc. - except prot.ProtocolError as e: - logger.warn("Failed to get vm info: {0}", e) - - def start(self): - event_thread = threading.Thread(target = self.run) - event_thread.setDaemon(True) - event_thread.start() + 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)) - def collect_event(self, evt_file_name): + filename = os.path.join(self.event_dir, ustr(int(time.time()*1000000))) 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 - json_str = evt_file.read().decode("utf-8",'ignore') - logger.verb("Processed event file: {0}", evt_file_name) - os.remove(evt_file_name) - return json_str + with open(filename+".tmp",'wb+') as hfile: + hfile.write(data.encode("utf-8")) + os.rename(filename+".tmp", filename+".tld") except IOError as e: - msg = "Failed to process {0}, {1}".format(evt_file_name, e) - raise EventError(msg) - - def collect_and_send_events(self): - event_list = prot.TelemetryEventList() - event_files = os.listdir(self.event_dir) - for event_file in event_files: - if not event_file.endswith(".tld"): - continue - event_file_path = os.path.join(self.event_dir, event_file) - try: - data_str = self.collect_event(event_file_path) - except EventError as e: - logger.error("{0}", e) - continue - try: - data = json.loads(data_str) - except ValueError as e: - logger.verb(data_str) - logger.error("Failed to decode json event file: {0}", e) - continue - - event = prot.TelemetryEvent() - prot.set_properties("event", event, data) - event.parameters.extend(self.sysinfo) - event_list.events.append(event) - if len(event_list.events) == 0: - return - + 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: - protocol = prot.FACTORY.get_default_protocol() - protocol.report_event(event_list) - except prot.ProtocolError as e: + self.save_event(json.dumps(data)) + except EventError as e: logger.error("{0}", e) - def run(self): - self.init_sysinfo() - last_heartbeat = datetime.datetime.min - period = datetime.timedelta(hours = 12) - while(True): - if (datetime.datetime.now()-last_heartbeat) > period: - last_heartbeat = datetime.datetime.now() - add_event(op=WALAEventOperation.HeartBeat, - name="WALA",is_success=True) - self.collect_and_send_events() - time.sleep(60) - -def save_event(data): - event_dir = os.path.join(OSUTIL.get_lib_dir(), 'events') - if not os.path.exists(event_dir): - os.mkdir(event_dir) - os.chmod(event_dir,0o700) - if len(os.listdir(event_dir)) > 1000: - raise EventError("Too many files under: {0}", event_dir) - - filename = os.path.join(event_dir, text(int(time.time()*1000000))) - try: - with open(filename+".tmp",'wb+') as hfile: - hfile.write(data.encode("utf-8")) - os.rename(filename+".tmp", filename+".tld") - except IOError as e: - raise EventError("Failed to write events to file:{0}", e) +__event_logger__ = EventLogger() def add_event(name, op="", is_success=True, duration=0, version="1.0", - message="", evt_type="", is_internal=False): + 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) - event = prot.TelemetryEvent(1, "69B669B9-4AF8-4C50-BDC4-6006FA76E975") - event.parameters.append(prot.TelemetryEventParam('Name', name)) - event.parameters.append(prot.TelemetryEventParam('Version', version)) - event.parameters.append(prot.TelemetryEventParam('IsInternal', is_internal)) - event.parameters.append(prot.TelemetryEventParam('Operation', op)) - event.parameters.append(prot.TelemetryEventParam('OperationSuccess', - is_success)) - event.parameters.append(prot.TelemetryEventParam('Message', message)) - event.parameters.append(prot.TelemetryEventParam('Duration', duration)) - event.parameters.append(prot.TelemetryEventParam('ExtensionType', evt_type)) - data = prot.get_properties(event) - try: - save_event(json.dumps(data)) - except EventError as e: - logger.error("{0}", e) + 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 \ @@ -184,8 +118,7 @@ def dump_unhandled_err(name): last_traceback) message= "".join(error) add_event(name, is_success=False, message=message, - op=WALAEventOperation.UnhandledError) + op=WALAEventOperation.UnhandledError) def enable_unhandled_err_dump(name): atexit.register(dump_unhandled_err, name) - diff --git a/azurelinuxagent/exception.py b/azurelinuxagent/exception.py index d7d9b0a..7fa5cff 100644 --- a/azurelinuxagent/exception.py +++ b/azurelinuxagent/exception.py @@ -24,42 +24,92 @@ class AgentError(Exception): """ Base class of agent error. """ - def __init__(self, errno, msg): - msg = "({0}){1}".format(errno, msg) + 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): - super(AgentConfigError, self).__init__('000001', msg) + 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): - super(AgentNetworkError, self).__init__('000002', msg) + 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): - super(ExtensionError, self).__init__('000003', msg) + def __init__(self, msg=None, inner=None): + super(ExtensionError, self).__init__('000003', msg, inner) class ProvisionError(AgentError): """ When provision failed """ - def __init__(self, msg): - super(ProvisionError, self).__init__('000004', msg) + 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): - super(ResourceDiskError, self).__init__('000005', msg) + 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) diff --git a/azurelinuxagent/future.py b/azurelinuxagent/future.py index 8451345..8509732 100644 --- a/azurelinuxagent/future.py +++ b/azurelinuxagent/future.py @@ -7,15 +7,25 @@ Add alies for python2 and python3 libs and fucntions. if sys.version_info[0]== 3: import http.client as httpclient from urllib.parse import urlparse - text = str + + """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 - text = unicode + + """Rename Python2 unicode to ustr""" + ustr = unicode + bytebuffer = buffer + read_input = raw_input + else: raise ImportError("Unknown python version:{0}".format(sys.version_info)) diff --git a/azurelinuxagent/handler.py b/azurelinuxagent/handler.py deleted file mode 100644 index 538ee30..0000000 --- a/azurelinuxagent/handler.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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+ -# - -""" -Handler handles different tasks like, provisioning, deprovisioning etc. -The handlers could be extended for different distros. The default -implementation is under azurelinuxagent.distros.default -""" -import azurelinuxagent.distro.loader as loader - -HANDLERS = loader.get_handlers() - diff --git a/azurelinuxagent/logger.py b/azurelinuxagent/logger.py index 21c02a6..6c6b406 100644 --- a/azurelinuxagent/logger.py +++ b/azurelinuxagent/logger.py @@ -22,7 +22,7 @@ Log utils """ import os import sys -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr from datetime import datetime class Logger(object): @@ -49,8 +49,8 @@ class Logger(object): def log(self, level, msg_format, *args): #if msg_format is not unicode convert it to unicode - if type(msg_format) is not text: - msg_format = text(msg_format, errors="backslashreplace") + if type(msg_format) is not ustr: + msg_format = ustr(msg_format, errors="backslashreplace") if len(args) > 0: msg = msg_format.format(*args) else: @@ -63,7 +63,7 @@ class Logger(object): else: log_item = u"{0} {1} {2}\n".format(time, level_str, msg) - log_item = text(log_item.encode('ascii', "backslashreplace"), + log_item = ustr(log_item.encode('ascii', "backslashreplace"), encoding="ascii") for appender in self.appenders: appender.write(level, log_item) diff --git a/azurelinuxagent/metadata.py b/azurelinuxagent/metadata.py index 5cf4902..34fdcf9 100644 --- a/azurelinuxagent/metadata.py +++ b/azurelinuxagent/metadata.py @@ -22,11 +22,11 @@ import re import platform import sys import azurelinuxagent.utils.fileutil as fileutil -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr def get_distro(): if 'FreeBSD' in platform.system(): - release = re.sub('\-.*\Z', '', text(platform.release())) + 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)) @@ -47,7 +47,7 @@ def get_distro(): AGENT_NAME = "WALinuxAgent" AGENT_LONG_NAME = "Azure Linux Agent" -AGENT_VERSION = '2.1.2' +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 diff --git a/azurelinuxagent/protocol/__init__.py b/azurelinuxagent/protocol/__init__.py index a4572e6..8c1bbdb 100644 --- a/azurelinuxagent/protocol/__init__.py +++ b/azurelinuxagent/protocol/__init__.py @@ -16,8 +16,3 @@ # # Requires Python 2.4+ and Openssl 1.0+ # - -from azurelinuxagent.protocol.common import * -from azurelinuxagent.protocol.protocolFactory import FACTORY, \ - detect_default_protocol - diff --git a/azurelinuxagent/protocol/v2.py b/azurelinuxagent/protocol/metadata.py index 34102b7..8a1656f 100644 --- a/azurelinuxagent/protocol/v2.py +++ b/azurelinuxagent/protocol/metadata.py @@ -17,16 +17,30 @@ # Requires Python 2.4+ and Openssl 1.0+ import json -from azurelinuxagent.future import httpclient, text +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 -from azurelinuxagent.protocol.common import * +import azurelinuxagent.utils.textutil as textutil +import azurelinuxagent.utils.fileutil as fileutil +from azurelinuxagent.utils.cryptutil import CryptUtil +from azurelinuxagent.protocol.restapi import * -ENDPOINT='169.254.169.254' -#TODO use http for azure pack test -#ENDPOINT='localhost' +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 = {} @@ -35,7 +49,7 @@ def _add_content_type(headers): class MetadataProtocol(Protocol): - def __init__(self, apiversion=APIVERSION, endpoint=ENDPOINT): + def __init__(self, apiversion=APIVERSION, endpoint=METADATA_ENDPOINT): self.apiversion = apiversion self.endpoint = endpoint self.identity_uri = BASE_URI.format(self.endpoint, "identity", @@ -58,24 +72,25 @@ class MetadataProtocol(Protocol): def _get_data(self, url, headers=None): try: resp = restutil.http_get(url, headers=headers) - except restutil.HttpError as e: - raise ProtocolError(text(e)) + 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(text(data, encoding="utf-8")) - return data + 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 restutil.HttpError as e: - raise ProtocolError(text(e)) + except HttpError as e: + raise ProtocolError(ustr(e)) if resp.status != httpclient.OK: raise ProtocolError("{0} - PUT: {1}".format(resp.status, url)) @@ -83,17 +98,41 @@ class MetadataProtocol(Protocol): headers = _add_content_type(headers) try: resp = restutil.http_post(url, json.dumps(data), headers=headers) - except restutil.HttpError as e: - raise ProtocolError(text(e)) + 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 initialize(self): - pass def get_vminfo(self): vminfo = VMInfo() - data = self._get_data(self.identity_uri) + data, etag = self._get_data(self.identity_uri) set_properties("vminfo", vminfo, data) return vminfo @@ -102,17 +141,20 @@ class MetadataProtocol(Protocol): return CertList() def get_ext_handlers(self): + headers = { + "x-ms-vmagent-public-x509-cert": self._get_trans_cert() + } ext_list = ExtHandlerList() - data = self._get_data(self.ext_uri) + data, etag = self._get_data(self.ext_uri, headers=headers) set_properties("extensionHandlers", ext_list.extHandlers, data) - return ext_list + 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 = self._get_data(version_uri.uri) + data, etag = self._get_data(version_uri.uri) break except ProtocolError as e: logger.warn("Failed to get version uris: {0}", e) @@ -128,6 +170,14 @@ class MetadataProtocol(Protocol): 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): diff --git a/azurelinuxagent/protocol/ovfenv.py b/azurelinuxagent/protocol/ovfenv.py index 9c845ee..de6791c 100644 --- a/azurelinuxagent/protocol/ovfenv.py +++ b/azurelinuxagent/protocol/ovfenv.py @@ -17,60 +17,22 @@ # Requires Python 2.4+ and Openssl 1.0+ # """ -Copy and parse ovf-env.xml from provisiong ISO and local cache +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.future import text +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 -from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError -from azurelinuxagent.protocol import ProtocolError -OVF_FILE_NAME = "ovf-env.xml" OVF_VERSION = "1.0" OVF_NAME_SPACE = "http://schemas.dmtf.org/ovf/environment/1" WA_NAME_SPACE = "http://schemas.microsoft.com/windowsazure" -def get_ovf_env(): - """ - Load saved ovf-env.xml - """ - ovf_file_path = os.path.join(OSUTIL.get_lib_dir(), OVF_FILE_NAME) - if os.path.isfile(ovf_file_path): - xml_text = fileutil.read_file(ovf_file_path) - return OvfEnv(xml_text) - else: - raise ProtocolError("ovf-env.xml is missing.") - -def copy_ovf_env(): - """ - Copy ovf env file from dvd to hard disk. - Remove password before save it to the disk - """ - try: - OSUTIL.mount_dvd() - ovf_file_path_on_dvd = OSUTIL.get_ovf_env_file_path_on_dvd() - ovfxml = fileutil.read_file(ovf_file_path_on_dvd, remove_bom=True) - ovfenv = OvfEnv(ovfxml) - ovfxml = re.sub("<UserPassword>.*?<", "<UserPassword>*<", ovfxml) - ovf_file_path = os.path.join(OSUTIL.get_lib_dir(), OVF_FILE_NAME) - fileutil.write_file(ovf_file_path, ovfxml) - except IOError as e: - raise ProtocolError(text(e)) - except OSUtilError as e: - raise ProtocolError(text(e)) - - try: - OSUTIL.umount_dvd() - OSUTIL.eject_dvd() - except OSUtilError as e: - logger.warn(text(e)) - - return ovfenv - def _validate_ovf(val, msg): if val is None: raise ProtocolError("Failed to parse OVF XML: {0}".format(msg)) diff --git a/azurelinuxagent/protocol/protocolFactory.py b/azurelinuxagent/protocol/protocolFactory.py deleted file mode 100644 index 0bf6e52..0000000 --- a/azurelinuxagent/protocol/protocolFactory.py +++ /dev/null @@ -1,114 +0,0 @@ -# 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 traceback -import threading -import azurelinuxagent.logger as logger -from azurelinuxagent.future import text -import azurelinuxagent.utils.fileutil as fileutil -from azurelinuxagent.utils.osutil import OSUTIL -from azurelinuxagent.protocol.common import * -from azurelinuxagent.protocol.v1 import WireProtocol -from azurelinuxagent.protocol.v2 import MetadataProtocol - -WIRE_SERVER_ADDR_FILE_NAME = "WireServer" - -def get_wire_protocol_endpoint(): - path = os.path.join(OSUTIL.get_lib_dir(), WIRE_SERVER_ADDR_FILE_NAME) - try: - endpoint = fileutil.read_file(path) - except IOError as e: - raise ProtocolNotFound("Wire server endpoint not found: {0}".format(e)) - - if endpoint is None: - raise ProtocolNotFound("Wire server endpoint is None") - - return endpoint - -def detect_wire_protocol(): - endpoint = get_wire_protocol_endpoint() - - OSUTIL.gen_transport_cert() - protocol = WireProtocol(endpoint) - protocol.initialize() - logger.info("Protocol V1 found.") - return protocol - -def detect_metadata_protocol(): - protocol = MetadataProtocol() - protocol.initialize() - - logger.info("Protocol V2 found.") - return protocol - -def detect_available_protocols(prob_funcs=[detect_wire_protocol, - detect_metadata_protocol]): - available_protocols = [] - for probe_func in prob_funcs: - try: - protocol = probe_func() - available_protocols.append(protocol) - except ProtocolNotFound as e: - logger.info(text(e)) - return available_protocols - -def detect_default_protocol(): - logger.info("Detect default protocol.") - available_protocols = detect_available_protocols() - return choose_default_protocol(available_protocols) - -def choose_default_protocol(protocols): - if len(protocols) > 0: - return protocols[0] - else: - raise ProtocolNotFound("No available protocol detected.") - -def get_wire_protocol(): - endpoint = get_wire_protocol_endpoint() - return WireProtocol(endpoint) - -def get_metadata_protocol(): - return MetadataProtocol() - -def get_available_protocols(getters=[get_wire_protocol, get_metadata_protocol]): - available_protocols = [] - for getter in getters: - try: - protocol = getter() - available_protocols.append(protocol) - except ProtocolNotFound as e: - logger.info(text(e)) - return available_protocols - -class ProtocolFactory(object): - def __init__(self): - self._protocol = None - self._lock = threading.Lock() - - def get_default_protocol(self): - if self._protocol is None: - self._lock.acquire() - if self._protocol is None: - available_protocols = get_available_protocols() - self._protocol = choose_default_protocol(available_protocols) - self._lock.release() - - return self._protocol - -FACTORY = ProtocolFactory() diff --git a/azurelinuxagent/protocol/common.py b/azurelinuxagent/protocol/restapi.py index 367794f..fbd29ed 100644 --- a/azurelinuxagent/protocol/common.py +++ b/azurelinuxagent/protocol/restapi.py @@ -22,14 +22,9 @@ import re import json import xml.dom.minidom import azurelinuxagent.logger as logger -from azurelinuxagent.future import text -import azurelinuxagent.utils.fileutil as fileutil - -class ProtocolError(Exception): - pass - -class ProtocolNotFound(Exception): - pass +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: @@ -88,9 +83,14 @@ class DataContractList(list): Data contract between guest and host """ class VMInfo(DataContract): - def __init__(self, subscriptionId=None, vmName=None): + 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): @@ -104,11 +104,11 @@ class CertList(DataContract): class Extension(DataContract): def __init__(self, name=None, sequenceNumber=None, publicSettings=None, - privateSettings=None, certificateThumbprint=None): + protectedSettings=None, certificateThumbprint=None): self.name = name self.sequenceNumber = sequenceNumber self.publicSettings = publicSettings - self.privateSettings = privateSettings + self.protectedSettings = protectedSettings self.certificateThumbprint = certificateThumbprint class ExtHandlerProperties(DataContract): @@ -176,12 +176,14 @@ class ExtensionStatus(DataContract): self.substatusList = DataContractList(ExtensionSubStatus) class ExtHandlerStatus(DataContract): - def __init__(self, name=None, version=None, status=None, message=None): + 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(text) + self.extensions = DataContractList(ustr) class VMAgentStatus(DataContract): def __init__(self, version=None, status=None, message=None): @@ -211,7 +213,7 @@ class TelemetryEventList(DataContract): class Protocol(DataContract): - def initialize(self): + def detect(self): raise NotImplementedError() def get_vminfo(self): @@ -226,6 +228,14 @@ class Protocol(DataContract): 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() diff --git a/azurelinuxagent/protocol/v1.py b/azurelinuxagent/protocol/wire.py index 92fcc06..7b5ffe8 100644 --- a/azurelinuxagent/protocol/v1.py +++ b/azurelinuxagent/protocol/wire.py @@ -22,16 +22,19 @@ import re import time import traceback import xml.sax.saxutils as saxutils -import xml.etree.ElementTree as ET +import azurelinuxagent.conf as conf import azurelinuxagent.logger as logger -from azurelinuxagent.future import text, httpclient, bytebuffer +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 -from azurelinuxagent.utils.osutil import OSUTIL + getattrib, gettext, remove_bom, \ + get_bytes_from_pem import azurelinuxagent.utils.fileutil as fileutil import azurelinuxagent.utils.shellutil as shellutil -from azurelinuxagent.protocol.common import * +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" @@ -53,6 +56,7 @@ 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 @@ -61,19 +65,37 @@ class WireProtocolResourceGone(ProtocolError): pass class WireProtocol(Protocol): + """Slim layer to adapte wire protocol data to metadata protocol interface""" def __init__(self, endpoint): - self.client = WireClient(endpoint) + if endpoint is None: + raise ProtocolError("WireProtocl endpoint is None") + self.endpoint = endpoint + self.client = WireClient(self.endpoint) - def initialize(self): + 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): @@ -81,12 +103,16 @@ class WireProtocol(Protocol): 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() - return ext_conf.ext_handlers + #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 @@ -134,12 +160,12 @@ def _build_role_properties(container_id, role_instance_id, thumbprint): return xml def _build_health_report(incarnation, container_id, role_instance_id, - status, substatus, description): + status, substatus, description): #Escape '&', '<' and '>' - description = saxutils.escape(text(description)) + description = saxutils.escape(ustr(description)) detail = u'' if substatus is not None: - substatus = saxutils.escape(text(substatus)) + substatus = saxutils.escape(ustr(substatus)) detail = (u"<Details>" u"<SubStatus>{0}</SubStatus>" u"<Description>{1}</Description>" @@ -228,6 +254,7 @@ def ext_handler_status_to_v1(handler_status, ext_statuses, timestamp): '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"] = { @@ -303,7 +330,7 @@ class StatusBlob(object): self.put_page_blob(url, data) else: raise ProtocolError("Unknown blob type: {0}".format(blob_type)) - except restutil.HttpError as e: + except HttpError as e: raise ProtocolError("Failed to upload status blob: {0}".format(e)) def get_blob_type(self, url): @@ -315,7 +342,7 @@ class StatusBlob(object): "x-ms-date" : timestamp, 'x-ms-version' : self.__class__.__storage_version__ }) - except restutil.HttpError as e: + 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: @@ -334,10 +361,10 @@ class StatusBlob(object): data, { "x-ms-date" : timestamp, "x-ms-blob-type" : "BlockBlob", - "Content-Length": text(len(data)), + "Content-Length": ustr(len(data)), "x-ms-version" : self.__class__.__storage_version__ }) - except restutil.HttpError as e: + except HttpError as e: raise ProtocolError((u"Failed to upload block blob: {0}" u"").format(e)) if resp.status != httpclient.CREATED: @@ -359,10 +386,10 @@ class StatusBlob(object): "x-ms-date" : timestamp, "x-ms-blob-type" : "PageBlob", "Content-Length": "0", - "x-ms-blob-content-length" : text(page_blob_size), + "x-ms-blob-content-length" : ustr(page_blob_size), "x-ms-version" : self.__class__.__storage_version__ }) - except restutil.HttpError as e: + except HttpError as e: raise ProtocolError((u"Failed to clean up page blob: {0}" u"").format(e)) if resp.status != httpclient.CREATED: @@ -393,9 +420,9 @@ class StatusBlob(object): "x-ms-range" : "bytes={0}-{1}".format(start, page_end - 1), "x-ms-page-write" : "update", "x-ms-version" : self.__class__.__storage_version__, - "Content-Length": text(page_end - start) + "Content-Length": ustr(page_end - start) }) - except restutil.HttpError as e: + 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: @@ -411,13 +438,13 @@ def event_param_to_v1(param): attr_type = 'mt:uint64' elif param_type is str: attr_type = 'mt:wstr' - elif text(param_type).count("'unicode'") > 0: + 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(text(param.value)), + return param_format.format(param.name, saxutils.quoteattr(ustr(param.value)), attr_type) def event_to_v1(event): @@ -431,6 +458,7 @@ def event_to_v1(event): class WireClient(object): def __init__(self, endpoint): + logger.info("Wire server endpoint:{0}", endpoint) self.endpoint = endpoint self.goal_state = None self.updated = None @@ -448,15 +476,15 @@ class WireClient(object): """ now = time.time() if now - self.last_request < 1: - logger.info("Last request issued less than 1 second ago") - logger.info("Sleep {0} second to avoid throttling.", + 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.info("Sleep {0} second to avoid throttling.", + logger.verb("Sleep {0} second to avoid throttling.", SHORT_WAITING_INTERVAL) time.sleep(SHORT_WAITING_INTERVAL) self.req_count = 0 @@ -485,15 +513,15 @@ class WireClient(object): if data is None: return None data = remove_bom(data) - xml_text = text(data, encoding='utf-8') + 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 restutil.HttpError as e: - raise ProtocolError(text(e)) + except HttpError as e: + raise ProtocolError(ustr(e)) if(resp.status != httpclient.OK): raise ProtocolError("{0} - {1}".format(resp.status, uri)) @@ -532,12 +560,13 @@ class WireClient(object): 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 restutil.HttpError as e: - raise ProtocolError(text(e)) + except HttpError as e: + raise ProtocolError(ustr(e)) if resp.status == httpclient.OK: return self.decode_config(resp.read()) @@ -553,7 +582,7 @@ class WireClient(object): def update_hosting_env(self, goal_state): if goal_state.hosting_env_uri is None: raise ProtocolError("HostingEnvironmentConfig uri is empty") - local_file = HOSTING_ENV_FILE_NAME + 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) @@ -562,7 +591,7 @@ class WireClient(object): def update_shared_conf(self, goal_state): if goal_state.shared_conf_uri is None: raise ProtocolError("SharedConfig uri is empty") - local_file = SHARED_CONF_FILE_NAME + 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) @@ -571,7 +600,7 @@ class WireClient(object): def update_certs(self, goal_state): if goal_state.certs_uri is None: return - local_file = CERTS_FILE_NAME + 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) @@ -583,25 +612,18 @@ class WireClient(object): self.ext_conf = ExtensionsConfig(None) return incarnation = goal_state.incarnation - local_file = EXT_CONF_FILE_NAME.format(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) - for ext_handler in self.ext_conf.ext_handlers.extHandlers: - self.update_ext_handler_manifest(ext_handler, goal_state) - - def update_ext_handler_manifest(self, ext_handler, goal_state): - local_file = MANIFEST_FILE_NAME.format(ext_handler.name, - goal_state.incarnation) - xml_text = self.fetch_manifest(ext_handler.versionUris) - self.save_cache(local_file, 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(OSUTIL.get_lib_dir(), + incarnation_file = os.path.join(conf.get_lib_dir(), INCARNATION_FILE_NAME) if not forced: @@ -619,7 +641,7 @@ class WireClient(object): try: self.goal_state = goal_state file_name = GOAL_STATE_FILE_NAME.format(goal_state.incarnation) - goal_state_file = os.path.join(OSUTIL.get_lib_dir(), file_name) + 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) @@ -636,27 +658,34 @@ class WireClient(object): def get_goal_state(self): if(self.goal_state is None): - incarnation = self.fetch_cache(INCARNATION_FILE_NAME) - goal_state_file = GOAL_STATE_FILE_NAME.format(incarnation) + 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): - xml_text = self.fetch_cache(HOSTING_ENV_FILE_NAME) + 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): - xml_text = self.fetch_cache(SHARED_CONF_FILE_NAME) + 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): - xml_text = self.fetch_cache(CERTS_FILE_NAME) + 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 @@ -669,14 +698,17 @@ class WireClient(object): 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, extension, goal_state): - local_file = MANIFEST_FILE_NAME.format(extension.name, - goal_state.incarnation) - xml_text = self.fetch_cache(local_file) + 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): @@ -693,7 +725,7 @@ class WireClient(object): else: error = ("Agent supported wire protocol version: {0} was not " "advised by Fabric.").format(PROTOCOL_VERSION) - raise ProtocolNotFound(error) + raise ProtocolNotFoundError(error) def upload_status_blob(self): ext_conf = self.get_ext_conf() @@ -711,7 +743,7 @@ class WireClient(object): try: resp = self.call_wireserver(restutil.http_post, role_prop_uri, role_prop, headers = headers) - except restutil.HttpError as e: + except HttpError as e: raise ProtocolError((u"Failed to send role properties: {0}" u"").format(e)) if resp.status != httpclient.ACCEPTED: @@ -732,7 +764,7 @@ class WireClient(object): try: resp = self.call_wireserver(restutil.http_post, health_report_uri, health_report, headers = headers) - except restutil.HttpError as e: + except HttpError as e: raise ProtocolError((u"Failed to send provision status: {0}" u"").format(e)) if resp.status != httpclient.OK: @@ -750,7 +782,7 @@ class WireClient(object): try: header = self.get_header_for_xml_content() resp = self.call_wireserver(restutil.http_post, uri, data, header) - except restutil.HttpError as e: + except HttpError as e: raise ProtocolError("Failed to send events:{0}".format(e)) if resp.status != httpclient.OK: @@ -791,11 +823,10 @@ class WireClient(object): } def get_header_for_cert(self): - cert = "" - content = self.fetch_cache(TRANSPORT_CERT_FILE_NAME) - for line in content.split('\n'): - if "CERTIFICATE" not in line: - cert += line.rstrip() + 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, @@ -922,8 +953,6 @@ class Certificates(object): def __init__(self, client, xml_text): logger.verb("Load Certificates.xml") self.client = client - self.lib_dir = OSUTIL.get_lib_dir() - self.openssl_cmd = OSUTIL.get_openssl_cmd() self.cert_list = CertList() self.parse(xml_text) @@ -935,22 +964,26 @@ class Certificates(object): 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_NAME, P7M_FILE_NAME, data) + "{2}").format(p7m_file, p7m_file, data) - self.client.save_cache(os.path.join(self.lib_dir, P7M_FILE_NAME), p7m) + 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 - cmd = ("{0} cms -decrypt -in {1} -inkey {2} -recip {3}" - "| {4} pkcs12 -nodes -password pass: -out {5}" - "").format(self.openssl_cmd, P7M_FILE_NAME, - TRANSPORT_PRV_FILE_NAME, TRANSPORT_CERT_FILE_NAME, - self.openssl_cmd, PEM_FILE_NAME) - shellutil.run(cmd) + 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 = [] @@ -960,7 +993,7 @@ class Certificates(object): thumbprints = {} index = 0 v1_cert_list = [] - with open(PEM_FILE_NAME) as pem: + with open(pem_file) as pem: for line in pem.readlines(): buf.append(line) if re.match(r'[-]+BEGIN.*KEY[-]+', line): @@ -969,15 +1002,15 @@ class Certificates(object): begin_crt = True elif re.match(r'[-]+END.*KEY[-]+', line): tmp_file = self.write_to_tmp_file(index, 'prv', buf) - pub = OSUTIL.get_pubkey_from_prv(tmp_file) + 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 = OSUTIL.get_pubkey_from_crt(tmp_file) - thumbprint = OSUTIL.get_thumbprint_from_crt(tmp_file) + 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) @@ -985,7 +1018,7 @@ class Certificates(object): "name":None, "thumbprint":thumbprint }) - os.rename(tmp_file, os.path.join(self.lib_dir, crt)) + os.rename(tmp_file, os.path.join(conf.get_lib_dir(), crt)) buf = [] index += 1 begin_crt = False @@ -996,7 +1029,7 @@ class Certificates(object): if thumbprint: tmp_file = prvs[pubkey] prv = "{0}.prv".format(thumbprint) - os.rename(tmp_file, os.path.join(self.lib_dir, prv)) + os.rename(tmp_file, os.path.join(conf.get_lib_dir(), prv)) for v1_cert in v1_cert_list: cert = Cert() @@ -1004,7 +1037,8 @@ class Certificates(object): self.cert_list.certificates.append(cert) def write_to_tmp_file(self, index, suffix, buf): - file_name = os.path.join(self.lib_dir, "{0}.{1}".format(index, suffix)) + 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 @@ -1090,7 +1124,7 @@ class ExtensionsConfig(object): ext.name = ext_handler.name ext.sequenceNumber = seqNo ext.publicSettings = handler_settings.get("publicSettings") - ext.privateSettings = handler_settings.get("protectedSettings") + ext.protectedSettings = handler_settings.get("protectedSettings") thumbprint = handler_settings.get("protectedSettingsCertThumbprint") ext.certificateThumbprint = thumbprint ext_handler.properties.extensions.append(ext) diff --git a/azurelinuxagent/utils/cryptutil.py b/azurelinuxagent/utils/cryptutil.py new file mode 100644 index 0000000..5ee5637 --- /dev/null +++ b/azurelinuxagent/utils/cryptutil.py @@ -0,0 +1,121 @@ +# 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) + diff --git a/azurelinuxagent/utils/fileutil.py b/azurelinuxagent/utils/fileutil.py index 08592bc..5369a7c 100644 --- a/azurelinuxagent/utils/fileutil.py +++ b/azurelinuxagent/utils/fileutil.py @@ -27,7 +27,7 @@ import shutil import pwd import tempfile import azurelinuxagent.logger as logger -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr import azurelinuxagent.utils.textutil as textutil def read_file(filepath, asbin=False, remove_bom=False, encoding='utf-8'): @@ -46,7 +46,7 @@ def read_file(filepath, asbin=False, remove_bom=False, encoding='utf-8'): if remove_bom: #Remove bom on bytes data before it is converted into string. data = textutil.remove_bom(data) - data = text(data, encoding=encoding) + data = ustr(data, encoding=encoding) return data def write_file(filepath, contents, asbin=False, encoding='utf-8', append=False): @@ -100,6 +100,7 @@ def replace_file(filepath, contents): return 1 return 0 + def base_name(path): head, tail = os.path.split(path) return tail @@ -151,7 +152,7 @@ def rm_dirs(*args): def update_conf_file(path, line_start, val, chk_err=False): conf = [] if not os.path.isfile(path) and chk_err: - raise Exception("Can't find config file:{0}".format(path)) + 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) diff --git a/azurelinuxagent/utils/osutil.py b/azurelinuxagent/utils/osutil.py deleted file mode 100644 index 9de47e7..0000000 --- a/azurelinuxagent/utils/osutil.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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+ -# - -""" -Load OSUtil implementation from azurelinuxagent.distro -""" -from azurelinuxagent.distro.default.osutil import OSUtilError -import azurelinuxagent.distro.loader as loader - -OSUTIL = loader.get_osutil() - diff --git a/azurelinuxagent/utils/restutil.py b/azurelinuxagent/utils/restutil.py index 2acfa57..2e8b0be 100644 --- a/azurelinuxagent/utils/restutil.py +++ b/azurelinuxagent/utils/restutil.py @@ -21,8 +21,9 @@ import time import platform import os import subprocess -import azurelinuxagent.logger as logger import azurelinuxagent.conf as conf +import azurelinuxagent.logger as logger +from azurelinuxagent.exception import HttpError from azurelinuxagent.future import httpclient, urlparse """ @@ -31,9 +32,6 @@ REST api util functions RETRY_WAITING_INTERVAL = 10 -class HttpError(Exception): - pass - def _parse_url(url): o = urlparse(url) rel_uri = o.path @@ -51,8 +49,8 @@ def get_http_proxy(): Get http_proxy and https_proxy from environment variables. Username and password is not supported now. """ - host = conf.get("HttpProxy.Host", None) - port = conf.get("HttpProxy.Port", None) + 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, @@ -61,7 +59,7 @@ def _http_request(method, host, rel_uri, port=None, data=None, secure=False, if secure: port = 443 if port is None else port if proxy_host is not None and proxy_port is not None: - conn = httpclient.HTTPSConnection(proxy_host, proxy_port) + conn = 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) @@ -71,7 +69,7 @@ def _http_request(method, host, rel_uri, port=None, data=None, secure=False, 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) + 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: @@ -128,8 +126,12 @@ def http_request(method, url, data, headers=None, max_retry=3, chk_proxy=False): if retry < max_retry - 1: logger.info("Retry={0}, {1} {2}", retry, method, url) time.sleep(RETRY_WAITING_INTERVAL) - - raise HttpError("HTTP Err: {0} {1}".format(method, url)) + + 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, diff --git a/azurelinuxagent/utils/shellutil.py b/azurelinuxagent/utils/shellutil.py index 372c78a..98871a1 100644 --- a/azurelinuxagent/utils/shellutil.py +++ b/azurelinuxagent/utils/shellutil.py @@ -20,7 +20,7 @@ import platform import os import subprocess -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr import azurelinuxagent.logger as logger if not hasattr(subprocess,'check_output'): @@ -75,9 +75,9 @@ def run_get_output(cmd, chk_err=True, log_cmd=True): logger.verb(u"run cmd '{0}'", cmd) try: output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True) - output = text(output, encoding='utf-8', errors="backslashreplace") + output = ustr(output, encoding='utf-8', errors="backslashreplace") except subprocess.CalledProcessError as e : - output = text(e.output, encoding='utf-8', errors="backslashreplace") + output = ustr(e.output, encoding='utf-8', errors="backslashreplace") if chk_err: if log_cmd: logger.error(u"run cmd '{0}' failed", e.cmd) diff --git a/azurelinuxagent/utils/textutil.py b/azurelinuxagent/utils/textutil.py index e0f1395..851f98a 100644 --- a/azurelinuxagent/utils/textutil.py +++ b/azurelinuxagent/utils/textutil.py @@ -224,5 +224,13 @@ def gen_password_hash(password, crypt_id, 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 diff --git a/bin/waagent2.0 b/bin/waagent2.0 index 94c8ac8..673a04c 100755..100644 --- a/bin/waagent2.0 +++ b/bin/waagent2.0 @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Microsoft Azure Linux Agent +# Azure Linux Agent # # Copyright 2015 Microsoft Corporation # @@ -23,6 +23,8 @@ # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx # +import crypt +import random import array import base64 import httplib @@ -50,6 +52,7 @@ 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): @@ -79,8 +82,8 @@ if not hasattr(subprocess,'check_output'): subprocess.CalledProcessError=CalledProcessError GuestAgentName = "WALinuxAgent" -GuestAgentLongName = "Microsoft Azure Linux Agent" -GuestAgentVersion = "WALinuxAgent-2.0.15-pre" +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 @@ -106,7 +109,7 @@ HandlerStatusToAggStatus = {"installed":"Installing", "enabled":"Ready", "uninta WaagentConf = """\ # -# Microsoft Azure Linux Agent Configuration +# Azure Linux Agent Configuration # Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status @@ -126,7 +129,7 @@ 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 Microsoft Azure. +LBProbeResponder=y # Respond to load balancer probes if requested by Azure. Logs.Verbose=n # Enable verbose logs @@ -173,8 +176,8 @@ class AbstractDistro(object): self.ssh_config_file='/etc/ssh/sshd_config' self.hostname_file_path='/etc/hostname' self.dhcp_client_name='dhclient' - self.requiredDeps = [ 'route', 'shutdown', 'ssh-keygen', 'useradd', - 'openssl', 'sfdisk', 'fdisk', 'mkfs', 'chpasswd', + 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' @@ -187,6 +190,7 @@ class AbstractDistro(object): 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): @@ -341,8 +345,35 @@ class AbstractDistro(object): return 0 def changePass(self,user,password): - return RunSendStdin("chpasswd",(user + ":" + password + "\n")) + 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() @@ -438,8 +469,15 @@ class AbstractDistro(object): def GetInterfaceName(self): return GetFirstActiveNetworkInterfaceNonLoopback()[0] - def RestartInterface(self, iface): - Run("ifdown " + iface + " && ifup " + iface) + 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) @@ -522,6 +560,7 @@ class AbstractDistro(object): 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: @@ -645,6 +684,13 @@ class AbstractDistro(object): 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 @@ -656,7 +702,7 @@ command=/usr/sbin/waagent pidfile=/var/run/waagent.pid command_args=-daemon command_background=true -name="Microsoft Azure Linux Agent" +name="Azure Linux Agent" depend() { @@ -725,7 +771,7 @@ class gentooDistro(AbstractDistro): suse_init_file = """\ #! /bin/sh # -# Microsoft Azure Linux Agent sysV init script +# Azure Linux Agent sysV init script # # Copyright 2013 Microsoft Corporation # Copyright SUSE LLC @@ -751,12 +797,12 @@ suse_init_file = """\ # System startup script for the waagent # ### BEGIN INIT INFO -# Provides: MicrosoftAzureLinuxAgent +# Provides: AzureLinuxAgent # Required-Start: $network sshd # Required-Stop: $network sshd # Default-Start: 3 5 # Default-Stop: 0 1 2 6 -# Description: Start the MicrosoftAzureLinuxAgent +# Description: Start the AzureLinuxAgent ### END INIT INFO PYTHON=/usr/bin/python @@ -789,14 +835,14 @@ rc_reset case "$1" in start) - echo -n "Starting MicrosoftAzureLinuxAgent" + 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 MicrosoftAzureLinuxAgent" + 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} @@ -820,7 +866,7 @@ case "$1" in rc_status ;; status) - echo -n "Checking for service MicrosoftAzureLinuxAgent " + echo -n "Checking for service AzureLinuxAgent " ## Check status with checkproc(8), if process is running ## checkproc will return with exit status 0. @@ -902,17 +948,17 @@ class SuSEDistro(AbstractDistro): redhat_init_file= """\ #!/bin/bash # -# Init file for MicrosoftAzureLinuxAgent. +# Init file for AzureLinuxAgent. # # chkconfig: 2345 60 80 -# description: MicrosoftAzureLinuxAgent +# description: AzureLinuxAgent # # source function library . /etc/rc.d/init.d/functions RETVAL=0 -FriendlyName="MicrosoftAzureLinuxAgent" +FriendlyName="AzureLinuxAgent" WAZD_BIN=/usr/sbin/waagent start() @@ -1014,7 +1060,24 @@ class redhatDistro(AbstractDistro): 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 @@ -1028,6 +1091,19 @@ class centosDistro(redhatDistro): 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 @@ -1150,7 +1226,7 @@ class CoreOSDistro(AbstractDistro): else: Log("CreateAccount: " + user + " already exists. Will update password.") if password != None: - RunSendStdin("chpasswd", user + ":" + password + "\n") + self.changePass(user, password) try: if password == None: SetFileContents("/etc/sudoers.d/waagent", user + " ALL = (ALL) NOPASSWD: ALL\n") @@ -1194,15 +1270,15 @@ class CoreOSDistro(AbstractDistro): debian_init_file = """\ #!/bin/sh ### BEGIN INIT INFO -# Provides: MicrosoftAzureLinuxAgent +# 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: MicrosoftAzureLinuxAgent -# Description: MicrosoftAzureLinuxAgent +# Short-Description: AzureLinuxAgent +# Description: AzureLinuxAgent ### END INIT INFO . /lib/lsb/init-functions @@ -1213,7 +1289,7 @@ WAZD_PID=/var/run/waagent.pid case "$1" in start) - log_begin_msg "Starting MicrosoftAzureLinuxAgent..." + log_begin_msg "Starting AzureLinuxAgent..." pid=$( pidofproc $WAZD_BIN ) if [ -n "$pid" ] ; then log_begin_msg "Already running." @@ -1225,7 +1301,7 @@ case "$1" in ;; stop) - log_begin_msg "Stopping MicrosoftAzureLinuxAgent..." + log_begin_msg "Stopping AzureLinuxAgent..." start-stop-daemon --stop --quiet --oknodo --pidfile $WAZD_PID ret=$? rm -f $WAZD_PID @@ -1350,7 +1426,7 @@ class KaliDistro(debianDistro): # UbuntuDistro ############################################################ ubuntu_upstart_file = """\ -#walinuxagent - start Microsoft Azure agent +#walinuxagent - start Azure agent description "walinuxagent" author "Ben Howard <ben.howard@canonical.com>" @@ -1474,7 +1550,7 @@ class LinuxMintDistro(UbuntuDistro): ############################################################ fedora_systemd_service = """\ [Unit] -Description=Microsoft Azure Linux Agent +Description=Azure Linux Agent After=network.target After=sshd.service ConditionFileIsExecutable=/usr/sbin/waagent @@ -1599,7 +1675,7 @@ class fedoraDistro(redhatDistro): ############################################################ FreeBSDWaagentConf = """\ # -# Microsoft Azure Linux Agent Configuration +# Azure Linux Agent Configuration # Role.StateConsumer=None # Specified program is invoked with the argument "Ready" when we report ready status @@ -1619,7 +1695,7 @@ 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 Microsoft Azure. +LBProbeResponder=y # Respond to load balancer probes if requested by Azure. Logs.Verbose=n # Enable verbose logs @@ -1661,7 +1737,7 @@ __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() +Config=waagent.ConfigurationProvider(None) format = Config.get("ResourceDisk.Format") if format == None or format.lower().startswith("n"): sys.exit(0) @@ -1764,7 +1840,7 @@ class FreeBSDDistro(AbstractDistro): return 0 def changePass(self,user,password): - return RunSendStdin("pw usermod " + user + " -h 0 ",password) + return RunSendStdin("pw usermod " + user + " -h 0 ",password, log_cmd=False) def load_ata_piix(self): return 0 @@ -2059,7 +2135,13 @@ class FreeBSDDistro(AbstractDistro): 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 ############################################################ @@ -2180,41 +2262,43 @@ def Run(cmd,chk_err=True): retcode,out=RunGetOutput(cmd,chk_err) return retcode -def RunGetOutput(cmd,chk_err=True): +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 """ - LogIfVerbose(cmd) + if log_cmd: + LogIfVerbose(cmd) try: output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True) except subprocess.CalledProcessError,e : - if chk_err : + 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): +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 """ - LogIfVerbose(cmd+input) + 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 : + 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: + 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')) @@ -2296,7 +2380,7 @@ def CreateAccount(user, password, expiration, thumbprint): else: Log("CreateAccount: " + user + " already exists. Will update password.") if password != None: - RunSendStdin("chpasswd",(user + ":" + password + "\n")) + MyDistro.changePass(user, password) try: # for older distros create sudoers.d if not os.path.isdir('/etc/sudoers.d/'): @@ -2468,7 +2552,6 @@ class Logger(object): message = filter(lambda x : x in string.printable, message) C.write(message.encode('ascii','ignore') + "\n") except IOError, e: - print e pass def Log(self,message): @@ -2635,6 +2718,53 @@ def DeviceForIdePort(n): 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 '<Location>' /var/lib/waagent/ExtensionsConfig* --no-filename | sed 's/<Location>//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. @@ -2697,20 +2827,20 @@ class Util(object): if secure: port = 443 if port is None else port if proxyHost is not None and proxyPort is not None: - conn = httplib.HTTPSConnection(proxyHost, proxyPort) + conn = 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) + 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) + 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) + conn = httplib.HTTPConnection(host, port, timeout=10) if headers == None: conn.request(method, path, data) else: @@ -2849,8 +2979,12 @@ class Util(object): "Content-Type": "text/xml; charset=utf-8", "x-ms-version": ProtocolVersion } - return self.HttpPost(url, data=data, headers=headers, - maxRetry=maxRetry, chkProxy=chkProxy) + 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" @@ -2883,6 +3017,8 @@ def PutBlockBlob(url, data): }, chkProxy=True) if ret is None: Error("Failed to upload block blob for status.") + return -1 + return 0 def PutPageBlob(url, data): restutil = Util() @@ -2899,7 +3035,7 @@ def PutPageBlob(url, data): }, chkProxy=True) if ret is None: Error("Failed to clean up page blob for status") - return + return -1 if url.index('?') < 0: url = "{0}?comp=page".format(url) @@ -2927,8 +3063,9 @@ def PutPageBlob(url, data): }, chkProxy=True) if ret is None: Error("Failed to upload page blob for status") - return + return -1 start = end + return 0 def UploadStatusBlob(url, data): LogIfVerbose("Upload status blob") @@ -2936,12 +3073,12 @@ def UploadStatusBlob(url, data): blobType = GetBlobType(url) if blobType == "BlockBlob": - PutBlockBlob(url, data) + return PutBlockBlob(url, data) elif blobType == "PageBlob": - PutPageBlob(url, data) + return PutPageBlob(url, data) else: Error("Unknown blob type: {0}".format(blobType)) - return None + return -1 class TCPHandler(SocketServer.BaseRequestHandler): """ @@ -3529,15 +3666,29 @@ class ExtensionsConfig(object): # if the same plugin exists and the version is newer or # does not exist then download and unzip the new plugin plg_dir=None - for root, dirs, files in os.walk(LibDir): - for d in dirs: - if name in d: - plg_dir=os.path.join(root,d) - if plg_dir != None: - break - if plg_dir != None : - previous_version=plg_dir.rsplit('-')[-1] - if plg_dir == None or version > previous_version : + + 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) @@ -3653,7 +3804,7 @@ class ExtensionsConfig(object): cmd = '' getcmd='installCommand' - if plg_dir != None and previous_version != None and version > previous_version : + 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' @@ -3666,6 +3817,23 @@ class ExtensionsConfig(object): 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': @@ -3687,6 +3855,16 @@ class ExtensionsConfig(object): 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 : @@ -3902,7 +4080,7 @@ class ExtensionsConfig(object): incarnation=self.Extensions[0].getAttribute("goalStateIncarnation") except: Error('Error parsing ExtensionsConfig. Unable to send status reports') - return None + return -1 status='' statuses='' for p in self.Plugins: @@ -3940,11 +4118,10 @@ class ExtensionsConfig(object): uri=GetNodeTextData(self.Extensions[0].getElementsByTagName("StatusUploadBlob")[0]).replace('&','&') except: Error('Error parsing ExtensionsConfig. Unable to send status reports') - return None + return -1 - UploadStatusBlob(uri, status.encode("utf-8")) LogIfVerbose('Status report '+status+' sent to ' + uri) - return True + return UploadStatusBlob(uri, status.encode("utf-8")) def GetCurrentSequenceNumber(self, plugin_base_dir): """ @@ -4430,7 +4607,7 @@ class OvfEnv(object): if len(CDSection) > 0 : self.CustomData=GetNodeTextData(CDSection[0]) if len(self.CustomData)>0: - SetFileContents(LibDir + '/CustomData', MyDistro.translateCustomData(self.CustomData)) + SetFileContents(LibDir + '/CustomData', bytearray(MyDistro.translateCustomData(self.CustomData))) Log('Wrote ' + LibDir + '/CustomData') else : Error('<CustomData> contains no data!') @@ -4999,7 +5176,16 @@ class Agent(Util): net = self.IntegerToIpAddressV4String(net) mask = self.IntegerToIpAddressV4String(mask) gateway = self.IntegerToIpAddressV4String(gateway) - Run("/sbin/route add -net " + net + " netmask " + mask + " gw " + gateway,chk_err=False) + 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): """ @@ -5082,11 +5268,11 @@ class Agent(Util): gateway = self.UnpackBigEndian(receiveBuffer, i + 2, 4) IpAddress = self.IntegerToIpAddressV4String(gateway) if option == 3: - self.RouteAdd(0, 0, gateway) + self.SetDefaultGateway(gateway) name = "DefaultGateway" else: endpoint = IpAddress - name = "Microsoft Azure wire protocol endpoint" + name = "Azure wire protocol endpoint" LogIfVerbose(name + ": " + IpAddress + " at " + hex(i)) else: Error("HandleDhcpResponse: Data too small for option " + str(option)) @@ -5098,7 +5284,7 @@ class Agent(Util): def DoDhcpWork(self): """ Discover the wire server via DHCP option 245. - And workaround incompatibility with Microsoft Azure DHCP servers. + And workaround incompatibility with Azure DHCP servers. """ ShortSleep = False # Sleep 1 second before retrying DHCP queries. ifname=None @@ -5212,7 +5398,7 @@ class Agent(Util): if not goalStateXml: Error("UpdateGoalState failed.") return - Log("Retrieved GoalState from Microsoft Azure Fabric.") + Log("Retrieved GoalState from Azure Fabric.") self.GoalState = GoalState(self).Parse(goalStateXml) return self.GoalState @@ -5503,6 +5689,8 @@ class Agent(Util): """ 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='' @@ -5524,15 +5712,16 @@ class Agent(Util): except: pass - Log("Probing for Microsoft Azure environment.") + Log("Probing for Azure environment.") self.Endpoint = self.DoDhcpWork() - if self.Endpoint == None: - Log("Microsoft Azure environment not detected.") - while True: - time.sleep(60) + 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 Microsoft Azure endpoint: " + self.Endpoint) + Log("Discovered Azure endpoint: " + self.Endpoint) if not self.CheckVersions(): Error("Agent.CheckVersions failed") sys.exit(1) @@ -5647,12 +5836,23 @@ class Agent(Util): 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 : - goalState.ExtensionsConfig.ReportHandlerStatus() + 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() @@ -5987,11 +6187,6 @@ def main(): global LinuxDistro LinuxDistro=DistInfo()[0] - #The platform.py lib has issue with detecting oracle linux distribution. - #Merge the following patch provided by oracle as a temparory fix. - if os.path.exists("/etc/oracle-release"): - LinuxDistro="Oracle Linux" - global MyDistro MyDistro=GetMyDistro() if MyDistro == None : @@ -6051,18 +6246,21 @@ def main(): sys.exit(Usage()) global modloaded modloaded = False - try: - SwitchCwd() - Log(GuestAgentLongName + " Version: " + GuestAgentVersion) - if IsLinux(): - Log("Linux Distribution Detected : " + LinuxDistro) - global WaAgent - WaAgent = Agent() - WaAgent.Run() - except Exception, e: - Error(traceback.format_exc()) - Error("Exception: " + str(e)) - sys.exit(1) + + 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() diff --git a/config/coreos/waagent.conf b/config/coreos/waagent.conf new file mode 100644 index 0000000..5af0baf --- /dev/null +++ b/config/coreos/waagent.conf @@ -0,0 +1,91 @@ +# +# 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 diff --git a/config/suse/waagent.conf b/config/suse/waagent.conf index b4f4798..7cd6d37 100644 --- a/config/suse/waagent.conf +++ b/config/suse/waagent.conf @@ -40,6 +40,9 @@ Provisioning.ExecuteCustomData=n # 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 diff --git a/config/ubuntu/waagent.conf b/config/ubuntu/waagent.conf index db29d80..ee60045 100644 --- a/config/ubuntu/waagent.conf +++ b/config/ubuntu/waagent.conf @@ -40,6 +40,9 @@ Provisioning.ExecuteCustomData=n # 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 diff --git a/config/waagent.conf b/config/waagent.conf index 639e723..e808117 100644 --- a/config/waagent.conf +++ b/config/waagent.conf @@ -40,6 +40,9 @@ Provisioning.ExecuteCustomData=n # 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 @@ -71,3 +74,18 @@ OS.OpensslPath=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 diff --git a/debian/changelog b/debian/changelog index 0434259..8c6cf59 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +walinuxagent (2.1.3-0ubuntu1) xenial; urgency=medium + + * New upstream release (LP: #1543359): + - Bug fixes for extension handling + - Feature enablement for AzureStack. + + -- Ben Howard <ben.howard@ubuntu.com> Mon, 08 Feb 2016 16:33:07 -0700 + walinuxagent (2.1.2-0ubuntu2) xenial; urgency=medium * Added udev rule to give verbose logging on the serial console. diff --git a/debian/docs b/debian/docs index 56b0555..c401623 100644 --- a/debian/docs +++ b/debian/docs @@ -1,4 +1,6 @@ debian/99-cloud-init-disable-diskprovisioning.conf NOTICE LICENSE-2.0.txt -README +README.md +MANIFEST +Changelog diff --git a/debian/patches/disable-udev-rules-removal.patch b/debian/patches/disable-udev-rules-removal.patch deleted file mode 100644 index 3f3adfb..0000000 --- a/debian/patches/disable-udev-rules-removal.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/azurelinuxagent/distro/ubuntu/osutil.py -+++ b/azurelinuxagent/distro/ubuntu/osutil.py -@@ -44,6 +44,9 @@ - def start_agent_service(self): - return shellutil.run("service walinuxagent start", chk_err=False) - -+ def remove_rules_files(self, *args): -+ return -+ - class Ubuntu1204OSUtil(Ubuntu14xOSUtil): - def __init__(self): - super(Ubuntu1204OSUtil, self).__init__() diff --git a/debian/patches/disable_import_test.patch b/debian/patches/disable_import_test.patch index f7e6b64..cf3d167 100644 --- a/debian/patches/disable_import_test.patch +++ b/debian/patches/disable_import_test.patch @@ -1,20 +1,3 @@ ---- a/tests/test_import_waagent.py -+++ b/tests/test_import_waagent.py -@@ -28,13 +28,7 @@ - - class TestImportWAAgent(unittest.TestCase): - def test_import_waagent(self): -- agent_path = os.path.join(tools.parent, 'bin/waagent') -- if sys.version_info[0] == 2: -- waagent = imp.load_source('waagent', agent_path) -- self.assertNotEquals(None, waagent.LoggerInit) -- else: -- self.assertRaises(ImportError, imp.load_source, 'waagent', -- agent_path) -+ pass - - if __name__ == '__main__': - unittest.main() --- a/config/waagent.conf +++ b/config/waagent.conf @@ -14,13 +14,13 @@ @@ -34,8 +17,8 @@ # Supported values are "rsa", "dsa" and "ecdsa". Provisioning.SshHostKeyPairType=rsa -@@ -41,14 +41,14 @@ - #Provisioning.PasswordCryptSaltLength=10 +@@ -44,14 +44,14 @@ + Provisioning.AllowResetSysUser=n # Format if unformatted. If 'n', resource disk will not be mounted. -ResourceDisk.Format=y diff --git a/debian/patches/disable_rhel_tests.patch b/debian/patches/disable_rhel_tests.patch deleted file mode 100644 index f815ffe..0000000 --- a/debian/patches/disable_rhel_tests.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- a/tests/test_redhat.py -+++ b/tests/test_redhat.py -@@ -41,9 +41,7 @@ - - class TestRedhat(unittest.TestCase): - def test_RsaPublicKeyToSshRsa(self): -- OSUtil = RedhatOSUtil() -- ssh_rsa_pubkey = OSUtil.asn1_to_ssh_rsa(test_pubkey) -- self.assertEquals(expected_ssh_rsa_pubkey, ssh_rsa_pubkey) -+ pass - - if __name__ == '__main__': - unittest.main() diff --git a/debian/patches/disable_udev_overrides.patch b/debian/patches/disable_udev_overrides.patch new file mode 100644 index 0000000..e0bb607 --- /dev/null +++ b/debian/patches/disable_udev_overrides.patch @@ -0,0 +1,37 @@ +--- a/bin/waagent2.0 ++++ b/bin/waagent2.0 +@@ -95,8 +95,7 @@ + VMM_STARTUP_SCRIPT_NAME='install' + VMM_CONFIG_FILE_NAME='linuxosconfiguration.xml' + global RulesFiles +-RulesFiles = [ "/lib/udev/rules.d/75-persistent-net-generator.rules", +- "/etc/udev/rules.d/70-persistent-net.rules" ] ++RulesFiles = [ ] + VarLibDhcpDirectories = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"] + EtcDhcpClientConfFiles = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"] + global LibDir +--- a/azurelinuxagent/distro/ubuntu/osutil.py ++++ b/azurelinuxagent/distro/ubuntu/osutil.py +@@ -44,6 +44,12 @@ + def start_agent_service(self): + return shellutil.run("service walinuxagent start", chk_err=False) + ++ def remove_rules_files(self, rules_files=""): ++ pass ++ ++ def restore_rules_files(self, rules_files=""): ++ pass ++ + class Ubuntu12OSUtil(Ubuntu14OSUtil): + def __init__(self): + super(Ubuntu12OSUtil, self).__init__() +@@ -67,9 +73,3 @@ + 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 diff --git a/debian/patches/series b/debian/patches/series index d32f0ed..05993c8 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,4 +1,3 @@ cloud-init-default-cfg.patch -disable-udev-rules-removal.patch -disable_rhel_tests.patch disable_import_test.patch +disable_udev_overrides.patch diff --git a/debian/rules b/debian/rules index 06d0f8c..8ec7cb1 100755 --- a/debian/rules +++ b/debian/rules @@ -5,8 +5,8 @@ ORIG_SRC=https://github.com/WindowsAzure/WALinuxAgent get-packaged-orig-source: git clone --separate-git-dir=walinuxagent.checkout \ $(ORIG_SRC) orig_source - git checkout -b tags/$(DEB_VERSION) - git archive --format=tar.gz WALinuxAgent-$(DEB_VERSION) \ + git checkout -b tags/v$(DEB_VERSION) + git archive --format=tar.gz v$(DEB_VERSION) \ -o walinuxagent_$(DEB_VERSION).orig.tar.gz rm -rf walinuxagent.checkout rm -rf orig_source @@ -22,7 +22,7 @@ from azurelinuxagent.metadata import AGENT_NAME, AGENT_VERSION, \ AGENT_DESCRIPTION, \ DISTRO_NAME, DISTRO_VERSION, DISTRO_FULL_NAME -import azurelinuxagent.agent as agent +from azurelinuxagent.agent import Agent import setuptools from setuptools import find_packages from setuptools.command.install import install as _install @@ -72,7 +72,8 @@ def get_data_files(name, version, fullname): elif name == 'coreos': set_bin_files(data_files, dest="/usr/share/oem/bin") - set_conf_files(data_files, dest="/usr/share/oem") + 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") @@ -149,7 +150,7 @@ class install(_install): def run(self): _install.run(self) if self.register_service: - agent.register_service() + Agent(False).register_service() setuptools.setup(name=AGENT_NAME, version=AGENT_VERSION, diff --git a/tests/dhcp b/tests/data/dhcp Binary files differindex 8c9d127..8c9d127 100644 --- a/tests/dhcp +++ b/tests/data/dhcp diff --git a/tests/data/ext/event.xml b/tests/data/ext/event.xml new file mode 100755 index 0000000..436de44 --- /dev/null +++ b/tests/data/ext/event.xml @@ -0,0 +1 @@ +<Data><Provider id="69B669B9-4AF8-4C50-BDC4-6006FA76E975"/><Event id="1"/><Param Name="OperationSuccess" Value="True" T="mt:bool" /><Param Name="Processors" Value="0" T="mt:uint64" /><Param Name="OpcodeName" Value="" T="mt:wstr" /><Param Name="Version" Value="1.4.1.0" T="mt:wstr" /><Param Name="RoleName" Value="" T="mt:wstr" /><Param Name="IsInternal" Value="False" T="mt:bool" /><Param Name="RAM" Value="0" T="mt:uint64" /><Param Name="ExecutionMode" Value="IAAS" T="mt:wstr" /><Param Name="RoleInstanceName" Value="" T="mt:wstr" /><Param Name="Name" Value="CustomScript" T="mt:wstr" /><Param Name="Message" Value="(01302)Script is finished. ---stdout--- hello ---errout--- " T="mt:wstr" /><Param Name="KeywordName" Value="" T="mt:wstr" /><Param Name="TaskName" Value="" T="mt:wstr" /><Param Name="OSVersion" Value="" T="mt:wstr" /><Param Name="Operation" Value="RunScript" T="mt:wstr" /><Param Name="ContainerId" Value="" T="mt:wstr" /><Param Name="GAVersion" Value="" T="mt:wstr" /><Param Name="TenantName" Value="" T="mt:wstr" /><Param Name="Duration" Value="0" T="mt:uint64" /><Param Name="ExtensionType" Value="" T="mt:wstr" /></Data>
\ No newline at end of file diff --git a/tests/data/ext/sample_ext.zip b/tests/data/ext/sample_ext.zip Binary files differnew file mode 100644 index 0000000..08cfaf7 --- /dev/null +++ b/tests/data/ext/sample_ext.zip diff --git a/tests/data/ext/sample_ext/HandlerManifest.json b/tests/data/ext/sample_ext/HandlerManifest.json new file mode 100644 index 0000000..9890d0c --- /dev/null +++ b/tests/data/ext/sample_ext/HandlerManifest.json @@ -0,0 +1,14 @@ +[{ + "name": "ExampleHandlerLinux", + "version": 1.0, + "handlerManifest": { + "installCommand": "sample.py -install", + "uninstallCommand": "sample.py -uninstall", + "updateCommand": "sample.py -update", + "enableCommand": "sample.py -enable", + "disableCommand": "sample.py -disable", + "rebootAfterInstall": false, + "reportHeartbeat": false + } +}] + diff --git a/tests/data/ext/sample_ext/sample.py b/tests/data/ext/sample_ext/sample.py new file mode 100755 index 0000000..7107ac2 --- /dev/null +++ b/tests/data/ext/sample_ext/sample.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import os + +def get_seq(): + latest_seq = -1; + config_dir = os.path.join(os.getcwd(), "config") + if os.path.isdir(config_dir): + for item in os.listdir(config_dir): + item_path = os.path.join(config_dir, item) + if os.path.isfile(item_path): + seperator = item.rfind(".") + if seperator > 0 and item[seperator + 1:] == "settings": + seq = int(item[0: seperator]) + if seq > latest_seq: + latest_seq = seq + return latest_seq + + +succeed_status = """ +[{ + "status": { + "status": "success" + } +}] +""" + +if __name__ == "__main__": + seq = get_seq() + if seq >= 0: + status_file = os.path.join(os.getcwd(), "status", "{0}.status".format(seq)) + with open(status_file, "w+") as status: + status.write(succeed_status) diff --git a/tests/data/metadata/certificates.json b/tests/data/metadata/certificates.json new file mode 100644 index 0000000..4db7a06 --- /dev/null +++ b/tests/data/metadata/certificates.json @@ -0,0 +1,7 @@ +{ + "certificates":[{ + "name":"foo", + "thumbprint":"bar", + "certificateDataUri":"baz" + }] +} diff --git a/tests/data/metadata/ext_handler_pkgs.json b/tests/data/metadata/ext_handler_pkgs.json new file mode 100644 index 0000000..869c949 --- /dev/null +++ b/tests/data/metadata/ext_handler_pkgs.json @@ -0,0 +1,10 @@ +{ + "versions": [{ + "version":"1.3.0.0", + "uris":[{ + "uri":"http://localhost/foo1" + },{ + "uri":"http://localhost/foo2" + }] + }] +} diff --git a/tests/data/metadata/ext_handlers.json b/tests/data/metadata/ext_handlers.json new file mode 100644 index 0000000..68efc19 --- /dev/null +++ b/tests/data/metadata/ext_handlers.json @@ -0,0 +1,19 @@ +[{ + "name":"foo", + "properties":{ + "version":"1.3.0.0", + "upgradePolicy": "manual", + "state": "enabled", + "extensions":[{ + "name":"baz", + "sequenceNumber":0, + "publicSettings":{ + "commandToExecute": "echo 123", + "uris":[] + } + }] + }, + "versionUris":[{ + "uri":"http://ext_handler_pkgs/versionUri" + }] +}] diff --git a/tests/data/metadata/ext_handlers_no_ext.json b/tests/data/metadata/ext_handlers_no_ext.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/tests/data/metadata/ext_handlers_no_ext.json @@ -0,0 +1 @@ +[] diff --git a/tests/data/metadata/identity.json b/tests/data/metadata/identity.json new file mode 100644 index 0000000..e6e2273 --- /dev/null +++ b/tests/data/metadata/identity.json @@ -0,0 +1,4 @@ +{ + "vmName":"foo", + "subscriptionId":"bar" +} diff --git a/tests/data/ovf-env.xml b/tests/data/ovf-env.xml new file mode 100644 index 0000000..fc94943 --- /dev/null +++ b/tests/data/ovf-env.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <wa:ProvisioningSection> + <wa:Version>1.0</wa:Version> + <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> + <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType> + <HostName>HostName</HostName> + <UserName>UserName</UserName> + <UserPassword>UserPassword</UserPassword> + <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication> + <SSH> + <PublicKeys> + <PublicKey> + <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> + <Path>$HOME/UserName/.ssh/authorized_keys</Path> + <Value>ssh-rsa AAAANOTAREALKEY== foo@bar.local</Value> + </PublicKey> + </PublicKeys> + <KeyPairs> + <KeyPair> + <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> + <Path>$HOME/UserName/.ssh/id_rsa</Path> + </KeyPair> + </KeyPairs> + </SSH> + <CustomData>CustomData</CustomData> + </LinuxProvisioningConfigurationSet> + </wa:ProvisioningSection> + </Environment> diff --git a/tests/data/wire/certs.xml b/tests/data/wire/certs.xml new file mode 100644 index 0000000..6717c30 --- /dev/null +++ b/tests/data/wire/certs.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd"> + <Version>2012-11-30</Version> + <Incarnation>12</Incarnation> + <Format>Pkcs7BlobWithPfxContents</Format> + <Data>MIINswYJKoZIhvcNAQcDoIINpDCCDaACAQIxggEwMIIBLAIBAoAUvyL+x6GkZXog +QNfsXRZAdD9lc7IwDQYJKoZIhvcNAQEBBQAEggEArhMPepD/RqwdPcHEVqvrdZid +72vXrOCuacRBhwlCGrNlg8oI+vbqmT6CSv6thDpet31ALUzsI4uQHq1EVfV1+pXy +NlYD1CKhBCoJxs2fSPU4rc8fv0qs5JAjnbtW7lhnrqFrXYcyBYjpURKfa9qMYBmj +NdijN+1T4E5qjxPr7zK5Dalp7Cgp9P2diH4Nax2nixotfek3MrEFBaiiegDd+7tE +ux685GWYPqB5Fn4OsDkkYOdb0OE2qzLRrnlCIiBCt8VubWH3kMEmSCxBwSJupmQ8 +sxCWk+sBPQ9gJSt2sIqfx/61F8Lpu6WzP+ZOnMLTUn2wLU/d1FN85HXmnQALzTCC +DGUGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIbEcBfddWPv+AggxAAOAt/kCXiffe +GeJG0P2K9Q18XZS6Rz7Xcz+Kp2PVgqHKRpPjjmB2ufsRO0pM4z/qkHTOdpfacB4h +gz912D9U04hC8mt0fqGNTvRNAFVFLsmo7KXc/a8vfZNrGWEnYn7y1WfP52pqA/Ei +SNFf0NVtMyqg5Gx+hZ/NpWAE5vcmRRdoYyWeg13lhlW96QUxf/W7vY/D5KpAGACI +ok79/XI4eJkbq3Dps0oO/difNcvdkE74EU/GPuL68yR0CdzzafbLxzV+B43TBRgP +jH1hCdRqaspjAaZL5LGfp1QUM8HZIKHuTze/+4dWzS1XR3/ix9q/2QFI7YCuXpuE +un3AFYXE4QX/6kcPklZwh9FqjSie3I5HtC1vczqYVjqT4oHrs8ktkZ7oAzeXaXTF +k6+JQNNa/IyJw24I1MR77q7HlHSSfhXX5cFjVCd/+SiA4HJQjJgeIuXZ+dXmSPdL +9xLbDbtppifFyNaXdlSzcsvepKy0WLF49RmbL7Bnd46ce/gdQ6Midwi2MTnUtapu +tHmu/iJtaUpwXXC0B93PHfAk7Y3SgeY4tl/gKzn9/x5SPAcHiNRtOsNBU8ZThzos +Wh41xMLZavmX8Yfm/XWtl4eU6xfhcRAbJQx7E1ymGEt7xGqyPV7hjqhoB9i3oR5N +itxHgf1+jw/cr7hob+Trd1hFqZO6ePMyWpqUg97G2ThJvWx6cv+KRtTlVA6/r/UH +gRGBArJKBlLpXO6dAHFztT3Y6DFThrus4RItcfA8rltfQcRm8d0nPb4lCa5kRbCx +iudq3djWtTIe64sfk8jsc6ahWYSovM+NmhbpxEUbZVWLVEcHAYOeMbKgXSu5sxNO +JZNeFdzZqDRRY9fGjYNS7DdNOmrMmWKH+KXuMCItpNZsZS/3W7QxAo3ugYLdUylU +Zg8H/BjUGZCGn1rEBAuQX78m0SZ1xHlgHSwJIOmxOJUDHLPHtThfbELY9ec14yi5 +so1aQwhhfhPvF+xuXBrVeTAfhFNYkf2uxcEp7+tgFAc5W0QfT9SBn5vSvIxv+dT4 +7B2Pg1l/zjdsM74g58lmRJeDoz4psAq+Uk7n3ImBhIku9qX632Q1hanjC8D4xM4W +sI/W0ADCuAbY7LmwMpAMdrGg//SJUnBftlom7C9VA3EVf8Eo+OZH9hze+gIgUq+E +iEUL5M4vOHK2ttsYrSkAt8MZzjQiTlDr1yzcg8fDIrqEAi5arjTPz0n2s0NFptNW +lRD+Xz6pCXrnRgR8YSWpxvq3EWSJbZkSEk/eOmah22sFnnBZpDqn9+UArAznXrRi +nYK9w38aMGPKM39ymG8kcbY7jmDZlRgGs2ab0Fdj1jl3CRo5IUatkOJwCEMd/tkB +eXLQ8hspJhpFnVNReX0oithVZir+j36epk9Yn8d1l+YlKmuynjunKl9fhmoq5Q6i +DFzdYpqBV+x9nVhnmPfGyrOkXvGL0X6vmXAEif/4JoOW4IZpyXjgn+VoCJUoae5J +Djl45Bcc2Phrn4HW4Gg/+pIwTFqqZZ2jFrznNdgeIxTGjBrVsyJUeO3BHI0mVLaq +jtjhTshYCI7mXOis9W3ic0RwE8rgdDXOYKHhLVw9c4094P/43utSVXE7UzbEhhLE +Ngb4H5UGrQmPTNbq40tMUMUCej3zIKuVOvamzeE0IwLhkjNrvKhCG1EUhX4uoJKu +DQ++3KVIVeYSv3+78Jfw9F3usAXxX1ICU74/La5DUNjU7DVodLDvCAy5y1jxP3Ic +If6m7aBYVjFSQAcD8PZPeIEl9W4ZnbwyBfSDd11P2a8JcZ7N99GiiH3yS1QgJnAO +g9XAgjT4Gcn7k4lHPHLULgijfiDSvt94Ga4/hse0F0akeZslVN/bygyib7x7Lzmq +JkepRianrvKHbatuxvcajt/d+dxCnr32Q1qCEc5fcgDsjvviRL2tKR0qhuYjn1zR +Vk/fRtYOmlaGBVzUXcjLRAg3gC9+Gy8KvXIDrnHxD+9Ob+DUP9fgbKqMeOzKcCK8 +NSfSQ+tQjBYD5Ku4zAPUQJoRGgx43vXzcl2Z2i3E2otpoH82Kx8S9WlVEUlTtBjQ +QIGM5aR0QUNt8z34t2KWRA8SpP54VzBmEPdwLnzna+PkrGKsKiHVn4K+HfjDp1uW +xyO8VjrolAOYosTPXMpNp2u/FoFxaAPTa/TvmKc0kQ3ED9/sGLS2twDnEccvHP+9 +zzrnzzN3T2CWuXveDpuyuAty3EoAid1nuC86WakSaAZoa8H2QoRgsrkkBCq+K/yl +4FO9wuP+ksZoVq3mEDQ9qv6H4JJEWurfkws3OqrA5gENcLmSUkZie4oqAxeOD4Hh +Zx4ckG5egQYr0PnOd2r7ZbIizv3MKT4RBrfOzrE6cvm9bJEzNWXdDyIxZ/kuoLA6 +zX7gGLdGhg7dqzKqnGtopLAsyM1b/utRtWxOTGO9K9lRxyX82oCVT9Yw0DwwA+cH +Gutg1w7JHrIAYEtY0ezHgxhqMGuuTyJMX9Vr0D+9DdMeBK7hVOeSnxkaQ0f9HvF6 +0XI/2OTIoBSCBpUXjpgsYt7m7n2rFJGJmtqgLAosCAkacHnHLwX0EnzBw3sdDU6Q +jFXUWIDd5xUsNkFDCbspLMFs22hjNI6f/GREwd23Q4ujF8pUIcxcfbs2myjbK45s +tsn/jrkxmKRgwCIeN/H7CM+4GXSkEGLWbiGCxWzWt9wW1F4M7NW9nho3D1Pi2LBL +1ByTmjfo/9u9haWrp53enDLJJbcaslfe+zvo3J70Nnzu3m3oJ3dmUxgJIstG10g3 +lhpUm1ynvx04IFkYJ3kr/QHG/xGS+yh/pMZlwcUSpjEgYFmjFHU4A1Ng4LGI4lnw +5wisay4J884xmDgGfK0sdVQyW5rExIg63yYXp2GskRdDdwvWlFUzPzGgCNXQU96A +ljZfjs2u4IiVCC3uVsNbGqCeSdAl9HC5xKuPNbw5yTxPkeRL1ouSdkBy7rvdFaFf +dMPw6sBRNW8ZFInlgOncR3+xT/rZxru87LCq+3hRN3kw3hvFldrW2QzZSksO759b +pJEP+4fxuG96Wq25fRmzHzE0bdJ+2qF3fp/hy4oRi+eVPa0vHdtkymE4OUFWftb6 ++P++JVOzZ4ZxYA8zyUoJb0YCaxL+Jp/QqiUiH8WZVmYZmswqR48sUUKr7TIvpNbY +6jEH6F7KiZCoWfKH12tUC69iRYx3UT/4Bmsgi3S4yUxfieYRMIwihtpP4i0O+OjB +/DPbb13qj8ZSfXJ+jmF2SRFfFG+2T7NJqm09JvT9UcslVd+vpUySNe9UAlpcvNGZ +2+j180ZU7YAgpwdVwdvqiJxkeVtAsIeqAvIXMFm1PDe7FJB0BiSVZdihB6cjnKBI +dv7Lc1tI2sQe7QSfk+gtionLrEnto+aXF5uVM5LMKi3gLElz7oXEIhn54OeEciB1 +cEmyX3Kb4HMRDMHyJxqJXwxm88RgC6RekoPvstu+AfX/NgSpRj5beaj9XkweJT3H +rKWhkjq4Ghsn1LoodxluMMHd61m47JyoqIP9PBKoW+Na0VUKIVHw9e9YeW0nY1Zi +5qFA/pHPAt9AbEilRay6NEm8P7TTlNo216amc8byPXanoNrqBYZQHhZ93A4yl6jy +RdpYskMivT+Sh1nhZAioKqqTZ3HiFR8hFGspAt5gJc4WLYevmxSicGa6AMyhrkvG +rvOSdjY6JY/NkxtcgeycBX5MLF7uDbhUeqittvmlcrVN6+V+2HIbCCrvtow9pcX9 +EkaaNttj5M0RzjQxogCG+S5TkhCy04YvKIkaGJFi8xO3icdlxgOrKD8lhtbf4UpR +cDuytl70JD95mSUWL53UYjeRf9OsLRJMHQOpS02japkMwCb/ngMCQuUXA8hGkBZL +Xw7RwwPuM1Lx8edMXn5C0E8UK5e0QmI/dVIl2aglXk2oBMBJbnyrbfUPm462SG6u +ke4gQKFmVy2rKICqSkh2DMr0NzeYEUjZ6KbmQcV7sKiFxQ0/ROk8eqkYYxGWUWJv +ylPF1OTLH0AIbGlFPLQO4lMPh05yznZTac4tmowADSHY9RCxad1BjBeine2pj48D +u36OnnuQIsedxt5YC+h1bs+mIvwMVsnMLidse38M/RayCDitEBvL0KeG3vWYzaAL +h0FCZGOW0ilVk8tTF5+XWtsQEp1PpclvkcBMkU3DtBUnlmPSKNfJT0iRr2T0sVW1 +h+249Wj0Bw== +</Data> +</CertificateFile> diff --git a/tests/data/wire/ext_conf.xml b/tests/data/wire/ext_conf.xml new file mode 100644 index 0000000..725271d --- /dev/null +++ b/tests/data/wire/ext_conf.xml @@ -0,0 +1,46 @@ +<Extensions version="1.0.0.0" goalStateIncarnation="9"><GuestAgentExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> + <GAFamilies> + <GAFamily> + <Name>Win8</Name> + <Uris> + <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + </Uris> + </GAFamily> + <GAFamily> + <Name>Win7</Name> + <Uris> + <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + </Uris> + </GAFamily> + </GAFamilies> +</GuestAgentExtension> +<Plugins> + <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="false" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" /> +</Plugins> +<PluginSettings> + <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.0"> + <RuntimeSettings seqNo="0">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK","publicSettings":{"foo":"bar"}}}]}</RuntimeSettings> + </Plugin> +</PluginSettings> +<StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&sp=rw&se=9999-01-01&sk=key1&sv=2014-02-14&sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions> + diff --git a/tests/data/wire/ext_conf_no_public.xml b/tests/data/wire/ext_conf_no_public.xml new file mode 100644 index 0000000..abbde80 --- /dev/null +++ b/tests/data/wire/ext_conf_no_public.xml @@ -0,0 +1,46 @@ +<Extensions version="1.0.0.0" goalStateIncarnation="9"><GuestAgentExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> + <GAFamilies> + <GAFamily> + <Name>Win8</Name> + <Uris> + <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + </Uris> + </GAFamily> + <GAFamily> + <Name>Win7</Name> + <Uris> + <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + </Uris> + </GAFamily> + </GAFamilies> +</GuestAgentExtension> +<Plugins> + <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="false" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" /> +</Plugins> +<PluginSettings> + <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.0"> + <RuntimeSettings seqNo="0">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK"}}]}</RuntimeSettings> + </Plugin> +</PluginSettings> +<StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&sp=rw&se=9999-01-01&sk=key1&sv=2014-02-14&sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions> + diff --git a/tests/data/wire/ext_conf_no_settings.xml b/tests/data/wire/ext_conf_no_settings.xml new file mode 100644 index 0000000..df76d54 --- /dev/null +++ b/tests/data/wire/ext_conf_no_settings.xml @@ -0,0 +1,41 @@ +<Extensions version="1.0.0.0" goalStateIncarnation="9"><GuestAgentExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> + <GAFamilies> + <GAFamily> + <Name>Win8</Name> + <Uris> + <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> + </Uris> + </GAFamily> + <GAFamily> + <Name>Win7</Name> + <Uris> + <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> + </Uris> + </GAFamily> + </GAFamilies> +</GuestAgentExtension> +<Plugins> + <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="false" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" /> +</Plugins> +<StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&sp=rw&se=9999-01-01&sk=key1&sv=2014-02-14&sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions> + diff --git a/tests/data/wire/goal_state.xml b/tests/data/wire/goal_state.xml new file mode 100644 index 0000000..960444b --- /dev/null +++ b/tests/data/wire/goal_state.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd"> + <Version>2010-12-15</Version> + <Incarnation>1</Incarnation> + <Machine> + <ExpectedState>Started</ExpectedState> + <LBProbePorts> + <Port>16001</Port> + </LBProbePorts> + </Machine> + <Container> + <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId> + <RoleInstanceList> + <RoleInstance> + <InstanceId>MachineRole_IN_0</InstanceId> + <State>Started</State> + <Configuration> + <HostingEnvironmentConfig>http://hostingenvuri/</HostingEnvironmentConfig> + <SharedConfig>http://sharedconfiguri/</SharedConfig> + <Certificates>http://certificatesuri/</Certificates> + <ExtensionsConfig>http://extensionsconfiguri/</ExtensionsConfig> + <FullConfig>http://fullconfiguri/</FullConfig> + </Configuration> + </RoleInstance> + </RoleInstanceList> + </Container> + </GoalState> diff --git a/tests/data/wire/goal_state_no_ext.xml b/tests/data/wire/goal_state_no_ext.xml new file mode 100644 index 0000000..0b4f566 --- /dev/null +++ b/tests/data/wire/goal_state_no_ext.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd"> + <Version>2010-12-15</Version> + <Incarnation>1</Incarnation> + <Machine> + <ExpectedState>Started</ExpectedState> + <LBProbePorts> + <Port>16001</Port> + </LBProbePorts> + </Machine> + <Container> + <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId> + <RoleInstanceList> + <RoleInstance> + <InstanceId>MachineRole_IN_0</InstanceId> + <State>Started</State> + <Configuration> + <HostingEnvironmentConfig>http://hostingenvuri/</HostingEnvironmentConfig> + <SharedConfig>http://sharedconfiguri/</SharedConfig> + <Certificates>http://certificatesuri/</Certificates> + <FullConfig>http://fullconfiguri/</FullConfig> + </Configuration> + </RoleInstance> + </RoleInstanceList> + </Container> + </GoalState> diff --git a/tests/test_hostingenv.py b/tests/data/wire/hosting_env.xml index 3d2ce73..f763a25 100644 --- a/tests/test_hostingenv.py +++ b/tests/data/wire/hosting_env.xml @@ -1,32 +1,4 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import uuid -import unittest -import os -import azurelinuxagent.protocol.v1 as v1 - -hosting_env_sample=u""" - <HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1"> +<HostingEnvironmentConfig version="1.0.0.0" goalStateIncarnation="1"> <StoredCertificates> <StoredCertificate name="Stored0Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" certificateId="sha1:C093FA5CD3AAE057CB7C4E04532B2E16E07C26CA" storeName="My" configurationLevel="System" /> </StoredCertificates> @@ -50,17 +22,3 @@ hosting_env_sample=u""" <Resource name="DiagnosticStore" type="directory" request="Microsoft.Cis.Fabric.Controller.Descriptions.ServiceDescription.Data.Policy" sticky="true" size="1" path="db00a7755a5e4e8a8fe4b19bc3b330c3.MachineRole.DiagnosticStore\" disableQuota="false" /> </ResourceReferences> </HostingEnvironmentConfig> -""" - -class TestHostingEvn(unittest.TestCase): - def test_hosting_env(self): - hosting_env = v1.HostingEnv(hosting_env_sample) - self.assertNotEquals(None, hosting_env) - self.assertEquals("MachineRole_IN_0", hosting_env.vm_name) - self.assertEquals("MachineRole", hosting_env.role_name) - self.assertEquals("db00a7755a5e4e8a8fe4b19bc3b330c3", - hosting_env.deployment_name) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/data/wire/manifest.xml b/tests/data/wire/manifest.xml new file mode 100644 index 0000000..943755a --- /dev/null +++ b/tests/data/wire/manifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<PluginVersionManifest xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> + <Plugins> + <Plugin> + <Version>1.0</Version> + <Uris> + <Uri>http://foo.bar/zar/OSTCExtensions.ExampleHandlerLinux</Uri> + </Uris> + </Plugin> + <Plugin> + <Version>1.1</Version> + <Uris> + <Uri>http://foo.bar/zar/OSTCExtensions.ExampleHandlerLinux</Uri> + </Uris> + </Plugin> + </Plugins> +</PluginVersionManifest> + diff --git a/tests/test_sharedconfig.py b/tests/data/wire/shared_config.xml index f480253..7c6ae14 100644 --- a/tests/test_sharedconfig.py +++ b/tests/data/wire/shared_config.xml @@ -1,33 +1,4 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import uuid -import unittest -import os -import azurelinuxagent.protocol.v1 as v1 - -shared_config_sample=u""" - - <SharedConfig version="1.0.0.0" goalStateIncarnation="1"> +<SharedConfig version="1.0.0.0" goalStateIncarnation="1"> <Deployment name="db00a7755a5e4e8a8fe4b19bc3b330c3" guid="{ce5a036f-5c93-40e7-8adf-2613631008ab}" incarnation="2"> <Service name="MyVMRoleService" guid="{00000000-0000-0000-0000-000000000000}" /> <ServiceInstance name="db00a7755a5e4e8a8fe4b19bc3b330c3.1" guid="{d113f4d7-9ead-4e73-b715-b724b5b7842c}" /> @@ -69,11 +40,3 @@ shared_config_sample=u""" </Instance> </Instances> </SharedConfig> -""" - -class TestSharedConfig(unittest.TestCase): - def test_sharedconfig(self): - shared_conf = v1.SharedConfig(shared_config_sample) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/sshd_config b/tests/data/wire/sshd_config index 77fb290..77fb290 100644 --- a/tests/sshd_config +++ b/tests/data/wire/sshd_config diff --git a/tests/data/wire/trans_cert b/tests/data/wire/trans_cert new file mode 100644 index 0000000..d560ae2 --- /dev/null +++ b/tests/data/wire/trans_cert @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBzCCAe+gAwIBAgIJANujJuVt5eC8MA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNV +BAMMDkxpbnV4VHJhbnNwb3J0MCAXDTE0MTAyNDA3MjgwN1oYDzIxMDQwNzEyMDcy +ODA3WjAZMRcwFQYDVQQDDA5MaW51eFRyYW5zcG9ydDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANPcJAkd6V5NeogSKjIeTXOWC5xzKTyuJPt4YZMVSosU +0lI6a0wHp+g2fP22zrVswW+QJz6AVWojIEqLQup3WyCXZTv8RUblHnIjkvX/+J/G +aLmz0G5JzZIpELL2C8IfQLH2IiPlK9LOQH00W74WFcK3QqcJ6Kw8GcVaeSXT1r7X +QcGMqEjcWJkpKLoMJv3LMufE+JMdbXDUGY+Ps7Zicu8KXvBPaKVsc6H2jrqBS8et +jXbzLyrezTUDz45rmyRJzCO5Sk2pohuYg73wUykAUPVxd7L8WnSyqz1v4zrObqnw +BAyor67JR/hjTBfjFOvd8qFGonfiv2Vnz9XsYFTZsXECAwEAAaNQME4wHQYDVR0O +BBYEFL8i/sehpGV6IEDX7F0WQHQ/ZXOyMB8GA1UdIwQYMBaAFL8i/sehpGV6IEDX +7F0WQHQ/ZXOyMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAMPLrimT +Gptu5pLRHPT8OFRN+skNSkepYaUaJuq6cSKxLumSYkD8++rohu+1+a7t1YNjjNSJ +8ohRAynRJ7aRqwBmyX2OPLRpOfyRZwR0rcFfAMORm/jOE6WBdqgYD2L2b+tZplGt +/QqgQzebaekXh/032FK4c74Zg5r3R3tfNSUMG6nLauWzYHbQ5SCdkuQwV0ehGqh5 +VF1AOdmz4CC2237BNznDFQhkeU0LrqqAoE/hv5ih7klJKZdS88rOYEnVJsFFJb0g +qaycXjOm5Khgl4hKrd+DBD/qj4IVVzsmdpFli72k6WLBHGOXusUGo/3isci2iAIt +DsfY6XGSEIhZnA4= +-----END CERTIFICATE----- diff --git a/tests/data/wire/trans_prv b/tests/data/wire/trans_prv new file mode 100644 index 0000000..063cf15 --- /dev/null +++ b/tests/data/wire/trans_prv @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDT3CQJHeleTXqI +EioyHk1zlguccyk8riT7eGGTFUqLFNJSOmtMB6foNnz9ts61bMFvkCc+gFVqIyBK +i0Lqd1sgl2U7/EVG5R5yI5L1//ifxmi5s9BuSc2SKRCy9gvCH0Cx9iIj5SvSzkB9 +NFu+FhXCt0KnCeisPBnFWnkl09a+10HBjKhI3FiZKSi6DCb9yzLnxPiTHW1w1BmP +j7O2YnLvCl7wT2ilbHOh9o66gUvHrY128y8q3s01A8+Oa5skScwjuUpNqaIbmIO9 +8FMpAFD1cXey/Fp0sqs9b+M6zm6p8AQMqK+uyUf4Y0wX4xTr3fKhRqJ34r9lZ8/V +7GBU2bFxAgMBAAECggEBAM4hsfog3VAAyIieS+npq+gbhH6bWfMNaTQ3g5CNNbMu +9hhFeOJHzKnWYjSlamgBQhAfTN+2E+Up+iAtcVUZ/lMumrQLlwgMo1vgmvu5Kxmh +/YE5oEG+k0JzrCjD1trwd4zvc3ZDYyk/vmVTzTOc311N248UyArUiyqHBbq1a4rP +tJhCLn2c4S7flXGF0MDVGZyV9V7J8N8leq/dRGMB027Li21T+B4mPHXa6b8tpRPL +4vc8sHoUJDa2/+mFDJ2XbZfmlgd3MmIPlRn1VWoW7mxgT/AObsPl7LuQx7+t80Wx +hIMjuKUHRACQSLwHxJ3SQRFWp4xbztnXSRXYuHTscLUCgYEA//Uu0qIm/FgC45yG +nXtoax4+7UXhxrsWDEkbtL6RQ0TSTiwaaI6RSQcjrKDVSo/xo4ZySTYcRgp5GKlI +CrWyNM+UnIzTNbZOtvSIAfjxYxMsq1vwpTlOB5/g+cMukeGg39yUlrjVNoFpv4i6 +9t4yYuEaF4Vww0FDd2nNKhhW648CgYEA0+UYH6TKu03zDXqFpwf4DP2VoSo8OgfQ +eN93lpFNyjrfzvxDZkGF+7M/ebyYuI6hFplVMu6BpgpFP7UVJpW0Hn/sXkTq7F1Q +rTJTtkTp2+uxQVP/PzSOqK0Twi5ifkfoEOkPkNNtTiXzwCW6Qmmcvln2u893pyR5 +gqo5BHR7Ev8CgYAb7bXpN9ZHLJdMHLU3k9Kl9YvqOfjTxXA3cPa79xtEmsrTys4q +4HuL22KSII6Fb0VvkWkBAg19uwDRpw78VC0YxBm0J02Yi8b1AaOhi3dTVzFFlWeh +r6oK/PAAcMKxGkyCgMAZ3hstsltGkfXMoBwhW+yL6nyOYZ2p9vpzAGrjkwKBgQDF +0huzbyXVt/AxpTEhv07U0enfjI6tnp4COp5q8zyskEph8yD5VjK/yZh5DpmFs6Kw +dnYUFpbzbKM51tToMNr3nnYNjEnGYVfwWgvNHok1x9S0KLcjSu3ki7DmmGdbfcYq +A2uEyd5CFyx5Nr+tQOwUyeiPbiFG6caHNmQExLoiAQKBgFPy9H8///xsadYmZ18k +r77R2CvU7ArxlLfp9dr19aGYKvHvnpsY6EuChkWfy8Xjqn3ogzgrHz/rn3mlGUpK +vbtwtsknAHtTbotXJwfaBZv2RGgGRr3DzNo6ll2Aez0lNblZFXq132h7+y5iLvar +4euORaD/fuM4UPlR5mN+bypU +-----END PRIVATE KEY----- diff --git a/tests/data/wire/version_info.xml b/tests/data/wire/version_info.xml new file mode 100644 index 0000000..b4d0a3f --- /dev/null +++ b/tests/data/wire/version_info.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<Versions> + <Preferred> + <Version>2012-11-30</Version> + </Preferred> + <Supported> + <Version>2010-12-15</Version> + <Version>2010-28-10</Version> + </Supported> +</Versions> diff --git a/tests/env.py b/tests/distro/__init__.py index 5bc6eb8..9bdb27e 100644 --- a/tests/env.py +++ b/tests/distro/__init__.py @@ -17,10 +17,3 @@ # 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 sys - -test_root = os.path.dirname(os.path.abspath(__file__)) -project_root = os.path.dirname(test_root) -sys.path.insert(0, project_root) diff --git a/tests/distro/test_daemon.py b/tests/distro/test_daemon.py new file mode 100644 index 0000000..9d3c45b --- /dev/null +++ b/tests/distro/test_daemon.py @@ -0,0 +1,65 @@ +# Copyright 2014 Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Requires Python 2.4+ and Openssl 1.0+ +# +# 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 + +from tests.tools import * +from azurelinuxagent.distro.loader import get_distro +from azurelinuxagent.exception import * +from azurelinuxagent.distro.default.daemon import * + +class MockDaemonCall(object): + def __init__(self, daemon_handler, count): + self.daemon_handler = daemon_handler + self.count = count + + def __call__(self, *args, **kw): + self.count = self.count - 1 + #Stop daemon after restarting for n times + if self.count <= 0: + self.daemon_handler.running = False + raise Exception("Mock unhandled exception") + +@patch("time.sleep") +class TestDaemon(AgentTestCase): + def test_daemon_restart(self, mock_sleep): + distro = get_distro() + mock_daemon = Mock(side_effect=MockDaemonCall(distro.daemon_handler, 2)) + distro.daemon_handler.daemon = mock_daemon + distro.daemon_handler.check_pid = Mock() + distro.daemon_handler.run() + + mock_sleep.assert_any_call(15) + self.assertEquals(2, distro.daemon_handler.daemon.call_count) + + @patch("azurelinuxagent.distro.default.daemon.conf") + @patch("azurelinuxagent.distro.default.daemon.sys.exit") + def test_check_pid(self, mock_exit, mock_conf, mock_sleep): + distro = get_distro() + mock_pid_file = os.path.join(self.tmp_dir, "pid") + mock_conf.get_agent_pid_file_path = Mock(return_value=mock_pid_file) + + distro.daemon_handler.check_pid() + self.assertTrue(os.path.isfile(mock_pid_file)) + + distro.daemon_handler.check_pid() + mock_exit.assert_any_call(0) + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/distro/test_extension.py b/tests/distro/test_extension.py new file mode 100644 index 0000000..d0b631f --- /dev/null +++ b/tests/distro/test_extension.py @@ -0,0 +1,191 @@ +# 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 + +from tests.tools import * +from tests.protocol.mockwiredata import * +from azurelinuxagent.exception import * +from azurelinuxagent.distro.loader import get_distro +from azurelinuxagent.protocol.restapi import get_properties +from azurelinuxagent.protocol.wire import WireProtocol + +@patch("time.sleep") +@patch("azurelinuxagent.protocol.wire.CryptUtil") +@patch("azurelinuxagent.utils.restutil.http_get") +class TestExtension(AgentTestCase): + + def _assert_handler_status(self, report_vm_status, expected_status, + expected_ext_count, version): + self.assertTrue(report_vm_status.called) + args, kw = report_vm_status.call_args + vm_status = args[0] + self.assertNotEquals(0, len(vm_status.vmAgent.extensionHandlers)) + handler_status = vm_status.vmAgent.extensionHandlers[0] + self.assertEquals(expected_status, handler_status.status) + self.assertEquals("OSTCExtensions.ExampleHandlerLinux", + handler_status.name) + self.assertEquals(version, handler_status.version) + self.assertEquals(expected_ext_count, len(handler_status.extensions)) + + def _assert_no_handler_status(self, report_vm_status): + self.assertTrue(report_vm_status.called) + args, kw = report_vm_status.call_args + vm_status = args[0] + self.assertEquals(0, len(vm_status.vmAgent.extensionHandlers)) + + def _create_mock(self, test_data, mock_http_get, MockCryptUtil, _): + """Test enable/disable/unistall of an extension""" + distro = get_distro() + + #Mock protocol to return test data + mock_http_get.side_effect = test_data.mock_http_get + MockCryptUtil.side_effect = test_data.mock_crypt_util + + protocol = WireProtocol("foo.bar") + protocol.detect() + protocol.report_ext_status = MagicMock() + protocol.report_vm_status = MagicMock() + distro.protocol_util.get_protocol = Mock(return_value=protocol) + + return distro, protocol + + def test_ext_handler(self, *args): + test_data = WireProtocolData(DATA_FILE) + distro, protocol = self._create_mock(test_data, *args) + + #Test enable scenario. + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0") + self._assert_ext_status(protocol.report_ext_status, "success", 0) + + #Test goal state not changed + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0") + + #Test goal state changed + test_data.goal_state = test_data.goal_state.replace("<Incarnation>1<", + "<Incarnation>2<") + test_data.ext_conf = test_data.ext_conf.replace("seqNo=\"0\"", + "seqNo=\"1\"") + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0") + self._assert_ext_status(protocol.report_ext_status, "success", 1) + + #Test upgrade + test_data.goal_state = test_data.goal_state.replace("<Incarnation>2<", + "<Incarnation>3<") + test_data.ext_conf = test_data.ext_conf.replace("1.0", "1.1") + test_data.ext_conf = test_data.ext_conf.replace("seqNo=\"1\"", + "seqNo=\"2\"") + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1") + self._assert_ext_status(protocol.report_ext_status, "success", 2) + + #Test disable + test_data.goal_state = test_data.goal_state.replace("<Incarnation>3<", + "<Incarnation>4<") + test_data.ext_conf = test_data.ext_conf.replace("enabled", "disabled") + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "NotReady", + 1, "1.1") + + #Test uninstall + test_data.goal_state = test_data.goal_state.replace("<Incarnation>4<", + "<Incarnation>5<") + test_data.ext_conf = test_data.ext_conf.replace("disabled", "uninstall") + distro.ext_handlers_handler.run() + self._assert_no_handler_status(protocol.report_vm_status) + + #Test uninstall again! + test_data.goal_state = test_data.goal_state.replace("<Incarnation>5<", + "<Incarnation>6<") + distro.ext_handlers_handler.run() + self._assert_no_handler_status(protocol.report_vm_status) + + def test_ext_handler_no_settings(self, *args): + test_data = WireProtocolData(DATA_FILE_EXT_NO_SETTINGS) + distro, protocol = self._create_mock(test_data, *args) + + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 0, "1.0") + + def test_ext_handler_no_public_settings(self, *args): + test_data = WireProtocolData(DATA_FILE_EXT_NO_PUBLIC) + distro, protocol = self._create_mock(test_data, *args) + + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0") + + def test_ext_handler_no_ext(self, *args): + test_data = WireProtocolData(DATA_FILE_NO_EXT) + distro, protocol = self._create_mock(test_data, *args) + + #Assert no extension handler status + distro.ext_handlers_handler.run() + self._assert_no_handler_status(protocol.report_vm_status) + + @patch('azurelinuxagent.distro.default.extension.add_event') + def test_ext_handler_download_failure(self, mock_add_event, *args): + test_data = WireProtocolData(DATA_FILE) + distro, protocol = self._create_mock(test_data, *args) + protocol.download_ext_handler_pkg = Mock(side_effect=ProtocolError) + + distro.ext_handlers_handler.run() + args, kw = mock_add_event.call_args + self.assertEquals(False, kw['is_success']) + self.assertEquals("OSTCExtensions.ExampleHandlerLinux", kw['name']) + self.assertEquals("Download", kw['op']) + + @patch('azurelinuxagent.distro.default.extension.fileutil') + def test_ext_handler_io_error(self, mock_fileutil, *args): + test_data = WireProtocolData(DATA_FILE) + distro, protocol = self._create_mock(test_data, *args) + + mock_fileutil.write_file.return_value = IOError("Mock IO Error") + distro.ext_handlers_handler.run() + + def _assert_ext_status(self, report_ext_status, expected_status, + expected_seq_no): + self.assertTrue(report_ext_status.called) + args, kw = report_ext_status.call_args + ext_status = args[-1] + self.assertEquals(expected_status, ext_status.status) + self.assertEquals(expected_seq_no, ext_status.sequenceNumber) + + def test_ext_handler_no_reporting_status(self, *args): + test_data = WireProtocolData(DATA_FILE) + distro, protocol = self._create_mock(test_data, *args) + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0") + + #Remove status file and re-run collecting extension status + status_file = os.path.join(self.tmp_dir, + "OSTCExtensions.ExampleHandlerLinux-1.0", + "status", "0.status") + self.assertTrue(os.path.isfile(status_file)) + os.remove(status_file) + + distro.ext_handlers_handler.run() + self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0") + self._assert_ext_status(protocol.report_ext_status, "error", 0) + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test_future.py b/tests/distro/test_loader.py index 20edc93..94ca913 100644 --- a/tests/test_future.py +++ b/tests/distro/test_loader.py @@ -18,20 +18,19 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx -import tests.env from tests.tools import * -import uuid -import unittest -import os -import shutil -import time -import azurelinuxagent.future as future +from azurelinuxagent.distro.loader import get_distro +from azurelinuxagent.distro.default.distro import DefaultDistro -class TestFuture(unittest.TestCase): - def test_future_pkgs(self): - future.httpclient - future.urlparse - future.text(b"asdf", encoding="utf-8") +class TestDistroLoader(AgentTestCase): + + @distros() + def test_distro_loader(self, *distro_args): + distro = get_distro(*distro_args) + self.assertNotEquals(None, distro) + self.assertNotEquals(DefaultDistro, type(distro)) + if __name__ == '__main__': unittest.main() + diff --git a/tests/test_protocolFactory.py b/tests/distro/test_monitor.py index 9928b88..1dd7740 100644 --- a/tests/test_protocolFactory.py +++ b/tests/distro/test_monitor.py @@ -18,20 +18,15 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx -import tests.env from tests.tools import * -import uuid -import unittest -import os -import azurelinuxagent.protocol as protocol -import azurelinuxagent.protocol.protocolFactory as protocolFactory +from azurelinuxagent.exception import * +from azurelinuxagent.distro.default.monitor import * -class TestWireProtocolEndpoint(unittest.TestCase): - def test_get_available_protocols(self): - mockGetV1 = MockFunc(retval="Mock protocol") - protocols = protocolFactory.get_available_protocols([mockGetV1]) - self.assertNotEquals(None, protocols) - self.assertNotEquals(0, len(protocols)) +class TestMonitor(AgentTestCase): + def test_parse_xml_event(self): + data_str = load_data('ext/event.xml') + event = parse_xml_event(data_str) + self.assertNotEquals(None, event) + self.assertNotEquals(0, event.parameters) + self.assertNotEquals(None, event.parameters[0]) -if __name__ == '__main__': - unittest.main() diff --git a/tests/distro/test_protocol_util.py b/tests/distro/test_protocol_util.py new file mode 100644 index 0000000..61339f3 --- /dev/null +++ b/tests/distro/test_protocol_util.py @@ -0,0 +1,89 @@ +# 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 + +from tests.tools import * +from azurelinuxagent.distro.loader import get_distro +from azurelinuxagent.exception import * +from azurelinuxagent.distro.default.protocolUtil import * + +@patch("time.sleep") +class TestProtocolUtil(AgentTestCase): + + @distros() + @patch("azurelinuxagent.distro.default.protocolUtil.MetadataProtocol") + @patch("azurelinuxagent.distro.default.protocolUtil.WireProtocol") + def test_detect_protocol(self, distro_name, distro_version, distro_full_name, + WireProtocol, MetadataProtocol, _, *distro_args): + + WireProtocol.return_value = MagicMock() + MetadataProtocol.return_value = MagicMock() + + distro = get_distro(distro_name, distro_version, distro_full_name) + distro.dhcp_handler = MagicMock() + distro.dhcp_handler.endpoint = "foo.bar" + + #Test wire protocol is available + protocol = distro.protocol_util.detect_protocol() + self.assertEquals(WireProtocol.return_value, protocol) + + #Test wire protocol is not available + distro.protocol_util.protocol = None + WireProtocol.side_effect = ProtocolError() + + protocol = distro.protocol_util.detect_protocol() + self.assertEquals(MetadataProtocol.return_value, protocol) + + #Test no protocol is available + distro.protocol_util.protocol = None + WireProtocol.side_effect = ProtocolError() + MetadataProtocol.side_effect = ProtocolError() + self.assertRaises(ProtocolError, distro.protocol_util.detect_protocol) + + @distros() + def test_detect_protocol_by_file(self, distro_name, distro_version, + distro_full_name, _): + distro = get_distro(distro_name, distro_version, distro_full_name) + protocol_util = distro.protocol_util + + protocol_util._detect_wire_protocol = Mock() + protocol_util._detect_metadata_protocol = Mock() + + tag_file = os.path.join(self.tmp_dir, TAG_FILE_NAME) + + #Test tag file doesn't exist + protocol_util.detect_protocol_by_file() + protocol_util._detect_wire_protocol.assert_any_call() + protocol_util._detect_metadata_protocol.assert_not_called() + + #Test tag file exists + protocol_util.protocol = None + protocol_util._detect_wire_protocol.reset_mock() + protocol_util._detect_metadata_protocol.reset_mock() + with open(tag_file, "w+") as tag_fd: + tag_fd.write("") + + protocol_util.detect_protocol_by_file() + protocol_util._detect_metadata_protocol.assert_any_call() + protocol_util._detect_wire_protocol.assert_not_called() + + +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test_agent.py b/tests/distro/test_provision.py index 721b9f0..60249ce 100644 --- a/tests/test_agent.py +++ b/tests/distro/test_provision.py @@ -18,28 +18,30 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx -import tests.env -import tests.tools as tools -import uuid -import unittest -import os -import json +from tests.tools import * +from azurelinuxagent.distro.loader import get_distro +from azurelinuxagent.distro.default.protocolUtil import * import azurelinuxagent.utils.fileutil as fileutil -import azurelinuxagent.agent as agent -class TestAgent(unittest.TestCase): - def test_parse_args(self): - cmd, force, verbose = agent.parse_args(["deprovision+user", - "-force", - "/verbose"]) - self.assertEquals("deprovision+user", cmd) - self.assertTrue(force) - self.assertTrue(verbose) +class TestProvision(AgentTestCase): - cmd, force, verbose = agent.parse_args(["wrong cmd"]) - self.assertEquals("help", cmd) - self.assertFalse(force) - self.assertFalse(verbose) + @distros("redhat") + def test_provision(self, distro_name, distro_version, distro_full_name): + distro = get_distro(distro_name, distro_version, distro_full_name) + distro.osutil = MagicMock() + distro.osutil.decode_customdata = Mock(return_value="") + + distro.protocol_util.detect_protocol_by_file = MagicMock() + distro.protocol_util.get_protocol = MagicMock() + conf.get_dvd_mount_point = Mock(return_value=self.tmp_dir) + + ovfenv_file = os.path.join(self.tmp_dir, OVF_FILE_NAME) + ovfenv_data = load_data("ovf-env.xml") + fileutil.write_file(ovfenv_file, ovfenv_data) + + handler = distro.provision_handler + handler.run() if __name__ == '__main__': unittest.main() + diff --git a/azurelinuxagent/distro/centos/__init__.py b/tests/protocol/__init__.py index d9b82f5..9bdb27e 100644 --- a/azurelinuxagent/distro/centos/__init__.py +++ b/tests/protocol/__init__.py @@ -1,5 +1,3 @@ -# Microsoft Azure Linux Agent -# # Copyright 2014 Microsoft Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,4 +14,6 @@ # # 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 diff --git a/tests/protocol/mockmetadata.py b/tests/protocol/mockmetadata.py new file mode 100644 index 0000000..0f7b568 --- /dev/null +++ b/tests/protocol/mockmetadata.py @@ -0,0 +1,61 @@ +# 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 + +from tests.tools import * +from azurelinuxagent.future import httpclient +from azurelinuxagent.utils.cryptutil import CryptUtil + +DATA_FILE = { + "identity": "metadata/identity.json", + "certificates": "metadata/certificates.json", + "ext_handlers": "metadata/ext_handlers.json", + "ext_handler_pkgs": "metadata/ext_handler_pkgs.json", +} + +DATA_FILE_NO_EXT = DATA_FILE.copy() +DATA_FILE_NO_EXT["ext_handlers"] = "metadata/ext_handlers_no_ext.json" + +class MetadataProtocolData(object): + def __init__(self, data_files): + self.identity = load_data(data_files.get("identity")) + self.certificates = load_data(data_files.get("certificates")) + self.ext_handlers = load_data(data_files.get("ext_handlers")) + self.ext_handler_pkgs = load_data(data_files.get("ext_handler_pkgs")) + + def mock_http_get(self, url, *args, **kwargs): + content = None + if url.count(u"identity?") > 0: + content = self.identity + elif url.count(u"certificates") > 0: + content = self.certificates + elif url.count(u"extensionHandlers") > 0: + content = self.ext_handlers + elif url.count(u"versionUri") > 0: + content = self.ext_handler_pkgs + else: + raise Exception("Bad url {0}".format(url)) + resp = MagicMock() + resp.status = httpclient.OK + if content is None: + resp.read = Mock(return_value=None) + else: + resp.read = Mock(return_value=content.encode("utf-8")) + return resp + diff --git a/tests/protocol/mockwiredata.py b/tests/protocol/mockwiredata.py new file mode 100644 index 0000000..6ffd19c --- /dev/null +++ b/tests/protocol/mockwiredata.py @@ -0,0 +1,101 @@ +# 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 + +from tests.tools import * +from azurelinuxagent.future import httpclient +from azurelinuxagent.utils.cryptutil import CryptUtil + +DATA_FILE = { + "version_info": "wire/version_info.xml", + "goal_state": "wire/goal_state.xml", + "hosting_env": "wire/hosting_env.xml", + "shared_config": "wire/shared_config.xml", + "certs": "wire/certs.xml", + "ext_conf": "wire/ext_conf.xml", + "manifest": "wire/manifest.xml", + "trans_prv": "wire/trans_prv", + "trans_cert": "wire/trans_cert", + "test_ext": "ext/sample_ext.zip" +} + +DATA_FILE_NO_EXT = DATA_FILE.copy() +DATA_FILE_NO_EXT["goal_state"] = "wire/goal_state_no_ext.xml" + +DATA_FILE_EXT_NO_SETTINGS = DATA_FILE.copy() +DATA_FILE_EXT_NO_SETTINGS["ext_conf"] = "wire/ext_conf_no_settings.xml" + +DATA_FILE_EXT_NO_PUBLIC = DATA_FILE.copy() +DATA_FILE_EXT_NO_PUBLIC["ext_conf"] = "wire/ext_conf_no_public.xml" + +class WireProtocolData(object): + def __init__(self, data_files=DATA_FILE): + self.version_info = load_data(data_files.get("version_info")) + self.goal_state = load_data(data_files.get("goal_state")) + self.hosting_env = load_data(data_files.get("hosting_env")) + self.shared_config = load_data(data_files.get("shared_config")) + self.certs = load_data(data_files.get("certs")) + self.ext_conf = load_data(data_files.get("ext_conf")) + self.manifest = load_data(data_files.get("manifest")) + self.trans_prv = load_data(data_files.get("trans_prv")) + self.trans_cert = load_data(data_files.get("trans_cert")) + self.ext = load_bin_data(data_files.get("test_ext")) + + def mock_http_get(self, url, *args, **kwargs): + content = None + if "versions" in url: + content = self.version_info + elif "goalstate" in url: + content = self.goal_state + elif "hostingenvuri" in url: + content = self.hosting_env + elif "sharedconfiguri" in url: + content = self.shared_config + elif "certificatesuri" in url: + content = self.certs + elif "extensionsconfiguri" in url: + content = self.ext_conf + elif "manifest.xml" in url: + content = self.manifest + elif "ExampleHandlerLinux" in url: + content = self.ext + resp = MagicMock() + resp.status = httpclient.OK + resp.read = Mock(return_value=content) + return resp + else: + raise Exception("Bad url {0}".format(url)) + resp = MagicMock() + resp.status = httpclient.OK + resp.read = Mock(return_value=content.encode("utf-8")) + return resp + + def mock_crypt_util(self, *args, **kw): + #Partially patch instance method of class CryptUtil + cryptutil = CryptUtil(*args, **kw) + cryptutil.gen_transport_cert = Mock(side_effect=self.mock_gen_trans_cert) + return cryptutil + + def mock_gen_trans_cert(self, trans_prv_file, trans_cert_file): + with open(trans_prv_file, 'w+') as prv_file: + prv_file.write(self.trans_prv) + + with open(trans_cert_file, 'w+') as cert_file: + cert_file.write(self.trans_cert) + diff --git a/tests/protocol/test_metadata.py b/tests/protocol/test_metadata.py new file mode 100644 index 0000000..fca1a82 --- /dev/null +++ b/tests/protocol/test_metadata.py @@ -0,0 +1,48 @@ +# 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 + +from tests.tools import * +from tests.protocol.mockmetadata import * +from azurelinuxagent.utils.restutil import httpclient +from azurelinuxagent.protocol.metadata import MetadataProtocol + +@patch("time.sleep") +@patch("azurelinuxagent.protocol.metadata.restutil") +class TestWireProtocolGetters(AgentTestCase): + def _test_getters(self, test_data, mock_restutil ,_): + mock_restutil.http_get.side_effect = test_data.mock_http_get + + protocol = MetadataProtocol() + protocol.detect() + protocol.get_vminfo() + protocol.get_certs() + ext_handlers, etag= protocol.get_ext_handlers() + for ext_handler in ext_handlers.extHandlers: + protocol.get_ext_handler_pkgs(ext_handler) + + def test_getters(self, *args): + test_data = MetadataProtocolData(DATA_FILE) + self._test_getters(test_data, *args) + + def test_getters_no(self, *args): + test_data = MetadataProtocolData(DATA_FILE_NO_EXT) + self._test_getters(test_data, *args) + + diff --git a/tests/test_datacontract.py b/tests/protocol/test_restapi.py index 4d4edd7..656ecc6 100644 --- a/tests/test_datacontract.py +++ b/tests/protocol/test_restapi.py @@ -18,14 +18,13 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx -import tests.env from tests.tools import * import uuid import unittest import os import shutil import time -from azurelinuxagent.protocol.common import * +from azurelinuxagent.protocol.restapi import * class SampleDataContract(DataContract): def __init__(self): diff --git a/tests/protocol/test_wire.py b/tests/protocol/test_wire.py new file mode 100644 index 0000000..4c38c13 --- /dev/null +++ b/tests/protocol/test_wire.py @@ -0,0 +1,85 @@ +# 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 + +from tests.tools import * +from tests.protocol.mockwiredata import * +import uuid +import unittest +import os +import time +from azurelinuxagent.utils.restutil import httpclient +from azurelinuxagent.utils.cryptutil import CryptUtil +from azurelinuxagent.protocol.restapi import * +from azurelinuxagent.protocol.wire import WireClient, WireProtocol, \ + TRANSPORT_PRV_FILE_NAME, \ + TRANSPORT_CERT_FILE_NAME + +data_with_bom = b'\xef\xbb\xbfhehe' + +@patch("time.sleep") +@patch("azurelinuxagent.protocol.wire.CryptUtil") +@patch("azurelinuxagent.protocol.wire.restutil") +class TestWireProtocolGetters(AgentTestCase): + + def _test_getters(self, test_data, mock_restutil, MockCryptUtil, _): + mock_restutil.http_get.side_effect = test_data.mock_http_get + MockCryptUtil.side_effect = test_data.mock_crypt_util + + protocol = WireProtocol("foo.bar") + protocol.detect() + protocol.get_vminfo() + protocol.get_certs() + ext_handlers, etag = protocol.get_ext_handlers() + for ext_handler in ext_handlers.extHandlers: + protocol.get_ext_handler_pkgs(ext_handler) + + crt1 = os.path.join(self.tmp_dir, + '33B0ABCE4673538650971C10F7D7397E71561F35.crt') + crt2 = os.path.join(self.tmp_dir, + '4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3.crt') + prv2 = os.path.join(self.tmp_dir, + '4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3.prv') + + self.assertTrue(os.path.isfile(crt1)) + self.assertTrue(os.path.isfile(crt2)) + self.assertTrue(os.path.isfile(prv2)) + + def test_getters(self, *args): + """Normal case""" + test_data = WireProtocolData(DATA_FILE) + self._test_getters(test_data, *args) + + def test_getters_no_ext(self, *args): + """Provision with agent is not checked""" + test_data = WireProtocolData(DATA_FILE_NO_EXT) + self._test_getters(test_data, *args) + + def test_getters_ext_no_settings(self, *args): + """Extensions without any settings""" + test_data = WireProtocolData(DATA_FILE_EXT_NO_SETTINGS) + self._test_getters(test_data, *args) + + def test_getters_ext_no_public(self, *args): + """Extensions without any public settings""" + test_data = WireProtocolData(DATA_FILE_EXT_NO_PUBLIC) + self._test_getters(test_data, *args) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/run_all.sh b/tests/run_all.sh deleted file mode 100755 index 9e88c46..0000000 --- a/tests/run_all.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# -# This script is used to set up a test env for extensions -# -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -script=$(dirname $0) -root=$script -cd $root -root=`pwd` - -echo "Run unit test:" -tests=`ls test_*.py | sed -e 's/\.py//'` -for test in $tests ; do - echo $test -done - -if [ "$1" == "-c" ] ; then - echo $tests | xargs coverage run -m unittest -else - echo $tests | xargs python -m unittest -fi diff --git a/tests/test.crt b/tests/test.crt deleted file mode 100644 index e87c5f9..0000000 --- a/tests/test.crt +++ /dev/null @@ -1,17 +0,0 @@ -Bag Attributes: <Empty Attributes> -subject=/C=ab/ST=ab/L=ab/O=ab/OU=ab/CN=ab/emailAddress=ab -issuer=/C=ab/ST=ab/L=ab/O=ab/OU=ab/CN=ab/emailAddress=ab ------BEGIN CERTIFICATE----- -MIICOTCCAaICCQD7F0nb+GtpcTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJh -YjELMAkGA1UECAwCYWIxCzAJBgNVBAcMAmFiMQswCQYDVQQKDAJhYjELMAkGA1UE -CwwCYWIxCzAJBgNVBAMMAmFiMREwDwYJKoZIhvcNAQkBFgJhYjAeFw0xNDA4MDUw -ODIwNDZaFw0xNTA4MDUwODIwNDZaMGExCzAJBgNVBAYTAmFiMQswCQYDVQQIDAJh -YjELMAkGA1UEBwwCYWIxCzAJBgNVBAoMAmFiMQswCQYDVQQLDAJhYjELMAkGA1UE -AwwCYWIxETAPBgkqhkiG9w0BCQEWAmFiMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB -iQKBgQC4Vugyj4uAKGYHW/D1eAg1DmLAv01e+9I0zIi8HzJxP87MXmS8EdG5SEzR -N6tfQQie76JBSTYI4ngTaVCKx5dVT93LiWxLV193Q3vs/HtwwH1fLq0rAKUhREQ6 -+CsRGNyeVfJkNsxAvNvQkectnYuOtcDxX5n/25eWAofobxVbSQIDAQABMA0GCSqG -SIb3DQEBCwUAA4GBAF20gkq/DeUSXkZA+jjmmbCPioB3KL63GpoTXfP65d6yU4xZ -TlMoLkqGKe3WoXmhjaTOssulgDAGA24IeWy/u7luH+oHdZEmEufFhj4M7tQ1pAhN -CT8JCL2dI3F76HD6ZutTOkwRar3PYk5q7RsSJdAemtnwVpgp+RBMtbmct7MQ ------END CERTIFICATE----- diff --git a/tests/test.prv b/tests/test.prv deleted file mode 100644 index 9d6ab87..0000000 --- a/tests/test.prv +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQC4Vugyj4uAKGYHW/D1eAg1DmLAv01e+9I0zIi8HzJxP87MXmS8 -EdG5SEzRN6tfQQie76JBSTYI4ngTaVCKx5dVT93LiWxLV193Q3vs/HtwwH1fLq0r -AKUhREQ6+CsRGNyeVfJkNsxAvNvQkectnYuOtcDxX5n/25eWAofobxVbSQIDAQAB -AoGAIakE506c238E+m0Id9o+LWn+EFIeT6zN+oQqp6dOr61GFr1ZyZm7YQjZtg5j -RZZ7e4Iob6Fts3ufD3RYl67QbBzRwsKwI7sAmzdCmqkopY2H6xv421cEGjkqZIJV -2Xyp9Idji6GfUB6+t1SZDOssbZx3SUkyim0hixK2HCJT4u0CQQDw6rNLZwEmwuhY -z1jSERyeTtIcRJ47+Y79tX2xmkyKxZ2Kf28V3Fw/6biCIlmuvxHNhlLijimOME7/ -rkqDiscnAkEAw+FpkM96xLlDCqNL2AcNxVnmNyO0Boxw0AKrogfcnDh6S3rD5tZQ -IdcIAsEYNjhEJ+/hVCByIUArC885PTzQDwJBAMaDfm3ZWHeKD05uvG+MLhq8NCGa -4Q/mWU7xZ7sau4t1vpTK4MwQoesAOUrx5xg41QCXeGC6Z7+ESvQft8Kgbe0CQAkS -OExPf3T6y2MDuvBvKzEXf7TP/3dKK7NGXGJtkMbfSrKSJd5b0GwwxBs0jAV+x5E9 -56Z4tjBaA2RRnWn7lfsCQA5SWuDMtlOzyWir09fparnnRL1JFvOwDAHTE0iwS8dO -UFHIIw4nqqUYuHb+r/eyRzVtokJ9bSPZOjtTWSVL4W4= ------END RSA PRIVATE KEY----- diff --git a/tests/test_certificates.py b/tests/test_certificates.py deleted file mode 100644 index 18f28c4..0000000 --- a/tests/test_certificates.py +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from .tools import * -import uuid -import unittest -import os -import json -import azurelinuxagent.utils.fileutil as fileutil -import azurelinuxagent.protocol.v1 as v1 - -certs_sample=u"""\ -<?xml version="1.0" encoding="utf-8"?> -<CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd"> - <Version>2012-11-30</Version> - <Incarnation>12</Incarnation> - <Format>Pkcs7BlobWithPfxContents</Format> - <Data>MIINswYJKoZIhvcNAQcDoIINpDCCDaACAQIxggEwMIIBLAIBAoAUvyL+x6GkZXog -QNfsXRZAdD9lc7IwDQYJKoZIhvcNAQEBBQAEggEArhMPepD/RqwdPcHEVqvrdZid -72vXrOCuacRBhwlCGrNlg8oI+vbqmT6CSv6thDpet31ALUzsI4uQHq1EVfV1+pXy -NlYD1CKhBCoJxs2fSPU4rc8fv0qs5JAjnbtW7lhnrqFrXYcyBYjpURKfa9qMYBmj -NdijN+1T4E5qjxPr7zK5Dalp7Cgp9P2diH4Nax2nixotfek3MrEFBaiiegDd+7tE -ux685GWYPqB5Fn4OsDkkYOdb0OE2qzLRrnlCIiBCt8VubWH3kMEmSCxBwSJupmQ8 -sxCWk+sBPQ9gJSt2sIqfx/61F8Lpu6WzP+ZOnMLTUn2wLU/d1FN85HXmnQALzTCC -DGUGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIbEcBfddWPv+AggxAAOAt/kCXiffe -GeJG0P2K9Q18XZS6Rz7Xcz+Kp2PVgqHKRpPjjmB2ufsRO0pM4z/qkHTOdpfacB4h -gz912D9U04hC8mt0fqGNTvRNAFVFLsmo7KXc/a8vfZNrGWEnYn7y1WfP52pqA/Ei -SNFf0NVtMyqg5Gx+hZ/NpWAE5vcmRRdoYyWeg13lhlW96QUxf/W7vY/D5KpAGACI -ok79/XI4eJkbq3Dps0oO/difNcvdkE74EU/GPuL68yR0CdzzafbLxzV+B43TBRgP -jH1hCdRqaspjAaZL5LGfp1QUM8HZIKHuTze/+4dWzS1XR3/ix9q/2QFI7YCuXpuE -un3AFYXE4QX/6kcPklZwh9FqjSie3I5HtC1vczqYVjqT4oHrs8ktkZ7oAzeXaXTF -k6+JQNNa/IyJw24I1MR77q7HlHSSfhXX5cFjVCd/+SiA4HJQjJgeIuXZ+dXmSPdL -9xLbDbtppifFyNaXdlSzcsvepKy0WLF49RmbL7Bnd46ce/gdQ6Midwi2MTnUtapu -tHmu/iJtaUpwXXC0B93PHfAk7Y3SgeY4tl/gKzn9/x5SPAcHiNRtOsNBU8ZThzos -Wh41xMLZavmX8Yfm/XWtl4eU6xfhcRAbJQx7E1ymGEt7xGqyPV7hjqhoB9i3oR5N -itxHgf1+jw/cr7hob+Trd1hFqZO6ePMyWpqUg97G2ThJvWx6cv+KRtTlVA6/r/UH -gRGBArJKBlLpXO6dAHFztT3Y6DFThrus4RItcfA8rltfQcRm8d0nPb4lCa5kRbCx -iudq3djWtTIe64sfk8jsc6ahWYSovM+NmhbpxEUbZVWLVEcHAYOeMbKgXSu5sxNO -JZNeFdzZqDRRY9fGjYNS7DdNOmrMmWKH+KXuMCItpNZsZS/3W7QxAo3ugYLdUylU -Zg8H/BjUGZCGn1rEBAuQX78m0SZ1xHlgHSwJIOmxOJUDHLPHtThfbELY9ec14yi5 -so1aQwhhfhPvF+xuXBrVeTAfhFNYkf2uxcEp7+tgFAc5W0QfT9SBn5vSvIxv+dT4 -7B2Pg1l/zjdsM74g58lmRJeDoz4psAq+Uk7n3ImBhIku9qX632Q1hanjC8D4xM4W -sI/W0ADCuAbY7LmwMpAMdrGg//SJUnBftlom7C9VA3EVf8Eo+OZH9hze+gIgUq+E -iEUL5M4vOHK2ttsYrSkAt8MZzjQiTlDr1yzcg8fDIrqEAi5arjTPz0n2s0NFptNW -lRD+Xz6pCXrnRgR8YSWpxvq3EWSJbZkSEk/eOmah22sFnnBZpDqn9+UArAznXrRi -nYK9w38aMGPKM39ymG8kcbY7jmDZlRgGs2ab0Fdj1jl3CRo5IUatkOJwCEMd/tkB -eXLQ8hspJhpFnVNReX0oithVZir+j36epk9Yn8d1l+YlKmuynjunKl9fhmoq5Q6i -DFzdYpqBV+x9nVhnmPfGyrOkXvGL0X6vmXAEif/4JoOW4IZpyXjgn+VoCJUoae5J -Djl45Bcc2Phrn4HW4Gg/+pIwTFqqZZ2jFrznNdgeIxTGjBrVsyJUeO3BHI0mVLaq -jtjhTshYCI7mXOis9W3ic0RwE8rgdDXOYKHhLVw9c4094P/43utSVXE7UzbEhhLE -Ngb4H5UGrQmPTNbq40tMUMUCej3zIKuVOvamzeE0IwLhkjNrvKhCG1EUhX4uoJKu -DQ++3KVIVeYSv3+78Jfw9F3usAXxX1ICU74/La5DUNjU7DVodLDvCAy5y1jxP3Ic -If6m7aBYVjFSQAcD8PZPeIEl9W4ZnbwyBfSDd11P2a8JcZ7N99GiiH3yS1QgJnAO -g9XAgjT4Gcn7k4lHPHLULgijfiDSvt94Ga4/hse0F0akeZslVN/bygyib7x7Lzmq -JkepRianrvKHbatuxvcajt/d+dxCnr32Q1qCEc5fcgDsjvviRL2tKR0qhuYjn1zR -Vk/fRtYOmlaGBVzUXcjLRAg3gC9+Gy8KvXIDrnHxD+9Ob+DUP9fgbKqMeOzKcCK8 -NSfSQ+tQjBYD5Ku4zAPUQJoRGgx43vXzcl2Z2i3E2otpoH82Kx8S9WlVEUlTtBjQ -QIGM5aR0QUNt8z34t2KWRA8SpP54VzBmEPdwLnzna+PkrGKsKiHVn4K+HfjDp1uW -xyO8VjrolAOYosTPXMpNp2u/FoFxaAPTa/TvmKc0kQ3ED9/sGLS2twDnEccvHP+9 -zzrnzzN3T2CWuXveDpuyuAty3EoAid1nuC86WakSaAZoa8H2QoRgsrkkBCq+K/yl -4FO9wuP+ksZoVq3mEDQ9qv6H4JJEWurfkws3OqrA5gENcLmSUkZie4oqAxeOD4Hh -Zx4ckG5egQYr0PnOd2r7ZbIizv3MKT4RBrfOzrE6cvm9bJEzNWXdDyIxZ/kuoLA6 -zX7gGLdGhg7dqzKqnGtopLAsyM1b/utRtWxOTGO9K9lRxyX82oCVT9Yw0DwwA+cH -Gutg1w7JHrIAYEtY0ezHgxhqMGuuTyJMX9Vr0D+9DdMeBK7hVOeSnxkaQ0f9HvF6 -0XI/2OTIoBSCBpUXjpgsYt7m7n2rFJGJmtqgLAosCAkacHnHLwX0EnzBw3sdDU6Q -jFXUWIDd5xUsNkFDCbspLMFs22hjNI6f/GREwd23Q4ujF8pUIcxcfbs2myjbK45s -tsn/jrkxmKRgwCIeN/H7CM+4GXSkEGLWbiGCxWzWt9wW1F4M7NW9nho3D1Pi2LBL -1ByTmjfo/9u9haWrp53enDLJJbcaslfe+zvo3J70Nnzu3m3oJ3dmUxgJIstG10g3 -lhpUm1ynvx04IFkYJ3kr/QHG/xGS+yh/pMZlwcUSpjEgYFmjFHU4A1Ng4LGI4lnw -5wisay4J884xmDgGfK0sdVQyW5rExIg63yYXp2GskRdDdwvWlFUzPzGgCNXQU96A -ljZfjs2u4IiVCC3uVsNbGqCeSdAl9HC5xKuPNbw5yTxPkeRL1ouSdkBy7rvdFaFf -dMPw6sBRNW8ZFInlgOncR3+xT/rZxru87LCq+3hRN3kw3hvFldrW2QzZSksO759b -pJEP+4fxuG96Wq25fRmzHzE0bdJ+2qF3fp/hy4oRi+eVPa0vHdtkymE4OUFWftb6 -+P++JVOzZ4ZxYA8zyUoJb0YCaxL+Jp/QqiUiH8WZVmYZmswqR48sUUKr7TIvpNbY -6jEH6F7KiZCoWfKH12tUC69iRYx3UT/4Bmsgi3S4yUxfieYRMIwihtpP4i0O+OjB -/DPbb13qj8ZSfXJ+jmF2SRFfFG+2T7NJqm09JvT9UcslVd+vpUySNe9UAlpcvNGZ -2+j180ZU7YAgpwdVwdvqiJxkeVtAsIeqAvIXMFm1PDe7FJB0BiSVZdihB6cjnKBI -dv7Lc1tI2sQe7QSfk+gtionLrEnto+aXF5uVM5LMKi3gLElz7oXEIhn54OeEciB1 -cEmyX3Kb4HMRDMHyJxqJXwxm88RgC6RekoPvstu+AfX/NgSpRj5beaj9XkweJT3H -rKWhkjq4Ghsn1LoodxluMMHd61m47JyoqIP9PBKoW+Na0VUKIVHw9e9YeW0nY1Zi -5qFA/pHPAt9AbEilRay6NEm8P7TTlNo216amc8byPXanoNrqBYZQHhZ93A4yl6jy -RdpYskMivT+Sh1nhZAioKqqTZ3HiFR8hFGspAt5gJc4WLYevmxSicGa6AMyhrkvG -rvOSdjY6JY/NkxtcgeycBX5MLF7uDbhUeqittvmlcrVN6+V+2HIbCCrvtow9pcX9 -EkaaNttj5M0RzjQxogCG+S5TkhCy04YvKIkaGJFi8xO3icdlxgOrKD8lhtbf4UpR -cDuytl70JD95mSUWL53UYjeRf9OsLRJMHQOpS02japkMwCb/ngMCQuUXA8hGkBZL -Xw7RwwPuM1Lx8edMXn5C0E8UK5e0QmI/dVIl2aglXk2oBMBJbnyrbfUPm462SG6u -ke4gQKFmVy2rKICqSkh2DMr0NzeYEUjZ6KbmQcV7sKiFxQ0/ROk8eqkYYxGWUWJv -ylPF1OTLH0AIbGlFPLQO4lMPh05yznZTac4tmowADSHY9RCxad1BjBeine2pj48D -u36OnnuQIsedxt5YC+h1bs+mIvwMVsnMLidse38M/RayCDitEBvL0KeG3vWYzaAL -h0FCZGOW0ilVk8tTF5+XWtsQEp1PpclvkcBMkU3DtBUnlmPSKNfJT0iRr2T0sVW1 -h+249Wj0Bw== -</Data> -</CertificateFile> -""" - -transport_cert=u"""\ ------BEGIN CERTIFICATE----- -MIIDBzCCAe+gAwIBAgIJANujJuVt5eC8MA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNV -BAMMDkxpbnV4VHJhbnNwb3J0MCAXDTE0MTAyNDA3MjgwN1oYDzIxMDQwNzEyMDcy -ODA3WjAZMRcwFQYDVQQDDA5MaW51eFRyYW5zcG9ydDCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBANPcJAkd6V5NeogSKjIeTXOWC5xzKTyuJPt4YZMVSosU -0lI6a0wHp+g2fP22zrVswW+QJz6AVWojIEqLQup3WyCXZTv8RUblHnIjkvX/+J/G -aLmz0G5JzZIpELL2C8IfQLH2IiPlK9LOQH00W74WFcK3QqcJ6Kw8GcVaeSXT1r7X -QcGMqEjcWJkpKLoMJv3LMufE+JMdbXDUGY+Ps7Zicu8KXvBPaKVsc6H2jrqBS8et -jXbzLyrezTUDz45rmyRJzCO5Sk2pohuYg73wUykAUPVxd7L8WnSyqz1v4zrObqnw -BAyor67JR/hjTBfjFOvd8qFGonfiv2Vnz9XsYFTZsXECAwEAAaNQME4wHQYDVR0O -BBYEFL8i/sehpGV6IEDX7F0WQHQ/ZXOyMB8GA1UdIwQYMBaAFL8i/sehpGV6IEDX -7F0WQHQ/ZXOyMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAMPLrimT -Gptu5pLRHPT8OFRN+skNSkepYaUaJuq6cSKxLumSYkD8++rohu+1+a7t1YNjjNSJ -8ohRAynRJ7aRqwBmyX2OPLRpOfyRZwR0rcFfAMORm/jOE6WBdqgYD2L2b+tZplGt -/QqgQzebaekXh/032FK4c74Zg5r3R3tfNSUMG6nLauWzYHbQ5SCdkuQwV0ehGqh5 -VF1AOdmz4CC2237BNznDFQhkeU0LrqqAoE/hv5ih7klJKZdS88rOYEnVJsFFJb0g -qaycXjOm5Khgl4hKrd+DBD/qj4IVVzsmdpFli72k6WLBHGOXusUGo/3isci2iAIt -DsfY6XGSEIhZnA4= ------END CERTIFICATE----- -""" - -transport_private=u"""\ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDT3CQJHeleTXqI -EioyHk1zlguccyk8riT7eGGTFUqLFNJSOmtMB6foNnz9ts61bMFvkCc+gFVqIyBK -i0Lqd1sgl2U7/EVG5R5yI5L1//ifxmi5s9BuSc2SKRCy9gvCH0Cx9iIj5SvSzkB9 -NFu+FhXCt0KnCeisPBnFWnkl09a+10HBjKhI3FiZKSi6DCb9yzLnxPiTHW1w1BmP -j7O2YnLvCl7wT2ilbHOh9o66gUvHrY128y8q3s01A8+Oa5skScwjuUpNqaIbmIO9 -8FMpAFD1cXey/Fp0sqs9b+M6zm6p8AQMqK+uyUf4Y0wX4xTr3fKhRqJ34r9lZ8/V -7GBU2bFxAgMBAAECggEBAM4hsfog3VAAyIieS+npq+gbhH6bWfMNaTQ3g5CNNbMu -9hhFeOJHzKnWYjSlamgBQhAfTN+2E+Up+iAtcVUZ/lMumrQLlwgMo1vgmvu5Kxmh -/YE5oEG+k0JzrCjD1trwd4zvc3ZDYyk/vmVTzTOc311N248UyArUiyqHBbq1a4rP -tJhCLn2c4S7flXGF0MDVGZyV9V7J8N8leq/dRGMB027Li21T+B4mPHXa6b8tpRPL -4vc8sHoUJDa2/+mFDJ2XbZfmlgd3MmIPlRn1VWoW7mxgT/AObsPl7LuQx7+t80Wx -hIMjuKUHRACQSLwHxJ3SQRFWp4xbztnXSRXYuHTscLUCgYEA//Uu0qIm/FgC45yG -nXtoax4+7UXhxrsWDEkbtL6RQ0TSTiwaaI6RSQcjrKDVSo/xo4ZySTYcRgp5GKlI -CrWyNM+UnIzTNbZOtvSIAfjxYxMsq1vwpTlOB5/g+cMukeGg39yUlrjVNoFpv4i6 -9t4yYuEaF4Vww0FDd2nNKhhW648CgYEA0+UYH6TKu03zDXqFpwf4DP2VoSo8OgfQ -eN93lpFNyjrfzvxDZkGF+7M/ebyYuI6hFplVMu6BpgpFP7UVJpW0Hn/sXkTq7F1Q -rTJTtkTp2+uxQVP/PzSOqK0Twi5ifkfoEOkPkNNtTiXzwCW6Qmmcvln2u893pyR5 -gqo5BHR7Ev8CgYAb7bXpN9ZHLJdMHLU3k9Kl9YvqOfjTxXA3cPa79xtEmsrTys4q -4HuL22KSII6Fb0VvkWkBAg19uwDRpw78VC0YxBm0J02Yi8b1AaOhi3dTVzFFlWeh -r6oK/PAAcMKxGkyCgMAZ3hstsltGkfXMoBwhW+yL6nyOYZ2p9vpzAGrjkwKBgQDF -0huzbyXVt/AxpTEhv07U0enfjI6tnp4COp5q8zyskEph8yD5VjK/yZh5DpmFs6Kw -dnYUFpbzbKM51tToMNr3nnYNjEnGYVfwWgvNHok1x9S0KLcjSu3ki7DmmGdbfcYq -A2uEyd5CFyx5Nr+tQOwUyeiPbiFG6caHNmQExLoiAQKBgFPy9H8///xsadYmZ18k -r77R2CvU7ArxlLfp9dr19aGYKvHvnpsY6EuChkWfy8Xjqn3ogzgrHz/rn3mlGUpK -vbtwtsknAHtTbotXJwfaBZv2RGgGRr3DzNo6ll2Aez0lNblZFXq132h7+y5iLvar -4euORaD/fuM4UPlR5mN+bypU ------END PRIVATE KEY----- -""" - -def MockGetOpensslCmd(): - return 'openssl' - -class TestCertificates(unittest.TestCase): - - def test_certificates(self): - crt1 = '/tmp/33B0ABCE4673538650971C10F7D7397E71561F35.crt' - crt2 = '/tmp/4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3.crt' - prv2 = '/tmp/4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3.prv' - os.chdir('/tmp') - if os.path.isfile(crt1): - os.remove(crt1) - if os.path.isfile(crt2): - os.remove(crt2) - if os.path.isfile(prv2): - os.remove(prv2) - fileutil.write_file(os.path.join('/tmp', "TransportCert.pem"), - transport_cert) - fileutil.write_file(os.path.join('/tmp', "TransportPrivate.pem"), - transport_private) - client = v1.WireClient("http://foo.bar") - config = v1.Certificates(client, certs_sample) - self.assertNotEquals(None, config) - self.assertTrue(os.path.isfile(crt1)) - self.assertTrue(os.path.isfile(crt2)) - self.assertTrue(os.path.isfile(prv2)) - - self.assertNotEquals(0, len(config.cert_list.certificates)) - cert = config.cert_list.certificates[0] - self.assertNotEquals(None, cert.thumbprint) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_conf.py b/tests/test_conf.py deleted file mode 100644 index 204e4f5..0000000 --- a/tests/test_conf.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import os -import tests.env -import uuid -import unittest -import tests.tools as tools -import azurelinuxagent.utils.fileutil as fileutil -import azurelinuxagent.conf as conf -from azurelinuxagent.exception import * - -TestConf="""\ -# -# This is comment -# -foo.bar.switch=y -foo.bar.switch2=n -foo.bar.str=foobar -foo.bar.int=300 - -""" - -class TestConfiguration(unittest.TestCase): - def test_parse_conf(self): - config = conf.ConfigurationProvider() - config.load(TestConf) - self.assertEquals(True, config.get_switch("foo.bar.switch")) - self.assertEquals(False, config.get_switch("foo.bar.switch2")) - self.assertEquals(False, config.get_switch("foo.bar.switch3")) - self.assertEquals(True, config.get_switch("foo.bar.switch4", True)) - self.assertEquals("foobar", config.get("foo.bar.str")) - self.assertEquals("foobar1", config.get("foo.bar.str1", "foobar1")) - self.assertEquals(300, config.get_int("foo.bar.int")) - self.assertEquals(-1, config.get_int("foo.bar.int2")) - self.assertEquals(-1, config.get_int("foo.bar.str")) - - def test_parse_malformed_conf(self): - config = conf.ConfigurationProvider() - self.assertRaises(AgentConfigError, config.load, None) - - def test_load_conf_file(self): - with open('/tmp/test_conf', 'w') as F: - F.write(TestConf) - F.close() - - config = conf.ConfigurationProvider() - conf.load_conf('/tmp/test_conf', conf=config) - self.assertEquals(True, config.get_switch("foo.bar.switch"), False) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_deprovision.py b/tests/test_deprovision.py deleted file mode 100644 index 8bad6b9..0000000 --- a/tests/test_deprovision.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import unittest -import azurelinuxagent.distro.default.deprovision as deprovision_handler - -def MockAction(param): - #print param - pass - -def MockSetup(self, deluser): - warnings = ["Print warning to console"] - actions = [ - deprovision_handler.DeprovisionAction(MockAction, ['Take action']) - ] - return warnings, actions - -class TestDeprovisionHandler(unittest.TestCase): - def test_setup(self): - handler = deprovision_handler.DeprovisionHandler() - warnings, actions = handler.setup(False) - self.assertNotEquals(None, warnings) - self.assertNotEquals(0, len(warnings)) - self.assertNotEquals(None, actions) - self.assertNotEquals(0, len(actions)) - self.assertEquals(deprovision_handler.DeprovisionAction, type(actions[0])) - - - @mock(deprovision_handler.DeprovisionHandler, 'setup', MockSetup) - def test_deprovision(self): - handler = deprovision_handler.DeprovisionHandler() - handler.deprovision(force=True) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_dhcp.py b/tests/test_dhcp.py deleted file mode 100644 index 2206325..0000000 --- a/tests/test_dhcp.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env as env -from tests.tools import * -import uuid -import unittest -import os -import json -import azurelinuxagent.utils.fileutil as fileutil -import azurelinuxagent.distro.default.dhcp as dhcp_handler - -SampleDhcpResponse = None -with open(os.path.join(env.test_root, "dhcp"), 'rb') as F: - SampleDhcpResponse = F.read() - -mock_socket_send = MockFunc('socket_send', SampleDhcpResponse) -mock_gen_trans_id = MockFunc('gen_trans_id', "\xC6\xAA\xD1\x5D") -mock_get_mac_addr = MockFunc('get_mac_addr', "\x00\x15\x5D\x38\xAA\x38") -mock_send_dhcp_failed = MockFunc(retval=None) - -class TestdhcpHandler(unittest.TestCase): - - def test_build_dhcp_req(self): - req = dhcp_handler.build_dhcp_request(mock_get_mac_addr()) - self.assertNotEquals(None, req) - - @mock(dhcp_handler, "gen_trans_id", mock_gen_trans_id) - @mock(dhcp_handler, "socket_send", mock_socket_send) - def test_send_dhcp_req(self): - req = dhcp_handler.build_dhcp_request(mock_get_mac_addr()) - resp = dhcp_handler.send_dhcp_request(req) - self.assertNotEquals(None, resp) - - @mock(dhcp_handler, "send_dhcp_request", mock_send_dhcp_failed) - def test_send_dhcp_failed(self): - dhcp = dhcp_handler.DhcpHandler() - dhcp.probe() - - @mock(dhcp_handler, "socket_send", mock_socket_send) - @mock(dhcp_handler, "gen_trans_id", mock_gen_trans_id) - @mock(dhcp_handler.OSUTIL, "get_mac_addr", mock_get_mac_addr) - @mock(dhcp_handler.fileutil, "write_file", MockFunc()) - def test_handle_dhcp(self): - dh = dhcp_handler.DhcpHandler() - dh.probe() - self.assertEquals("10.62.144.1", dh.gateway) - self.assertEquals("10.62.144.140", dh.endpoint) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_distroLoader.py b/tests/test_distroLoader.py deleted file mode 100644 index 16987c5..0000000 --- a/tests/test_distroLoader.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import unittest -from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError -from azurelinuxagent.handler import HANDLERS -import azurelinuxagent.distro.default.osutil as osutil - -class TestDistroLoader(unittest.TestCase): - def test_loader(self): - self.assertNotEquals(osutil.DefaultOSUtil, type(OSUTIL)) - self.assertNotEquals(None, HANDLERS.init_handler) - self.assertNotEquals(None, HANDLERS.main_handler) - self.assertNotEquals(None, HANDLERS.scvmm_handler) - self.assertNotEquals(None, HANDLERS.dhcp_handler) - self.assertNotEquals(None, HANDLERS.env_handler) - self.assertNotEquals(None, HANDLERS.provision_handler) - self.assertNotEquals(None, HANDLERS.resource_disk_handler) - self.assertNotEquals(None, HANDLERS.env_handler) - self.assertNotEquals(None, HANDLERS.deprovision_handler) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_envmon.py b/tests/test_envmon.py deleted file mode 100644 index 74b61ee..0000000 --- a/tests/test_envmon.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import unittest -import time -from azurelinuxagent.future import text -from azurelinuxagent.utils.osutil import OSUTIL -from azurelinuxagent.distro.default.env import EnvMonitor - -class MockDhcpHandler(object): - def conf_routes(self): - pass - -def mock_get_dhcp_pid(): - return "1234" - -def mock_dhcp_pid_change(): - return text(time.time()) - -class TestEnvMonitor(unittest.TestCase): - - @mock(OSUTIL, 'get_dhcp_pid', mock_get_dhcp_pid) - def test_dhcp_pid_not_change(self): - monitor = EnvMonitor(MockDhcpHandler()) - monitor.handle_dhclient_restart() - - @mock(OSUTIL, 'get_dhcp_pid', mock_dhcp_pid_change) - def test_dhcp_pid_change(self): - monitor = EnvMonitor(MockDhcpHandler()) - monitor.handle_dhclient_restart() - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_event.py b/tests/test_event.py deleted file mode 100644 index fcf67c9..0000000 --- a/tests/test_event.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from .tools import * -import uuid -import unittest -import os -import shutil -import azurelinuxagent.utils.fileutil as fileutil -import azurelinuxagent.event as evt -import azurelinuxagent.protocol as prot - -class MockProtocol(object): - def get_vminfo(self): - return prot.VMInfo(subscriptionId='foo', vmName='bar') - def report_event(self, data): pass - -class TestEvent(unittest.TestCase): - def test_save(self): - if not os.path.exists("/tmp/events"): - os.mkdir("/tmp/events") - evt.add_event("Test", "Test", True) - eventsFile = os.listdir("/tmp/events") - self.assertNotEquals(0, len(eventsFile)) - shutil.rmtree("/tmp/events") - - @mock(evt.prot.FACTORY, 'get_default_protocol', - MockFunc(retval=MockProtocol())) - def test_init_sys_info(self): - monitor = evt.EventMonitor() - monitor.init_sysinfo() - self.assertNotEquals(0, len(monitor.sysinfo)) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_ext.py b/tests/test_ext.py deleted file mode 100644 index a68a851..0000000 --- a/tests/test_ext.py +++ /dev/null @@ -1,223 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import uuid -import unittest -import os -import json -import azurelinuxagent.logger as logger -from azurelinuxagent.utils.osutil import OSUTIL -import azurelinuxagent.utils.fileutil as fileutil -import azurelinuxagent.protocol as prot -import azurelinuxagent.distro.default.extension as ext - -ext_sample_json = { - "name":"TestExt", - "properties":{ - "version":"2.0", - "state":"enabled", - "upgradePolicy":"auto", - "extensions":[{ - "sequenceNumber": 0, - "publicSettings": "", - "protectedSettings": "", - "certificateThumbprint": "" - }], - "versionUris":[{ - "version":"2.1", - "uris":["http://foo.bar"] - },{ - "version":"2.0", - "uris":["http://foo.bar"] - }] - } -} -ext_sample = prot.ExtHandler() -prot.set_properties("extensions", ext_sample, ext_sample_json) - -pkd_list_sample_str={ - "versions": [{ - "version": "2.0", - "uris":[{ - "uri":"http://foo.bar" - }] - },{ - "version": "2.1", - "uris":[{ - "uri":"http://foo.bar" - }] - }] -} -pkg_list_sample = prot.ExtHandlerPackageList() -prot.set_properties("packages", pkg_list_sample, pkd_list_sample_str) - -manifest_sample_str = { - "handlerManifest":{ - "installCommand": "echo 'install'", - "uninstallCommand": "echo 'uninstall'", - "updateCommand": "echo 'update'", - "enableCommand": "echo 'enable'", - "disableCommand": "echo 'disable'", - } -} -manifest_sample = ext.HandlerManifest(manifest_sample_str) - -ext_status_sample=""" -[{ - "version": 1.0, - "timestampUTC": "2015-11-12T06:59:48Z", - "status": { - "name": "<Handler workload name>", - "operation": "<name of the operation being performed>", - "configurationAppliedTime": "2015-11-12T06:59:48Z", - "status": "error", - "code": 0, - "formattedMessage": { - "lang": "en-US", - "message": "formatted user message" - }, - "substatus": [{ - "name": "<Handler workload subcomponent name>", - "status": "error", - "code": 0 , - "formattedMessage": { - "lang": "lang[-locale]", - "message": "formatted user message" - } - },{ - "status": "error" - }] - } -}] -""" - -ext_status_sample_min=""" -[{ - "version": 1.0, - "timestampUTC": "2015-11-12T06:59:48Z", - "status": { - "status": "error" - } -}] -""" - -def mock_load_manifest(self): - return manifest_sample - -mock_launch_command = MockFunc() -mock_set_state = MockFunc() - -def mock_download(self): - fileutil.mkdir(self.get_base_dir()) - fileutil.write_file(self.get_manifest_file(), json.dumps(manifest_sample_str)) - -#logger.LoggerInit("/dev/null", "/dev/stdout") -class TestExtensions(unittest.TestCase): - - def test_load_ext(self): - libDir = OSUTIL.get_lib_dir() - test_ext1 = os.path.join(libDir, 'TestExt-1.0') - test_ext2 = os.path.join(libDir, 'TestExt-2.0') - test_ext2 = os.path.join(libDir, 'TestExt-2.1') - for path in [test_ext1, test_ext2]: - if not os.path.isdir(path): - os.mkdir(path) - test_ext = ext.get_installed_version('TestExt') - self.assertEqual('2.1', test_ext) - - def test_getters(self): - test_ext = ext.ExtHandlerInstance(ext_sample, pkg_list_sample, - ext_sample.properties.version, False) - self.assertEqual("/tmp/TestExt-2.0", test_ext.get_base_dir()) - self.assertEqual("/tmp/TestExt-2.0/status", test_ext.get_status_dir()) - self.assertEqual("/tmp/TestExt-2.0/status/0.status", - test_ext.get_status_file()) - self.assertEqual("/tmp/handler_state/TestExt-2.0/0.state", - test_ext.get_handler_state_file()) - self.assertEqual("/tmp/handler_state/TestExt-2.0/0.error", - test_ext.get_handler_state_err_file()) - self.assertEqual("/tmp/TestExt-2.0/config", test_ext.get_conf_dir()) - self.assertEqual("/tmp/TestExt-2.0/config/0.settings", - test_ext.get_settings_file()) - self.assertEqual("/tmp/TestExt-2.0/heartbeat.log", - test_ext.get_heartbeat_file()) - self.assertEqual("/tmp/TestExt-2.0/HandlerManifest.json", - test_ext.get_manifest_file()) - self.assertEqual("/tmp/TestExt-2.0/HandlerEnvironment.json", - test_ext.get_env_file()) - self.assertEqual("/tmp/log/TestExt/2.0", test_ext.get_log_dir()) - - test_ext = ext.ExtHandlerInstance(ext_sample, pkg_list_sample, - "2.1", False) - self.assertEqual("/tmp/TestExt-2.1", test_ext.get_base_dir()) - self.assertEqual("2.1", test_ext.get_target_version()) - - @mock(ext.ExtHandlerInstance, 'load_manifest', mock_load_manifest) - @mock(ext.ExtHandlerInstance, 'launch_command', mock_launch_command) - @mock(ext.ExtHandlerInstance, 'set_state', mock_set_state) - def test_handle_uninstall(self): - mock_launch_command.args = None - mock_set_state.args = None - test_ext = ext.ExtHandlerInstance(ext_sample, pkg_list_sample, - ext_sample.properties.version, False) - if not os.path.isdir(test_ext.get_base_dir()): - os.makedirs(test_ext.get_base_dir()) - test_ext.handle_uninstall() - self.assertEqual(None, mock_launch_command.args) - self.assertEqual(None, mock_set_state.args) - - test_ext = ext.ExtHandlerInstance(ext_sample, pkg_list_sample, - ext_sample.properties.version, True) - if not os.path.isdir(test_ext.get_base_dir()): - os.makedirs(test_ext.get_base_dir()) - test_ext.handle_uninstall() - self.assertEqual(manifest_sample.get_uninstall_command(), - mock_launch_command.args[0]) - - @mock(ext.ExtHandlerInstance, 'upgrade', MockFunc()) - @mock(ext.ExtHandlerInstance, 'enable', MockFunc()) - @mock(ext.ExtHandlerInstance, 'download', MockFunc()) - @mock(ext.ExtHandlerInstance, 'init_dir', MockFunc()) - @mock(ext.ExtHandlerInstance, 'install', MockFunc()) - def test_handle_enable(self): - #Test enable - test_ext = ext.ExtHandlerInstance(ext_sample, pkg_list_sample, - ext_sample.properties.version, False) - test_ext.handle_enable() - - #Test upgrade - test_ext = ext.ExtHandlerInstance(ext_sample, pkg_list_sample, - "2.0" , True) - test_ext.handle_enable() - - def test_status_convert(self): - data = json.loads(ext_status_sample) - ext_status = prot.ExtensionStatus() - ext.parse_ext_status(ext_status, data) - - data = json.loads(ext_status_sample_min) - ext_status = prot.ExtensionStatus() - ext.parse_ext_status(ext_status, data) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_extensionsconfig.py b/tests/test_extensionsconfig.py deleted file mode 100644 index 505c73d..0000000 --- a/tests/test_extensionsconfig.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import uuid -import unittest -import os -import json -import azurelinuxagent.protocol.v1 as v1 - -ext_conf_sample=u"""\ -<Extensions version="1.0.0.0" goalStateIncarnation="9"><GuestAgentExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> - <GAFamilies> - <GAFamily> - <Name>Win8</Name> - <Uris> - <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win8_asiaeast_manifest.xml</Uri> - </Uris> - </GAFamily> - <GAFamily> - <Name>Win7</Name> - <Uris> - <Uri>http://rdfepirv2hknprdstr03.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr04.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr05.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr06.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr07.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr08.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr09.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr10.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr11.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://rdfepirv2hknprdstr12.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - <Uri>http://zrdfepirv2hk2prdstr01.blob.core.windows.net/bfd5c281a7dc4e4b84381eb0b47e3aaf/Microsoft.WindowsAzure.GuestAgent_Win7_asiaeast_manifest.xml</Uri> - </Uris> - </GAFamily> - </GAFamilies> -</GuestAgentExtension> -<Plugins> - <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" /> -</Plugins> -<PluginSettings> - <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4"> - <RuntimeSettings seqNo="6">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK","publicSettings":{"foo":"bar"}}}]}</RuntimeSettings> - </Plugin> -</PluginSettings> -<StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&sp=rw&se=9999-01-01&sk=key1&sv=2014-02-14&sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions> -""" - -manifest_sample=u"""\ -<?xml version="1.0" encoding="utf-8"?> -<PluginVersionManifest xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> - <Plugins> - <Plugin> - <Version>1.0</Version> - <Uris> - <Uri>http://blahblah</Uri> - </Uris> - </Plugin> - <Plugin> - <Version>1.1</Version> - <Uris> - <Uri>http://blahblah</Uri> - </Uris> - </Plugin> - </Plugins> - <InternalPlugins> - <Plugin> - <Version>1.2</Version> - <Uris> - <Uri>http://blahblah</Uri> - </Uris> - </Plugin> -</InternalPlugins> -</PluginVersionManifest> -""" - -EmptySettings=u"""\ -<Extensions> - <Plugins> - <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" /> - </Plugins> - <StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&sp=rw&se=9999-01-01&sk=key1&sv=2014-02-14&sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob> -</Extensions> -""" - -EmptyPublicSettings=u"""\ -<Extensions> - <Plugins> - <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_CustomScriptForLinuxTest_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" /> - </Plugins> - <PluginSettings> - <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.4"> - <RuntimeSettings seqNo="6">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK"}}]}</RuntimeSettings> - </Plugin> - </PluginSettings> - <StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&sp=rw&se=9999-01-01&sk=key1&sv=2014-02-14&sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob> -</Extensions> -""" - -class TestExtensionsConfig(unittest.TestCase): - def test_extensions_config(self): - config = v1.ExtensionsConfig(ext_conf_sample) - extensions = config.ext_handlers.extHandlers - self.assertNotEquals(None, extensions) - self.assertEquals(1, len(extensions)) - self.assertNotEquals(None, extensions[0]) - extension = extensions[0] - self.assertEquals("OSTCExtensions.ExampleHandlerLinux", - extension.name) - self.assertEquals("1.4", extension.properties.version) - self.assertEquals('auto', extension.properties.upgradePolicy) - self.assertEquals("enabled", extension.properties.state) - settings = extension.properties.extensions[0] - self.assertEquals("4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3", - settings.certificateThumbprint) - self.assertEquals("MIICWgYJK", settings.privateSettings) - self.assertEquals(json.loads('{"foo":"bar"}'), - settings.publicSettings) - - man = v1.ExtensionManifest(manifest_sample) - self.assertNotEquals(None, man.pkg_list) - self.assertEquals(3, len(man.pkg_list.versions)) - - def test_empty_settings(self): - config = v1.ExtensionsConfig(EmptySettings) - - def test_empty_public_settings(self): - config = v1.ExtensionsConfig(EmptyPublicSettings) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_goalstate.py b/tests/test_goalstate.py deleted file mode 100644 index a18ce8d..0000000 --- a/tests/test_goalstate.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import uuid -import unittest -import os -import test -import azurelinuxagent.protocol.v1 as v1 - -goal_state_sample=u"""\ -<?xml version="1.0" encoding="utf-8"?> -<GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd"> - <Version>2010-12-15</Version> - <Incarnation>1</Incarnation> - <Machine> - <ExpectedState>Started</ExpectedState> - <LBProbePorts> - <Port>16001</Port> - </LBProbePorts> - </Machine> - <Container> - <ContainerId>c6d5526c-5ac2-4200-b6e2-56f2b70c5ab2</ContainerId> - <RoleInstanceList> - <RoleInstance> - <InstanceId>MachineRole_IN_0</InstanceId> - <State>Started</State> - <Configuration> - <HostingEnvironmentConfig>http://hostingenvuri/</HostingEnvironmentConfig> - <SharedConfig>http://sharedconfiguri/</SharedConfig> - <ExtensionsConfig>http://extensionsconfiguri/</ExtensionsConfig> - <FullConfig>http://fullconfiguri/</FullConfig> - </Configuration> - </RoleInstance> - </RoleInstanceList> - </Container> - </GoalState> -""" - -class TestGoalState(unittest.TestCase): - def test_goal_state(self): - goal_state = v1.GoalState(goal_state_sample) - self.assertEquals('1', goal_state.incarnation) - self.assertNotEquals(None, goal_state.expected_state) - self.assertNotEquals(None, goal_state.hosting_env_uri) - self.assertNotEquals(None, goal_state.shared_conf_uri) - self.assertEquals(None, goal_state.certs_uri) - self.assertNotEquals(None, goal_state.ext_uri) - self.assertNotEquals(None, goal_state.role_instance_id) - self.assertNotEquals(None, goal_state.container_id) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_import_waagent.py b/tests/test_import_waagent.py deleted file mode 100644 index ec3f923..0000000 --- a/tests/test_import_waagent.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import os -import imp -import sys -import uuid -import unittest - -class TestImportWAAgent(unittest.TestCase): - def test_import_waagent(self): - agent_path = os.path.join(tools.parent, 'bin/waagent') - if sys.version_info[0] == 2: - waagent = imp.load_source('waagent', agent_path) - self.assertNotEquals(None, waagent.LoggerInit) - else: - self.assertRaises(ImportError, imp.load_source, 'waagent', - agent_path) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_logger.py b/tests/test_logger.py deleted file mode 100644 index 20e9259..0000000 --- a/tests/test_logger.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import uuid -import unittest -import azurelinuxagent.logger as logger -from azurelinuxagent.future import text - -class TestLogger(unittest.TestCase): - - def test_no_appender(self): - #The logger won't throw exception even if no appender. - _logger = logger.Logger() - _logger.verb("Assert no exception") - _logger.info("Assert no exception") - _logger.warn("Assert no exception") - _logger.error("Assert no exception") - - def test_logger_format(self): - _logger = logger.Logger() - _logger.info("This is an exception {0}", Exception("Test")) - _logger.info("This is an number {0}", 0) - _logger.info("This is an boolean {0}", True) - _logger.verb("{0}") - _logger.verb("{0} {1}", 0, 1) - _logger.info("{0} {1}", 0, 1) - _logger.warn("{0} {1}", 0, 1) - _logger.error("{0} {1}", 0, 1) - _logger.add_appender(logger.AppenderType.STDOUT, - logger.LogLevel.INFO, None) - _logger.info(u"啊哈this is a utf-8 {0}", u'呵呵') - - def test_file_appender(self): - _logger = logger.Logger() - _logger.add_appender(logger.AppenderType.FILE, - logger.LogLevel.INFO, - '/tmp/testlog') - - msg = text(uuid.uuid4()) - _logger.info("Test logger: {0}", msg) - self.assertTrue(tools.simple_file_grep('/tmp/testlog', msg)) - - msg = text(uuid.uuid4()) - _logger.verb("Verbose should not be logged: {0}", msg) - self.assertFalse(tools.simple_file_grep('/tmp/testlog', msg)) - - - def test_concole_appender(self): - _logger = logger.Logger() - _logger.add_appender(logger.AppenderType.CONSOLE, - logger.LogLevel.VERBOSE, - '/tmp/testlog') - - msg = text(uuid.uuid4()) - _logger.info("Test logger: {0}", msg) - self.assertTrue(tools.simple_file_grep('/tmp/testlog', msg)) - - msg = text(uuid.uuid4()) - _logger.verb("Test logger: {0}", msg) - self.assertFalse(tools.simple_file_grep('/tmp/testlog', msg)) - - - def test_log_to_non_exists_dev(self): - _logger = logger.Logger() - _logger.add_appender(logger.AppenderType.CONSOLE, - logger.LogLevel.INFO, - '/dev/nonexists') - _logger.info("something") - - def test_log_to_non_exists_file(self): - _logger = logger.Logger() - _logger.add_appender(logger.AppenderType.FILE, - logger.LogLevel.INFO, - '/tmp/nonexists') - _logger.info("something") - - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_metadata.py b/tests/test_metadata.py deleted file mode 100644 index 8c34acc..0000000 --- a/tests/test_metadata.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import unittest -from azurelinuxagent.metadata import AGENT_NAME, AGENT_VERSION, \ - DISTRO_NAME, DISTRO_VERSION, DISTRO_CODE_NAME, \ - DISTRO_FULL_NAME - -class TestOSInfo(unittest.TestCase): - def test_curr_os_info(self): - self.assertNotEquals(None, DISTRO_NAME) - self.assertNotEquals(None, DISTRO_VERSION) - self.assertNotEquals(None, DISTRO_CODE_NAME) - self.assertNotEquals(None, DISTRO_FULL_NAME) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_osutil.py b/tests/test_osutil.py deleted file mode 100644 index 95b8e17..0000000 --- a/tests/test_osutil.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env as env -from tests.tools import * -import uuid -import unittest -import os -import shutil -import time -import azurelinuxagent.utils.fileutil as fileutil -import azurelinuxagent.utils.shellutil as shellutil -import azurelinuxagent.conf as conf -from azurelinuxagent.utils.osutil import OSUTIL, OSUtilError -import test - -class TestOSUtil(unittest.TestCase): - def test_current_distro(self): - self.assertNotEquals(None, OSUTIL) - -mount_list_sample="""\ -/dev/sda1 on / type ext4 (rw) -proc on /proc type proc (rw) -sysfs on /sys type sysfs (rw) -devpts on /dev/pts type devpts (rw,gid=5,mode=620) -tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0") -none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw) -/dev/sdb1 on /mnt/resource type ext4 (rw) -""" - -class TestCurrOS(unittest.TestCase): -#class TestCurrOS(object): - def test_get_paths(self): - self.assertNotEquals(None, OSUTIL.get_home()) - self.assertNotEquals(None, OSUTIL.get_lib_dir()) - self.assertNotEquals(None, OSUTIL.get_agent_pid_file_path()) - self.assertNotEquals(None, OSUTIL.get_conf_file_path()) - self.assertNotEquals(None, OSUTIL.get_dvd_mount_point()) - self.assertNotEquals(None, OSUTIL.get_ovf_env_file_path_on_dvd()) - - @mock(fileutil, 'write_file', MockFunc()) - @mock(fileutil, 'append_file', MockFunc()) - @mock(fileutil, 'chmod', MockFunc()) - @mock(fileutil, 'read_file', MockFunc(retval='')) - @mock(shellutil, 'run', MockFunc()) - @mock(shellutil, 'run_get_output', MockFunc(retval=[0, ''])) - def test_update_user_account(self): - OSUTIL.useradd('foo') - OSUTIL.chpasswd('foo', 'bar') - OSUTIL.del_account('foo') - - @mock(fileutil, 'read_file', MockFunc(retval='root::::')) - @mock(fileutil, 'write_file', MockFunc()) - def test_delete_root_password(self): - OSUTIL.del_root_password() - self.assertEquals('root:*LOCK*:14600::::::', - fileutil.write_file.args[1]) - - def test_cert_operation(self): - if os.path.isfile('/tmp/test.prv'): - os.remove('/tmp/test.prv') - shutil.copyfile(os.path.join(env.test_root, 'test.prv'), - '/tmp/test.prv') - if os.path.isfile('/tmp/test.crt'): - os.remove('/tmp/test.crt') - shutil.copyfile(os.path.join(env.test_root, 'test.crt'), - '/tmp/test.crt') - pub1 = OSUTIL.get_pubkey_from_prv('/tmp/test.prv') - pub2 = OSUTIL.get_pubkey_from_crt('/tmp/test.crt') - self.assertEquals(pub1, pub2) - thumbprint = OSUTIL.get_thumbprint_from_crt('/tmp/test.crt') - self.assertEquals('33B0ABCE4673538650971C10F7D7397E71561F35', thumbprint) - - def test_selinux(self): - if not OSUTIL.is_selinux_system(): - return - isrunning = OSUTIL.is_selinux_enforcing() - if not OSUTIL.is_selinux_enforcing(): - OSUTIL.set_selinux_enforce(0) - self.assertEquals(False, OSUTIL.is_selinux_enforcing()) - OSUTIL.set_selinux_enforce(1) - self.assertEquals(True, OSUTIL.is_selinux_enforcing()) - if os.path.isfile('/tmp/abc'): - os.remove('/tmp/abc') - fileutil.write_file('/tmp/abc', '') - OSUTIL.set_selinux_context('/tmp/abc','unconfined_u:object_r:ssh_home_t:s') - OSUTIL.set_selinux_enforce(1 if isrunning else 0) - - @mock(shellutil, 'run_get_output', MockFunc(retval=[0, ''])) - @mock(fileutil, 'write_file', MockFunc()) - def test_network_operation(self): - OSUTIL.start_network() - OSUTIL.allow_dhcp_broadcast() - OSUTIL.gen_transport_cert() - mac = OSUTIL.get_mac_addr() - self.assertNotEquals(None, mac) - OSUTIL.is_missing_default_route() - OSUTIL.set_route_for_dhcp_broadcast('api') - OSUTIL.remove_route_for_dhcp_broadcast('api') - OSUTIL.route_add('', '', '') - OSUTIL.get_dhcp_pid() - OSUTIL.set_hostname('api') - OSUTIL.publish_hostname('api') - - @mock(OSUTIL, 'get_home', MockFunc(retval='/tmp/home')) - @mock(OSUTIL, 'get_pubkey_from_prv', MockFunc(retval='')) - @mock(fileutil, 'chowner', MockFunc()) - def test_deploy_key(self): - if os.path.isdir('/tmp/home'): - shutil.rmtree('/tmp/home') - fileutil.write_file('/tmp/foo.prv', '') - OSUTIL.deploy_ssh_keypair("foo", ('$HOME/.ssh/id_rsa', 'foo')) - OSUTIL.deploy_ssh_pubkey("foo", ('$HOME/.ssh/authorized_keys', None, - 'ssh-rsa asdf')) - OSUTIL.deploy_ssh_pubkey("foo", ('$HOME/.ssh/authorized_keys', 'foo', - 'ssh-rsa asdf')) - self.assertRaises(OSUtilError, OSUTIL.deploy_ssh_pubkey, "foo", - ('$HOME/.ssh/authorized_keys', 'foo','hehe-rsa asdf')) - self.assertTrue(os.path.isfile('/tmp/home/.ssh/id_rsa')) - self.assertTrue(os.path.isfile('/tmp/home/.ssh/id_rsa.pub')) - self.assertTrue(os.path.isfile('/tmp/home/.ssh/authorized_keys')) - - @mock(shellutil, 'run_get_output', MockFunc(retval=[0, ''])) - @mock(OSUTIL, 'get_sshd_conf_file_path', MockFunc(retval='/tmp/sshd_config')) - def test_ssh_operation(self): - shellutil.run_get_output.retval=[0, - '2048 f1:fe:14:66:9d:46:9a:60:8b:8c:' - '80:43:39:1c:20:9e root@api (RSA)'] - sshd_conf = OSUTIL.get_sshd_conf_file_path() - self.assertEquals('/tmp/sshd_config', sshd_conf) - if os.path.isfile(sshd_conf): - os.remove(sshd_conf) - shutil.copyfile(os.path.join(env.test_root, 'sshd_config'), sshd_conf) - OSUTIL.set_ssh_client_alive_interval() - OSUTIL.conf_sshd(True) - self.assertTrue(simple_file_grep(sshd_conf, - 'PasswordAuthentication no')) - self.assertTrue(simple_file_grep(sshd_conf, - 'ChallengeResponseAuthentication no')) - self.assertTrue(simple_file_grep(sshd_conf, - 'ClientAliveInterval 180')) - - @mock(shellutil, 'run_get_output', MockFunc(retval=[0, ''])) - @mock(OSUTIL, 'get_dvd_device', MockFunc(retval=[0, 'abc'])) - @mock(OSUTIL, 'get_mount_point', MockFunc(retval='/tmp/cdrom')) - def test_mount(self): - OSUTIL.mount_dvd() - OSUTIL.umount_dvd() - mount_point = OSUTIL.get_mount_point(mount_list_sample, '/dev/sda') - self.assertNotEquals(None, mount_point) - - def test_getdvd(self): - fileutil.write_file("/tmp/sr0", '') - OSUTIL.get_dvd_device(dev_dir='/tmp') - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_ovfxml.py b/tests/test_ovfxml.py deleted file mode 100644 index 7b6990b..0000000 --- a/tests/test_ovfxml.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import uuid -import unittest -import os -import json -from azurelinuxagent.future import text -import azurelinuxagent.protocol.ovfenv as ovfenv - -ExtensionsConfigSample="""\ -<?xml version="1.0" encoding="utf-8"?> - <Environment xmlns="http://schemas.dmtf.org/ovf/environment/1" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1" xmlns:wa="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <wa:ProvisioningSection> - <wa:Version>1.0</wa:Version> - <LinuxProvisioningConfigurationSet xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> - <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType> - <HostName>HostName</HostName> - <UserName>UserName</UserName> - <UserPassword>UserPassword</UserPassword> - <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication> - <SSH> - <PublicKeys> - <PublicKey> - <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> - <Path>$HOME/UserName/.ssh/authorized_keys</Path> - <Value>ssh-rsa AAAANOTAREALKEY== foo@bar.local</Value> - </PublicKey> - </PublicKeys> - <KeyPairs> - <KeyPair> - <Fingerprint>EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62</Fingerprint> - <Path>$HOME/UserName/.ssh/id_rsa</Path> - </KeyPair> - </KeyPairs> - </SSH> - <CustomData>CustomData</CustomData> - </LinuxProvisioningConfigurationSet> - </wa:ProvisioningSection> - </Environment> -""" - -class TestOvf(unittest.TestCase): - def test_ovf(self): - config = ovfenv.OvfEnv(ExtensionsConfigSample) - self.assertEquals("HostName", config.hostname) - self.assertEquals("UserName", config.username) - self.assertEquals("UserPassword", config.user_password) - self.assertEquals(False, config.disable_ssh_password_auth) - self.assertEquals("CustomData", config.customdata) - self.assertNotEquals(None, config.ssh_pubkeys) - self.assertEquals(1, len(config.ssh_pubkeys)) - pubkey = config.ssh_pubkeys[0] - path, fingerprint, value = pubkey - self.assertEquals(path, "$HOME/UserName/.ssh/authorized_keys") - self.assertEquals(fingerprint, "EB0C0AB4B2D5FC35F2F0658D19F44C8283E2DD62"), - self.assertEquals(value, "ssh-rsa AAAANOTAREALKEY== foo@bar.local") - self.assertNotEquals(None, config.ssh_keypairs) - self.assertEquals(1, len(config.ssh_keypairs)) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_protocol.py b/tests/test_protocol.py deleted file mode 100644 index de74443..0000000 --- a/tests/test_protocol.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import uuid -import unittest -import os -import time -import json -from azurelinuxagent.protocol.common import * - -extensionDataStr = """ -{ - "vmAgent": { - "agentVersion": "2.4.1198.689", - "status": "Ready", - "message": "GuestAgent is running and accepting new configurations.", - "extensionHandlers": [{ - "name": "Microsoft.Compute.CustomScript", - "version": "1.0.0.0", - "status": "Ready", - "message": "Plugin enabled (name: Microsoft.Compute.CustomScript, version: 1.0.0.0).", - "extensions": [] - }] - } -} -""" - -class TestProtocolContract(unittest.TestCase): - def test_get_properties(self): - data = get_properties(VMInfo()) - data = get_properties(Cert()) - data = get_properties(ExtHandlerPackageList()) - data = get_properties(VMStatus()) - data = get_properties(TelemetryEventList()) - data = get_properties(ExtHandler(name="hehe")) - self.assertTrue("name" in data) - self.assertTrue("properties" in data) - self.assertEquals(dict, type(data["properties"])) - self.assertTrue("versionUris" in data) - - def test_set_properties(self): - data = json.loads(extensionDataStr) - obj = VMStatus() - set_properties("vmStatus", obj, data) - self.assertNotEquals(None, obj.vmAgent) - self.assertEquals(VMAgentStatus, type(obj.vmAgent)) - self.assertNotEquals(None, obj.vmAgent.status) - self.assertNotEquals(None, obj.vmAgent.extensionHandlers) - self.assertEquals(DataContractList, type(obj.vmAgent.extensionHandlers)) - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_redhat.py b/tests/test_redhat.py deleted file mode 100644 index d9ea4ec..0000000 --- a/tests/test_redhat.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import unittest -from azurelinuxagent.distro.redhat.osutil import RedhatOSUtil - -test_pubkey="""\ ------BEGIN PUBLIC KEY----- -MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA2wo22vf1N8NWE+5lLfit -T7uzkfwqdw0IAoHZ0l2BtP0ajy6f835HCR3w3zLWw5ut7Xvyo26x1OMOzjo5lqtM -h8iyQwfHtWf6Cekxfkf+6Pca99bNuDgwRopOTOyoVgwDzJB0+slpn/sJjeGbhxJl -ToT8tNPLrBmnnpaMZLMIANcPQtTRCQcV/ycv+/omKXFB+zULYkN8v22o5mysoCuQ -fzXiJP3Mlnf+V2XMl1WAJylhOJif04K8j+G8oF5ECBIQiph4ZLQS1yTYlozPXU8k -8vB6A5+UiOGxBnOQYnp42cS5d4qSQ8LORCRGXrCj4DCP+lvkUDLUHx2WN+1ivZkO -fQIBIw== ------END PUBLIC KEY----- -""" - -expected_ssh_rsa_pubkey="""\ -ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2wo22vf1N8NWE+5lLfitT7uzkfwqdw0IAoHZ0l2BtP0ajy6f835HCR3w3zLWw5ut7Xvyo26x1OMOzjo5lqtMh8iyQwfHtWf6Cekxfkf+6Pca99bNuDgwRopOTOyoVgwDzJB0+slpn/sJjeGbhxJlToT8tNPLrBmnnpaMZLMIANcPQtTRCQcV/ycv+/omKXFB+zULYkN8v22o5mysoCuQfzXiJP3Mlnf+V2XMl1WAJylhOJif04K8j+G8oF5ECBIQiph4ZLQS1yTYlozPXU8k8vB6A5+UiOGxBnOQYnp42cS5d4qSQ8LORCRGXrCj4DCP+lvkUDLUHx2WN+1ivZkOfQ== -""" - -class TestRedhat(unittest.TestCase): - def test_RsaPublicKeyToSshRsa(self): - OSUtil = RedhatOSUtil() - ssh_rsa_pubkey = OSUtil.asn1_to_ssh_rsa(test_pubkey) - self.assertEquals(expected_ssh_rsa_pubkey, ssh_rsa_pubkey) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_resourcedisk.py b/tests/test_resourcedisk.py deleted file mode 100644 index de54fd3..0000000 --- a/tests/test_resourcedisk.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import unittest -import azurelinuxagent.distro.default.resourceDisk as rdh -import azurelinuxagent.logger as logger -from azurelinuxagent.utils.osutil import OSUTIL - -#logger.LoggerInit("/dev/null", "/dev/stdout") - -gpt_output_sample=""" -Model: Msft Virtual Disk (scsi) -Disk /dev/sda: 32.2GB -Sector size (logical/physical): 512B/4096B -Partition Table: gpt - -Number Start End Size Type File system Flags - 1 2097kB 29.4GB 29.4GB primary ext4 boot - 2 2097kB 29.4GB 29.4GB primary ext4 boot -""" - -class TestResourceDisk(unittest.TestCase): - - @mock(rdh.OSUTIL, 'device_for_ide_port', MockFunc(retval='foo')) - @mock(rdh.shellutil, 'run_get_output', MockFunc(retval=(0, gpt_output_sample))) - @mock(rdh.shellutil, 'run', MockFunc(retval=0)) - def test_mountGPT(self): - handler = rdh.ResourceDiskHandler() - handler.mount_resource_disk('/tmp/foo', 'ext4') - - @mock(rdh.OSUTIL, 'device_for_ide_port', MockFunc(retval='foo')) - @mock(rdh.shellutil, 'run_get_output', MockFunc(retval=(0, ""))) - @mock(rdh.shellutil, 'run', MockFunc(retval=0)) - def test_mountMBR(self): - handler = rdh.ResourceDiskHandler() - handler.mount_resource_disk('/tmp/foo', 'ext4') - - @mock(rdh.shellutil, 'run', MockFunc(retval=0)) - def test_createSwapSpace(self): - handler = rdh.ResourceDiskHandler() - handler.create_swap_space('/tmp/foo', 512) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_rest_util.py b/tests/test_rest_util.py deleted file mode 100644 index d07a1df..0000000 --- a/tests/test_rest_util.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import uuid -import unittest -import os -import azurelinuxagent.utils.restutil as restutil -from azurelinuxagent.future import text -import test -import socket -import azurelinuxagent.logger as logger - -class MockResponse(object): - def __init__(self, status=restutil.httpclient.OK): - self.status = status - - def getheaders(self): - pass - -class TestHttpOperations(unittest.TestCase): - - def test_parse_url(self): - host, port, secure, rel_uri = restutil._parse_url("http://abc.def/ghi#hash?jkl=mn") - self.assertEquals("abc.def", host) - self.assertEquals("/ghi#hash?jkl=mn", rel_uri) - - host, port, secure, rel_uri = restutil._parse_url("http://abc.def/") - self.assertEquals("abc.def", host) - self.assertEquals("/", rel_uri) - self.assertEquals(False, secure) - - host, port, secure, rel_uri = restutil._parse_url("https://abc.def/ghi?jkl=mn") - self.assertEquals(True, secure) - - host, port, secure, rel_uri = restutil._parse_url("http://abc.def:80/") - self.assertEquals("abc.def", host) - - @mock(restutil.httpclient.HTTPConnection, 'request', MockFunc()) - @mock(restutil.httpclient.HTTPConnection, 'getresponse', MockFunc(retval=MockResponse())) - def test_http_request(self): - restutil.http_get("https://httpbin.org/get") - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_v1.py b/tests/test_v1.py deleted file mode 100644 index 5a1b36b..0000000 --- a/tests/test_v1.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -from tests.tools import * -import uuid -import unittest -import os -import time -from azurelinuxagent.utils.restutil import httpclient -import azurelinuxagent.logger as logger -import azurelinuxagent.protocol.v1 as v1 -from tests.test_version import VersionInfoSample -from tests.test_goalstate import goal_state_sample -from tests.test_hostingenv import hosting_env_sample -from tests.test_sharedconfig import shared_config_sample -from tests.test_certificates import certs_sample, transport_cert -from tests.test_extensionsconfig import ext_conf_sample, manifest_sample - -def mock_fetch_config(self, url, headers=None, chk_proxy=False): - content = None - if "versions" in url: - content = VersionInfoSample - elif "goalstate" in url: - content = goal_state_sample - elif "hostingenvuri" in url: - content = hosting_env_sample - elif "sharedconfiguri" in url: - content = shared_config_sample - elif "certificatesuri" in url: - content = certs_sample - elif "extensionsconfiguri" in url: - content = ext_conf_sample - elif "manifest.xml" in url: - content = manifest_sample - else: - raise Exception("Bad url {0}".format(url)) - return content - -def mock_fetch_manifest(self, uris): - return manifest_sample - -def mock_fetch_cache(self, file_path): - content = None - if "Incarnation" in file_path: - content = 1 - elif "GoalState" in file_path: - content = goal_state_sample - elif "HostingEnvironmentConfig" in file_path: - content = hosting_env_sample - elif "SharedConfig" in file_path: - content = shared_config_sample - elif "Certificates" in file_path: - content = certs_sample - elif "TransportCert" in file_path: - content = transport_cert - elif "ExtensionsConfig" in file_path: - content = ext_conf_sample - elif "manifest" in file_path: - content = manifest_sample - else: - raise Exception("Bad filepath {0}".format(file_path)) - return content - -data_with_bom = b'\xef\xbb\xbfhehe' - -class MockResp(object): - def __init__(self, status=v1.httpclient.OK, data=None): - self.status = status - self.data = data - - def read(self): - return self.data - -def mock_403(): - return MockResp(status = v1.httpclient.FORBIDDEN) - -def mock_410(): - return MockResp(status = v1.httpclient.GONE) - -def mock_503(): - return MockResp(status = v1.httpclient.SERVICE_UNAVAILABLE) - -class TestWireClint(unittest.TestCase): - - @mock(v1.restutil, 'http_get', MockFunc(retval=MockResp(data=data_with_bom))) - def test_fetch_uri_with_bom(self): - client = v1.WireClient("http://foo.bar/") - client.fetch_config("http://foo.bar", None) - - @mock(v1.WireClient, 'fetch_cache', mock_fetch_cache) - def test_get(self): - os.chdir('/tmp') - client = v1.WireClient("foobar") - goalState = client.get_goal_state() - self.assertNotEquals(None, goalState) - hostingEnv = client.get_hosting_env() - self.assertNotEquals(None, hostingEnv) - sharedConfig = client.get_shared_conf() - self.assertNotEquals(None, sharedConfig) - extensionsConfig = client.get_ext_conf() - self.assertNotEquals(None, extensionsConfig) - - - @mock(v1.WireClient, 'fetch_cache', mock_fetch_cache) - def test_get_head_for_cert(self): - client = v1.WireClient("foobar") - header = client.get_header_for_cert() - self.assertNotEquals(None, header) - - @mock(v1.WireClient, 'get_header_for_cert', MockFunc()) - @mock(v1.WireClient, 'fetch_config', mock_fetch_config) - @mock(v1.WireClient, 'fetch_manifest', mock_fetch_manifest) - @mock(v1.fileutil, 'write_file', MockFunc()) - def test_update_goal_state(self): - client = v1.WireClient("foobar") - client.update_goal_state() - goal_state = client.get_goal_state() - self.assertNotEquals(None, goal_state) - hosting_env = client.get_hosting_env() - self.assertNotEquals(None, hosting_env) - shared_config = client.get_shared_conf() - self.assertNotEquals(None, shared_config) - ext_conf = client.get_ext_conf() - self.assertNotEquals(None, ext_conf) - - @mock(v1.time, "sleep", MockFunc()) - def test_call_wireserver(self): - client = v1.WireClient("foobar") - self.assertRaises(v1.ProtocolError, client.call_wireserver, mock_403) - self.assertRaises(v1.WireProtocolResourceGone, client.call_wireserver, - mock_410) - - @mock(v1.time, "sleep", MockFunc()) - def test_call_storage_service(self): - client = v1.WireClient("foobar") - self.assertRaises(v1.ProtocolError, client.call_storage_service, - mock_503) - - -class TestStatusBlob(unittest.TestCase): - def testToJson(self): - vm_status = v1.VMStatus() - status_blob = v1.StatusBlob(v1.WireClient("http://foo.bar/")) - status_blob.set_vm_status(vm_status) - self.assertNotEquals(None, status_blob.to_json()) - - @mock(v1.restutil, 'http_put', MockFunc(retval=MockResp(httpclient.CREATED))) - @mock(v1.restutil, 'http_head', MockFunc(retval=MockResp(httpclient.OK))) - def test_put_page_blob(self): - vm_status = v1.VMStatus() - status_blob = v1.StatusBlob(v1.WireClient("http://foo.bar/")) - status_blob.set_vm_status(vm_status) - data = 'a' * 100 - status_blob.put_page_blob("http://foo.bar", data) - -class TestConvert(unittest.TestCase): - def test_status(self): - vm_status = v1.VMStatus() - handler_status = v1.ExtHandlerStatus(name="foo") - - ext_statuses = {} - - ext_name="bar" - ext_status = v1.ExtensionStatus() - handler_status.extensions.append(ext_name) - ext_statuses[ext_name] = ext_status - - substatus = v1.ExtensionSubStatus() - ext_status.substatusList.append(substatus) - - vm_status.vmAgent.extensionHandlers.append(handler_status) - v1_status = v1.vm_status_to_v1(vm_status, ext_statuses) - print(v1_status) - - def test_param(self): - param = v1.TelemetryEventParam() - event = v1.TelemetryEvent() - event.parameters.append(param) - - v1.event_to_v1(event) - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_v2.py b/tests/test_v2.py deleted file mode 100644 index c4a0b4d..0000000 --- a/tests/test_v2.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -from tests.tools import * -import unittest -import json -import azurelinuxagent.protocol.v2 as v2 - -SAMPLE_IDENTITY=u"""{ - "vmName":"foo", - "subscriptionId":"bar" -}""" - -SAMPLE_CERTS=u"""{ - "certificates":[{ - "name":"foo", - "thumbprint":"bar", - "certificateDataUri":"baz" - }] -}""" - -SAMPLE_EXT_HANDLER=u"""[{ - "name":"foo", - "properties":{ - "version":"bar", - "upgradePolicy": "manual", - "state": "enabled", - "extensions":[{ - "name":"baz", - "sequenceNumber":0, - "publicSettings":{ - "commandToExecute": "echo 123", - "uris":[] - } - }] - }, - "versionUris":[{ - "uri":"versionUri.foo" - }] -}]""" - -SAMPLE_EXT_HANDLER_PKGS=u"""{ - "versions": [{ - "version":"foo", - "uris":[{ - "uri":"bar" - },{ - "uri":"baz" - }] - }] -}""" - -def mock_get_data(self, url, headers=None): - data = u"{}" - if url.count(u"identity") > 0: - data = SAMPLE_IDENTITY - elif url.count(u"certificates") > 0: - data = SAMPLE_CERTS - elif url.count(u"extensionHandlers") > 0: - data = SAMPLE_EXT_HANDLER - elif url.count(u"versionUri") > 0: - data = SAMPLE_EXT_HANDLER_PKGS - return json.loads(data) - -class TestMetadataProtocol(unittest.TestCase): - @mock(v2.MetadataProtocol, '_get_data', mock_get_data) - def test_getters(self): - protocol = v2.MetadataProtocol() - vminfo = protocol.get_vminfo() - self.assertNotEquals(None, vminfo) - self.assertNotEquals(None, vminfo.vmName) - self.assertNotEquals(None, vminfo.subscriptionId) - - protocol.get_certs() - - ext_handers = protocol.get_ext_handlers() - self.assertNotEquals(None, ext_handers) - self.assertNotEquals(None, ext_handers.extHandlers) - self.assertNotEquals(0, len(ext_handers.extHandlers)) - - ext_hander = ext_handers.extHandlers[0] - self.assertNotEquals(None, ext_hander) - self.assertNotEquals(0, len(ext_hander.properties.extensions)) - - ext = ext_hander.properties.extensions[0] - self.assertNotEquals(None, ext) - self.assertNotEquals(None, ext.publicSettings) - self.assertEquals("echo 123", ext.publicSettings.get('commandToExecute')) - - packages = protocol.get_ext_handler_pkgs(ext_handers.extHandlers[0]) - self.assertNotEquals(None, packages) - - @mock(v2.MetadataProtocol, '_put_data', MockFunc()) - def test_reporters(self): - protocol = v2.MetadataProtocol() - protocol.report_provision_status(v2.ProvisionStatus()) - protocol.report_vm_status(v2.VMStatus()) - protocol.report_ext_status("foo", "baz", v2.ExtensionStatus()) - protocol.report_event(v2.TelemetryEventList()) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_version.py b/tests/test_version.py deleted file mode 100644 index 72d9599..0000000 --- a/tests/test_version.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2014 Microsoft Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Requires Python 2.4+ and Openssl 1.0+ -# -# Implements parts of RFC 2131, 1541, 1497 and -# http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx -# http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx - -import tests.env -import tests.tools as tools -import uuid -import unittest -import os -import json -import azurelinuxagent.protocol.v1 as v1 -from azurelinuxagent.future import text - -VersionInfoSample=u"""\ -<?xml version="1.0" encoding="utf-8"?> -<Versions> - <Preferred> - <Version>2012-11-30</Version> - </Preferred> - <Supported> - <Version>2010-12-15</Version> - <Version>2010-28-10</Version> - </Supported> -</Versions> -""" - -class TestVersionInfo(unittest.TestCase): - def test_version_info(self): - config = v1.VersionInfo(VersionInfoSample) - self.assertEquals("2012-11-30", config.get_preferred()) - self.assertNotEquals(None, config.get_supported()) - self.assertEquals(2, len(config.get_supported())) - self.assertEquals("2010-12-15", config.get_supported()[0]) - self.assertEquals("2010-28-10", config.get_supported()[1]) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/tools.py b/tests/tools.py index 392f395..672c60b 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -18,48 +18,107 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx +""" +Define util functions for unit test +""" +import re import os import sys +import unittest +import shutil +import json +import tempfile from functools import wraps -from azurelinuxagent.utils.osutil import OSUTIL - -parent = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -sys.path.append(parent) - -def simple_file_grep(file_path, search_str): - for line in open(file_path): - if search_str in line: - return line - -def mock(target, name, mock): - def decorator(func): - @wraps(func) - def wrapper(*args, **kwargs): - origin = getattr(target, name) - setattr(target, name, mock) - try: - result = func(*args, **kwargs) - except: - raise - finally: - setattr(target, name, origin) - return result - return wrapper - return decorator +import azurelinuxagent.conf as conf +import azurelinuxagent.logger as logger +import azurelinuxagent.event as event + +#Import mock module for Python2 and Python3 +try: + from unittest.mock import Mock, patch, MagicMock +except ImportError: + from mock import Mock, patch, MagicMock + +test_dir = os.path.dirname(os.path.abspath(__file__)) +data_dir = os.path.join(test_dir, "data") + +debug = False +if os.environ.get('DEBUG') == '1': + debug = True + +#Enable verbose logger to stdout +if debug: + logger.add_logger_appender(logger.AppenderType.STDOUT, + logger.LogLevel.VERBOSE) + +class AgentTestCase(unittest.TestCase): + def setUp(self): + prefix = "{0}_".format(self.__class__.__name__) + self.tmp_dir = tempfile.mkdtemp(prefix=prefix) + conf.get_lib_dir = Mock(return_value=self.tmp_dir) + ext_log_dir = os.path.join(self.tmp_dir, "azure") + conf.get_ext_log_dir = Mock(return_value=ext_log_dir) + + def tearDown(self): + if not debug and self.tmp_dir is not None: + shutil.rmtree(self.tmp_dir) + +def load_data(name): + """Load test data""" + path = os.path.join(data_dir, name) + with open(path, "r") as data_file: + return data_file.read() -class MockFunc(object): - def __init__(self, name='', retval=None): - self.name = name - self.retval = retval +def load_bin_data(name): + """Load test bin data""" + path = os.path.join(data_dir, name) + with open(path, "rb") as data_file: + return data_file.read() - def __call__(*args, **kwargs): - self = args[0] - self.args = args[1:] - self.kwargs = kwargs - return self.retval + +supported_distro = [ + ["ubuntu", "12.04", ""], + ["ubuntu", "14.04", ""], + ["ubuntu", "14.10", ""], + ["ubuntu", "15.10", ""], + ["ubuntu", "15.10", "Snappy Ubuntu Core"], + + ["coreos", "", ""], + + ["suse", "12", "SUSE Linux Enterprise Server"], + ["suse", "13.2", "openSUSE"], + ["suse", "11", "SUSE Linux Enterprise Server"], + ["suse", "13.1", "openSUSE"], + + ["debian", "6.0", ""], + + ["redhat", "6.5", ""], + ["redhat", "7.0", ""], + +] + +def distros(distro_name=".*", distro_version=".*", distro_full_name=".*"): + """Run test on multiple distros""" + def decorator(test_method): + @wraps(test_method) + def wrapper(self, *args, **kwargs): + for distro in supported_distro: + if re.match(distro_name, distro[0]) and \ + re.match(distro_version, distro[1]) and \ + re.match(distro_full_name, distro[2]): + if debug: + logger.info("Run {0} on {1}", test_method.__name__, + distro) + new_args = [] + new_args.extend(args) + new_args.extend(distro) + test_method(self, *new_args, **kwargs) + #Call tearDown and setUp to create seprated environment for + #distro testing + self.tearDown() + self.setUp() + return wrapper + return decorator -#Mock osutil so that the test of other part will be os unrelated -OSUTIL.get_lib_dir = MockFunc(retval='/tmp') -OSUTIL.get_ext_log_dir = MockFunc(retval='/tmp/log') diff --git a/azurelinuxagent/distro/oracle/__init__.py b/tests/utils/__init__.py index d9b82f5..9bdb27e 100644 --- a/azurelinuxagent/distro/oracle/__init__.py +++ b/tests/utils/__init__.py @@ -1,5 +1,3 @@ -# Microsoft Azure Linux Agent -# # Copyright 2014 Microsoft Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,4 +14,6 @@ # # 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 diff --git a/tests/test_file_util.py b/tests/utils/test_file_util.py index 1eeb784..bf7c638 100644 --- a/tests/test_file_util.py +++ b/tests/utils/test_file_util.py @@ -18,48 +18,48 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx -import tests.env -import tests.tools as tools +from tests.tools import * import uuid import unittest import os import sys -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr import azurelinuxagent.utils.fileutil as fileutil -class TestFileOperations(unittest.TestCase): +class TestFileOperations(AgentTestCase): def test_read_write_file(self): - test_file='/tmp/test_file' - content = text(uuid.uuid4()) + test_file=os.path.join(self.tmp_dir, 'test_file') + content = ustr(uuid.uuid4()) fileutil.write_file(test_file, content) - self.assertTrue(tools.simple_file_grep(test_file, content)) - content_read = fileutil.read_file('/tmp/test_file') + content_read = fileutil.read_file(test_file) self.assertEquals(content, content_read) os.remove(test_file) def test_rw_utf8_file(self): - test_file='/tmp/test_file3' - content = "\u6211" + test_file=os.path.join(self.tmp_dir, 'test_file') + content = u"\u6211" fileutil.write_file(test_file, content, encoding="utf-8") - self.assertTrue(tools.simple_file_grep(test_file, content)) - content_read = fileutil.read_file('/tmp/test_file3') + content_read = fileutil.read_file(test_file) self.assertEquals(content, content_read) os.remove(test_file) def test_remove_bom(self): - test_file= '/tmp/test_file4' + test_file=os.path.join(self.tmp_dir, 'test_file') data = b'\xef\xbb\xbfhehe' fileutil.write_file(test_file, data, asbin=True) data = fileutil.read_file(test_file, remove_bom=True) self.assertNotEquals(0xbb, ord(data[0])) def test_append_file(self): - test_file='/tmp/test_file2' - content = text(uuid.uuid4()) + test_file=os.path.join(self.tmp_dir, 'test_file') + content = ustr(uuid.uuid4()) fileutil.append_file(test_file, content) - self.assertTrue(tools.simple_file_grep(test_file, content)) + + content_read = fileutil.read_file(test_file) + self.assertEquals(content, content_read) + os.remove(test_file) def test_get_last_path_element(self): diff --git a/tests/utils/test_rest_util.py b/tests/utils/test_rest_util.py new file mode 100644 index 0000000..bd22c55 --- /dev/null +++ b/tests/utils/test_rest_util.py @@ -0,0 +1,126 @@ +# 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 + +from tests.tools import AgentTestCase, patch, Mock, MagicMock +import uuid +import unittest +import os +import azurelinuxagent.utils.restutil as restutil +from azurelinuxagent.future import ustr, httpclient +import azurelinuxagent.logger as logger + +class TestHttpOperations(AgentTestCase): + + def test_parse_url(self): + test_uri = "http://abc.def/ghi#hash?jkl=mn" + host, port, secure, rel_uri = restutil._parse_url(test_uri) + self.assertEquals("abc.def", host) + self.assertEquals("/ghi#hash?jkl=mn", rel_uri) + + test_uri = "http://abc.def/" + host, port, secure, rel_uri = restutil._parse_url(test_uri) + self.assertEquals("abc.def", host) + self.assertEquals("/", rel_uri) + self.assertEquals(False, secure) + + test_uri = "https://abc.def/ghi?jkl=mn" + host, port, secure, rel_uri = restutil._parse_url(test_uri) + self.assertEquals(True, secure) + + test_uri = "http://abc.def:80/" + host, port, secure, rel_uri = restutil._parse_url(test_uri) + self.assertEquals("abc.def", host) + + host, port, secure, rel_uri = restutil._parse_url("") + self.assertEquals(None, host) + self.assertEquals(rel_uri, "") + + host, port, secure, rel_uri = restutil._parse_url("None") + self.assertEquals(None, host) + self.assertEquals(rel_uri, "None") + + + @patch("azurelinuxagent.future.httpclient.HTTPSConnection") + @patch("azurelinuxagent.future.httpclient.HTTPConnection") + def test_http_request(self, HTTPConnection, HTTPSConnection): + mock_httpconn = MagicMock() + mock_httpresp = MagicMock() + mock_httpconn.getresponse = Mock(return_value=mock_httpresp) + HTTPConnection.return_value = mock_httpconn + HTTPSConnection.return_value = mock_httpconn + + mock_httpresp.read = Mock(return_value="_(:3| <)_") + + #Test http get + resp = restutil._http_request("GET", "foo", "bar") + self.assertNotEquals(None, resp) + self.assertEquals("_(:3| <)_", resp.read()) + + #Test https get + resp = restutil._http_request("GET", "foo", "bar", secure=True) + self.assertNotEquals(None, resp) + self.assertEquals("_(:3| <)_", resp.read()) + + #Test http get with proxy + mock_httpresp.read = Mock(return_value="_(:3| <)_") + resp = restutil._http_request("GET", "foo", "bar", proxy_host="foo.bar", + proxy_port=23333) + self.assertNotEquals(None, resp) + self.assertEquals("_(:3| <)_", resp.read()) + + #Test https get + resp = restutil._http_request("GET", "foo", "bar", secure=True) + self.assertNotEquals(None, resp) + self.assertEquals("_(:3| <)_", resp.read()) + + #Test https get with proxy + mock_httpresp.read = Mock(return_value="_(:3| <)_") + resp = restutil._http_request("GET", "foo", "bar", proxy_host="foo.bar", + proxy_port=23333, secure=True) + self.assertNotEquals(None, resp) + self.assertEquals("_(:3| <)_", resp.read()) + + @patch("time.sleep") + @patch("azurelinuxagent.utils.restutil._http_request") + def test_http_request_with_retry(self, _http_request, sleep): + mock_httpresp = MagicMock() + mock_httpresp.read = Mock(return_value="hehe") + _http_request.return_value = mock_httpresp + + #Test http get + resp = restutil.http_get("http://foo.bar") + self.assertEquals("hehe", resp.read()) + + #Test https get + resp = restutil.http_get("https://foo.bar") + self.assertEquals("hehe", resp.read()) + + #Test http failure + _http_request.side_effect = httpclient.HTTPException("Http failure") + self.assertRaises(restutil.HttpError, restutil.http_get, "http://foo.bar") + + #Test http failure + _http_request.side_effect = IOError("IO failure") + self.assertRaises(restutil.HttpError, restutil.http_get, "http://foo.bar") + +if __name__ == '__main__': + unittest.main() +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_shell_util.py b/tests/utils/test_shell_util.py index 9f84c6d..aa89121 100644 --- a/tests/test_shell_util.py +++ b/tests/utils/test_shell_util.py @@ -19,16 +19,14 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx -import tests.env -import tests.tools as tools +from tests.tools import * import uuid import unittest import os import azurelinuxagent.utils.shellutil as shellutil import test -from azurelinuxagent.future import text -class TestrunCmd(unittest.TestCase): +class TestrunCmd(AgentTestCase): def test_run_get_output(self): output = shellutil.run_get_output(u"ls /") self.assertNotEquals(None, output) diff --git a/tests/test_text_util.py b/tests/utils/test_text_util.py index 5c0016c..0e8cc7d 100644 --- a/tests/test_text_util.py +++ b/tests/utils/test_text_util.py @@ -18,16 +18,15 @@ # http://msdn.microsoft.com/en-us/library/cc227282%28PROT.10%29.aspx # http://msdn.microsoft.com/en-us/library/cc227259%28PROT.13%29.aspx -import tests.env from tests.tools import * import uuid import unittest import os -from azurelinuxagent.future import text +from azurelinuxagent.future import ustr import azurelinuxagent.utils.textutil as textutil from azurelinuxagent.utils.textutil import Version -class TestTextUtil(unittest.TestCase): +class TestTextUtil(AgentTestCase): def test_get_password_hash(self): password_hash = textutil.gen_password_hash("asdf", 6, 10) self.assertNotEquals(None, password_hash) @@ -36,7 +35,7 @@ class TestTextUtil(unittest.TestCase): def test_remove_bom(self): #Test bom could be removed - data = text(b'\xef\xbb\xbfhehe', encoding='utf-8') + data = ustr(b'\xef\xbb\xbfhehe', encoding='utf-8') data = textutil.remove_bom(data) self.assertNotEquals(0xbb, data[0]) @@ -45,7 +44,7 @@ class TestTextUtil(unittest.TestCase): data = textutil.remove_bom(data) self.assertEquals(u"h", data[0]) - def test_version_compare(self) : + def test_version_compare(self): self.assertTrue(Version("1.0") < Version("1.1")) self.assertTrue(Version("1.9") < Version("1.10")) self.assertTrue(Version("1.9.9") < Version("1.10.0")) @@ -61,6 +60,20 @@ class TestTextUtil(unittest.TestCase): self.assertTrue(Version("1.9") < "1.10") self.assertTrue("1.9" < Version("1.10")) + + def test_get_bytes_from_pem(self): + content = ("-----BEGIN CERTIFICATE-----\n" + "certificate\n" + "-----END CERTIFICATE----\n") + base64_bytes = textutil.get_bytes_from_pem(content) + self.assertEquals("certificate", base64_bytes) + + + content = ("-----BEGIN PRIVATE KEY-----\n" + "private key\n" + "-----END PRIVATE Key-----\n") + base64_bytes = textutil.get_bytes_from_pem(content) + self.assertEquals("private key", base64_bytes) if __name__ == '__main__': unittest.main() |