diff options
Diffstat (limited to 'azurelinuxagent/distro')
34 files changed, 1562 insertions, 1247 deletions
diff --git a/azurelinuxagent/distro/centos/__init__.py b/azurelinuxagent/distro/centos/__init__.py deleted file mode 100644 index d9b82f5..0000000 --- a/azurelinuxagent/distro/centos/__init__.py +++ /dev/null @@ -1,19 +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+ -# - 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/loader.py b/azurelinuxagent/distro/coreos/loader.py deleted file mode 100644 index 802f276..0000000 --- a/azurelinuxagent/distro/coreos/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+ -# - - -def get_osutil(): - from azurelinuxagent.distro.coreos.osutil import CoreOSUtil - return CoreOSUtil() - -def get_handlers(): - from azurelinuxagent.distro.coreos.handlerFactory import CoreOSHandlerFactory - return CoreOSHandlerFactory() - diff --git a/azurelinuxagent/distro/coreos/osutil.py b/azurelinuxagent/distro/coreos/osutil.py 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/oracle/loader.py b/azurelinuxagent/distro/debian/distro.py index 9dc428f..01f4e3e 100644 --- a/azurelinuxagent/distro/oracle/loader.py +++ b/azurelinuxagent/distro/debian/distro.py @@ -17,9 +17,11 @@ # Requires Python 2.4+ and Openssl 1.0+ # -from azurelinuxagent.metadata import DISTRO_NAME, DISTRO_VERSION -import azurelinuxagent.distro.redhat.loader as redhat +from azurelinuxagent.distro.default.distro import DefaultDistro +from azurelinuxagent.distro.debian.osutil import DebianOSUtil -def get_osutil(): - return redhat.get_osutil() +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/__init__.py b/azurelinuxagent/distro/oracle/__init__.py deleted file mode 100644 index d9b82f5..0000000 --- a/azurelinuxagent/distro/oracle/__init__.py +++ /dev/null @@ -1,19 +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+ -# - 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): |