summaryrefslogtreecommitdiff
path: root/azurelinuxagent/common
diff options
context:
space:
mode:
Diffstat (limited to 'azurelinuxagent/common')
-rw-r--r--azurelinuxagent/common/conf.py4
-rw-r--r--azurelinuxagent/common/event.py39
-rw-r--r--azurelinuxagent/common/osutil/debian.py13
-rw-r--r--azurelinuxagent/common/osutil/default.py77
-rw-r--r--azurelinuxagent/common/osutil/factory.py51
-rw-r--r--azurelinuxagent/common/osutil/freebsd.py2
-rw-r--r--azurelinuxagent/common/osutil/redhat.py29
-rw-r--r--azurelinuxagent/common/protocol/hostplugin.py29
-rw-r--r--azurelinuxagent/common/protocol/restapi.py56
-rw-r--r--azurelinuxagent/common/protocol/wire.py36
-rw-r--r--azurelinuxagent/common/rdma.py7
-rw-r--r--azurelinuxagent/common/utils/fileutil.py4
-rw-r--r--azurelinuxagent/common/utils/restutil.py34
-rw-r--r--azurelinuxagent/common/utils/shellutil.py28
-rw-r--r--azurelinuxagent/common/utils/textutil.py26
-rw-r--r--azurelinuxagent/common/version.py44
16 files changed, 317 insertions, 162 deletions
diff --git a/azurelinuxagent/common/conf.py b/azurelinuxagent/common/conf.py
index 9c79d10..7911699 100644
--- a/azurelinuxagent/common/conf.py
+++ b/azurelinuxagent/common/conf.py
@@ -98,6 +98,10 @@ def get_lib_dir(conf=__conf__):
return conf.get("Lib.Dir", "/var/lib/waagent")
+def get_published_hostname(conf=__conf__):
+ return os.path.join(get_lib_dir(conf), 'published_hostname')
+
+
def get_dvd_mount_point(conf=__conf__):
return conf.get("DVD.MountPoint", "/mnt/cdrom/secure")
diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index 4037622..9265820 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -28,29 +28,31 @@ import azurelinuxagent.common.logger as logger
from azurelinuxagent.common.exception import EventError, ProtocolError
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.protocol.restapi import TelemetryEventParam, \
- TelemetryEventList, \
- TelemetryEvent, \
- set_properties, get_properties
+ TelemetryEventList, \
+ TelemetryEvent, \
+ set_properties, get_properties
from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \
- DISTRO_CODE_NAME, AGENT_VERSION, \
- CURRENT_AGENT, CURRENT_VERSION
+ DISTRO_CODE_NAME, AGENT_VERSION, \
+ CURRENT_AGENT, CURRENT_VERSION
class WALAEventOperation:
- ActivateResourceDisk="ActivateResourceDisk"
+ ActivateResourceDisk = "ActivateResourceDisk"
Disable = "Disable"
Download = "Download"
Enable = "Enable"
HealthCheck = "HealthCheck"
- HeartBeat="HeartBeat"
+ HeartBeat = "HeartBeat"
Install = "Install"
+ InitializeHostPlugin = "InitializeHostPlugin"
Provision = "Provision"
- Restart="Restart"
- UnhandledError="UnhandledError"
+ Restart = "Restart"
+ UnhandledError = "UnhandledError"
UnInstall = "UnInstall"
Upgrade = "Upgrade"
Update = "Update"
+
class EventLogger(object):
def __init__(self):
self.event_dir = None
@@ -66,22 +68,24 @@ class EventLogger(object):
if len(os.listdir(self.event_dir)) > 1000:
raise EventError("Too many files under: {0}".format(self.event_dir))
- filename = os.path.join(self.event_dir, ustr(int(time.time()*1000000)))
+ filename = os.path.join(self.event_dir,
+ ustr(int(time.time() * 1000000)))
try:
- with open(filename+".tmp",'wb+') as hfile:
+ with open(filename + ".tmp", 'wb+') as hfile:
hfile.write(data.encode("utf-8"))
- os.rename(filename+".tmp", filename+".tld")
+ os.rename(filename + ".tmp", filename + ".tld")
except IOError as e:
raise EventError("Failed to write events to file:{0}", e)
- def add_event(self, name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
+ def add_event(self, name, op="", is_success=True, duration=0,
+ version=CURRENT_VERSION,
message="", evt_type="", is_internal=False):
event = TelemetryEvent(1, "69B669B9-4AF8-4C50-BDC4-6006FA76E975")
event.parameters.append(TelemetryEventParam('Name', name))
event.parameters.append(TelemetryEventParam('Version', str(version)))
event.parameters.append(TelemetryEventParam('IsInternal', is_internal))
event.parameters.append(TelemetryEventParam('Operation', op))
- event.parameters.append(TelemetryEventParam('OperationSuccess',
+ event.parameters.append(TelemetryEventParam('OperationSuccess',
is_success))
event.parameters.append(TelemetryEventParam('Message', message))
event.parameters.append(TelemetryEventParam('Duration', duration))
@@ -93,8 +97,10 @@ class EventLogger(object):
except EventError as e:
logger.error("{0}", e)
+
__event_logger__ = EventLogger()
+
def add_event(name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
message="", evt_type="", is_internal=False,
reporter=__event_logger__):
@@ -108,9 +114,11 @@ def add_event(name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
version=str(version), message=message, evt_type=evt_type,
is_internal=is_internal)
+
def init_event_logger(event_dir, reporter=__event_logger__):
reporter.event_dir = event_dir
+
def dump_unhandled_err(name):
if hasattr(sys, 'last_type') and hasattr(sys, 'last_value') and \
hasattr(sys, 'last_traceback'):
@@ -119,9 +127,10 @@ def dump_unhandled_err(name):
last_traceback = getattr(sys, 'last_traceback')
error = traceback.format_exception(last_type, last_value,
last_traceback)
- message= "".join(error)
+ message = "".join(error)
add_event(name, is_success=False, message=message,
op=WALAEventOperation.UnhandledError)
+
def enable_unhandled_err_dump(name):
atexit.register(dump_unhandled_err, name)
diff --git a/azurelinuxagent/common/osutil/debian.py b/azurelinuxagent/common/osutil/debian.py
index f455572..b3db921 100644
--- a/azurelinuxagent/common/osutil/debian.py
+++ b/azurelinuxagent/common/osutil/debian.py
@@ -37,7 +37,7 @@ class DebianOSUtil(DefaultOSUtil):
super(DebianOSUtil, self).__init__()
def restart_ssh_service(self):
- return shellutil.run("service sshd restart", chk_err=False)
+ return shellutil.run("systemctl --job-mode=ignore-dependencies try-reload-or-restart ssh", chk_err=False)
def stop_agent_service(self):
return shellutil.run("service azurelinuxagent stop", chk_err=False)
@@ -45,3 +45,14 @@ class DebianOSUtil(DefaultOSUtil):
def start_agent_service(self):
return shellutil.run("service azurelinuxagent start", chk_err=False)
+ def start_network(self):
+ pass
+
+ def remove_rules_files(self, rules_files=""):
+ pass
+
+ def restore_rules_files(self, rules_files=""):
+ pass
+
+ def get_dhcp_lease_endpoint(self):
+ return self.get_endpoint_from_leases_path('/var/lib/dhcp/dhclient.*.leases')
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index dc73379..4cd379b 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -51,7 +51,8 @@ class DefaultOSUtil(object):
def __init__(self):
self.agent_conf_file_path = '/etc/waagent.conf'
- self.selinux=None
+ self.selinux = None
+ self.disable_route_warning = False
def get_agent_conf_file_path(self):
return self.agent_conf_file_path
@@ -438,7 +439,8 @@ class DefaultOSUtil(object):
iface=sock[i:i+16].split(b'\0', 1)[0]
if len(iface) == 0 or self.is_loopback(iface) or iface != primary:
# test the next one
- logger.info('interface [{0}] skipped'.format(iface))
+ if len(iface) != 0 and not self.disable_route_warning:
+ logger.info('interface [{0}] skipped'.format(iface))
continue
else:
# use this one
@@ -470,7 +472,8 @@ class DefaultOSUtil(object):
primary = None
primary_metric = None
- logger.info("examine /proc/net/route for primary interface")
+ if not self.disable_route_warning:
+ logger.info("examine /proc/net/route for primary interface")
with open('/proc/net/route') as routing_table:
idx = 0
for header in filter(lambda h: len(h) > 0, routing_table.readline().strip(" \n").split("\t")):
@@ -494,11 +497,18 @@ class DefaultOSUtil(object):
if primary is None:
primary = ''
-
- logger.info('primary interface is [{0}]'.format(primary))
+ if not self.disable_route_warning:
+ with open('/proc/net/route') as routing_table_fh:
+ routing_table_text = routing_table_fh.read()
+ logger.error('could not determine primary interface, '
+ 'please ensure /proc/net/route is correct:\n'
+ '{0}'.format(routing_table_text))
+ self.disable_route_warning = True
+ else:
+ logger.info('primary interface is [{0}]'.format(primary))
+ self.disable_route_warning = False
return primary
-
def is_primary_interface(self, ifname):
"""
Indicate whether the specified interface is the primary.
@@ -507,13 +517,14 @@ class DefaultOSUtil(object):
"""
return self.get_primary_interface() == ifname
-
def is_loopback(self, ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
result = fcntl.ioctl(s.fileno(), 0x8913, struct.pack('256s', ifname[:15]))
flags, = struct.unpack('H', result[16:18])
isloopback = flags & 8 == 8
- logger.info('interface [{0}] has flags [{1}], is loopback [{2}]'.format(ifname, flags, isloopback))
+ if not self.disable_route_warning:
+ logger.info('interface [{0}] has flags [{1}], '
+ 'is loopback [{2}]'.format(ifname, flags, isloopback))
return isloopback
def get_dhcp_lease_endpoint(self):
@@ -675,6 +686,7 @@ class DefaultOSUtil(object):
def publish_hostname(self, hostname):
self.set_dhcp_hostname(hostname)
+ self.set_hostname_record(hostname)
ifname = self.get_if_name()
self.restart_if(ifname)
@@ -725,22 +737,39 @@ class DefaultOSUtil(object):
port_id = port_id - 2
device = None
path = "/sys/bus/vmbus/devices/"
- for vmbus in os.listdir(path):
- deviceid = fileutil.read_file(os.path.join(path, vmbus, "device_id"))
- guid = deviceid.lstrip('{').split('-')
- if guid[0] == g0 and guid[1] == "000" + ustr(port_id):
- for root, dirs, files in os.walk(path + vmbus):
- if root.endswith("/block"):
- device = dirs[0]
- break
- else : #older distros
- for d in dirs:
- if ':' in d and "block" == d.split(':')[0]:
- device = d.split(':')[1]
- break
- break
+ if os.path.exists(path):
+ for vmbus in os.listdir(path):
+ deviceid = fileutil.read_file(os.path.join(path, vmbus, "device_id"))
+ guid = deviceid.lstrip('{').split('-')
+ if guid[0] == g0 and guid[1] == "000" + ustr(port_id):
+ for root, dirs, files in os.walk(path + vmbus):
+ if root.endswith("/block"):
+ device = dirs[0]
+ break
+ else : #older distros
+ for d in dirs:
+ if ':' in d and "block" == d.split(':')[0]:
+ device = d.split(':')[1]
+ break
+ break
return device
+ def set_hostname_record(self, hostname):
+ fileutil.write_file(conf.get_published_hostname(), contents=hostname)
+
+ def get_hostname_record(self):
+ hostname_record = conf.get_published_hostname()
+ if not os.path.exists(hostname_record):
+ # this file is created at provisioning time with agents >= 2.2.3
+ hostname = socket.gethostname()
+ logger.warn('Hostname record does not exist, '
+ 'creating [{0}] with hostname [{1}]',
+ hostname_record,
+ hostname)
+ self.set_hostname_record(hostname)
+ record = fileutil.read_file(hostname_record)
+ return record
+
def del_account(self, username):
if self.is_sys_user(username):
logger.error("{0} is a system user. Will not delete it.", username)
@@ -749,10 +778,10 @@ class DefaultOSUtil(object):
self.conf_sudoer(username, remove=True)
def decode_customdata(self, data):
- return base64.b64decode(data)
+ return base64.b64decode(data).decode('utf-8')
def get_total_mem(self):
- # Get total memory in bytes and divide by 1024**2 to get the valu in MB.
+ # Get total memory in bytes and divide by 1024**2 to get the value in MB.
return os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / (1024**2)
def get_processor_cores(self):
diff --git a/azurelinuxagent/common/osutil/factory.py b/azurelinuxagent/common/osutil/factory.py
index 2718ba1..acd7f6e 100644
--- a/azurelinuxagent/common/osutil/factory.py
+++ b/azurelinuxagent/common/osutil/factory.py
@@ -17,9 +17,7 @@
import azurelinuxagent.common.logger as logger
from azurelinuxagent.common.utils.textutil import Version
-from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \
- DISTRO_FULL_NAME
-
+from azurelinuxagent.common.version import *
from .default import DefaultOSUtil
from .clearlinux import ClearLinuxUtil
from .coreos import CoreOSUtil
@@ -27,54 +25,65 @@ from .debian import DebianOSUtil
from .freebsd import FreeBSDOSUtil
from .redhat import RedhatOSUtil, Redhat6xOSUtil
from .suse import SUSEOSUtil, SUSE11OSUtil
-from .ubuntu import UbuntuOSUtil, Ubuntu12OSUtil, Ubuntu14OSUtil, \
- UbuntuSnappyOSUtil
+from .ubuntu import UbuntuOSUtil, Ubuntu12OSUtil, Ubuntu14OSUtil, UbuntuSnappyOSUtil
from .alpine import AlpineOSUtil
from .bigip import BigIpOSUtil
-def get_osutil(distro_name=DISTRO_NAME, distro_version=DISTRO_VERSION,
+
+def get_osutil(distro_name=DISTRO_NAME,
+ distro_code_name=DISTRO_CODE_NAME,
+ distro_version=DISTRO_VERSION,
distro_full_name=DISTRO_FULL_NAME):
+
if distro_name == "clear linux software for intel architecture":
return ClearLinuxUtil()
+
if distro_name == "ubuntu":
- if Version(distro_version) == Version("12.04") or \
- Version(distro_version) == Version("12.10"):
+ if Version(distro_version) == Version("12.04") or Version(distro_version) == Version("12.10"):
return Ubuntu12OSUtil()
- elif Version(distro_version) == Version("14.04") or \
- Version(distro_version) == Version("14.10"):
+ elif Version(distro_version) == Version("14.04") or Version(distro_version) == Version("14.10"):
return Ubuntu14OSUtil()
elif distro_full_name == "Snappy Ubuntu Core":
return UbuntuSnappyOSUtil()
else:
return UbuntuOSUtil()
+
if distro_name == "alpine":
return AlpineOSUtil()
+
if distro_name == "kali":
- return DebianOSUtil()
- if distro_name == "coreos":
+ return DebianOSUtil()
+
+ if distro_name == "coreos" or distro_code_name == "coreos":
return CoreOSUtil()
+
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'):
+ 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 SUSE11OSUtil()
else:
return SUSEOSUtil()
+
elif distro_name == "debian":
return DebianOSUtil()
- elif distro_name == "redhat" or distro_name == "centos" or \
- distro_name == "oracle":
+
+ elif distro_name == "redhat" \
+ or distro_name == "centos" \
+ or distro_name == "oracle":
if Version(distro_version) < Version("7"):
return Redhat6xOSUtil()
else:
return RedhatOSUtil()
+
elif distro_name == "freebsd":
return FreeBSDOSUtil()
+
elif distro_name == "bigip":
return BigIpOSUtil()
+
else:
- logger.warn("Unable to load distro implementation for {0}.", distro_name)
- logger.warn("Use default distro implementation instead.")
+ logger.warn("Unable to load distro implementation for {0}. Using "
+ "default distro implementation instead.",
+ distro_name)
return DefaultOSUtil()
-
diff --git a/azurelinuxagent/common/osutil/freebsd.py b/azurelinuxagent/common/osutil/freebsd.py
index 54c7452..d0c40b9 100644
--- a/azurelinuxagent/common/osutil/freebsd.py
+++ b/azurelinuxagent/common/osutil/freebsd.py
@@ -77,7 +77,7 @@ class FreeBSDOSUtil(DefaultOSUtil):
"").format(username, output))
def del_root_password(self):
- err = shellutil.run('pw mod user root -w no')
+ err = shellutil.run('pw usermod root -h -')
if err:
raise OSUtilError("Failed to delete root password: Failed to update password database.")
diff --git a/azurelinuxagent/common/osutil/redhat.py b/azurelinuxagent/common/osutil/redhat.py
index 80370a2..5254ea5 100644
--- a/azurelinuxagent/common/osutil/redhat.py
+++ b/azurelinuxagent/common/osutil/redhat.py
@@ -36,6 +36,7 @@ import azurelinuxagent.common.utils.textutil as textutil
from azurelinuxagent.common.utils.cryptutil import CryptUtil
from azurelinuxagent.common.osutil.default import DefaultOSUtil
+
class Redhat6xOSUtil(DefaultOSUtil):
def __init__(self):
super(Redhat6xOSUtil, self).__init__()
@@ -57,7 +58,7 @@ class Redhat6xOSUtil(DefaultOSUtil):
def unregister_agent_service(self):
return shellutil.run("chkconfig --del waagent", chk_err=False)
-
+
def openssl_to_openssh(self, input_file, output_file):
pubkey = fileutil.read_file(input_file)
try:
@@ -67,7 +68,7 @@ class Redhat6xOSUtil(DefaultOSUtil):
raise OSUtilError(ustr(e))
fileutil.write_file(output_file, ssh_rsa_pubkey)
- #Override
+ # Override
def get_dhcp_pid(self):
ret = shellutil.run_get_output("pidof dhclient", chk_err=False)
return ret[1] if ret[0] == 0 else None
@@ -84,22 +85,28 @@ 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))
def get_dhcp_lease_endpoint(self):
return self.get_endpoint_from_leases_path('/var/lib/dhclient/dhclient-*.leases')
+
class RedhatOSUtil(Redhat6xOSUtil):
def __init__(self):
super(RedhatOSUtil, self).__init__()
def set_hostname(self, hostname):
"""
- Set /etc/hostname
- Unlike redhat 6.x, redhat 7.x will set hostname to /etc/hostname
+ Unlike redhat 6.x, redhat 7.x will set hostname via hostnamectl
+ Due to a bug in systemd in Centos-7.0, if this call fails, fallback
+ to hostname.
"""
- DefaultOSUtil.set_hostname(self, hostname)
+ hostnamectl_cmd = "hostnamectl set-hostname {0}".format(hostname)
+ if shellutil.run(hostnamectl_cmd, chk_err=False) != 0:
+ logger.warn("[{0}] failed, attempting fallback".format(hostnamectl_cmd))
+ DefaultOSUtil.set_hostname(self, hostname)
def publish_hostname(self, hostname):
"""
@@ -118,5 +125,11 @@ class RedhatOSUtil(Redhat6xOSUtil):
DefaultOSUtil.openssl_to_openssh(self, input_file, output_file)
def get_dhcp_lease_endpoint(self):
- # centos7 has this weird naming with double hyphen like /var/lib/dhclient/dhclient--eth0.lease
- return self.get_endpoint_from_leases_path('/var/lib/dhclient/dhclient-*.lease')
+ # dhclient
+ endpoint = self.get_endpoint_from_leases_path('/var/lib/dhclient/dhclient-*.lease')
+
+ if endpoint is None:
+ # NetworkManager
+ endpoint = self.get_endpoint_from_leases_path('/var/lib/NetworkManager/dhclient-*.lease')
+
+ return endpoint
diff --git a/azurelinuxagent/common/protocol/hostplugin.py b/azurelinuxagent/common/protocol/hostplugin.py
index e83dd4b..bdae56e 100644
--- a/azurelinuxagent/common/protocol/hostplugin.py
+++ b/azurelinuxagent/common/protocol/hostplugin.py
@@ -16,7 +16,6 @@
#
# Requires Python 2.4+ and Openssl 1.0+
#
-
from azurelinuxagent.common.protocol.wire import *
from azurelinuxagent.common.utils import textutil
@@ -32,6 +31,7 @@ HEADER_HOST_CONFIG_NAME = "x-ms-host-config-name"
HEADER_ARTIFACT_LOCATION = "x-ms-artifact-location"
HEADER_ARTIFACT_MANIFEST_LOCATION = "x-ms-artifact-manifest-location"
+
class HostPluginProtocol(object):
def __init__(self, endpoint, container_id, role_config_name):
if endpoint is None:
@@ -41,6 +41,7 @@ class HostPluginProtocol(object):
self.api_versions = None
self.endpoint = endpoint
self.container_id = container_id
+ self.deployment_id = None
self.role_config_name = role_config_name
self.manifest_uri = None
@@ -49,6 +50,11 @@ class HostPluginProtocol(object):
self.api_versions = self.get_api_versions()
self.is_available = API_VERSION in self.api_versions
self.is_initialized = True
+
+ from azurelinuxagent.common.event import add_event, WALAEventOperation
+ add_event(name="WALA",
+ op=WALAEventOperation.InitializeHostPlugin,
+ is_success=self.is_available)
return self.is_available
def get_api_versions(self):
@@ -74,10 +80,10 @@ class HostPluginProtocol(object):
def get_artifact_request(self, artifact_url, artifact_manifest_url=None):
if not self.ensure_initialized():
logger.error("host plugin channel is not available")
- return
+ return None, None
if textutil.is_str_none_or_whitespace(artifact_url):
logger.error("no extension artifact url was provided")
- return
+ return None, None
url = URI_FORMAT_GET_EXTENSION_ARTIFACT.format(self.endpoint,
HOST_PLUGIN_PORT)
@@ -121,7 +127,10 @@ class HostPluginProtocol(object):
'content': status}, sort_keys=True)
response = restutil.http_put(url, data=data, headers=headers)
if response.status != httpclient.OK:
- logger.error("PUT failed [{0}]", response.status)
+ logger.warn("PUT {0} [{1}: {2}]",
+ url,
+ response.status,
+ response.reason)
else:
logger.verbose("Successfully uploaded status to host plugin")
except Exception as e:
@@ -140,18 +149,20 @@ class HostPluginProtocol(object):
if not self.ensure_initialized():
logger.error("host plugin channel is not available")
return
- if content is None or self.goal_state.container_id is None or self.goal_state.deployment_id is None:
+ if content is None \
+ or self.container_id is None \
+ or self.deployment_id is None:
logger.error(
"invalid arguments passed: "
"[{0}], [{1}], [{2}]".format(
content,
- self.goal_state.container_id,
- self.goal_state.deployment_id))
+ self.container_id,
+ self.deployment_id))
return
url = URI_FORMAT_PUT_LOG.format(self.endpoint, HOST_PLUGIN_PORT)
- headers = {"x-ms-vmagentlog-deploymentid": self.goal_state.deployment_id,
- "x-ms-vmagentlog-containerid": self.goal_state.container_id}
+ headers = {"x-ms-vmagentlog-deploymentid": self.deployment_id,
+ "x-ms-vmagentlog-containerid": self.container_id}
logger.info("put VM log at [{0}]".format(url))
try:
response = restutil.http_put(url, content, headers)
diff --git a/azurelinuxagent/common/protocol/restapi.py b/azurelinuxagent/common/protocol/restapi.py
index 5f71cf2..a42db37 100644
--- a/azurelinuxagent/common/protocol/restapi.py
+++ b/azurelinuxagent/common/protocol/restapi.py
@@ -16,11 +16,12 @@
#
# Requires Python 2.4+ and Openssl 1.0+
#
-
+import socket
import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.restutil as restutil
from azurelinuxagent.common.exception import ProtocolError, HttpError
from azurelinuxagent.common.future import ustr
+from azurelinuxagent.common.version import DISTRO_VERSION, DISTRO_NAME, CURRENT_VERSION
def validate_param(name, val, expected_type):
@@ -87,8 +88,13 @@ Data contract between guest and host
class VMInfo(DataContract):
- def __init__(self, subscriptionId=None, vmName=None, containerId=None,
- roleName=None, roleInstanceName=None, tenantName=None):
+ def __init__(self,
+ subscriptionId=None,
+ vmName=None,
+ containerId=None,
+ roleName=None,
+ roleInstanceName=None,
+ tenantName=None):
self.subscriptionId = subscriptionId
self.vmName = vmName
self.containerId = containerId
@@ -96,18 +102,26 @@ class VMInfo(DataContract):
self.roleInstanceName = roleInstanceName
self.tenantName = tenantName
+
class CertificateData(DataContract):
def __init__(self, certificateData=None):
self.certificateData = certificateData
+
class Cert(DataContract):
- def __init__(self, name=None, thumbprint=None, certificateDataUri=None, storeName=None, storeLocation=None):
+ def __init__(self,
+ name=None,
+ thumbprint=None,
+ certificateDataUri=None,
+ storeName=None,
+ storeLocation=None):
self.name = name
self.thumbprint = thumbprint
self.certificateDataUri = certificateDataUri
self.storeLocation = storeLocation
self.storeName = storeName
+
class CertList(DataContract):
def __init__(self):
self.certificates = DataContractList(Cert)
@@ -131,8 +145,12 @@ class VMAgentManifestList(DataContract):
class Extension(DataContract):
- def __init__(self, name=None, sequenceNumber=None, publicSettings=None,
- protectedSettings=None, certificateThumbprint=None):
+ def __init__(self,
+ name=None,
+ sequenceNumber=None,
+ publicSettings=None,
+ protectedSettings=None,
+ certificateThumbprint=None):
self.name = name
self.sequenceNumber = sequenceNumber
self.publicSettings = publicSettings
@@ -207,8 +225,13 @@ class ExtensionSubStatus(DataContract):
class ExtensionStatus(DataContract):
- def __init__(self, configurationAppliedTime=None, operation=None,
- status=None, seq_no=None, code=None, message=None):
+ def __init__(self,
+ configurationAppliedTime=None,
+ operation=None,
+ status=None,
+ seq_no=None,
+ code=None,
+ message=None):
self.configurationAppliedTime = configurationAppliedTime
self.operation = operation
self.status = status
@@ -219,7 +242,11 @@ class ExtensionStatus(DataContract):
class ExtHandlerStatus(DataContract):
- def __init__(self, name=None, version=None, status=None, code=0,
+ def __init__(self,
+ name=None,
+ version=None,
+ status=None,
+ code=0,
message=None):
self.name = name
self.version = version
@@ -230,16 +257,19 @@ class ExtHandlerStatus(DataContract):
class VMAgentStatus(DataContract):
- def __init__(self, version=None, status=None, message=None):
- self.version = version
+ def __init__(self, status=None, message=None):
self.status = status
self.message = message
+ self.hostname = socket.gethostname()
+ self.version = str(CURRENT_VERSION)
+ self.osname = DISTRO_NAME
+ self.osversion = DISTRO_VERSION
self.extensionHandlers = DataContractList(ExtHandlerStatus)
class VMStatus(DataContract):
- def __init__(self):
- self.vmAgent = VMAgentStatus()
+ def __init__(self, status, message):
+ self.vmAgent = VMAgentStatus(status=status, message=message)
class TelemetryEventParam(DataContract):
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 9f634e9..71c3e37 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -250,6 +250,9 @@ def ga_status_to_v1(ga_status):
v1_ga_status = {
'version': ga_status.version,
'status': ga_status.status,
+ 'osversion': ga_status.osversion,
+ 'osname': ga_status.osname,
+ 'hostname': ga_status.hostname,
'formattedMessage': formatted_msg
}
return v1_ga_status
@@ -338,7 +341,7 @@ def vm_status_to_v1(vm_status, ext_statuses):
'handlerAggregateStatus': v1_handler_status_list
}
v1_vm_status = {
- 'version': '1.0',
+ 'version': '1.1',
'timestampUTC': timestamp,
'aggregateStatus': v1_agg_status
}
@@ -638,9 +641,10 @@ class WireClient(object):
uri, headers = host.get_artifact_request(version.uri)
response = self.fetch(uri, headers)
if not response:
+ host = self.get_host_plugin(force_update=True)
logger.info("Retry fetch in {0} seconds",
- LONG_WAITING_INTERVAL)
- time.sleep(LONG_WAITING_INTERVAL)
+ SHORT_WAITING_INTERVAL)
+ time.sleep(SHORT_WAITING_INTERVAL)
else:
host.manifest_uri = version.uri
logger.verbose("Manifest downloaded successfully from host plugin")
@@ -659,9 +663,10 @@ class WireClient(object):
if resp.status == httpclient.OK:
return_value = self.decode_config(resp.read())
else:
- logger.warn("Could not fetch {0} [{1}]",
+ logger.warn("Could not fetch {0} [{1}: {2}]",
uri,
- resp.status)
+ resp.status,
+ resp.reason)
except (HttpError, ProtocolError) as e:
logger.verbose("Fetch failed from [{0}]", uri)
return return_value
@@ -716,7 +721,7 @@ class WireClient(object):
if not forced:
last_incarnation = None
- if (os.path.isfile(incarnation_file)):
+ if os.path.isfile(incarnation_file):
last_incarnation = fileutil.read_file(incarnation_file)
new_incarnation = goal_state.incarnation
if last_incarnation is not None and \
@@ -731,11 +736,11 @@ class WireClient(object):
file_name = GOAL_STATE_FILE_NAME.format(goal_state.incarnation)
goal_state_file = os.path.join(conf.get_lib_dir(), file_name)
self.save_cache(goal_state_file, xml_text)
- self.save_cache(incarnation_file, goal_state.incarnation)
self.update_hosting_env(goal_state)
self.update_shared_conf(goal_state)
self.update_certs(goal_state)
self.update_ext_conf(goal_state)
+ self.save_cache(incarnation_file, goal_state.incarnation)
if self.host_plugin is not None:
self.host_plugin.container_id = goal_state.container_id
self.host_plugin.role_config_name = goal_state.role_config_name
@@ -760,7 +765,7 @@ class WireClient(object):
return self.goal_state
def get_hosting_env(self):
- if (self.hosting_env is None):
+ if self.hosting_env is None:
local_file = os.path.join(conf.get_lib_dir(),
HOSTING_ENV_FILE_NAME)
xml_text = self.fetch_cache(local_file)
@@ -768,7 +773,7 @@ class WireClient(object):
return self.hosting_env
def get_shared_conf(self):
- if (self.shared_conf is None):
+ if self.shared_conf is None:
local_file = os.path.join(conf.get_lib_dir(),
SHARED_CONF_FILE_NAME)
xml_text = self.fetch_cache(local_file)
@@ -776,7 +781,7 @@ class WireClient(object):
return self.shared_conf
def get_certs(self):
- if (self.certs is None):
+ if self.certs is None:
local_file = os.path.join(conf.get_lib_dir(), CERTS_FILE_NAME)
xml_text = self.fetch_cache(local_file)
self.certs = Certificates(self, xml_text)
@@ -823,7 +828,7 @@ class WireClient(object):
logger.info("Wire protocol version:{0}", PROTOCOL_VERSION)
elif PROTOCOL_VERSION in version_info.get_supported():
logger.info("Wire protocol version:{0}", PROTOCOL_VERSION)
- logger.warn("Server prefered version:{0}", preferred)
+ logger.warn("Server preferred version:{0}", preferred)
else:
error = ("Agent supported wire protocol version: {0} was not "
"advised by Fabric.").format(PROTOCOL_VERSION)
@@ -982,8 +987,12 @@ class WireClient(object):
"x-ms-guest-agent-public-x509-cert": cert
}
- def get_host_plugin(self):
- if self.host_plugin is None:
+ def get_host_plugin(self, force_update=False):
+ if self.host_plugin is None or force_update:
+ if force_update:
+ logger.warn("Forcing update of goal state")
+ self.goal_state = None
+ self.update_goal_state(forced=True)
goal_state = self.get_goal_state()
self.host_plugin = HostPluginProtocol(self.endpoint,
goal_state.container_id,
@@ -1402,7 +1411,6 @@ class InVMArtifactsProfile(object):
* encryptedHealthChecks (optional)
* encryptedApplicationProfile (optional)
"""
-
def __init__(self, artifacts_profile):
if not textutil.is_str_none_or_whitespace(artifacts_profile):
self.__dict__.update(parse_json(artifacts_profile))
diff --git a/azurelinuxagent/common/rdma.py b/azurelinuxagent/common/rdma.py
index ba9a029..226482d 100644
--- a/azurelinuxagent/common/rdma.py
+++ b/azurelinuxagent/common/rdma.py
@@ -133,14 +133,14 @@ class RDMAHandler(object):
"""Load the kernel driver, this depends on the proper driver
to be installed with the install_driver() method"""
logger.info("RDMA: probing module '%s'" % self.driver_module_name)
- result = shellutil.run('modprobe %s' % self.driver_module_name)
+ result = shellutil.run('modprobe --first-time %s' % self.driver_module_name)
if result != 0:
error_msg = 'Could not load "%s" kernel module. '
- error_msg += 'Run "modprobe %s" as root for more details'
+ error_msg += 'Run "modprobe --first-time %s" as root for more details'
logger.error(
error_msg % (self.driver_module_name, self.driver_module_name)
)
- return
+ return False
logger.info('RDMA: Loaded the kernel driver successfully.')
return True
@@ -158,6 +158,7 @@ class RDMAHandler(object):
logger.info('RDMA: module loaded.')
return True
logger.info('RDMA: module not loaded.')
+ return False
def reboot_system(self):
"""Reboot the system. This is required as the kernel module for
diff --git a/azurelinuxagent/common/utils/fileutil.py b/azurelinuxagent/common/utils/fileutil.py
index b0b6fb7..8713d0c 100644
--- a/azurelinuxagent/common/utils/fileutil.py
+++ b/azurelinuxagent/common/utils/fileutil.py
@@ -140,9 +140,9 @@ def update_conf_file(path, line_start, val, chk_err=False):
if not os.path.isfile(path) and chk_err:
raise IOError("Can't find config file:{0}".format(path))
conf = read_file(path).split('\n')
- conf = [x for x in conf if not x.startswith(line_start)]
+ conf = [x for x in conf if x is not None and len(x) > 0 and not x.startswith(line_start)]
conf.append(val)
- write_file(path, '\n'.join(conf))
+ write_file(path, '\n'.join(conf) + '\n')
def search_file(target_dir_name, target_file_name):
for root, dirs, files in os.walk(target_dir_name):
diff --git a/azurelinuxagent/common/utils/restutil.py b/azurelinuxagent/common/utils/restutil.py
index 7c9ee17..7197370 100644
--- a/azurelinuxagent/common/utils/restutil.py
+++ b/azurelinuxagent/common/utils/restutil.py
@@ -29,6 +29,7 @@ REST api util functions
"""
RETRY_WAITING_INTERVAL = 10
+secure_warning = True
def _parse_url(url):
@@ -85,7 +86,7 @@ def _http_request(method, host, rel_uri, port=None, data=None, secure=False,
timeout=10)
url = rel_uri
- logger.verbose("HTTPConnection [{0}] [{1}] [{2}] [{3}]",
+ logger.verbose("HTTP connection [{0}] [{1}] [{2}] [{3}]",
method,
url,
data,
@@ -104,6 +105,7 @@ def http_request(method, url, data, headers=None, max_retry=3,
On error, sleep 10 and retry max_retry times.
"""
host, port, secure, rel_uri = _parse_url(url)
+ global secure_warning
# Check proxy
proxy_host, proxy_port = (None, None)
@@ -112,24 +114,22 @@ def http_request(method, url, data, headers=None, max_retry=3,
# If httplib module is not built with ssl support. Fallback to http
if secure and not hasattr(httpclient, "HTTPSConnection"):
- logger.warn("httplib is not built with ssl support")
secure = False
+ if secure_warning:
+ logger.warn("httplib is not built with ssl support")
+ secure_warning = False
# If httplib module doesn't support https tunnelling. Fallback to http
if secure and proxy_host is not None and proxy_port is not None \
and not hasattr(httpclient.HTTPSConnection, "set_tunnel"):
- logger.warn("httplib does not support https tunnelling "
- "(new in python 2.7)")
secure = False
+ if secure_warning:
+ logger.warn("httplib does not support https tunnelling "
+ "(new in python 2.7)")
+ secure_warning = False
- logger.verbose("HTTP method: [{0}]", method)
- logger.verbose("HTTP host: [{0}]", host)
- logger.verbose("HTTP uri: [{0}]", rel_uri)
- logger.verbose("HTTP port: [{0}]", port)
- logger.verbose("HTTP data: [{0}]", data)
- logger.verbose("HTTP secure: [{0}]", secure)
- logger.verbose("HTTP headers: [{0}]", headers)
- logger.verbose("HTTP proxy: [{0}:{1}]", proxy_host, proxy_port)
+ if proxy_host or proxy_port:
+ logger.verbose("HTTP proxy: [{0}:{1}]", proxy_host, proxy_port)
retry_msg = ''
log_msg = "HTTP {0}".format(method)
@@ -152,8 +152,14 @@ def http_request(method, url, data, headers=None, max_retry=3,
retry_interval = 5
except IOError as e:
retry_msg = 'IO error: {0} {1}'.format(log_msg, e)
- retry_interval = 0
- max_retry = 0
+ # error 101: network unreachable; when the adapter resets we may
+ # see this transient error for a short time, retry once.
+ if e.errno == 101:
+ retry_interval = RETRY_WAITING_INTERVAL
+ max_retry = 1
+ else:
+ retry_interval = 0
+ max_retry = 0
if retry < max_retry:
logger.info("Retry [{0}/{1} - {3}]",
diff --git a/azurelinuxagent/common/utils/shellutil.py b/azurelinuxagent/common/utils/shellutil.py
index d273c92..4efcbc4 100644
--- a/azurelinuxagent/common/utils/shellutil.py
+++ b/azurelinuxagent/common/utils/shellutil.py
@@ -17,13 +17,11 @@
# Requires Python 2.4+ and Openssl 1.0+
#
-import platform
-import os
import subprocess
-from azurelinuxagent.common.future import ustr
import azurelinuxagent.common.logger as logger
+from azurelinuxagent.common.future import ustr
-if not hasattr(subprocess,'check_output'):
+if not hasattr(subprocess, 'check_output'):
def check_output(*popenargs, **kwargs):
r"""Backport from subprocess module from python 2.7"""
if 'stdout' in kwargs:
@@ -39,51 +37,58 @@ if not hasattr(subprocess,'check_output'):
raise subprocess.CalledProcessError(retcode, cmd, output=output)
return output
+
# Exception classes used by this module.
class CalledProcessError(Exception):
def __init__(self, returncode, cmd, output=None):
self.returncode = returncode
self.cmd = cmd
self.output = output
+
def __str__(self):
return ("Command '{0}' returned non-zero exit status {1}"
"").format(self.cmd, self.returncode)
- subprocess.check_output=check_output
- subprocess.CalledProcessError=CalledProcessError
+ subprocess.check_output = check_output
+ subprocess.CalledProcessError = CalledProcessError
"""
Shell command util functions
"""
+
+
def run(cmd, chk_err=True):
"""
Calls run_get_output on 'cmd', returning only the return code.
If chk_err=True then errors will be reported in the log.
If chk_err=False then errors will be suppressed from the log.
"""
- retcode,out=run_get_output(cmd,chk_err)
+ retcode, out = run_get_output(cmd, chk_err)
return retcode
+
def run_get_output(cmd, chk_err=True, log_cmd=True):
"""
Wrapper for subprocess.check_output.
- Execute 'cmd'. Returns return code and STDOUT, trapping expected exceptions.
+ Execute 'cmd'. Returns return code and STDOUT, trapping expected
+ exceptions.
Reports exceptions to Error if chk_err parameter is True
"""
if log_cmd:
logger.verbose(u"run cmd '{0}'", cmd)
try:
- output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True)
+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT,
+ shell=True)
output = ustr(output, encoding='utf-8', errors="backslashreplace")
- except subprocess.CalledProcessError as e :
+ except subprocess.CalledProcessError as e:
output = ustr(e.output, encoding='utf-8', errors="backslashreplace")
if chk_err:
if log_cmd:
logger.error(u"run cmd '{0}' failed", e.cmd)
logger.error(u"Error Code:{0}", e.returncode)
logger.error(u"Result:{0}", output)
- return e.returncode, output
+ return e.returncode, output
return 0, output
@@ -103,5 +108,4 @@ def quote(word_list):
return " ".join(list("'{0}'".format(s.replace("'", "'\\''")) for s in word_list))
-
# End shell command util functions
diff --git a/azurelinuxagent/common/utils/textutil.py b/azurelinuxagent/common/utils/textutil.py
index 59b8fe7..2d99f6f 100644
--- a/azurelinuxagent/common/utils/textutil.py
+++ b/azurelinuxagent/common/utils/textutil.py
@@ -221,15 +221,24 @@ def hexstr_to_bytearray(a):
def set_ssh_config(config, name, val):
- notfound = True
+ found = False
+ no_match = -1
+
+ match_start = no_match
for i in range(0, len(config)):
- if config[i].startswith(name):
+ if config[i].startswith(name) and match_start == no_match:
config[i] = "{0} {1}".format(name, val)
- notfound = False
- elif config[i].startswith("Match"):
- # Match block must be put in the end of sshd config
- break
- if notfound:
+ found = True
+ elif config[i].lower().startswith("match"):
+ if config[i].lower().startswith("match all"):
+ # outside match block
+ match_start = no_match
+ elif match_start == no_match:
+ # inside match block
+ match_start = i
+ if not found:
+ if match_start != no_match:
+ i = match_start
config.insert(i, "{0} {1}".format(name, val))
return config
@@ -267,6 +276,9 @@ def gen_password_hash(password, crypt_id, salt_len):
collection = string.ascii_letters + string.digits
salt = ''.join(random.choice(collection) for _ in range(salt_len))
salt = "${0}${1}".format(crypt_id, salt)
+ if sys.version_info[0] == 2:
+ # if python 2.*, encode to type 'str' to prevent Unicode Encode Error from crypt.crypt
+ password = password.encode('utf-8')
return crypt.crypt(password, salt)
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index 1099e25..30b751c 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -26,13 +26,13 @@ from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
from azurelinuxagent.common.future import ustr
-"""
-Add this workaround for detecting F5 products because BIG-IP/IQ/etc do not show
-their version info in the /etc/product-version location. Instead, the version
-and product information is contained in the /VERSION file.
-"""
def get_f5_platform():
- result = [None,None,None,None]
+ """
+ Add this workaround for detecting F5 products because BIG-IP/IQ/etc do
+ not show their version info in the /etc/product-version location. Instead,
+ the version and product information is contained in the /VERSION file.
+ """
+ result = [None, None, None, None]
f5_version = re.compile("^Version: (\d+\.\d+\.\d+)")
f5_product = re.compile("^Product: ([\w-]+)")
@@ -56,13 +56,15 @@ def get_f5_platform():
result[2] = "iworkflow"
return result
+
def get_distro():
if 'FreeBSD' in platform.system():
release = re.sub('\-.*\Z', '', ustr(platform.release()))
osinfo = ['freebsd', release, '', 'freebsd']
elif 'linux_distribution' in dir(platform):
+ supported = platform._supported_dists + ('alpine',)
osinfo = list(platform.linux_distribution(full_distribution_name=0,
- supported_dists=platform._supported_dists+('alpine',)))
+ supported_dists=supported))
full_name = platform.linux_distribution()[0].strip()
osinfo.append(full_name)
else:
@@ -86,7 +88,7 @@ def get_distro():
AGENT_NAME = "WALinuxAgent"
AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.2.2'
+AGENT_VERSION = '2.2.6'
AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
AGENT_DESCRIPTION = """\
The Azure Linux Agent supports the provisioning and running of Linux
@@ -104,6 +106,7 @@ AGENT_DIR_PATTERN = re.compile(".*/{0}".format(AGENT_PATTERN))
EXT_HANDLER_PATTERN = b".*/WALinuxAgent-(\w.\w.\w[.\w]*)-.*-run-exthandlers"
EXT_HANDLER_REGEX = re.compile(EXT_HANDLER_PATTERN)
+
# Set the CURRENT_AGENT and CURRENT_VERSION to match the agent directory name
# - This ensures the agent will "see itself" using the same name and version
# as the code that downloads agents.
@@ -112,15 +115,19 @@ def set_current_agent():
lib_dir = conf.get_lib_dir()
if lib_dir[-1] != os.path.sep:
lib_dir += os.path.sep
- if path[:len(lib_dir)] != lib_dir:
+ agent = path[len(lib_dir):].split(os.path.sep)[0]
+ match = AGENT_NAME_PATTERN.match(agent)
+ if match:
+ version = match.group(1)
+ else:
agent = AGENT_LONG_VERSION
version = AGENT_VERSION
- else:
- agent = path[len(lib_dir):].split(os.path.sep)[0]
- version = AGENT_NAME_PATTERN.match(agent).group(1)
return agent, FlexibleVersion(version)
+
+
CURRENT_AGENT, CURRENT_VERSION = set_current_agent()
+
def set_goal_state_agent():
agent = None
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
@@ -136,8 +143,11 @@ def set_goal_state_agent():
if agent is None:
agent = CURRENT_VERSION
return agent
+
+
GOAL_STATE_AGENT_VERSION = set_goal_state_agent()
+
def is_current_agent_installed():
return CURRENT_AGENT == AGENT_LONG_VERSION
@@ -153,13 +163,12 @@ PY_VERSION_MAJOR = sys.version_info[0]
PY_VERSION_MINOR = sys.version_info[1]
PY_VERSION_MICRO = sys.version_info[2]
-"""
-Add this workaround for detecting Snappy Ubuntu Core temporarily, until ubuntu
-fixed this bug: https://bugs.launchpad.net/snappy/+bug/1481086
-"""
-
def is_snappy():
+ """
+ Add this workaround for detecting Snappy Ubuntu Core temporarily,
+ until ubuntu fixed this bug: https://bugs.launchpad.net/snappy/+bug/1481086
+ """
if os.path.exists("/etc/motd"):
motd = fileutil.read_file("/etc/motd")
if "snappy" in motd:
@@ -169,4 +178,3 @@ def is_snappy():
if is_snappy():
DISTRO_FULL_NAME = "Snappy Ubuntu Core"
-