summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--azurelinuxagent/agent.py9
-rw-r--r--azurelinuxagent/common/event.py70
-rw-r--r--azurelinuxagent/common/future.py4
-rw-r--r--azurelinuxagent/common/logger.py35
-rw-r--r--azurelinuxagent/common/osutil/bigip.py51
-rw-r--r--azurelinuxagent/common/osutil/default.py13
-rw-r--r--azurelinuxagent/common/osutil/factory.py4
-rw-r--r--azurelinuxagent/common/osutil/freebsd.py12
-rw-r--r--azurelinuxagent/common/osutil/gaia.py69
-rw-r--r--azurelinuxagent/common/osutil/openbsd.py345
-rw-r--r--azurelinuxagent/common/protocol/hostplugin.py37
-rw-r--r--azurelinuxagent/common/protocol/metadata.py6
-rw-r--r--azurelinuxagent/common/protocol/util.py5
-rw-r--r--azurelinuxagent/common/protocol/wire.py21
-rw-r--r--azurelinuxagent/common/version.py10
-rw-r--r--azurelinuxagent/daemon/main.py2
-rw-r--r--azurelinuxagent/daemon/resourcedisk/factory.py4
-rw-r--r--azurelinuxagent/daemon/resourcedisk/freebsd.py2
-rw-r--r--azurelinuxagent/daemon/resourcedisk/openbsd.py113
-rw-r--r--azurelinuxagent/ga/exthandlers.py1
-rw-r--r--azurelinuxagent/ga/update.py71
-rw-r--r--azurelinuxagent/pa/deprovision/default.py55
-rw-r--r--azurelinuxagent/pa/provision/default.py5
-rw-r--r--config/gaia/waagent.conf2
-rw-r--r--config/openbsd/waagent.conf105
-rw-r--r--debian/changelog8
-rw-r--r--debian/copyright9
-rw-r--r--init/openbsd/waagent10
-rwxr-xr-xsetup.py12
-rw-r--r--tests/common/osutil/test_bigip.py29
-rw-r--r--tests/common/test_event.py66
-rw-r--r--tests/common/test_logger.py66
-rw-r--r--tests/ga/test_update.py36
-rw-r--r--tests/pa/test_deprovision.py38
-rw-r--r--tests/protocol/test_hostplugin.py57
-rw-r--r--tests/protocol/test_wire.py24
37 files changed, 1177 insertions, 239 deletions
diff --git a/README.md b/README.md
index c921190..ee40187 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,9 @@
### INTRODUCTION
-The Microsoft Azure Linux Agent (waagent) manages Linux & FreeBSD provisioning,
+The Microsoft Azure Linux Agent (waagent) manages Linux & BSD provisioning,
and VM interaction with the Azure Fabric Controller. It provides the following
-functionality for Linux and FreeBSD IaaS deployments:
+functionality for Linux and BSD IaaS deployments:
* Image Provisioning
- Creation of a user account
@@ -59,6 +59,7 @@ of supported systems on the Microsoft Azure Platform as described here:
http://support.microsoft.com/kb/2805216
Supported Linux Distributions:
+ * Archlinux
* CoreOS
* CentOS 6.2+
* Red Hat Enterprise Linux 6.7+
@@ -70,6 +71,7 @@ Supported Linux Distributions:
Other Supported Systems:
* FreeBSD 10+ (Azure Linux Agent v2.0.10+)
+ * OpenBSD 6+ (Azure Linux Agent v2.2.11+)
Waagent depends on some system packages in order to function properly:
@@ -303,8 +305,8 @@ _Default: ext4_
This specifies the filesystem type for the resource disk. Supported values vary
by Linux distribution. If the string is X, then mkfs.X should be present on the
-Linux image. SLES 11 images should typically use 'ext3'. FreeBSD images should
-use 'ufs2' here.
+Linux image. SLES 11 images should typically use 'ext3'. BSD images should use
+'ufs2' here.
* __ResourceDisk.MountPoint__
_Type: String_
diff --git a/azurelinuxagent/agent.py b/azurelinuxagent/agent.py
index 90b4253..d1ac354 100644
--- a/azurelinuxagent/agent.py
+++ b/azurelinuxagent/agent.py
@@ -129,7 +129,7 @@ def main(args=[]):
elif command == "help":
usage()
elif command == "start":
- start()
+ start(conf_file_path=conf_file_path)
else:
try:
agent = Agent(verbose, conf_file_path=conf_file_path)
@@ -217,13 +217,16 @@ def usage():
"").format(sys.argv[0])))
print("")
-def start():
+def start(conf_file_path=None):
"""
Start agent daemon in a background process and set stdout/stderr to
/dev/null
"""
devnull = open(os.devnull, 'w')
- subprocess.Popen([sys.argv[0], '-daemon'], stdout=devnull, stderr=devnull)
+ args = [sys.argv[0], '-daemon']
+ if conf_file_path is not None:
+ args.append('-configuration-path:{0}'.format(conf_file_path))
+ subprocess.Popen(args, stdout=devnull, stderr=devnull)
if __name__ == '__main__' :
main()
diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index 116478b..723b8bf 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -25,7 +25,7 @@ import datetime
import threading
import platform
-from datetime import datetime
+from datetime import datetime, timedelta
import azurelinuxagent.common.logger as logger
@@ -39,6 +39,7 @@ from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \
DISTRO_CODE_NAME, AGENT_VERSION, \
CURRENT_AGENT, CURRENT_VERSION
+_EVENT_MSG = "Event: name={0}, op={1}, message={2}"
class WALAEventOperation:
ActivateResourceDisk = "ActivateResourceDisk"
@@ -47,6 +48,7 @@ class WALAEventOperation:
Enable = "Enable"
HealthCheck = "HealthCheck"
HeartBeat = "HeartBeat"
+ HostPlugin = "HostPlugin"
Install = "Install"
InitializeHostPlugin = "InitializeHostPlugin"
ProcessGoalState = "ProcessGoalState"
@@ -58,10 +60,19 @@ class WALAEventOperation:
Upgrade = "Upgrade"
Update = "Update"
+def _log_event(name, op, message, is_success=True):
+ global _EVENT_MSG
+
+ if not is_success:
+ logger.error(_EVENT_MSG, name, op, message)
+ else:
+ logger.info(_EVENT_MSG, name, op, message)
+
class EventLogger(object):
def __init__(self):
self.event_dir = None
+ self.periodic_events = {}
def save_event(self, data):
if self.event_dir is None:
@@ -92,9 +103,33 @@ class EventLogger(object):
except IOError as e:
raise EventError("Failed to write events to file:{0}", e)
+ def reset_periodic(self):
+ self.periodic_messages = {}
+
+ def is_period_elapsed(self, delta, h):
+ return h not in self.periodic_messages or \
+ (self.periodic_messages[h] + delta) <= datetime.now()
+
+ def add_periodic(self,
+ delta, name, op="", is_success=True, duration=0,
+ version=CURRENT_VERSION, message="", evt_type="",
+ is_internal=False, log_event=True, force=False):
+
+ h = hash(name+op+ustr(is_success)+message)
+
+ if force or self.is_period_elapsed(delta, h):
+ self.add_event(name,
+ op=op, is_success=is_success, duration=duration,
+ version=version, message=message, evt_type=evt_type,
+ is_internal=is_internal, log_event=log_event)
+ self.periodic_messages[h] = datetime.now()
+
def add_event(self, name, op="", is_success=True, duration=0,
version=CURRENT_VERSION,
- message="", evt_type="", is_internal=False):
+ message="", evt_type="", is_internal=False, log_event=True):
+ if not is_success or log_event:
+ _log_event(name, op, message, is_success=is_success)
+
event = TelemetryEvent(1, "69B669B9-4AF8-4C50-BDC4-6006FA76E975")
event.parameters.append(TelemetryEventParam('Name', name))
event.parameters.append(TelemetryEventParam('Version', str(version)))
@@ -129,21 +164,40 @@ def report_event(op, is_success=True, message=''):
message=message,
op=op)
+def report_periodic(delta, op, is_success=True, message=''):
+ from azurelinuxagent.common.version import AGENT_NAME, CURRENT_VERSION
+ add_periodic(delta, AGENT_NAME,
+ version=CURRENT_VERSION,
+ is_success=is_success,
+ message=message,
+ op=op)
def add_event(name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
message="", evt_type="", is_internal=False, log_event=True,
reporter=__event_logger__):
- if log_event or not is_success:
- log = logger.info if is_success else logger.error
- log("Event: name={0}, op={1}, message={2}", name, op, message)
+ if reporter.event_dir is None:
+ logger.warn("Event reporter is not initialized.")
+ _log_event(name, op, message, is_success=is_success)
+ return
+
+ reporter.add_event(
+ name, op=op, is_success=is_success, duration=duration,
+ version=str(version), message=message, evt_type=evt_type,
+ is_internal=is_internal, log_event=log_event)
+def add_periodic(
+ delta, name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
+ message="", evt_type="", is_internal=False, log_event=True, force=False,
+ reporter=__event_logger__):
if reporter.event_dir is None:
logger.warn("Event reporter is not initialized.")
+ _log_event(name, op, message, is_success=is_success)
return
- reporter.add_event(name, op=op, is_success=is_success, duration=duration,
- version=str(version), message=message, evt_type=evt_type,
- is_internal=is_internal)
+ reporter.add_periodic(
+ delta, name, op=op, is_success=is_success, duration=duration,
+ version=str(version), message=message, evt_type=evt_type,
+ is_internal=is_internal, log_event=log_event, force=force)
def init_event_logger(event_dir, reporter=__event_logger__):
reporter.event_dir = event_dir
diff --git a/azurelinuxagent/common/future.py b/azurelinuxagent/common/future.py
index 8509732..8d5b70b 100644
--- a/azurelinuxagent/common/future.py
+++ b/azurelinuxagent/common/future.py
@@ -13,8 +13,6 @@ if sys.version_info[0]== 3:
bytebuffer = memoryview
- read_input = input
-
elif sys.version_info[0] == 2:
import httplib as httpclient
from urlparse import urlparse
@@ -24,8 +22,6 @@ elif sys.version_info[0] == 2:
bytebuffer = buffer
- read_input = raw_input
-
else:
raise ImportError("Unknown python version:{0}".format(sys.version_info))
diff --git a/azurelinuxagent/common/logger.py b/azurelinuxagent/common/logger.py
index c1eb18f..bfdc73a 100644
--- a/azurelinuxagent/common/logger.py
+++ b/azurelinuxagent/common/logger.py
@@ -20,7 +20,13 @@ Log utils
import os
import sys
from azurelinuxagent.common.future import ustr
-from datetime import datetime
+from datetime import datetime, timedelta
+
+EVERY_DAY = timedelta(days=1)
+EVERY_HALF_DAY = timedelta(hours=12)
+EVERY_HOUR = timedelta(hours=1)
+EVERY_HALF_HOUR = timedelta(minutes=30)
+EVERY_FIFTEEN_MINUTES = timedelta(minutes=15)
class Logger(object):
"""
@@ -28,10 +34,23 @@ class Logger(object):
"""
def __init__(self, logger=None, prefix=None):
self.appenders = []
- if logger is not None:
- self.appenders.extend(logger.appenders)
+ self.logger = self if logger is None else logger
+ self.periodic_messages = {}
self.prefix = prefix
+ def reset_periodic(self):
+ self.logger.periodic_messages = {}
+
+ def is_period_elapsed(self, delta, h):
+ return h not in self.logger.periodic_messages or \
+ (self.logger.periodic_messages[h] + delta) <= datetime.now()
+
+ def periodic(self, delta, msg_format, *args):
+ h = hash(msg_format)
+ if self.is_period_elapsed(delta, h):
+ self.info(msg_format, *args)
+ self.logger.periodic_messages[h] = datetime.now()
+
def verbose(self, msg_format, *args):
self.log(LogLevel.VERBOSE, msg_format, *args)
@@ -62,8 +81,12 @@ class Logger(object):
log_item = ustr(log_item.encode('ascii', "backslashreplace"),
encoding="ascii")
+
for appender in self.appenders:
appender.write(level, log_item)
+ if self.logger != self:
+ for appender in self.logger.appenders:
+ appender.write(level, log_item)
def add_appender(self, appender_type, level, path):
appender = _create_logger_appender(appender_type, level, path)
@@ -129,6 +152,12 @@ class AppenderType(object):
def add_logger_appender(appender_type, level=LogLevel.INFO, path=None):
DEFAULT_LOGGER.add_appender(appender_type, level, path)
+def reset_periodic():
+ DEFAULT_LOGGER.reset_periodic()
+
+def periodic(delta, msg_format, *args):
+ DEFAULT_LOGGER.periodic(delta, msg_format, *args)
+
def verbose(msg_format, *args):
DEFAULT_LOGGER.verbose(msg_format, *args)
diff --git a/azurelinuxagent/common/osutil/bigip.py b/azurelinuxagent/common/osutil/bigip.py
index fea7aff..8f6570f 100644
--- a/azurelinuxagent/common/osutil/bigip.py
+++ b/azurelinuxagent/common/osutil/bigip.py
@@ -258,57 +258,6 @@ class BigIpOSUtil(DefaultOSUtil):
"""
logger.warn("Eject is not supported on this platform")
- def set_admin_access_to_ip(self, dest_ip):
- """Sets admin access to an IP address
-
- This method is primarily used to limit which user account is allowed to
- communicate with the Azure(Stack) metadata service. This service is at
- the address 169.254.169.254 and includes information about the device
- that "normal" users should not be allowed to see.
-
- We cannot use this iptables command that comes with the default class
- because we do not ship the 'ipt_owner' iptables extension with BIG-IP.
-
- This should not be a problem though as the only people who should have
- access to BIG-IP are people who are root anyways. Our system is not
- a "general purpose" user system. So for those reasons I am dropping
- that requirement from our implementation.
-
- :param dest_ip: The IP address that you want to allow admin access for
- """
- self._set_accept_admin_access_to_ip(dest_ip)
- self._set_drop_admin_access_to_ip(dest_ip)
-
- def _set_accept_admin_access_to_ip(self, dest_ip):
- """Sets the "accept" IP Tables rules
-
- I broke this out to a separate function so that I could more easily
- test it in the tests/common/osutil/test_default.py code
-
- :param dest_ip:
- :return:
- """
- # This allows root to access dest_ip
- rm_old = "iptables -D OUTPUT -d {0} -j ACCEPT"
- rule = "iptables -A OUTPUT -d {0} -j ACCEPT"
- shellutil.run(rm_old.format(dest_ip), chk_err=False)
- shellutil.run(rule.format(dest_ip))
-
- def _set_drop_admin_access_to_ip(self, dest_ip):
- """Sets the "drop" IP Tables rules
-
- I broke this out to a separate function so that I could more easily
- test it in the tests/common/osutil/test_default.py code
-
- :param dest_ip:
- :return:
- """
- # 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))
-
def get_first_if(self):
"""Return the interface name, and ip addr of the management interface.
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index 20dc1f3..58c0ef8 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -841,18 +841,5 @@ class DefaultOSUtil(object):
def get_processor_cores(self):
return multiprocessing.cpu_count()
- 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))
-
def check_pid_alive(self, pid):
return pid is not None and os.path.isdir(os.path.join('/proc', pid))
diff --git a/azurelinuxagent/common/osutil/factory.py b/azurelinuxagent/common/osutil/factory.py
index 3447651..2be90ab 100644
--- a/azurelinuxagent/common/osutil/factory.py
+++ b/azurelinuxagent/common/osutil/factory.py
@@ -24,6 +24,7 @@ from .clearlinux import ClearLinuxUtil
from .coreos import CoreOSUtil
from .debian import DebianOSUtil
from .freebsd import FreeBSDOSUtil
+from .openbsd import OpenBSDOSUtil
from .redhat import RedhatOSUtil, Redhat6xOSUtil
from .suse import SUSEOSUtil, SUSE11OSUtil
from .ubuntu import UbuntuOSUtil, Ubuntu12OSUtil, Ubuntu14OSUtil, UbuntuSnappyOSUtil
@@ -87,6 +88,9 @@ def get_osutil(distro_name=DISTRO_NAME,
elif distro_name == "freebsd":
return FreeBSDOSUtil()
+ elif distro_name == "openbsd":
+ return OpenBSDOSUtil()
+
elif distro_name == "bigip":
return BigIpOSUtil()
diff --git a/azurelinuxagent/common/osutil/freebsd.py b/azurelinuxagent/common/osutil/freebsd.py
index 0f465a9..39d1760 100644
--- a/azurelinuxagent/common/osutil/freebsd.py
+++ b/azurelinuxagent/common/osutil/freebsd.py
@@ -229,17 +229,21 @@ class FreeBSDOSUtil(DefaultOSUtil):
err, output = shellutil.run_get_output(cmd_search_blkvsc)
if err == 0:
output = output.rstrip()
- cmd_search_dev="camcontrol devlist | grep {0} | awk -F \( '{{print $2}}'|awk -F , '{{print $1}}'".format(output)
+ cmd_search_dev="camcontrol devlist | grep {0} | awk -F \( '{{print $2}}'|sed -e 's/.*(//'| sed -e 's/).*//'".format(output)
err, output = shellutil.run_get_output(cmd_search_dev)
if err == 0:
- return output.rstrip()
+ for possible in output.rstrip().split(','):
+ if not possible.startswith('pass'):
+ return possible
cmd_search_storvsc = "camcontrol devlist -b | grep storvsc{0} | awk '{{print $1}}'".format(output)
err, output = shellutil.run_get_output(cmd_search_storvsc)
if err == 0:
output = output.rstrip()
- cmd_search_dev="camcontrol devlist | grep {0} | awk -F \( '{{print $2}}'|awk -F , '{{print $1}}'".format(output)
+ cmd_search_dev="camcontrol devlist | grep {0} | awk -F \( '{{print $2}}'|sed -e 's/.*(//'| sed -e 's/).*//'".format(output)
err, output = shellutil.run_get_output(cmd_search_dev)
if err == 0:
- return output.rstrip()
+ for possible in output.rstrip().split(','):
+ if not possible.startswith('pass'):
+ return possible
return None
diff --git a/azurelinuxagent/common/osutil/gaia.py b/azurelinuxagent/common/osutil/gaia.py
index a1069d3..6a87b6b 100644
--- a/azurelinuxagent/common/osutil/gaia.py
+++ b/azurelinuxagent/common/osutil/gaia.py
@@ -16,15 +16,20 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import base64
import socket
import struct
import time
-import azurelinuxagent.common.logger as logger
+import azurelinuxagent.common.conf as conf
from azurelinuxagent.common.exception import OSUtilError
+from azurelinuxagent.common.future import ustr, bytebuffer
+import azurelinuxagent.common.logger as logger
+from azurelinuxagent.common.osutil.default import DefaultOSUtil
+from azurelinuxagent.common.utils.cryptutil import CryptUtil
+import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.shellutil as shellutil
import azurelinuxagent.common.utils.textutil as textutil
-from azurelinuxagent.common.osutil.default import DefaultOSUtil
class GaiaOSUtil(DefaultOSUtil):
@@ -64,12 +69,11 @@ class GaiaOSUtil(DefaultOSUtil):
if ret != 0:
raise OSUtilError("Failed to delete root password")
- def _replace_user(path, username):
+ def _replace_user(self, path, username):
+ if path.startswith('$HOME'):
+ path = '/home' + path[5:]
parts = path.split('/')
- for i in xrange(len(parts)):
- if parts[i] == '$HOME':
- parts[i + 1] = username
- break
+ parts[2] = username
return '/'.join(parts)
def deploy_ssh_keypair(self, username, keypair):
@@ -80,13 +84,57 @@ class GaiaOSUtil(DefaultOSUtil):
super(GaiaOSUtil, self).deploy_ssh_keypair(
username, (path, thumbprint))
+ def openssl_to_openssh(self, input_file, output_file):
+ cryptutil = CryptUtil(conf.get_openssl_cmd())
+ ret, out = shellutil.run_get_output(
+ conf.get_openssl_cmd() +
+ " rsa -pubin -noout -text -in '" + input_file + "'")
+ if ret != 0:
+ raise OSUtilError('openssl failed with {0}'.format(ret))
+
+ modulus = []
+ exponent = []
+ buf = None
+ for line in out.split('\n'):
+ if line.startswith('Modulus:'):
+ buf = modulus
+ buf.append(line)
+ continue
+ if line.startswith('Exponent:'):
+ buf = exponent
+ buf.append(line)
+ continue
+ if buf and line:
+ buf.append(line.strip().replace(':', ''))
+
+ def text_to_num(buf):
+ if len(buf) == 1:
+ return int(buf[0].split()[1])
+ return long(''.join(buf[1:]), 16)
+
+ n = text_to_num(modulus)
+ e = text_to_num(exponent)
+
+ keydata = bytearray()
+ keydata.extend(struct.pack('>I', len('ssh-rsa')))
+ keydata.extend(b'ssh-rsa')
+ keydata.extend(struct.pack('>I', len(cryptutil.num_to_bytes(e))))
+ keydata.extend(cryptutil.num_to_bytes(e))
+ keydata.extend(struct.pack('>I', len(cryptutil.num_to_bytes(n)) + 1))
+ keydata.extend(b'\0')
+ keydata.extend(cryptutil.num_to_bytes(n))
+ keydata_base64 = base64.b64encode(bytebuffer(keydata))
+ fileutil.write_file(output_file,
+ ustr(b'ssh-rsa ' + keydata_base64 + b'\n',
+ encoding='utf-8'))
+
def deploy_ssh_pubkey(self, username, pubkey):
logger.info('deploy_ssh_pubkey')
username = 'admin'
path, thumbprint, value = pubkey
path = self._replace_user(path, username)
super(GaiaOSUtil, self).deploy_ssh_pubkey(
- 'admin', (path, thumbprint, value))
+ username, (path, thumbprint, value))
def eject_dvd(self, chk_err=True):
logger.warn('eject is not supported on GAiA')
@@ -114,7 +162,7 @@ class GaiaOSUtil(DefaultOSUtil):
def restart_ssh_service(self):
return shellutil.run('/sbin/service sshd condrestart', chk_err=False)
- def _address_to_string(addr):
+ def _address_to_string(self, addr):
return socket.inet_ntoa(struct.pack("!I", addr))
def _get_prefix(self, mask):
@@ -146,6 +194,3 @@ class GaiaOSUtil(DefaultOSUtil):
def del_account(self, username):
logger.warn('del_account is ignored on GAiA')
-
- def set_admin_access_to_ip(self, dest_ip):
- logger.warn('set_admin_access_to_ip is ignored on GAiA')
diff --git a/azurelinuxagent/common/osutil/openbsd.py b/azurelinuxagent/common/osutil/openbsd.py
new file mode 100644
index 0000000..9bfe6de
--- /dev/null
+++ b/azurelinuxagent/common/osutil/openbsd.py
@@ -0,0 +1,345 @@
+# Microsoft Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+# Copyright 2017 Reyk Floeter <reyk@openbsd.org>
+#
+# 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 time
+import glob
+import datetime
+
+import azurelinuxagent.common.utils.fileutil as fileutil
+import azurelinuxagent.common.utils.shellutil as shellutil
+import azurelinuxagent.common.logger as logger
+import azurelinuxagent.common.conf as conf
+
+from azurelinuxagent.common.exception import OSUtilError
+from azurelinuxagent.common.osutil.default import DefaultOSUtil
+
+UUID_PATTERN = re.compile(
+ r'^\s*[A-F0-9]{8}(?:\-[A-F0-9]{4}){3}\-[A-F0-9]{12}\s*$',
+ re.IGNORECASE)
+
+class OpenBSDOSUtil(DefaultOSUtil):
+ def __init__(self):
+ super(OpenBSDOSUtil, self).__init__()
+ self._scsi_disks_timeout_set = False
+
+ def get_instance_id(self):
+ ret, output = shellutil.run_get_output("sysctl -n hw.uuid")
+ if ret != 0 or UUID_PATTERN.match(output) is None:
+ return ""
+ return output.strip()
+
+ def set_hostname(self, hostname):
+ fileutil.write_file("/etc/myname", "{}\n".format(hostname))
+ shellutil.run("hostname {0}".format(hostname), chk_err=False)
+
+ def restart_ssh_service(self):
+ return shellutil.run('rcctl restart sshd', chk_err=False)
+
+ def start_agent_service(self):
+ return shellutil.run('rcctl start waagent', chk_err=False)
+
+ def stop_agent_service(self):
+ return shellutil.run('rcctl stop waagent', chk_err=False)
+
+ def register_agent_service(self):
+ shellutil.run('chmod 0555 /etc/rc.d/waagent', chk_err=False)
+ return shellutil.run('rcctl enable waagent', chk_err=False)
+
+ def unregister_agent_service(self):
+ return shellutil.run('rcctl disable waagent', chk_err=False)
+
+ def del_account(self, username):
+ if self.is_sys_user(username):
+ logger.error("{0} is a system user. Will not delete it.",
+ username)
+ shellutil.run("> /var/run/utmp")
+ shellutil.run("userdel -r " + username)
+ self.conf_sudoer(username, remove=True)
+
+ def conf_sudoer(self, username, nopasswd=False, remove=False):
+ doas_conf = "/etc/doas.conf"
+ doas = None
+ if not remove:
+ if not os.path.isfile(doas_conf):
+ # always allow root to become root
+ doas = "permit keepenv nopass root\n"
+ fileutil.append_file(doas_conf, doas)
+ if nopasswd:
+ doas = "permit keepenv nopass {0}\n".format(username)
+ else:
+ doas = "permit keepenv persist {0}\n".format(username)
+ fileutil.append_file(doas_conf, doas)
+ fileutil.chmod(doas_conf, 0o644)
+ else:
+ # Remove user from doas.conf
+ if os.path.isfile(doas_conf):
+ try:
+ content = fileutil.read_file(doas_conf)
+ doas = content.split("\n")
+ doas = [x for x in doas if username not in x]
+ fileutil.write_file(doas_conf, "\n".join(doas))
+ except IOError as err:
+ raise OSUtilError("Failed to remove sudoer: "
+ "{0}".format(err))
+
+ def chpasswd(self, username, password, crypt_id=6, salt_len=10):
+ if self.is_sys_user(username):
+ raise OSUtilError(("User {0} is a system user. "
+ "Will not set passwd.").format(username))
+ cmd = "echo -n {0}|encrypt".format(password)
+ ret, output = shellutil.run_get_output(cmd, log_cmd=False)
+ if ret != 0:
+ raise OSUtilError(("Failed to encrypt password for {0}: {1}"
+ "").format(username, output))
+ passwd_hash = output.strip()
+ cmd = "usermod -p '{0}' {1}".format(passwd_hash, username)
+ ret, output = shellutil.run_get_output(cmd, log_cmd=False)
+ if ret != 0:
+ raise OSUtilError(("Failed to set password for {0}: {1}"
+ "").format(username, output))
+
+ def del_root_password(self):
+ ret, output = shellutil.run_get_output('usermod -p "*" root')
+ if ret:
+ raise OSUtilError("Failed to delete root password: "
+ "{0}".format(output))
+
+ def get_if_mac(self, ifname):
+ data = self._get_net_info()
+ if data[0] == ifname:
+ return data[2].replace(':', '').upper()
+ return None
+
+ def get_first_if(self):
+ return self._get_net_info()[:2]
+
+ def route_add(self, net, mask, gateway):
+ cmd = 'route add {0} {1} {2}'.format(net, gateway, mask)
+ return shellutil.run(cmd, chk_err=False)
+
+ def is_missing_default_route(self):
+ ret = shellutil.run("route -n get default", chk_err=False)
+ if ret == 0:
+ return False
+ return True
+
+ def is_dhcp_enabled(self):
+ pass
+
+ def start_dhcp_service(self):
+ pass
+
+ def stop_dhcp_service(self):
+ pass
+
+ def get_dhcp_lease_endpoint(self):
+ """
+ OpenBSD has a sligthly different lease file format.
+ """
+ endpoint = None
+ pathglob = '/var/db/dhclient.leases.{}'.format(self.get_first_if()[0])
+
+ HEADER_LEASE = "lease"
+ HEADER_OPTION = "option option-245"
+ HEADER_EXPIRE = "expire"
+ FOOTER_LEASE = "}"
+ FORMAT_DATETIME = "%Y/%m/%d %H:%M:%S %Z"
+
+ logger.info("looking for leases in path [{0}]".format(pathglob))
+ for lease_file in glob.glob(pathglob):
+ leases = open(lease_file).read()
+ if HEADER_OPTION in leases:
+ cached_endpoint = None
+ has_option_245 = False
+ expired = True # assume expired
+ for line in leases.splitlines():
+ if line.startswith(HEADER_LEASE):
+ cached_endpoint = None
+ has_option_245 = False
+ expired = True
+ elif HEADER_OPTION in line:
+ try:
+ ipaddr = line.split(" ")[-1].strip(";").split(":")
+ cached_endpoint = \
+ ".".join(str(int(d, 16)) for d in ipaddr)
+ has_option_245 = True
+ except ValueError:
+ logger.error("could not parse '{0}'".format(line))
+ elif HEADER_EXPIRE in line:
+ if "never" in line:
+ expired = False
+ else:
+ try:
+ expire_string = line.split(
+ " ", 4)[-1].strip(";")
+ expire_date = datetime.datetime.strptime(
+ expire_string, FORMAT_DATETIME)
+ if expire_date > datetime.datetime.utcnow():
+ expired = False
+ except ValueError:
+ logger.error("could not parse expiry token "
+ "'{0}'".format(line))
+ elif FOOTER_LEASE in line:
+ logger.info("dhcp entry:{0}, 245:{1}, expired: {2}"
+ .format(cached_endpoint, has_option_245, expired))
+ if not expired and cached_endpoint is not None and has_option_245:
+ endpoint = cached_endpoint
+ logger.info("found endpoint [{0}]".format(endpoint))
+ # we want to return the last valid entry, so
+ # keep searching
+ if endpoint is not None:
+ logger.info("cached endpoint found [{0}]".format(endpoint))
+ else:
+ logger.info("cached endpoint not found")
+ return endpoint
+
+ def allow_dhcp_broadcast(self):
+ pass
+
+ def set_route_for_dhcp_broadcast(self, ifname):
+ return shellutil.run("route add 255.255.255.255 -iface "
+ "{0}".format(ifname), chk_err=False)
+
+ def remove_route_for_dhcp_broadcast(self, ifname):
+ shellutil.run("route delete 255.255.255.255 -iface "
+ "{0}".format(ifname), chk_err=False)
+
+ def get_dhcp_pid(self):
+ ret, output = shellutil.run_get_output("pgrep -n dhclient",
+ chk_err=False)
+ return output if ret == 0 else None
+
+ def get_dvd_device(self, dev_dir='/dev'):
+ pattern = r'cd[0-9]c'
+ for dvd in [re.match(pattern, dev) for dev in os.listdir(dev_dir)]:
+ if dvd is not None:
+ return "/dev/{0}".format(dvd.group(0))
+ raise OSUtilError("Failed to get DVD device")
+
+ def mount_dvd(self,
+ max_retry=6,
+ chk_err=True,
+ dvd_device=None,
+ mount_point=None,
+ sleep_time=5):
+ if dvd_device is None:
+ dvd_device = self.get_dvd_device()
+ if mount_point is None:
+ mount_point = conf.get_dvd_mount_point()
+ if not os.path.isdir(mount_point):
+ os.makedirs(mount_point)
+
+ for retry in range(0, max_retry):
+ retcode = self.mount(dvd_device, mount_point, option="-o ro -t udf",
+ chk_err=chk_err)
+ if retcode == 0:
+ logger.info("Successfully mounted DVD")
+ return
+ if retry < max_retry - 1:
+ mountlist = shellutil.run_get_output("/sbin/mount")[1]
+ existing = self.get_mount_point(mountlist, dvd_device)
+ if existing is not None:
+ logger.info("{0} is mounted at {1}", dvd_device, existing)
+ return
+ logger.warn("Mount DVD failed: retry={0}, ret={1}", retry,
+ retcode)
+ time.sleep(sleep_time)
+ if chk_err:
+ raise OSUtilError("Failed to mount DVD.")
+
+ def eject_dvd(self, chk_err=True):
+ dvd = self.get_dvd_device()
+ retcode = shellutil.run("cdio eject {0}".format(dvd))
+ if chk_err and retcode != 0:
+ raise OSUtilError("Failed to eject DVD: ret={0}".format(retcode))
+
+ def restart_if(self, ifname, retries=3, wait=5):
+ # Restart dhclient only to publish hostname
+ shellutil.run("/sbin/dhclient {0}".format(ifname), chk_err=False)
+
+ def get_total_mem(self):
+ ret, output = shellutil.run_get_output("sysctl -n hw.physmem")
+ if ret:
+ raise OSUtilError("Failed to get total memory: {0}".format(output))
+ try:
+ return int(output)/1024/1024
+ except ValueError:
+ raise OSUtilError("Failed to get total memory: {0}".format(output))
+
+ def get_processor_cores(self):
+ ret, output = shellutil.run_get_output("sysctl -n hw.ncpu")
+ if ret:
+ raise OSUtilError("Failed to get processor cores.")
+
+ try:
+ return int(output)
+ except ValueError:
+ raise OSUtilError("Failed to get total memory: {0}".format(output))
+
+ def set_scsi_disks_timeout(self, timeout):
+ pass
+
+ def check_pid_alive(self, pid):
+ if not pid:
+ return
+ return shellutil.run('ps -p {0}'.format(pid), chk_err=False) == 0
+
+ @staticmethod
+ def _get_net_info():
+ """
+ There is no SIOCGIFCONF
+ on OpenBSD - just parse ifconfig.
+ Returns strings: iface, inet4_addr, and mac
+ or 'None,None,None' if unable to parse.
+ We will sleep and retry as the network must be up.
+ """
+ iface = ''
+ inet = ''
+ mac = ''
+
+ ret, output = shellutil.run_get_output(
+ 'ifconfig hvn | grep -E "^hvn.:" | sed "s/:.*//g"', chk_err=False)
+ if ret:
+ raise OSUtilError("Can't find ether interface:{0}".format(output))
+ ifaces = output.split()
+ if not ifaces:
+ raise OSUtilError("Can't find ether interface.")
+ iface = ifaces[0]
+
+ ret, output = shellutil.run_get_output(
+ 'ifconfig ' + iface, chk_err=False)
+ if ret:
+ raise OSUtilError("Can't get info for interface:{0}".format(iface))
+
+ for line in output.split('\n'):
+ if line.find('inet ') != -1:
+ inet = line.split()[1]
+ elif line.find('lladdr ') != -1:
+ mac = line.split()[1]
+ logger.verbose("Interface info: ({0},{1},{2})", iface, inet, mac)
+
+ return iface, inet, mac
+
+ def device_for_ide_port(self, port_id):
+ """
+ Return device name attached to ide port 'n'.
+ """
+ return "wd{0}".format(port_id)
diff --git a/azurelinuxagent/common/protocol/hostplugin.py b/azurelinuxagent/common/protocol/hostplugin.py
index 464fd35..9af8a97 100644
--- a/azurelinuxagent/common/protocol/hostplugin.py
+++ b/azurelinuxagent/common/protocol/hostplugin.py
@@ -70,7 +70,7 @@ class HostPluginProtocol(object):
if not self.is_initialized:
self.api_versions = self.get_api_versions()
self.is_available = API_VERSION in self.api_versions
- self.is_initialized = True
+ self.is_initialized = self.is_available
from azurelinuxagent.common.event import WALAEventOperation, report_event
report_event(WALAEventOperation.InitializeHostPlugin,
is_success=self.is_available)
@@ -143,7 +143,9 @@ class HostPluginProtocol(object):
headers = {"x-ms-vmagentlog-deploymentid": self.deployment_id,
"x-ms-vmagentlog-containerid": self.container_id}
- logger.info("HostGAPlugin: Put VM log to [{0}]".format(url))
+ logger.periodic(
+ logger.EVERY_FIFTEEN_MINUTES,
+ "HostGAPlugin: Put VM log to [{0}]".format(url))
try:
response = restutil.http_put(url, content, headers)
if response.status != httpclient.OK:
@@ -175,7 +177,7 @@ class HostPluginProtocol(object):
self._put_page_blob_status(sas_url, status_blob)
if not HostPluginProtocol.is_default_channel():
- logger.info("HostGAPlugin: Setting host plugin as default channel")
+ logger.verbose("HostGAPlugin: Setting host plugin as default channel")
HostPluginProtocol.set_default_channel(True)
except Exception as e:
message = "HostGAPlugin: Exception Put VM status: {0}, {1}".format(e, traceback.format_exc())
@@ -288,12 +290,23 @@ class HostPluginProtocol(object):
@staticmethod
def read_response_error(response):
- if response is None:
- return ''
- body = remove_bom(response.read())
- if PY_VERSION_MAJOR < 3 and body is not None:
- body = ustr(body, encoding='utf-8')
- return "{0}, {1}, {2}".format(
- response.status,
- response.reason,
- body)
+ result = ''
+ if response is not None:
+ try:
+ body = remove_bom(response.read())
+ result = "[{0}: {1}] {2}".format(response.status,
+ response.reason,
+ body)
+
+ # this result string is passed upstream to several methods
+ # which do a raise HttpError() or a format() of some kind;
+ # as a result it cannot have any unicode characters
+ if PY_VERSION_MAJOR < 3:
+ result = ustr(result, encoding='ascii', errors='ignore')
+ else:
+ result = result\
+ .encode(encoding='ascii', errors='ignore')\
+ .decode(encoding='ascii', errors='ignore')
+ except Exception:
+ logger.warn(traceback.format_exc())
+ return result
diff --git a/azurelinuxagent/common/protocol/metadata.py b/azurelinuxagent/common/protocol/metadata.py
index c50b3dd..b0b6f67 100644
--- a/azurelinuxagent/common/protocol/metadata.py
+++ b/azurelinuxagent/common/protocol/metadata.py
@@ -113,7 +113,7 @@ class MetadataProtocol(Protocol):
except HttpError as e:
raise ProtocolError(ustr(e))
if resp.status != httpclient.CREATED:
- raise ProtocolError("{0} - POST: {1}".format(resp.status, url))
+ logger.warn("{0} for POST {1}".format(resp.status, url))
def _get_trans_cert(self):
trans_crt_file = os.path.join(conf.get_lib_dir(),
@@ -236,14 +236,14 @@ class MetadataProtocol(Protocol):
return ext_list, etag
def get_ext_handler_pkgs(self, ext_handler):
- logger.info("Get extension handler packages")
+ logger.verbose("Get extension handler packages")
pkg_list = ExtHandlerPackageList()
manifest = None
for version_uri in ext_handler.versionUris:
try:
manifest, etag = self._get_data(version_uri.uri)
- logger.info("Successfully downloaded manifest")
+ logger.verbose("Successfully downloaded manifest")
break
except ProtocolError as e:
logger.warn("Failed to fetch manifest: {0}", e)
diff --git a/azurelinuxagent/common/protocol/util.py b/azurelinuxagent/common/protocol/util.py
index 0ba03ec..bb3500a 100644
--- a/azurelinuxagent/common/protocol/util.py
+++ b/azurelinuxagent/common/protocol/util.py
@@ -162,12 +162,7 @@ class ProtocolUtil(object):
def _detect_metadata_protocol(self):
protocol = MetadataProtocol()
protocol.detect()
-
- # only allow root access METADATA_ENDPOINT
- self.osutil.set_admin_access_to_ip(METADATA_ENDPOINT)
-
self.save_protocol("MetadataProtocol")
-
return protocol
def _detect_protocol(self, protocols):
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 936be8c..d731e11 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -597,9 +597,9 @@ class WireClient(object):
Call storage service, handle SERVICE_UNAVAILABLE(503)
"""
- # force the chk_proxy arg to True, since all calls to storage should
- # use a configured proxy
- kwargs['chk_proxy'] = True
+ # Default to use the configured HTTP proxy
+ if not 'chk_proxy' in kwargs or kwargs['chk_proxy'] is None:
+ kwargs['chk_proxy'] = True
for retry in range(0, 3):
resp = http_req(*args, **kwargs)
@@ -626,7 +626,7 @@ class WireClient(object):
logger.verbose("Manifest could not be downloaded, falling back to host plugin")
host = self.get_host_plugin()
uri, headers = host.get_artifact_request(version.uri)
- response = self.fetch(uri, headers)
+ response = self.fetch(uri, headers, chk_proxy=False)
if not response:
host = self.get_host_plugin(force_update=True)
logger.info("Retry fetch in {0} seconds",
@@ -642,14 +642,15 @@ class WireClient(object):
return response
raise ProtocolError("Failed to fetch manifest from all sources")
- def fetch(self, uri, headers=None):
+ def fetch(self, uri, headers=None, chk_proxy=None):
logger.verbose("Fetch [{0}] with headers [{1}]", uri, headers)
return_value = None
try:
resp = self.call_storage_service(
restutil.http_get,
uri,
- headers)
+ headers,
+ chk_proxy=chk_proxy)
if resp.status == httpclient.OK:
return_value = self.decode_config(resp.read())
else:
@@ -831,7 +832,7 @@ class WireClient(object):
if not blob_type in ["BlockBlob", "PageBlob"]:
blob_type = "BlockBlob"
- logger.info("Status Blob type is unspecified "
+ logger.verbose("Status Blob type is unspecified "
"-- assuming it is a BlockBlob")
try:
@@ -998,17 +999,17 @@ class WireClient(object):
artifacts_profile = None
if self.has_artifacts_profile_blob():
blob = self.ext_conf.artifacts_profile_blob
- logger.info("Getting the artifacts profile")
+ logger.verbose("Getting the artifacts profile")
profile = self.fetch(blob)
if profile is None:
logger.warn("Download failed, falling back to host plugin")
host = self.get_host_plugin()
uri, headers = host.get_artifact_request(blob)
- profile = self.decode_config(self.fetch(uri, headers))
+ profile = self.decode_config(self.fetch(uri, headers, chk_proxy=False))
if not textutil.is_str_none_or_whitespace(profile):
- logger.info("Artifacts profile downloaded successfully")
+ logger.verbose("Artifacts profile downloaded successfully")
artifacts_profile = InVMArtifactsProfile(profile)
return artifacts_profile
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index dc3592b..d1d4c62 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -77,6 +77,9 @@ def get_distro():
if 'FreeBSD' in platform.system():
release = re.sub('\-.*\Z', '', ustr(platform.release()))
osinfo = ['freebsd', release, '', 'freebsd']
+ elif 'OpenBSD' in platform.system():
+ release = re.sub('\-.*\Z', '', ustr(platform.release()))
+ osinfo = ['openbsd', release, '', 'openbsd']
elif 'linux_distribution' in dir(platform):
supported = platform._supported_dists + ('alpine',)
osinfo = list(platform.linux_distribution(full_distribution_name=0,
@@ -110,7 +113,7 @@ def get_distro():
AGENT_NAME = "WALinuxAgent"
AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.2.12'
+AGENT_VERSION = '2.2.14'
AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
AGENT_DESCRIPTION = """
The Azure Linux Agent supports the provisioning and running of Linux
@@ -160,7 +163,10 @@ 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()]
+ if os.path.isdir("/proc"):
+ pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
+ else:
+ pids = []
for pid in pids:
try:
pname = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read()
diff --git a/azurelinuxagent/daemon/main.py b/azurelinuxagent/daemon/main.py
index 5b8db2f..e8dbb37 100644
--- a/azurelinuxagent/daemon/main.py
+++ b/azurelinuxagent/daemon/main.py
@@ -79,7 +79,7 @@ class DaemonHandler(object):
err_msg = traceback.format_exc()
add_event(name=AGENT_NAME, is_success=False, message=ustr(err_msg),
op=WALAEventOperation.UnhandledError)
- logger.info("Sleep 15 seconds and restart daemon")
+ logger.warn("Daemon ended with exception -- Sleep 15 seconds and restart daemon")
time.sleep(15)
def check_pid(self):
diff --git a/azurelinuxagent/daemon/resourcedisk/factory.py b/azurelinuxagent/daemon/resourcedisk/factory.py
index 76e5a23..41a0cba 100644
--- a/azurelinuxagent/daemon/resourcedisk/factory.py
+++ b/azurelinuxagent/daemon/resourcedisk/factory.py
@@ -22,6 +22,7 @@ from azurelinuxagent.common.version import DISTRO_NAME, \
DISTRO_FULL_NAME
from .default import ResourceDiskHandler
from .freebsd import FreeBSDResourceDiskHandler
+from .openbsd import OpenBSDResourceDiskHandler
def get_resourcedisk_handler(distro_name=DISTRO_NAME,
distro_version=DISTRO_VERSION,
@@ -29,5 +30,8 @@ def get_resourcedisk_handler(distro_name=DISTRO_NAME,
if distro_name == "freebsd":
return FreeBSDResourceDiskHandler()
+ if distro_name == "openbsd":
+ return OpenBSDResourceDiskHandler()
+
return ResourceDiskHandler()
diff --git a/azurelinuxagent/daemon/resourcedisk/freebsd.py b/azurelinuxagent/daemon/resourcedisk/freebsd.py
index e43d9c4..35ae06b 100644
--- a/azurelinuxagent/daemon/resourcedisk/freebsd.py
+++ b/azurelinuxagent/daemon/resourcedisk/freebsd.py
@@ -59,7 +59,7 @@ class FreeBSDResourceDiskHandler(ResourceDiskHandler):
disks = self.parse_gpart_list(output)
device = self.osutil.device_for_ide_port(1)
- if device is None:
+ if device is None or not device in disks:
# fallback logic to find device
err, output = shellutil.run_get_output('camcontrol periphlist 2:1:0')
if err:
diff --git a/azurelinuxagent/daemon/resourcedisk/openbsd.py b/azurelinuxagent/daemon/resourcedisk/openbsd.py
new file mode 100644
index 0000000..1454f6f
--- /dev/null
+++ b/azurelinuxagent/daemon/resourcedisk/openbsd.py
@@ -0,0 +1,113 @@
+# Microsoft Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+# Copyright 2017 Reyk Floeter <reyk@openbsd.org>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and OpenSSL 1.0+
+#
+import azurelinuxagent.common.logger as logger
+import azurelinuxagent.common.utils.fileutil as fileutil
+import azurelinuxagent.common.utils.shellutil as shellutil
+import azurelinuxagent.common.conf as conf
+from azurelinuxagent.common.exception import ResourceDiskError
+from azurelinuxagent.daemon.resourcedisk.default import ResourceDiskHandler
+
+class OpenBSDResourceDiskHandler(ResourceDiskHandler):
+ def __init__(self):
+ super(OpenBSDResourceDiskHandler, self).__init__()
+ # Fase File System (FFS) is UFS
+ if self.fs == 'ufs' or self.fs == 'ufs2':
+ self.fs = 'ffs'
+
+ def create_swap_space(self, mount_point, size_mb):
+ pass
+
+ def enable_swap(self, mount_point):
+ size_mb = conf.get_resourcedisk_swap_size_mb()
+ if size_mb:
+ logger.info("Enable swap")
+ device = self.osutil.device_for_ide_port(1)
+ err, output = shellutil.run_get_output("swapctl -a /dev/"
+ "{0}b".format(device),
+ chk_err=False)
+ if err:
+ logger.error("Failed to enable swap, error {0}", output)
+
+ def mount_resource_disk(self, mount_point):
+ fs = self.fs
+ if fs != 'ffs':
+ raise ResourceDiskError("Unsupported filesystem type: {0}, only "
+ "ufs/ffs is supported.".format(fs))
+
+ # 1. Get device
+ device = self.osutil.device_for_ide_port(1)
+
+ if not device:
+ raise ResourceDiskError("Unable to detect resource disk device.")
+ logger.info('Resource disk device {0} found.', device)
+
+ # 2. Get partition
+ partition = "/dev/{0}a".format(device)
+
+ # 3. Mount partition
+ mount_list = shellutil.run_get_output("mount")[1]
+ existing = self.osutil.get_mount_point(mount_list, partition)
+
+ if existing:
+ logger.info("Resource disk {0} is already mounted", partition)
+ return existing
+
+ fileutil.mkdir(mount_point, mode=0o755)
+ mount_cmd = 'mount -t {0} {1} {2}'.format(self.fs,
+ partition, mount_point)
+ err = shellutil.run(mount_cmd, chk_err=False)
+ if err:
+ logger.info('Creating {0} filesystem on {1}'.format(fs, device))
+
+ fdisk_cmd = "/sbin/fdisk -yi {0}".format(device)
+ err, output = shellutil.run_get_output(fdisk_cmd, chk_err=False)
+ if err:
+ raise ResourceDiskError("Failed to create new MBR on {0}, "
+ "error: {1}".format(device, output))
+
+ size_mb = conf.get_resourcedisk_swap_size_mb()
+ if size_mb:
+ if size_mb > 512 * 1024:
+ size_mb = 512 * 1024
+ disklabel_cmd = ("echo -e '{0} 1G-* 50%\nswap 1-{1}M 50%' "
+ "| disklabel -w -A -T /dev/stdin "
+ "{2}").format(mount_point, size_mb, device)
+ ret, output = shellutil.run_get_output(
+ disklabel_cmd, chk_err=False)
+ if ret:
+ raise ResourceDiskError("Failed to create new disklabel "
+ "on {0}, error "
+ "{1}".format(device, output))
+
+ err, output = shellutil.run_get_output("newfs -O2 {0}a"
+ "".format(device))
+ if err:
+ raise ResourceDiskError("Failed to create new filesystem on "
+ "partition {0}, error "
+ "{1}".format(partition, output))
+
+ err, output = shellutil.run_get_output(mount_cmd, chk_err=False)
+ if err:
+ raise ResourceDiskError("Failed to mount partition {0}, "
+ "error {1}".format(partition, output))
+
+ logger.info("Resource disk partition {0} is mounted at {1} with fstype "
+ "{2}", partition, mount_point, fs)
+ return mount_point
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index b44ed6d..4324d92 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -781,7 +781,6 @@ class ExtHandlerInstance(object):
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):
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 67eb785..10eac82 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -38,7 +38,7 @@ import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.restutil as restutil
import azurelinuxagent.common.utils.textutil as textutil
-from azurelinuxagent.common.event import add_event, \
+from azurelinuxagent.common.event import add_event, add_periodic, \
elapsed_milliseconds, \
WALAEventOperation
from azurelinuxagent.common.exception import UpdateError, ProtocolError
@@ -46,6 +46,7 @@ from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.osutil import get_osutil
from azurelinuxagent.common.protocol import get_protocol_util
from azurelinuxagent.common.protocol.hostplugin import HostPluginProtocol
+from azurelinuxagent.common.protocol.wire import WireProtocol
from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
from azurelinuxagent.common.version import AGENT_NAME, AGENT_VERSION, AGENT_LONG_VERSION, \
AGENT_DIR_GLOB, AGENT_PKG_GLOB, \
@@ -67,7 +68,6 @@ CHILD_POLL_INTERVAL = 60
MAX_FAILURE = 3 # Max failure allowed for agent before blacklisted
GOAL_STATE_INTERVAL = 3
-REPORT_STATUS_INTERVAL = 15
ORPHAN_WAIT_INTERVAL = 15 * 60 * 60
@@ -207,19 +207,21 @@ class UpdateHandler(object):
latest_agent.mark_failure(is_fatal=True)
except Exception as e:
- msg = u"Agent {0} launched with command '{1}' failed with exception: {2}".format(
- agent_name,
- agent_cmd,
- ustr(e))
- logger.warn(msg)
- add_event(
- AGENT_NAME,
- version=agent_version,
- op=WALAEventOperation.Enable,
- is_success=False,
- message=msg)
- if latest_agent is not None:
- latest_agent.mark_failure(is_fatal=True)
+ # Ignore child errors during termination
+ if self.running:
+ msg = u"Agent {0} launched with command '{1}' failed with exception: {2}".format(
+ agent_name,
+ agent_cmd,
+ ustr(e))
+ logger.warn(msg)
+ add_event(
+ AGENT_NAME,
+ version=agent_version,
+ op=WALAEventOperation.Enable,
+ is_success=False,
+ message=msg)
+ if latest_agent is not None:
+ latest_agent.mark_failure(is_fatal=True)
self.child_process = None
return
@@ -266,17 +268,14 @@ class UpdateHandler(object):
last_etag = exthandlers_handler.last_etag
exthandlers_handler.run()
- log_event = last_etag != exthandlers_handler.last_etag or \
- (datetime.utcnow() >= send_event_time)
- add_event(
- AGENT_NAME,
- version=CURRENT_VERSION,
- op=WALAEventOperation.ProcessGoalState,
- is_success=True,
- duration=elapsed_milliseconds(utc_start),
- log_event=log_event)
- if log_event:
- send_event_time += timedelta(minutes=REPORT_STATUS_INTERVAL)
+ if last_etag != exthandlers_handler.last_etag:
+ add_event(
+ AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.ProcessGoalState,
+ is_success=True,
+ duration=elapsed_milliseconds(utc_start),
+ log_event=True)
test_agent = self.get_test_agent()
if test_agent is not None and test_agent.in_slice:
@@ -423,10 +422,7 @@ class UpdateHandler(object):
# The code leaves on disk available, but blacklisted, agents so as to
# preserve the state. Otherwise, those agents could be again
# downloaded and inappropriately retried.
- host = None
- if protocol and protocol.client:
- host = protocol.client.get_host_plugin()
-
+ host = self._get_host_plugin(protocol=protocol)
self._set_agents([GuestAgent(pkg=pkg, host=host) for pkg in pkg_list.versions])
self._purge_agents()
self._filter_blacklisted_agents()
@@ -505,6 +501,13 @@ class UpdateHandler(object):
logger.warn(u"Exception occurred loading available agents: {0}", ustr(e))
return
+ def _get_host_plugin(self, protocol=None):
+ return protocol.client.get_host_plugin() \
+ if protocol and \
+ type(protocol) is WireProtocol and \
+ protocol.client \
+ else None
+
def _get_pid_files(self):
pid_file = conf.get_agent_pid_file_path()
@@ -602,6 +605,8 @@ class UpdateHandler(object):
return os.path.join(conf.get_lib_dir(), AGENT_SENTINAL_FILE)
def _shutdown(self):
+ self.running = False
+
if not os.path.isfile(self._sentinal_file_path()):
return
@@ -647,7 +652,7 @@ class GuestAgent(object):
self.version = FlexibleVersion(version)
location = u"disk" if path is not None else u"package"
- logger.verbose(u"Instantiating Agent {0} from {1}", self.name, location)
+ logger.verbose(u"Loading Agent {0} from package {1}", self.name, location)
self.error = None
self.supported = None
@@ -716,7 +721,7 @@ class GuestAgent(object):
os.makedirs(self.get_agent_dir())
self.error.mark_failure(is_fatal=is_fatal)
self.error.save()
- if is_fatal:
+ if self.error.is_blacklisted:
logger.warn(u"Agent {0} is permanently blacklisted", self.name)
except Exception as e:
logger.warn(u"Agent {0} failed recording error state: {1}", self.name, ustr(e))
@@ -727,7 +732,7 @@ class GuestAgent(object):
logger.verbose(u"Ensuring Agent {0} is downloaded", self.name)
if self.is_blacklisted:
- logger.info(u"Agent {0} is blacklisted - skipping download", self.name)
+ logger.verbose(u"Agent {0} is blacklisted - skipping download", self.name)
return
if self.is_downloaded:
diff --git a/azurelinuxagent/pa/deprovision/default.py b/azurelinuxagent/pa/deprovision/default.py
index 90d16c7..e2c5613 100644
--- a/azurelinuxagent/pa/deprovision/default.py
+++ b/azurelinuxagent/pa/deprovision/default.py
@@ -27,10 +27,15 @@ import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.shellutil as shellutil
from azurelinuxagent.common.exception import ProtocolError
-from azurelinuxagent.common.future import read_input
from azurelinuxagent.common.osutil import get_osutil
from azurelinuxagent.common.protocol import get_protocol_util
+def read_input(message):
+ if sys.version_info[0] >= 3:
+ return input(message)
+ else:
+ return raw_input(message)
+
class DeprovisionAction(object):
def __init__(self, func, args=[], kwargs={}):
self.func = func
@@ -86,6 +91,15 @@ class DeprovisionHandler(object):
files = ['/root/.bash_history', '/var/log/waagent.log']
actions.append(DeprovisionAction(fileutil.rm_files, files))
+ # For OpenBSD
+ actions.append(DeprovisionAction(fileutil.rm_files,
+ ["/etc/random.seed",
+ "/var/db/host.random",
+ "/etc/isakmpd/local.pub",
+ "/etc/isakmpd/private/local.key",
+ "/etc/iked/private/local.key",
+ "/etc/iked/local.pub"]))
+
def del_resolv(self, warnings, actions):
warnings.append("WARNING! /etc/resolv.conf will be deleted.")
files_to_del = ["/etc/resolv.conf"]
@@ -96,9 +110,13 @@ class DeprovisionHandler(object):
dirs_to_del = ["/var/lib/dhclient", "/var/lib/dhcpcd", "/var/lib/dhcp"]
actions.append(DeprovisionAction(fileutil.rm_dirs, dirs_to_del))
- # For Freebsd, NM controlled
- actions.append(DeprovisionAction(fileutil.rm_files, ["/var/db/dhclient.leases.hn0",
- "/var/lib/NetworkManager/dhclient-*.lease"]))
+ # For FreeBSD and OpenBSD
+ actions.append(DeprovisionAction(fileutil.rm_files,
+ ["/var/db/dhclient.leases.*"]))
+
+ # For FreeBSD, NM controlled
+ actions.append(DeprovisionAction(fileutil.rm_files,
+ ["/var/lib/NetworkManager/dhclient-*.lease"]))
def del_lib_dir_files(self, warnings, actions):
@@ -137,23 +155,28 @@ class DeprovisionHandler(object):
]
return dirs
- def cloud_init_files(self, include_once=True):
- files = [
- "/etc/sudoers.d/90-cloud-init-users"
- ]
+ def cloud_init_files(self, include_once=True, deluser=False):
+ files = []
+ if deluser:
+ files += [
+ "/etc/sudoers.d/90-cloud-init-users"
+ ]
if include_once:
files += [
"/var/lib/cloud/sem/config_scripts_per_once.once"
]
return files
- def del_cloud_init(self, warnings, actions, include_once=True):
+ def del_cloud_init(self, warnings, actions,
+ include_once=True, deluser=False):
dirs = [d for d in self.cloud_init_dirs(include_once=include_once) \
if os.path.isdir(d)]
if len(dirs) > 0:
actions.append(DeprovisionAction(fileutil.rm_dirs, dirs))
- files = [f for f in self.cloud_init_files(include_once=include_once) \
+ files = [f for f in self.cloud_init_files(
+ include_once=include_once,
+ deluser=deluser) \
if os.path.isfile(f)]
if len(files) > 0:
actions.append(DeprovisionAction(fileutil.rm_files, files))
@@ -179,7 +202,7 @@ class DeprovisionHandler(object):
if conf.get_delete_root_password():
self.del_root_password(warnings, actions)
- self.del_cloud_init(warnings, actions)
+ self.del_cloud_init(warnings, actions, deluser=deluser)
self.del_dirs(warnings, actions)
self.del_files(warnings, actions)
self.del_resolv(warnings, actions)
@@ -193,10 +216,10 @@ class DeprovisionHandler(object):
warnings = []
actions = []
- self.del_cloud_init(warnings, actions, include_once=False)
+ self.del_cloud_init(warnings, actions,
+ include_once=False, deluser=False)
self.del_dhcp_lease(warnings, actions)
self.del_lib_dir_files(warnings, actions)
- self.del_resolv(warnings, actions)
return warnings, actions
@@ -204,8 +227,8 @@ class DeprovisionHandler(object):
warnings, actions = self.setup(deluser)
self.do_warnings(warnings)
- self.do_confirmation(force=force)
- self.do_actions(actions)
+ if self.do_confirmation(force=force):
+ self.do_actions(actions)
def run_changed_unique_id(self):
'''
@@ -246,5 +269,3 @@ class DeprovisionHandler(object):
print ('Deprovisioning may not be interrupted.')
return
-
-
diff --git a/azurelinuxagent/pa/provision/default.py b/azurelinuxagent/pa/provision/default.py
index d4870f1..959a2fe 100644
--- a/azurelinuxagent/pa/provision/default.py
+++ b/azurelinuxagent/pa/provision/default.py
@@ -103,8 +103,11 @@ class ProvisionHandler(object):
@staticmethod
def validate_cloud_init(is_expected=True):
- pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
is_running = False
+ if os.path.isdir("/proc"):
+ pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
+ else:
+ pids = []
for pid in pids:
try:
pname = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read()
diff --git a/config/gaia/waagent.conf b/config/gaia/waagent.conf
index 43ad35d..75550a6 100644
--- a/config/gaia/waagent.conf
+++ b/config/gaia/waagent.conf
@@ -55,7 +55,7 @@ ResourceDisk.SwapSizeMB=1024
ResourceDisk.MountOptions=None
# Enable verbose logging (y|n)
-Logs.Verbose=y
+Logs.Verbose=n
# Is FIPS enabled
OS.EnableFIPS=n
diff --git a/config/openbsd/waagent.conf b/config/openbsd/waagent.conf
new file mode 100644
index 0000000..09e7db7
--- /dev/null
+++ b/config/openbsd/waagent.conf
@@ -0,0 +1,105 @@
+#
+# Microsoft Azure Linux Agent Configuration
+#
+
+# Enable instance creation
+Provisioning.Enabled=y
+
+# Rely on cloud-init to provision
+Provisioning.UseCloudInit=n
+
+# Password authentication for root account will be unavailable.
+Provisioning.DeleteRootPassword=y
+
+# Generate fresh host key pair.
+Provisioning.RegenerateSshHostKeyPair=y
+
+# Supported values are "rsa", "dsa", "ecdsa", and "ed25519".
+Provisioning.SshHostKeyPairType=ed25519
+
+# Monitor host name changes and publish changes via DHCP requests.
+Provisioning.MonitorHostName=y
+
+# Decode CustomData from Base64.
+Provisioning.DecodeCustomData=n
+
+# Execute CustomData after provisioning.
+Provisioning.ExecuteCustomData=n
+
+# Algorithm used by crypt when generating password hash.
+#Provisioning.PasswordCryptId=6
+
+# Length of random salt used when generating password hash.
+#Provisioning.PasswordCryptSaltLength=10
+
+# Format if unformatted. If 'n', resource disk will not be mounted.
+ResourceDisk.Format=y
+
+# File system on the resource disk
+# Typically ext3 or ext4. OpenBSD images should use 'ufs2' here.
+ResourceDisk.Filesystem=ufs2
+
+# Mount point for the resource disk
+ResourceDisk.MountPoint=/mnt/resource
+
+# Create and use swapfile on resource disk.
+ResourceDisk.EnableSwap=y
+
+# Max size of the swap partition in MB
+ResourceDisk.SwapSizeMB=65536
+
+# Comma-seperated list of mount options. See man(8) for valid options.
+ResourceDisk.MountOptions=None
+
+# Enable verbose logging (y|n)
+Logs.Verbose=n
+
+# Is FIPS enabled
+OS.EnableFIPS=n
+
+# Root device timeout in seconds.
+OS.RootDeviceScsiTimeout=300
+
+# If "None", the system default version is used.
+OS.OpensslPath=/usr/local/bin/eopenssl
+
+# Set the path to SSH keys and configuration files
+OS.SshDir=/etc/ssh
+
+OS.PasswordPath=/etc/master.passwd
+
+# If set, agent will use proxy server to access internet
+#HttpProxy.Host=None
+#HttpProxy.Port=None
+
+# Detect Scvmm environment, default is n
+# DetectScvmmEnv=n
+
+#
+# Lib.Dir=/var/lib/waagent
+
+#
+# DVD.MountPoint=/mnt/cdrom/secure
+
+#
+# Pid.File=/var/run/waagent.pid
+
+#
+# Extension.LogDir=/var/log/azure
+
+#
+# Home.Dir=/home
+
+# Enable RDMA management and set up, should only be used in HPC images
+# OS.EnableRDMA=y
+
+# Enable or disable goal state processing auto-update, default is enabled
+# AutoUpdate.Enabled=y
+
+# Determine the update family, this should not be changed
+# AutoUpdate.GAFamily=Prod
+
+# Determine if the overprovisioning feature is enabled. If yes, hold extension
+# handling until inVMArtifactsProfile.OnHold is false.
+# Default is disabled
+# EnableOverProvisioning=n
diff --git a/debian/changelog b/debian/changelog
index 0d09768..453beee 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+walinuxagent (2.2.14-0ubuntu1) artful; urgency=medium
+
+ * New upstream release (LP: #1701350).
+ * debian/copyright:
+ - Refreshed copyright content.
+
+ -- Ɓukasz 'sil2100' Zemczak <lukasz.zemczak@ubuntu.com> Mon, 03 Jul 2017 13:44:00 +0200
+
walinuxagent (2.2.12-0ubuntu1) artful; urgency=medium
* New upstream release (LP: #1690854).
diff --git a/debian/copyright b/debian/copyright
index fba1f44..1447fe2 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -4,16 +4,11 @@ Upstream-Contact: Microsoft Corporation <walinuxagent@microsoft.com>
Source: https://github.com/Windows-Azure/WALinuxAgent/
Files: *
-Copyright: 2012, Microsoft Corporation <walinuxagent@microsoft.com>
-License: Apache-2.0
-
-Files: waaagent
-Copyright: 2012, Microsoft Corporation <walinuxagent@microsoft.com>
- 2012, Ben Howard <ben.howard@canonical.com>
+Copyright: 2012-2017, Microsoft Corporation <walinuxagent@microsoft.com>
License: Apache-2.0
Files: debian/*
-Copyright: 2012, Ben Howard <ben.howard@canonical.com>
+Copyright: 2012-2017, Canonical Group, Ltd
License: Apache-2.0
License: Apache-2.0
diff --git a/init/openbsd/waagent b/init/openbsd/waagent
new file mode 100644
index 0000000..43f8948
--- /dev/null
+++ b/init/openbsd/waagent
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+daemon="python2.7 /usr/local/sbin/waagent -start"
+
+. /etc/rc.d/rc.subr
+
+pexp="python /usr/local/sbin/waagent -daemon"
+rc_reload=NO
+
+rc_cmd $1
diff --git a/setup.py b/setup.py
index fb53be7..094abb5 100755
--- a/setup.py
+++ b/setup.py
@@ -58,7 +58,11 @@ def set_systemd_files(data_files, dest="/lib/systemd/system",
data_files.append((dest, src))
-def set_rc_files(data_files, dest="/etc/rc.d/", src=["init/freebsd/waagent"]):
+def set_freebsd_rc_files(data_files, dest="/etc/rc.d/", src=["init/freebsd/waagent"]):
+ data_files.append((dest, src))
+
+
+def set_openbsd_rc_files(data_files, dest="/etc/rc.d/", src=["init/openbsd/waagent"]):
data_files.append((dest, src))
@@ -143,7 +147,11 @@ def get_data_files(name, version, fullname):
elif name == 'freebsd':
set_bin_files(data_files, dest="/usr/local/sbin")
set_conf_files(data_files, src=["config/freebsd/waagent.conf"])
- set_rc_files(data_files)
+ set_freebsd_rc_files(data_files)
+ elif name == 'openbsd':
+ set_bin_files(data_files, dest="/usr/local/sbin")
+ set_conf_files(data_files, src=["config/openbsd/waagent.conf"])
+ set_openbsd_rc_files(data_files)
else:
# Use default setting
set_bin_files(data_files)
diff --git a/tests/common/osutil/test_bigip.py b/tests/common/osutil/test_bigip.py
index 4d1b006..f5958cf 100644
--- a/tests/common/osutil/test_bigip.py
+++ b/tests/common/osutil/test_bigip.py
@@ -282,35 +282,6 @@ class TestBigIpOSUtil_mount_dvd(AgentTestCase):
self.assertEqual(args[1].call_count, 1)
-class TestBigIpOSUtil_set_admin_access_to_ip(AgentTestCase):
-
- @patch.object(shellutil, "run", return_value=0)
- @patch.object(osutil.BigIpOSUtil,
- '_set_accept_admin_access_to_ip', return_value=None)
- @patch.object(osutil.BigIpOSUtil,
- '_set_drop_admin_access_to_ip', return_value=None)
- def test_success(self, *args):
- osutil.BigIpOSUtil.set_admin_access_to_ip(
- osutil.BigIpOSUtil(), '192.168.10.10'
- )
- self.assertEqual(args[0].call_count, 1)
- self.assertEqual(args[1].call_count, 1)
-
- @patch.object(shellutil, "run", return_value=0)
- def test_accept_access(self, *args):
- osutil.BigIpOSUtil._set_accept_admin_access_to_ip(
- osutil.BigIpOSUtil(), '192.168.10.10'
- )
- self.assertEqual(args[0].call_count, 2)
-
- @patch.object(shellutil, "run", return_value=0)
- def test_drop_access(self, *args):
- osutil.BigIpOSUtil._set_drop_admin_access_to_ip(
- osutil.BigIpOSUtil(), '192.168.10.10'
- )
- self.assertEqual(args[0].call_count, 2)
-
-
class TestBigIpOSUtil_route_add(AgentTestCase):
@patch.object(shellutil, "run", return_value=0)
diff --git a/tests/common/test_event.py b/tests/common/test_event.py
index f535411..a485edf 100644
--- a/tests/common/test_event.py
+++ b/tests/common/test_event.py
@@ -17,12 +17,78 @@
from __future__ import print_function
+from datetime import datetime
+
+import azurelinuxagent.common.event as event
+import azurelinuxagent.common.logger as logger
+
from azurelinuxagent.common.event import init_event_logger, add_event
from azurelinuxagent.common.future import ustr
+from azurelinuxagent.common.version import CURRENT_VERSION
+
from tests.tools import *
class TestEvent(AgentTestCase):
+
+ @patch('azurelinuxagent.common.event.EventLogger.add_event')
+ def test_periodic_emits_if_not_previously_sent(self, mock_event):
+ init_event_logger(tempfile.mkdtemp())
+ event.__event_logger__.reset_periodic()
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ mock_event.assert_called_once()
+
+ @patch('azurelinuxagent.common.event.EventLogger.add_event')
+ def test_periodic_does_not_emit_if_previously_sent(self, mock_event):
+ init_event_logger(tempfile.mkdtemp())
+ event.__event_logger__.reset_periodic()
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ self.assertEqual(1, mock_event.call_count)
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ self.assertEqual(1, mock_event.call_count)
+
+ @patch('azurelinuxagent.common.event.EventLogger.add_event')
+ def test_periodic_emits_if_forced(self, mock_event):
+ init_event_logger(tempfile.mkdtemp())
+ event.__event_logger__.reset_periodic()
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ self.assertEqual(1, mock_event.call_count)
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent", force=True)
+ self.assertEqual(2, mock_event.call_count)
+
+ @patch('azurelinuxagent.common.event.EventLogger.add_event')
+ def test_periodic_emits_after_elapsed_delta(self, mock_event):
+ init_event_logger(tempfile.mkdtemp())
+ event.__event_logger__.reset_periodic()
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ self.assertEqual(1, mock_event.call_count)
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ self.assertEqual(1, mock_event.call_count)
+
+ h = hash("FauxEvent"+""+ustr(True)+"")
+ event.__event_logger__.periodic_messages[h] = \
+ datetime.now() - logger.EVERY_DAY - logger.EVERY_HOUR
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ self.assertEqual(2, mock_event.call_count)
+
+ @patch('azurelinuxagent.common.event.EventLogger.add_event')
+ def test_periodic_forwards_args(self, mock_event):
+ init_event_logger(tempfile.mkdtemp())
+ event.__event_logger__.reset_periodic()
+
+ event.add_periodic(logger.EVERY_DAY, "FauxEvent")
+ mock_event.assert_called_once_with(
+ "FauxEvent",
+ duration=0, evt_type='', is_internal=False, is_success=True,
+ log_event=True, message='', op='', version=str(CURRENT_VERSION))
+
def test_save_event(self):
tmp_evt = tempfile.mkdtemp()
init_event_logger(tmp_evt)
diff --git a/tests/common/test_logger.py b/tests/common/test_logger.py
new file mode 100644
index 0000000..9e298b3
--- /dev/null
+++ b/tests/common/test_logger.py
@@ -0,0 +1,66 @@
+# Copyright 2016 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 datetime import datetime
+
+import azurelinuxagent.common.logger as logger
+
+from tests.tools import *
+
+_MSG = "This is our test logging message {0} {1}"
+_DATA = ["arg1", "arg2"]
+
+class TestLogger(AgentTestCase):
+
+ @patch('azurelinuxagent.common.logger.Logger.info')
+ def test_periodic_emits_if_not_previously_sent(self, mock_info):
+ logger.reset_periodic()
+
+ logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+ mock_info.assert_called_once()
+
+ @patch('azurelinuxagent.common.logger.Logger.info')
+ def test_periodic_does_not_emit_if_previously_sent(self, mock_info):
+ logger.reset_periodic()
+
+ logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+ self.assertEqual(1, mock_info.call_count)
+
+ logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+ self.assertEqual(1, mock_info.call_count)
+
+ @patch('azurelinuxagent.common.logger.Logger.info')
+ def test_periodic_emits_after_elapsed_delta(self, mock_info):
+ logger.reset_periodic()
+
+ logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+ self.assertEqual(1, mock_info.call_count)
+
+ logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+ self.assertEqual(1, mock_info.call_count)
+
+ logger.DEFAULT_LOGGER.periodic_messages[hash(_MSG)] = \
+ datetime.now() - logger.EVERY_DAY - logger.EVERY_HOUR
+ logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+ self.assertEqual(2, mock_info.call_count)
+
+ @patch('azurelinuxagent.common.logger.Logger.info')
+ def test_periodic_forwards_message_and_args(self, mock_info):
+ logger.reset_periodic()
+
+ logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
+ mock_info.assert_called_once_with(_MSG, *_DATA)
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index a83db95..0c8642c 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -23,6 +23,7 @@ import json
import shutil
from azurelinuxagent.common.protocol.hostplugin import *
+from azurelinuxagent.common.protocol.metadata import *
from azurelinuxagent.common.protocol.wire import *
from azurelinuxagent.common.utils.fileutil import *
from azurelinuxagent.ga.update import *
@@ -1019,6 +1020,21 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(kept_agents, self.update_handler.agents)
return
+ @patch('azurelinuxagent.common.protocol.wire.WireClient.get_host_plugin')
+ def test_get_host_plugin_returns_host_for_wireserver(self, mock_get_host):
+ protocol = WireProtocol('12.34.56.78')
+ mock_get_host.return_value = "faux host"
+ host = self.update_handler._get_host_plugin(protocol=protocol)
+ mock_get_host.assert_called_once()
+ self.assertEqual("faux host", host)
+
+ @patch('azurelinuxagent.common.protocol.wire.WireClient.get_host_plugin')
+ def test_get_host_plugin_returns_none_otherwise(self, mock_get_host):
+ protocol = MetadataProtocol()
+ host = self.update_handler._get_host_plugin(protocol=protocol)
+ mock_get_host.assert_not_called()
+ self.assertEqual(None, host)
+
def test_get_latest_agent(self):
latest_version = self.prepare_agents()
@@ -1324,6 +1340,24 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(1, latest_agent.error.failure_count)
return
+ def test_run_latest_exception_does_not_blacklist_if_terminating(self):
+ self.prepare_agents()
+
+ latest_agent = self.update_handler.get_latest_agent()
+ self.assertTrue(latest_agent.is_available)
+ self.assertEqual(0.0, latest_agent.error.last_failure)
+ self.assertEqual(0, latest_agent.error.failure_count)
+
+ with patch('azurelinuxagent.ga.update.UpdateHandler.get_latest_agent', return_value=latest_agent):
+ self.update_handler.running = False
+ self._test_run_latest(mock_child=ChildMock(side_effect=Exception("Attempt blacklisting")))
+
+ self.assertTrue(latest_agent.is_available)
+ self.assertFalse(latest_agent.error.is_blacklisted)
+ self.assertEqual(0.0, latest_agent.error.last_failure)
+ self.assertEqual(0, latest_agent.error.failure_count)
+ return
+
@patch('signal.signal')
def test_run_latest_captures_signals(self, mock_signal):
self._test_run_latest()
@@ -1462,12 +1496,14 @@ class TestUpdate(UpdateTestCase):
def test_shutdown(self):
self.update_handler._set_sentinal()
self.update_handler._shutdown()
+ self.assertFalse(self.update_handler.running)
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
return
def test_shutdown_ignores_missing_sentinal_file(self):
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
self.update_handler._shutdown()
+ self.assertFalse(self.update_handler.running)
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
return
diff --git a/tests/pa/test_deprovision.py b/tests/pa/test_deprovision.py
index c4cd9b4..b2f7f0c 100644
--- a/tests/pa/test_deprovision.py
+++ b/tests/pa/test_deprovision.py
@@ -15,6 +15,7 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import signal
import tempfile
import azurelinuxagent.common.utils.fileutil as fileutil
@@ -25,16 +26,44 @@ from tests.tools import *
class TestDeprovision(AgentTestCase):
+ @patch('signal.signal')
+ @patch('azurelinuxagent.common.osutil.get_osutil')
+ @patch('azurelinuxagent.common.protocol.get_protocol_util')
+ @patch('azurelinuxagent.pa.deprovision.default.read_input')
+ def test_confirmation(self,
+ mock_read, mock_protocol, mock_util, mock_signal):
+ dh = DeprovisionHandler()
+
+ dh.setup = Mock()
+ dh.setup.return_value = ([], [])
+ dh.do_actions = Mock()
+
+ # Do actions if confirmed
+ mock_read.return_value = "y"
+ dh.run()
+ self.assertEqual(1, dh.do_actions.call_count)
+
+ # Skip actions if not confirmed
+ mock_read.return_value = "n"
+ dh.run()
+ self.assertEqual(1, dh.do_actions.call_count)
+
+ # Do actions if forced
+ mock_read.return_value = "n"
+ dh.run(force=True)
+ self.assertEqual(2, dh.do_actions.call_count)
+
@patch("azurelinuxagent.pa.deprovision.default.DeprovisionHandler.cloud_init_dirs")
@patch("azurelinuxagent.pa.deprovision.default.DeprovisionHandler.cloud_init_files")
def test_del_cloud_init_without_once(self,
mock_files,
mock_dirs):
deprovision_handler = get_deprovision_handler("","","")
- deprovision_handler.del_cloud_init([], [], include_once=False)
+ deprovision_handler.del_cloud_init([], [],
+ include_once=False, deluser=False)
mock_dirs.assert_called_with(include_once=False)
- mock_files.assert_called_with(include_once=False)
+ mock_files.assert_called_with(include_once=False, deluser=False)
@patch("signal.signal")
@patch("azurelinuxagent.common.protocol.get_protocol_util")
@@ -59,10 +88,11 @@ class TestDeprovision(AgentTestCase):
mock_files.return_value = files
deprovision_handler = get_deprovision_handler("","","")
- deprovision_handler.del_cloud_init(warnings, actions)
+ deprovision_handler.del_cloud_init(warnings, actions,
+ deluser=True)
mock_dirs.assert_called_with(include_once=True)
- mock_files.assert_called_with(include_once=True)
+ mock_files.assert_called_with(include_once=True, deluser=True)
self.assertEqual(len(warnings), 0)
self.assertEqual(len(actions), 2)
diff --git a/tests/protocol/test_hostplugin.py b/tests/protocol/test_hostplugin.py
index e203615..b18b691 100644
--- a/tests/protocol/test_hostplugin.py
+++ b/tests/protocol/test_hostplugin.py
@@ -19,7 +19,7 @@ import base64
import json
import sys
-
+from azurelinuxagent.common.future import ustr
if sys.version_info[0] == 3:
import http.client as httpclient
@@ -224,6 +224,61 @@ class TestHostPlugin(AgentTestCase):
test_goal_state,
exp_method, exp_url, exp_data)
+ def test_read_response_error(self):
+ """
+ Validate the read_response_error method handles encoding correctly
+ """
+ responses = ['message', b'message', '\x80message\x80']
+ response = MagicMock()
+ response.status = 'status'
+ response.reason = 'reason'
+ with patch.object(response, 'read') as patch_response:
+ for s in responses:
+ patch_response.return_value = s
+ result = hostplugin.HostPluginProtocol.read_response_error(response)
+ self.assertTrue('[status: reason]' in result)
+ self.assertTrue('message' in result)
+
+ def test_read_response_bytes(self):
+ response_bytes = '7b:0a:20:20:20:20:22:65:72:72:6f:72:43:6f:64:65:22:' \
+ '3a:20:22:54:68:65:20:62:6c:6f:62:20:74:79:70:65:20:' \
+ '69:73:20:69:6e:76:61:6c:69:64:20:66:6f:72:20:74:68:' \
+ '69:73:20:6f:70:65:72:61:74:69:6f:6e:2e:22:2c:0a:20:' \
+ '20:20:20:22:6d:65:73:73:61:67:65:22:3a:20:22:c3:af:' \
+ 'c2:bb:c2:bf:3c:3f:78:6d:6c:20:76:65:72:73:69:6f:6e:' \
+ '3d:22:31:2e:30:22:20:65:6e:63:6f:64:69:6e:67:3d:22:' \
+ '75:74:66:2d:38:22:3f:3e:3c:45:72:72:6f:72:3e:3c:43:' \
+ '6f:64:65:3e:49:6e:76:61:6c:69:64:42:6c:6f:62:54:79:' \
+ '70:65:3c:2f:43:6f:64:65:3e:3c:4d:65:73:73:61:67:65:' \
+ '3e:54:68:65:20:62:6c:6f:62:20:74:79:70:65:20:69:73:' \
+ '20:69:6e:76:61:6c:69:64:20:66:6f:72:20:74:68:69:73:' \
+ '20:6f:70:65:72:61:74:69:6f:6e:2e:0a:52:65:71:75:65:' \
+ '73:74:49:64:3a:63:37:34:32:39:30:63:62:2d:30:30:30:' \
+ '31:2d:30:30:62:35:2d:30:36:64:61:2d:64:64:36:36:36:' \
+ '61:30:30:30:22:2c:0a:20:20:20:20:22:64:65:74:61:69:' \
+ '6c:73:22:3a:20:22:22:0a:7d'.split(':')
+ expected_response = '[status: reason] {\n "errorCode": "The blob ' \
+ 'type is invalid for this operation.",\n ' \
+ '"message": "<?xml version="1.0" ' \
+ 'encoding="utf-8"?>' \
+ '<Error><Code>InvalidBlobType</Code><Message>The ' \
+ 'blob type is invalid for this operation.\n' \
+ 'RequestId:c74290cb-0001-00b5-06da-dd666a000",' \
+ '\n "details": ""\n}'
+
+ response_string = ''.join(chr(int(b, 16)) for b in response_bytes)
+ response = MagicMock()
+ response.status = 'status'
+ response.reason = 'reason'
+ with patch.object(response, 'read') as patch_response:
+ patch_response.return_value = response_string
+ result = hostplugin.HostPluginProtocol.read_response_error(response)
+ self.assertEqual(result, expected_response)
+ try:
+ raise HttpError("{0}".format(result))
+ except HttpError as e:
+ self.assertTrue(result in ustr(e))
+
def test_no_fallback(self):
"""
Validate fallback to upload status using HostGAPlugin is not happening
diff --git a/tests/protocol/test_wire.py b/tests/protocol/test_wire.py
index ba9fc7d..02976ca 100644
--- a/tests/protocol/test_wire.py
+++ b/tests/protocol/test_wire.py
@@ -80,26 +80,36 @@ class TestWireProtocolGetters(AgentTestCase):
url = testurl
headers = {}
- # no kwargs
+ # no kwargs -- Default to True
WireClient.call_storage_service(http_req)
- # kwargs, no chk_proxy
+
+ # kwargs, no chk_proxy -- Default to True
WireClient.call_storage_service(http_req,
url,
headers)
- # kwargs, chk_proxy False
+
+ # kwargs, chk_proxy None -- Default to True
+ WireClient.call_storage_service(http_req,
+ url,
+ headers,
+ chk_proxy=None)
+
+ # kwargs, chk_proxy False -- Keep False
WireClient.call_storage_service(http_req,
url,
headers,
chk_proxy=False)
- # kwargs, chk_proxy True
+
+ # kwargs, chk_proxy True -- Keep True
WireClient.call_storage_service(http_req,
url,
headers,
chk_proxy=True)
# assert
- self.assertTrue(http_patch.call_count == 4)
- for c in http_patch.call_args_list:
- self.assertTrue(c[-1]['chk_proxy'] == True)
+ self.assertTrue(http_patch.call_count == 5)
+ for i in range(0,5):
+ c = http_patch.call_args_list[i][-1]['chk_proxy']
+ self.assertTrue(c == (True if i != 3 else False))
def test_status_blob_parsing(self, *args):
wire_protocol_client = WireProtocol(wireserver_url).client