1# Copyright 2019 Microsoft Corporation 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# 15# Requires Python 2.6+ and Openssl 1.0+ 16# 17 18import os 19import re 20import platform 21import sys 22 23import azurelinuxagent.common.conf as conf 24import azurelinuxagent.common.utils.shellutil as shellutil 25from azurelinuxagent.common.utils.flexible_version import FlexibleVersion 26from azurelinuxagent.common.future import ustr, get_linux_distribution 27 28__DAEMON_VERSION_ENV_VARIABLE = '_AZURE_GUEST_AGENT_DAEMON_VERSION_' 29""" 30 The daemon process sets this variable's value to the daemon's version number. 31 The variable is set only on versions >= 2.2.53 32""" 33 34 35def set_daemon_version(version): 36 """ 37 Sets the value of the _AZURE_GUEST_AGENT_DAEMON_VERSION_ environment variable. 38 39 The given 'version' can be a FlexibleVersion or a string that can be parsed into a FlexibleVersion 40 """ 41 flexible_version = version if isinstance(version, FlexibleVersion) else FlexibleVersion(version) 42 os.environ[__DAEMON_VERSION_ENV_VARIABLE] = ustr(flexible_version) 43 44 45def get_daemon_version(): 46 """ 47 Retrieves the value of the _AZURE_GUEST_AGENT_DAEMON_VERSION_ environment variable. 48 The value indicates the version of the daemon that started the current agent process or, if the current 49 process is the daemon, the version of the current process. 50 If the variable is not set (because the agent is < 2.2.53, or the process was not started by the daemon and 51 the process is not the daemon itself) the function returns "0.0.0.0" 52 """ 53 if __DAEMON_VERSION_ENV_VARIABLE in os.environ: 54 return FlexibleVersion(os.environ[__DAEMON_VERSION_ENV_VARIABLE]) 55 return FlexibleVersion("0.0.0.0") 56 57 58def get_f5_platform(): 59 """ 60 Add this workaround for detecting F5 products because BIG-IP/IQ/etc do 61 not show their version info in the /etc/product-version location. Instead, 62 the version and product information is contained in the /VERSION file. 63 """ 64 result = [None, None, None, None] 65 f5_version = re.compile("^Version: (\d+\.\d+\.\d+)") # pylint: disable=W1401 66 f5_product = re.compile("^Product: ([\w-]+)") # pylint: disable=W1401 67 68 with open('/VERSION', 'r') as fh: 69 content = fh.readlines() 70 for line in content: 71 version_matches = f5_version.match(line) 72 product_matches = f5_product.match(line) 73 if version_matches: 74 result[1] = version_matches.group(1) 75 elif product_matches: 76 result[3] = product_matches.group(1) 77 if result[3] == "BIG-IP": 78 result[0] = "bigip" 79 result[2] = "bigip" 80 elif result[3] == "BIG-IQ": 81 result[0] = "bigiq" 82 result[2] = "bigiq" 83 elif result[3] == "iWorkflow": 84 result[0] = "iworkflow" 85 result[2] = "iworkflow" 86 return result 87 88 89def get_checkpoint_platform(): 90 take = build = release = "" 91 full_name = open("/etc/cp-release").read().strip() 92 with open("/etc/cloud-version") as f: 93 for line in f: 94 k, _, v = line.partition(": ") 95 v = v.strip() 96 if k == "release": 97 release = v 98 elif k == "take": 99 take = v 100 elif k == "build": 101 build = v 102 return ["gaia", take + "." + build, release, full_name] 103 104 105def get_distro(): 106 if 'FreeBSD' in platform.system(): 107 release = re.sub('\-.*\Z', '', ustr(platform.release())) # pylint: disable=W1401 108 osinfo = ['freebsd', release, '', 'freebsd'] 109 elif 'OpenBSD' in platform.system(): 110 release = re.sub('\-.*\Z', '', ustr(platform.release())) # pylint: disable=W1401 111 osinfo = ['openbsd', release, '', 'openbsd'] 112 elif 'Linux' in platform.system(): 113 osinfo = get_linux_distribution(0, 'alpine') 114 elif 'NS-BSD' in platform.system(): 115 release = re.sub('\-.*\Z', '', ustr(platform.release())) # pylint: disable=W1401 116 osinfo = ['nsbsd', release, '', 'nsbsd'] 117 else: 118 try: 119 # dist() removed in Python 3.8 120 osinfo = list(platform.dist()) + [''] # pylint: disable=W1505,E1101 121 except Exception: 122 osinfo = ['UNKNOWN', 'FFFF', '', ''] 123 124 # The platform.py lib has issue with detecting oracle linux distribution. 125 # Merge the following patch provided by oracle as a temporary fix. 126 if os.path.exists("/etc/oracle-release"): 127 osinfo[2] = "oracle" 128 osinfo[3] = "Oracle Linux" 129 130 if os.path.exists("/etc/euleros-release"): 131 osinfo[0] = "euleros" 132 133 if os.path.exists("/etc/mariner-release"): 134 osinfo[0] = "mariner" 135 136 # The platform.py lib has issue with detecting BIG-IP linux distribution. 137 # Merge the following patch provided by F5. 138 if os.path.exists("/shared/vadc"): 139 osinfo = get_f5_platform() 140 141 if os.path.exists("/etc/cp-release"): 142 osinfo = get_checkpoint_platform() 143 144 if os.path.exists("/home/guestshell/azure"): 145 osinfo = ['iosxe', 'csr1000v', '', 'Cisco IOSXE Linux'] 146 147 # Remove trailing whitespace and quote in distro name 148 osinfo[0] = osinfo[0].strip('"').strip(' ').lower() 149 return osinfo 150 151COMMAND_ABSENT = ustr("Absent") 152COMMAND_FAILED = ustr("Failed") 153 154 155def get_lis_version(): 156 """ 157 This uses the Linux kernel's 'modinfo' command to retrieve the 158 "version" field for the "hv_vmbus" kernel module (the LIS 159 drivers). This is the documented method to retrieve the LIS module 160 version. Every Linux guest on Hyper-V will have this driver, but 161 it may not be installed as a module (it could instead be built 162 into the kernel). In that case, this will return "Absent" instead 163 of the version, indicating the driver version can be deduced from 164 the kernel version. It will only return "Failed" in the presence 165 of an exception. 166 167 This function is used to generate telemetry for the version of the 168 LIS drivers installed on the VM. The function and associated 169 telemetry can be removed after a few releases. 170 """ 171 try: 172 modinfo_output = shellutil.run_command(["modinfo", "-F", "version", "hv_vmbus"]) 173 if modinfo_output: 174 return modinfo_output 175 # If the system doesn't have LIS drivers, 'modinfo' will 176 # return nothing on stdout, which will cause 'run_command' 177 # to return an empty string. 178 return COMMAND_ABSENT 179 except Exception: 180 # Ignore almost every possible exception because this is in a 181 # critical code path. Unfortunately the logger isn't already 182 # imported in this module or we'd log this too. 183 return COMMAND_FAILED 184 185def has_logrotate(): 186 try: 187 logrotate_version = shellutil.run_command(["logrotate", "--version"]).split("\n")[0] 188 return logrotate_version 189 except shellutil.CommandError: 190 # A non-zero return code means that logrotate isn't present on 191 # the system; --version shouldn't fail otherwise. 192 return COMMAND_ABSENT 193 except Exception: 194 return COMMAND_FAILED 195 196 197AGENT_NAME = "WALinuxAgent" 198AGENT_LONG_NAME = "Azure Linux Agent" 199AGENT_VERSION = '2.2.54.2' 200AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION) 201AGENT_DESCRIPTION = """ 202The Azure Linux Agent supports the provisioning and running of Linux 203VMs in the Azure cloud. This package should be installed on Linux disk 204images that are built to run in the Azure environment. 205""" 206 207AGENT_DIR_GLOB = "{0}-*".format(AGENT_NAME) 208AGENT_PKG_GLOB = "{0}-*.zip".format(AGENT_NAME) 209 210AGENT_PATTERN = "{0}-(.*)".format(AGENT_NAME) 211AGENT_NAME_PATTERN = re.compile(AGENT_PATTERN) 212AGENT_PKG_PATTERN = re.compile(AGENT_PATTERN+"\.zip") # pylint: disable=W1401 213AGENT_DIR_PATTERN = re.compile(".*/{0}".format(AGENT_PATTERN)) 214 215# The execution mode of the VM - IAAS or PAAS. Linux VMs are only executed in IAAS mode. 216AGENT_EXECUTION_MODE = "IAAS" 217 218EXT_HANDLER_PATTERN = b".*/WALinuxAgent-(\d+.\d+.\d+[.\d+]*).*-run-exthandlers" # pylint: disable=W1401 219EXT_HANDLER_REGEX = re.compile(EXT_HANDLER_PATTERN) 220 221__distro__ = get_distro() 222DISTRO_NAME = __distro__[0] 223DISTRO_VERSION = __distro__[1] 224DISTRO_CODE_NAME = __distro__[2] 225DISTRO_FULL_NAME = __distro__[3] 226 227PY_VERSION = sys.version_info 228PY_VERSION_MAJOR = sys.version_info[0] 229PY_VERSION_MINOR = sys.version_info[1] 230PY_VERSION_MICRO = sys.version_info[2] 231 232 233# Set the CURRENT_AGENT and CURRENT_VERSION to match the agent directory name 234# - This ensures the agent will "see itself" using the same name and version 235# as the code that downloads agents. 236def set_current_agent(): 237 path = os.getcwd() 238 lib_dir = conf.get_lib_dir() 239 if lib_dir[-1] != os.path.sep: 240 lib_dir += os.path.sep 241 agent = path[len(lib_dir):].split(os.path.sep)[0] 242 match = AGENT_NAME_PATTERN.match(agent) 243 if match: 244 version = match.group(1) 245 else: 246 agent = AGENT_LONG_VERSION 247 version = AGENT_VERSION 248 return agent, FlexibleVersion(version) 249 250 251def is_agent_package(path): 252 path = os.path.basename(path) 253 return not re.match(AGENT_PKG_PATTERN, path) is None 254 255 256def is_agent_path(path): 257 path = os.path.basename(path) 258 return not re.match(AGENT_NAME_PATTERN, path) is None 259 260 261CURRENT_AGENT, CURRENT_VERSION = set_current_agent() 262 263 264def set_goal_state_agent(): 265 agent = None 266 if os.path.isdir("/proc"): 267 pids = [pid for pid in os.listdir('/proc') if pid.isdigit()] 268 else: 269 pids = [] 270 for pid in pids: 271 try: 272 pname = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read() 273 match = EXT_HANDLER_REGEX.match(pname) 274 if match: 275 agent = match.group(1) 276 if PY_VERSION_MAJOR > 2: 277 agent = agent.decode('UTF-8') 278 break 279 except IOError: 280 continue 281 if agent is None: 282 agent = CURRENT_VERSION 283 return agent 284 285 286GOAL_STATE_AGENT_VERSION = set_goal_state_agent() 287 288 289def is_current_agent_installed(): 290 return CURRENT_AGENT == AGENT_LONG_VERSION 291