diff options
Diffstat (limited to 'azurelinuxagent/pa')
-rw-r--r-- | azurelinuxagent/pa/deprovision/arch.py | 33 | ||||
-rw-r--r-- | azurelinuxagent/pa/deprovision/default.py | 129 | ||||
-rw-r--r-- | azurelinuxagent/pa/deprovision/factory.py | 3 | ||||
-rw-r--r-- | azurelinuxagent/pa/provision/cloudinit.py | 132 | ||||
-rw-r--r-- | azurelinuxagent/pa/provision/default.py | 188 | ||||
-rw-r--r-- | azurelinuxagent/pa/provision/factory.py | 10 | ||||
-rw-r--r-- | azurelinuxagent/pa/provision/ubuntu.py | 102 |
7 files changed, 426 insertions, 171 deletions
diff --git a/azurelinuxagent/pa/deprovision/arch.py b/azurelinuxagent/pa/deprovision/arch.py new file mode 100644 index 0000000..e661f79 --- /dev/null +++ b/azurelinuxagent/pa/deprovision/arch.py @@ -0,0 +1,33 @@ +# Microsoft Azure Linux Agent +# +# Copyright 2014 Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Requires Python 2.4+ and Openssl 1.0+ +# + +import azurelinuxagent.common.utils.fileutil as fileutil +from azurelinuxagent.pa.deprovision.default import DeprovisionHandler, \ + DeprovisionAction + +class ArchDeprovisionHandler(DeprovisionHandler): + def __init__(self): + super(ArchDeprovisionHandler, self).__init__() + + def setup(self, deluser): + warnings, actions = super(ArchDeprovisionHandler, self).setup(deluser) + warnings.append("WARNING! /etc/machine-id will be removed.") + files_to_del = ['/etc/machine-id'] + actions.append(DeprovisionAction(fileutil.rm_files, files_to_del)) + return warnings, actions diff --git a/azurelinuxagent/pa/deprovision/default.py b/azurelinuxagent/pa/deprovision/default.py index ced87ee..90d16c7 100644 --- a/azurelinuxagent/pa/deprovision/default.py +++ b/azurelinuxagent/pa/deprovision/default.py @@ -17,13 +17,17 @@ # Requires Python 2.4+ and Openssl 1.0+ # +import glob +import os.path import signal import sys + import azurelinuxagent.common.conf as conf -from azurelinuxagent.common.exception import ProtocolError -from azurelinuxagent.common.future import read_input import azurelinuxagent.common.utils.fileutil as fileutil import azurelinuxagent.common.utils.shellutil as shellutil + +from azurelinuxagent.common.exception import ProtocolError +from azurelinuxagent.common.future import read_input from azurelinuxagent.common.osutil import get_osutil from azurelinuxagent.common.protocol import get_protocol_util @@ -68,15 +72,19 @@ class DeprovisionHandler(object): def regen_ssh_host_key(self, warnings, actions): warnings.append("WARNING! All SSH host key pairs will be deleted.") actions.append(DeprovisionAction(fileutil.rm_files, - ['/etc/ssh/ssh_host_*key*'])) + [conf.get_ssh_key_glob()])) def stop_agent_service(self, warnings, actions): warnings.append("WARNING! The waagent service will be stopped.") actions.append(DeprovisionAction(self.osutil.stop_agent_service)) + def del_dirs(self, warnings, actions): + dirs = [conf.get_lib_dir(), conf.get_ext_log_dir()] + actions.append(DeprovisionAction(fileutil.rm_dirs, dirs)) + def del_files(self, warnings, actions): - files_to_del = ['/root/.bash_history', '/var/log/waagent.log'] - actions.append(DeprovisionAction(fileutil.rm_files, files_to_del)) + files = ['/root/.bash_history', '/var/log/waagent.log'] + actions.append(DeprovisionAction(fileutil.rm_files, files)) def del_resolv(self, warnings, actions): warnings.append("WARNING! /etc/resolv.conf will be deleted.") @@ -92,9 +100,63 @@ class DeprovisionHandler(object): actions.append(DeprovisionAction(fileutil.rm_files, ["/var/db/dhclient.leases.hn0", "/var/lib/NetworkManager/dhclient-*.lease"])) - def del_lib_dir(self, warnings, actions): - dirs_to_del = [conf.get_lib_dir()] - actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del)) + + def del_lib_dir_files(self, warnings, actions): + known_files = [ + 'HostingEnvironmentConfig.xml', + 'Incarnation', + 'Protocol', + 'SharedConfig.xml', + 'WireServerEndpoint' + ] + known_files_glob = [ + 'Extensions.*.xml', + 'ExtensionsConfig.*.xml', + 'GoalState.*.xml' + ] + + lib_dir = conf.get_lib_dir() + files = [f for f in \ + [os.path.join(lib_dir, kf) for kf in known_files] \ + if os.path.isfile(f)] + for p in known_files_glob: + files += glob.glob(os.path.join(lib_dir, p)) + + if len(files) > 0: + actions.append(DeprovisionAction(fileutil.rm_files, files)) + + def cloud_init_dirs(self, include_once=True): + dirs = [ + "/var/lib/cloud/instance", + "/var/lib/cloud/instances/", + "/var/lib/cloud/data" + ] + if include_once: + dirs += [ + "/var/lib/cloud/scripts/per-once" + ] + return dirs + + def cloud_init_files(self, include_once=True): + files = [ + "/etc/sudoers.d/90-cloud-init-users" + ] + if include_once: + files += [ + "/var/lib/cloud/sem/config_scripts_per_once.once" + ] + return files + + def del_cloud_init(self, warnings, actions, include_once=True): + dirs = [d for d in self.cloud_init_dirs(include_once=include_once) \ + if os.path.isdir(d)] + if len(dirs) > 0: + actions.append(DeprovisionAction(fileutil.rm_dirs, dirs)) + + files = [f for f in self.cloud_init_files(include_once=include_once) \ + if os.path.isfile(f)] + if len(files) > 0: + actions.append(DeprovisionAction(fileutil.rm_files, files)) def reset_hostname(self, warnings, actions): localhost = ["localhost.localdomain"] @@ -117,7 +179,8 @@ class DeprovisionHandler(object): if conf.get_delete_root_password(): self.del_root_password(warnings, actions) - self.del_lib_dir(warnings, actions) + self.del_cloud_init(warnings, actions) + self.del_dirs(warnings, actions) self.del_files(warnings, actions) self.del_resolv(warnings, actions) @@ -126,19 +189,55 @@ class DeprovisionHandler(object): return warnings, actions + def setup_changed_unique_id(self): + warnings = [] + actions = [] + + self.del_cloud_init(warnings, actions, include_once=False) + self.del_dhcp_lease(warnings, actions) + self.del_lib_dir_files(warnings, actions) + self.del_resolv(warnings, actions) + + return warnings, actions + def run(self, force=False, deluser=False): warnings, actions = self.setup(deluser) - for warning in warnings: - print(warning) - if not force: - confirm = read_input("Do you want to proceed (y/n)") - if not confirm.lower().startswith('y'): - return + self.do_warnings(warnings) + self.do_confirmation(force=force) + self.do_actions(actions) + + def run_changed_unique_id(self): + ''' + Clean-up files and directories that may interfere when the VM unique + identifier has changed. + While users *should* manually deprovision a VM, the files removed by + this routine will help keep the agent from getting confused + (since incarnation and extension settings, among other items, will + no longer be monotonically increasing). + ''' + warnings, actions = self.setup_changed_unique_id() + + self.do_warnings(warnings) + self.do_actions(actions) + + def do_actions(self, actions): self.actions_running = True for action in actions: action.invoke() + self.actions_running = False + + def do_confirmation(self, force=False): + if force: + return True + + confirm = read_input("Do you want to proceed (y/n)") + return True if confirm.lower().startswith('y') else False + + def do_warnings(self, warnings): + for warning in warnings: + print(warning) def handle_interrupt_signal(self, signum, frame): if not self.actions_running: diff --git a/azurelinuxagent/pa/deprovision/factory.py b/azurelinuxagent/pa/deprovision/factory.py index 72a5be1..5ac35a7 100644 --- a/azurelinuxagent/pa/deprovision/factory.py +++ b/azurelinuxagent/pa/deprovision/factory.py @@ -21,6 +21,7 @@ from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \ DISTRO_FULL_NAME from .default import DeprovisionHandler +from .arch import ArchDeprovisionHandler from .clearlinux import ClearLinuxDeprovisionHandler from .coreos import CoreOSDeprovisionHandler from .ubuntu import UbuntuDeprovisionHandler @@ -28,6 +29,8 @@ from .ubuntu import UbuntuDeprovisionHandler def get_deprovision_handler(distro_name=DISTRO_NAME, distro_version=DISTRO_VERSION, distro_full_name=DISTRO_FULL_NAME): + if distro_name == "arch": + return ArchDeprovisionHandler() if distro_name == "ubuntu": return UbuntuDeprovisionHandler() if distro_name == "coreos": diff --git a/azurelinuxagent/pa/provision/cloudinit.py b/azurelinuxagent/pa/provision/cloudinit.py new file mode 100644 index 0000000..5789e9a --- /dev/null +++ b/azurelinuxagent/pa/provision/cloudinit.py @@ -0,0 +1,132 @@ +# 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 os.path +import time + +from datetime import datetime + +import azurelinuxagent.common.conf as conf +import azurelinuxagent.common.logger as logger +import azurelinuxagent.common.utils.fileutil as fileutil +import azurelinuxagent.common.utils.shellutil as shellutil + +from azurelinuxagent.common.event import elapsed_milliseconds +from azurelinuxagent.common.exception import ProvisionError, ProtocolError +from azurelinuxagent.common.future import ustr +from azurelinuxagent.common.protocol import OVF_FILE_NAME +from azurelinuxagent.common.protocol.ovfenv import OvfEnv +from azurelinuxagent.pa.provision.default import ProvisionHandler + + +class CloudInitProvisionHandler(ProvisionHandler): + def __init__(self): + super(CloudInitProvisionHandler, self).__init__() + + def run(self): + # If provision is enabled, run default provision handler + if conf.get_provision_enabled(): + logger.warn("Provisioning flag is enabled, which overrides using " + "cloud-init; running the default provisioning code") + super(CloudInitProvisionHandler, self).run() + return + + try: + if super(CloudInitProvisionHandler, self).is_provisioned(): + logger.info("Provisioning already completed, skipping.") + return + + utc_start = datetime.utcnow() + logger.info("Running CloudInit provisioning handler") + self.wait_for_ovfenv() + self.protocol_util.get_protocol() + self.report_not_ready("Provisioning", "Starting") + + thumbprint = self.wait_for_ssh_host_key() + self.write_provisioned() + logger.info("Finished provisioning") + + self.report_ready(thumbprint) + self.report_event("Provision succeed", + is_success=True, + duration=elapsed_milliseconds(utc_start)) + + except ProvisionError as e: + logger.error("Provisioning failed: {0}", ustr(e)) + self.report_not_ready("ProvisioningFailed", ustr(e)) + self.report_event(ustr(e)) + return + + def wait_for_ovfenv(self, max_retry=360, sleep_time=5): + """ + Wait for cloud-init to copy ovf-env.xml file from provision ISO + """ + ovf_file_path = os.path.join(conf.get_lib_dir(), OVF_FILE_NAME) + for retry in range(0, max_retry): + if os.path.isfile(ovf_file_path): + try: + OvfEnv(fileutil.read_file(ovf_file_path)) + return + except ProtocolError as pe: + raise ProvisionError("OVF xml could not be parsed " + "[{0}]: {1}".format(ovf_file_path, + ustr(pe))) + else: + if retry < max_retry - 1: + logger.info( + "Waiting for cloud-init to copy ovf-env.xml to {0} " + "[{1} retries remaining, " + "sleeping {2}s]".format(ovf_file_path, + max_retry - retry, + sleep_time)) + if not self.validate_cloud_init(): + logger.warn("cloud-init does not appear to be running") + time.sleep(sleep_time) + raise ProvisionError("Giving up, ovf-env.xml was not copied to {0} " + "after {1}s".format(ovf_file_path, + max_retry * sleep_time)) + + def wait_for_ssh_host_key(self, max_retry=360, sleep_time=5): + """ + Wait for cloud-init to generate ssh host key + """ + keypair_type = conf.get_ssh_host_keypair_type() + path = conf.get_ssh_key_public_path() + for retry in range(0, max_retry): + if os.path.isfile(path): + logger.info("ssh host key found at: {0}".format(path)) + try: + thumbprint = self.get_ssh_host_key_thumbprint(chk_err=False) + logger.info("Thumbprint obtained from : {0}".format(path)) + return thumbprint + except ProvisionError: + logger.warn("Could not get thumbprint from {0}".format(path)) + if retry < max_retry - 1: + logger.info("Waiting for ssh host key be generated at {0} " + "[{1} attempts remaining, " + "sleeping {2}s]".format(path, + max_retry - retry, + sleep_time)) + if not self.validate_cloud_init(): + logger.warn("cloud-init does not appear to be running") + time.sleep(sleep_time) + raise ProvisionError("Giving up, ssh host key was not found at {0} " + "after {1}s".format(path, + max_retry * sleep_time)) diff --git a/azurelinuxagent/pa/provision/default.py b/azurelinuxagent/pa/provision/default.py index 3a3f36f..d4870f1 100644 --- a/azurelinuxagent/pa/provision/default.py +++ b/azurelinuxagent/pa/provision/default.py @@ -20,19 +20,31 @@ Provision handler """ import os +import os.path +import re + +from datetime import datetime + +import azurelinuxagent.common.conf as conf import azurelinuxagent.common.logger as logger +import azurelinuxagent.common.utils.shellutil as shellutil +import azurelinuxagent.common.utils.fileutil as fileutil + from azurelinuxagent.common.future import ustr -import azurelinuxagent.common.conf as conf -from azurelinuxagent.common.event import add_event, WALAEventOperation +from azurelinuxagent.common.event import add_event, WALAEventOperation, \ + elapsed_milliseconds from azurelinuxagent.common.exception import ProvisionError, ProtocolError, \ OSUtilError -from azurelinuxagent.common.protocol.restapi import ProvisionStatus -import azurelinuxagent.common.utils.shellutil as shellutil -import azurelinuxagent.common.utils.fileutil as fileutil from azurelinuxagent.common.osutil import get_osutil +from azurelinuxagent.common.protocol.restapi import ProvisionStatus from azurelinuxagent.common.protocol import get_protocol_util +from azurelinuxagent.common.version import AGENT_NAME CUSTOM_DATA_FILE = "CustomData" +CLOUD_INIT_PATTERN = b".*/bin/cloud-init.*" +CLOUD_INIT_REGEX = re.compile(CLOUD_INIT_PATTERN) + +PROVISIONED_FILE = 'provisioned' class ProvisionHandler(object): @@ -41,54 +53,85 @@ class ProvisionHandler(object): self.protocol_util = get_protocol_util() def run(self): - # if provisioning is already done, return - provisioned = os.path.join(conf.get_lib_dir(), "provisioned") - if os.path.isfile(provisioned): - logger.info("Provisioning already completed, skipping.") - return - - thumbprint = None # If provision is not enabled, report ready and then return if not conf.get_provision_enabled(): logger.info("Provisioning is disabled, skipping.") - else: - logger.info("Running provisioning handler") - try: - logger.info("Copying ovf-env.xml") - ovf_env = self.protocol_util.copy_ovf_env() - self.protocol_util.get_protocol_by_file() - self.report_not_ready("Provisioning", "Starting") - logger.info("Starting provisioning") - self.provision(ovf_env) - thumbprint = self.reg_ssh_host_key() - self.osutil.restart_ssh_service() - self.report_event("Provision succeed", is_success=True) - except ProtocolError as e: - logger.error("[ProtocolError] Provisioning failed: {0}", e) - self.report_not_ready("ProvisioningFailed", ustr(e)) - self.report_event("Failed to copy ovf-env.xml: {0}".format(e)) - return - except ProvisionError as e: - logger.error("[ProvisionError] Provisioning failed: {0}", e) - self.report_not_ready("ProvisioningFailed", ustr(e)) - self.report_event(ustr(e)) + return + + try: + utc_start = datetime.utcnow() + thumbprint = None + + # if provisioning is already done, return + if self.is_provisioned(): + logger.info("Provisioning already completed, skipping.") return - # write out provisioned file and report Ready - fileutil.write_file(provisioned, "") - self.report_ready(thumbprint) - logger.info("Provisioning complete") + + logger.info("Running default provisioning handler") + + if not self.validate_cloud_init(is_expected=False): + raise ProvisionError("cloud-init appears to be running, " + "this is not expected, cannot continue") + + logger.info("Copying ovf-env.xml") + ovf_env = self.protocol_util.copy_ovf_env() + + self.protocol_util.get_protocol_by_file() + self.report_not_ready("Provisioning", "Starting") + logger.info("Starting provisioning") + + self.provision(ovf_env) + + thumbprint = self.reg_ssh_host_key() + self.osutil.restart_ssh_service() + + # write out provisioned file and report Ready + self.write_provisioned() + + self.report_event("Provision succeed", + is_success=True, + duration=elapsed_milliseconds(utc_start)) + + self.report_ready(thumbprint) + logger.info("Provisioning complete") + + except (ProtocolError, ProvisionError) as e: + self.report_not_ready("ProvisioningFailed", ustr(e)) + self.report_event(ustr(e)) + logger.error("Provisioning failed: {0}", ustr(e)) + return + + @staticmethod + def validate_cloud_init(is_expected=True): + pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] + is_running = False + for pid in pids: + try: + pname = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read() + if CLOUD_INIT_REGEX.match(pname): + is_running = True + msg = "cloud-init is running [PID {0}, {1}]".format(pid, + pname) + if is_expected: + logger.verbose(msg) + else: + logger.error(msg) + break + except IOError: + continue + return is_running == is_expected def reg_ssh_host_key(self): keypair_type = conf.get_ssh_host_keypair_type() if conf.get_regenerate_ssh_host_key(): - fileutil.rm_files("/etc/ssh/ssh_host_*key*") - keygen_cmd = "ssh-keygen -N '' -t {0} -f /etc/ssh/ssh_host_{1}_key" - shellutil.run(keygen_cmd.format(keypair_type, keypair_type)) - thumbprint = self.get_ssh_host_key_thumbprint(keypair_type) - return thumbprint - - def get_ssh_host_key_thumbprint(self, keypair_type, chk_err=True): - cmd = "ssh-keygen -lf /etc/ssh/ssh_host_{0}_key.pub".format(keypair_type) + fileutil.rm_files(conf.get_ssh_key_glob()) + keygen_cmd = "ssh-keygen -N '' -t {0} -f {1}" + shellutil.run(keygen_cmd.format(keypair_type, + conf.get_ssh_key_private_path())) + return self.get_ssh_host_key_thumbprint() + + def get_ssh_host_key_thumbprint(self, chk_err=True): + cmd = "ssh-keygen -lf {0}".format(conf.get_ssh_key_public_path()) ret = shellutil.run_get_output(cmd, chk_err=chk_err) if ret[0] == 0: return ret[1].rstrip().split()[1].replace(':', '') @@ -96,6 +139,45 @@ class ProvisionHandler(object): raise ProvisionError(("Failed to generate ssh host key: " "ret={0}, out= {1}").format(ret[0], ret[1])) + def provisioned_file_path(self): + return os.path.join(conf.get_lib_dir(), PROVISIONED_FILE) + + def is_provisioned(self): + ''' + A VM is considered provisionend *anytime* the provisioning + sentinel file exists and not provisioned *anytime* the file + is absent. + + If the VM was provisioned using an agent that did not record + the VM unique identifier, the provisioning file will be re-written + to include the identifier. + + A warning is logged *if* the VM unique identifier has changed + since VM was provisioned. + ''' + if not os.path.isfile(self.provisioned_file_path()): + return False + + s = fileutil.read_file(self.provisioned_file_path()).strip() + if s != self.osutil.get_instance_id(): + if len(s) > 0: + logger.warn("VM is provisioned, " + "but the VM unique identifier has changed -- " + "clearing cached state") + from azurelinuxagent.pa.deprovision \ + import get_deprovision_handler + deprovision_handler = get_deprovision_handler() + deprovision_handler.run_changed_unique_id() + + self.write_provisioned() + + return True + + def write_provisioned(self): + fileutil.write_file( + self.provisioned_file_path(), + get_osutil().get_instance_id()) + def provision(self, ovfenv): logger.info("Handle ovf-env.xml.") try: @@ -113,7 +195,7 @@ class ProvisionHandler(object): self.osutil.del_root_password() except OSUtilError as e: - raise ProvisionError("Failed to handle ovf-env.xml: {0}".format(e)) + raise ProvisionError("Failed to provision: {0}".format(ustr(e))) def config_user_account(self, ovfenv): logger.info("Create user account if not exists") @@ -141,11 +223,12 @@ class ProvisionHandler(object): if customdata is None: return - logger.info("Save custom data") lib_dir = conf.get_lib_dir() - if conf.get_decode_customdata(): + if conf.get_decode_customdata() or conf.get_execute_customdata(): + logger.info("Decode custom data") customdata = self.osutil.decode_customdata(customdata) + logger.info("Save custom data") customdata_file = os.path.join(lib_dir, CUSTOM_DATA_FILE) fileutil.write_file(customdata_file, customdata) @@ -164,9 +247,12 @@ class ProvisionHandler(object): logger.info("Deploy ssh key pairs.") self.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_event(self, message, is_success=False, duration=0): + add_event(name=AGENT_NAME, + message=message, + duration=duration, + is_success=is_success, + op=WALAEventOperation.Provision) def report_not_ready(self, sub_status, description): status = ProvisionStatus(status="NotReady", subStatus=sub_status, diff --git a/azurelinuxagent/pa/provision/factory.py b/azurelinuxagent/pa/provision/factory.py index 9bbe35c..d87765f 100644 --- a/azurelinuxagent/pa/provision/factory.py +++ b/azurelinuxagent/pa/provision/factory.py @@ -15,18 +15,22 @@ # Requires Python 2.4+ and Openssl 1.0+ # +import azurelinuxagent.common.conf as conf import azurelinuxagent.common.logger as logger + from azurelinuxagent.common.utils.textutil import Version from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \ DISTRO_FULL_NAME + from .default import ProvisionHandler -from .ubuntu import UbuntuProvisionHandler +from .cloudinit import CloudInitProvisionHandler def get_provision_handler(distro_name=DISTRO_NAME, distro_version=DISTRO_VERSION, distro_full_name=DISTRO_FULL_NAME): - if distro_name == "ubuntu": - return UbuntuProvisionHandler() + + if conf.get_provision_cloudinit(): + return CloudInitProvisionHandler() return ProvisionHandler() diff --git a/azurelinuxagent/pa/provision/ubuntu.py b/azurelinuxagent/pa/provision/ubuntu.py deleted file mode 100644 index 66866b2..0000000 --- a/azurelinuxagent/pa/provision/ubuntu.py +++ /dev/null @@ -1,102 +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 azurelinuxagent.common.conf as conf -import azurelinuxagent.common.logger as logger -import azurelinuxagent.common.utils.fileutil as fileutil -from azurelinuxagent.common.exception import ProvisionError, ProtocolError -from azurelinuxagent.common.future import ustr -from azurelinuxagent.pa.provision.default import ProvisionHandler - -""" -On ubuntu image, provision could be disabled. -""" - - -class UbuntuProvisionHandler(ProvisionHandler): - def __init__(self): - super(UbuntuProvisionHandler, self).__init__() - - def run(self): - # If provision is enabled, run default provision handler - if conf.get_provision_enabled(): - super(UbuntuProvisionHandler, self).run() - return - - logger.info("run Ubuntu provision handler") - provisioned = os.path.join(conf.get_lib_dir(), "provisioned") - if os.path.isfile(provisioned): - return - - logger.info("Waiting cloud-init to copy ovf-env.xml.") - self.wait_for_ovfenv() - self.protocol_util.get_protocol() - self.report_not_ready("Provisioning", "Starting") - logger.info("Sleeping 1 second to avoid throttling.") - time.sleep(1) - try: - logger.info("Wait for ssh host key to be generated.") - thumbprint = self.wait_for_ssh_host_key() - fileutil.write_file(provisioned, "") - logger.info("Finished provisioning") - except ProvisionError as e: - logger.error("Provision failed: {0}", e) - self.report_not_ready("ProvisioningFailed", ustr(e)) - self.report_event(ustr(e)) - return - - self.report_ready(thumbprint) - self.report_event("Provision succeed", is_success=True) - - def wait_for_ovfenv(self, max_retry=60): - """ - Wait for cloud-init to copy ovf-env.xml file from provision ISO - """ - for retry in range(0, max_retry): - try: - self.protocol_util.get_ovf_env() - return - except ProtocolError: - if retry < max_retry - 1: - logger.info("Wait for cloud-init to copy ovf-env.xml") - time.sleep(5) - raise ProvisionError("ovf-env.xml is not copied") - - def wait_for_ssh_host_key(self, max_retry=60): - """ - Wait for cloud-init to generate ssh host key - """ - keypair_type = conf.get_ssh_host_keypair_type() - path = '/etc/ssh/ssh_host_{0}_key.pub'.format(keypair_type) - for retry in range(0, max_retry): - if os.path.isfile(path): - logger.info("ssh host key found at: {0}".format(path)) - try: - thumbprint = self.get_ssh_host_key_thumbprint(keypair_type, chk_err=False) - logger.info("Thumbprint obtained from : {0}".format(path)) - return thumbprint - except ProvisionError: - logger.warn("Could not get thumbprint from {0}".format(path)) - if retry < max_retry - 1: - logger.info("Wait for ssh host key be generated: {0}".format(path)) - time.sleep(5) - raise ProvisionError("ssh host key is not generated.") |