summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2013-08-05 06:58:54 +0100
committerScott Moser <smoser@ubuntu.com>2013-08-05 06:58:54 +0100
commit65e49111bcea78342289c671376eba85410ea781 (patch)
tree64555168d9767f63074729cdbe9d34e5ae37eb01
parent696bcc1f0acc67646872cd6ce1b90375ca0ae068 (diff)
parent219191673b5491fab683ca5ff1befe845c81f6cf (diff)
downloadvyos-cloud-init-65e49111bcea78342289c671376eba85410ea781.tar.gz
vyos-cloud-init-65e49111bcea78342289c671376eba85410ea781.zip
merge from trunk
-rw-r--r--ChangeLog4
-rw-r--r--Requires4
-rwxr-xr-xbin/cloud-init4
-rw-r--r--cloudinit/config/cc_growpart.py3
-rw-r--r--cloudinit/config/cc_resizefs.py11
-rw-r--r--cloudinit/sources/DataSourceAzure.py136
-rw-r--r--cloudinit/util.py35
-rw-r--r--doc/examples/cloud-config-datasources.txt5
-rw-r--r--doc/sources/azure/README.rst134
-rwxr-xr-xpackages/bddeb11
-rwxr-xr-xpackages/brpm2
-rwxr-xr-xsetup.py4
-rw-r--r--sysvinit/debian/cloud-config64
-rw-r--r--sysvinit/debian/cloud-final66
-rwxr-xr-xsysvinit/debian/cloud-init64
-rw-r--r--sysvinit/debian/cloud-init-local63
-rwxr-xr-xsysvinit/redhat/cloud-config (renamed from sysvinit/cloud-config)0
-rwxr-xr-xsysvinit/redhat/cloud-final (renamed from sysvinit/cloud-final)0
-rwxr-xr-xsysvinit/redhat/cloud-init (renamed from sysvinit/cloud-init)0
-rwxr-xr-xsysvinit/redhat/cloud-init-local (renamed from sysvinit/cloud-init-local)0
-rw-r--r--tests/unittests/test_datasource/test_azure.py76
21 files changed, 640 insertions, 46 deletions
diff --git a/ChangeLog b/ChangeLog
index 47c25027..68d03376 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -10,6 +10,10 @@
- support individual MIME segments to be gzip compressed (LP: #1203203)
- always finalize handlers even if processing failed (LP: #1203368)
- support merging into cloud-config via jsonp. (LP: #1200476)
+ - add datasource 'SmartOS' for Joyent Cloud. Adds a dependency on serial.
+ - add 'log_time' helper to util for timing how long things take
+ which also reads from uptime. uptime is useful as clock may change during
+ boot due to ntp.
0.7.2:
- add a debian watch file
- add 'sudo' entry to ubuntu's default user (LP: #1080717)
diff --git a/Requires b/Requires
index 5086230d..f19c9691 100644
--- a/Requires
+++ b/Requires
@@ -10,6 +10,10 @@ PrettyTable
# datasource is removed, this is no longer needed
oauth
+# This one is currently used only by the SmartOS datasource. If that
+# datasource is removed, this is no longer needed
+pyserial
+
# This is only needed for places where we need to support configs in a manner
# that the built-in config parser is not sufficent (ie
# when we need to preserve comments, or do not have a top-level
diff --git a/bin/cloud-init b/bin/cloud-init
index c5a5b949..b4f9fd07 100755
--- a/bin/cloud-init
+++ b/bin/cloud-init
@@ -502,7 +502,9 @@ def main():
signal_handler.attach_handlers()
(name, functor) = args.action
- return functor(name, args)
+
+ return util.log_time(logfunc=LOG.debug, msg="cloud-init mode '%s'" % name,
+ get_uptime=True, func=functor, args=(name, args))
if __name__ == '__main__':
diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py
index 4f8c8f80..ba6c58af 100644
--- a/cloudinit/config/cc_growpart.py
+++ b/cloudinit/config/cc_growpart.py
@@ -264,7 +264,8 @@ def handle(_name, cfg, _cloud, log, _args):
raise e
return
- resized = resize_devices(resizer, devices)
+ resized = util.log_time(logfunc=log.debug, msg="resize_devices",
+ func=resize_devices, args=(resizer, devices))
for (entry, action, msg) in resized:
if action == RESIZE.CHANGED:
log.info("'%s' resized: %s" % (entry, msg))
diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
index b4ee16b2..56040fdd 100644
--- a/cloudinit/config/cc_resizefs.py
+++ b/cloudinit/config/cc_resizefs.py
@@ -21,7 +21,6 @@
import errno
import os
import stat
-import time
from cloudinit.settings import PER_ALWAYS
from cloudinit import util
@@ -120,9 +119,12 @@ def handle(name, cfg, _cloud, log, args):
if resize_root == NOBLOCK:
# Fork to a child that will run
# the resize command
- util.fork_cb(do_resize, resize_cmd, log)
+ util.fork_cb(
+ util.log_time(logfunc=log.debug, msg="backgrounded Resizing",
+ func=do_resize, args=(resize_cmd, log)))
else:
- do_resize(resize_cmd, log)
+ util.log_time(logfunc=log.debug, msg="Resizing",
+ func=do_resize, args=(resize_cmd, log))
action = 'Resized'
if resize_root == NOBLOCK:
@@ -132,13 +134,10 @@ def handle(name, cfg, _cloud, log, args):
def do_resize(resize_cmd, log):
- start = time.time()
try:
util.subp(resize_cmd)
except util.ProcessExecutionError:
util.logexc(log, "Failed to resize filesystem (cmd=%s)", resize_cmd)
raise
- tot_time = time.time() - start
- log.debug("Resizing took %.3f seconds", tot_time)
# TODO(harlowja): Should we add a fsck check after this to make
# sure we didn't corrupt anything?
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 0a5caebe..1a74de21 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -31,9 +31,21 @@ LOG = logging.getLogger(__name__)
DS_NAME = 'Azure'
DEFAULT_METADATA = {"instance-id": "iid-AZURE-NODE"}
AGENT_START = ['service', 'walinuxagent', 'start']
-BUILTIN_DS_CONFIG = {'datasource': {DS_NAME: {
- 'agent_command': AGENT_START,
- 'data_dir': "/var/lib/waagent"}}}
+BOUNCE_COMMAND = ['sh', '-xc',
+ "i=$interface; x=0; ifdown $i || x=$?; ifup $i || x=$?; exit $x"]
+
+BUILTIN_DS_CONFIG = {
+ 'agent_command': AGENT_START,
+ 'data_dir': "/var/lib/waagent",
+ 'set_hostname': True,
+ 'hostname_bounce': {
+ 'interface': 'eth0',
+ 'policy': True,
+ 'command': BOUNCE_COMMAND,
+ 'hostname_command': 'hostname',
+ }
+}
+DS_CFG_PATH = ['datasource', DS_NAME]
class DataSourceAzureNet(sources.DataSource):
@@ -42,19 +54,19 @@ class DataSourceAzureNet(sources.DataSource):
self.seed_dir = os.path.join(paths.seed_dir, 'azure')
self.cfg = {}
self.seed = None
+ self.ds_cfg = util.mergemanydict([
+ util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}),
+ BUILTIN_DS_CONFIG])
def __str__(self):
root = sources.DataSource.__str__(self)
return "%s [seed=%s]" % (root, self.seed)
def get_data(self):
- ddir_cfgpath = ['datasource', DS_NAME, 'data_dir']
# azure removes/ejects the cdrom containing the ovf-env.xml
# file on reboot. So, in order to successfully reboot we
# need to look in the datadir and consider that valid
- ddir = util.get_cfg_by_path(self.sys_cfg, ddir_cfgpath)
- if ddir is None:
- ddir = util.get_cfg_by_path(BUILTIN_DS_CONFIG, ddir_cfgpath)
+ ddir = self.ds_cfg['data_dir']
candidates = [self.seed_dir]
candidates.extend(list_possible_azure_ds_devs())
@@ -91,44 +103,46 @@ class DataSourceAzureNet(sources.DataSource):
return False
if found == ddir:
- LOG.debug("using cached datasource in %s", ddir)
-
- fields = [('cmd', ['datasource', DS_NAME, 'agent_command']),
- ('datadir', ddir_cfgpath)]
- mycfg = {}
- for cfg in (self.cfg, self.sys_cfg, BUILTIN_DS_CONFIG):
- for name, path in fields:
- if name in mycfg:
- continue
- value = util.get_cfg_by_path(cfg, keyp=path)
- if value is not None:
- mycfg[name] = value
+ LOG.debug("using files cached in %s", ddir)
+
+ # now update ds_cfg to reflect contents pass in config
+ usercfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
+ self.ds_cfg = util.mergemanydict([usercfg, self.ds_cfg])
+ mycfg = self.ds_cfg
# walinux agent writes files world readable, but expects
# the directory to be protected.
- write_files(mycfg['datadir'], files, dirmode=0700)
+ write_files(mycfg['data_dir'], files, dirmode=0700)
+
+ # handle the hostname 'publishing'
+ try:
+ handle_set_hostname(mycfg.get('set_hostname'),
+ self.metadata.get('local-hostname'),
+ mycfg['hostname_bounce'])
+ except Exception as e:
+ LOG.warn("Failed publishing hostname: %s" % e)
+ util.logexc(LOG, "handling set_hostname failed")
try:
- invoke_agent(mycfg['cmd'])
+ invoke_agent(mycfg['agent_command'])
except util.ProcessExecutionError:
# claim the datasource even if the command failed
- util.logexc(LOG, "agent command '%s' failed.", mycfg['cmd'])
+ util.logexc(LOG, "agent command '%s' failed.",
+ mycfg['agent_command'])
- shcfgxml = os.path.join(mycfg['datadir'], "SharedConfig.xml")
+ shcfgxml = os.path.join(mycfg['data_dir'], "SharedConfig.xml")
wait_for = [shcfgxml]
fp_files = []
for pk in self.cfg.get('_pubkeys', []):
bname = pk['fingerprint'] + ".crt"
- fp_files += [os.path.join(mycfg['datadir'], bname)]
+ fp_files += [os.path.join(mycfg['data_dir'], bname)]
- start = time.time()
- missing = wait_for_files(wait_for + fp_files)
+ missing = util.log_time(logfunc=LOG.debug, msg="waiting for files",
+ func=wait_for_files,
+ args=(wait_for + fp_files,))
if len(missing):
LOG.warn("Did not find files, but going on: %s", missing)
- else:
- LOG.debug("waited %.3f seconds for %d files to appear",
- time.time() - start, len(wait_for))
if shcfgxml in missing:
LOG.warn("SharedConfig.xml missing, using static instance-id")
@@ -148,6 +162,56 @@ class DataSourceAzureNet(sources.DataSource):
return self.cfg
+def handle_set_hostname(enabled, hostname, cfg):
+ if not util.is_true(enabled):
+ return
+
+ if not hostname:
+ LOG.warn("set_hostname was true but no local-hostname")
+ return
+
+ apply_hostname_bounce(hostname=hostname, policy=cfg['policy'],
+ interface=cfg['interface'],
+ command=cfg['command'],
+ hostname_command=cfg['hostname_command'])
+
+
+def apply_hostname_bounce(hostname, policy, interface, command,
+ hostname_command="hostname"):
+ # set the hostname to 'hostname' if it is not already set to that.
+ # then, if policy is not off, bounce the interface using command
+ prev_hostname = util.subp(hostname_command, capture=True)[0].strip()
+
+ util.subp([hostname_command, hostname])
+
+ msg = ("phostname=%s hostname=%s policy=%s interface=%s" %
+ (prev_hostname, hostname, policy, interface))
+
+ if util.is_false(policy):
+ LOG.debug("pubhname: policy false, skipping [%s]", msg)
+ return
+
+ if prev_hostname == hostname and policy != "force":
+ LOG.debug("pubhname: no change, policy != force. skipping. [%s]", msg)
+ return
+
+ env = os.environ.copy()
+ env['interface'] = interface
+ env['hostname'] = hostname
+ env['old_hostname'] = prev_hostname
+
+ if command == "builtin":
+ command = BOUNCE_COMMAND
+
+ LOG.debug("pubhname: publishing hostname [%s]", msg)
+ shell = not isinstance(command, (list, tuple))
+ # capture=False, see comments in bug 1202758 and bug 1206164.
+ util.log_time(logfunc=LOG.debug, msg="publishing hostname",
+ get_uptime=True, func=util.subp,
+ kwargs={'command': command, 'shell': shell, 'capture': False,
+ 'env': env})
+
+
def crtfile_to_pubkey(fname):
pipeline = ('openssl x509 -noout -pubkey < "$0" |'
'ssh-keygen -i -m PKCS8 -f /dev/stdin')
@@ -319,15 +383,21 @@ def read_azure_ovf(contents):
name = child.localName.lower()
simple = False
+ value = ""
if (len(child.childNodes) == 1 and
child.childNodes[0].nodeType == dom.TEXT_NODE):
simple = True
value = child.childNodes[0].wholeText
+ attrs = {k: v for k, v in child.attributes.items()}
+
# we accept either UserData or CustomData. If both are present
# then behavior is undefined.
if (name == "userdata" or name == "customdata"):
- ud = base64.b64decode(''.join(value.split()))
+ if attrs.get('encoding') in (None, "base64"):
+ ud = base64.b64decode(''.join(value.split()))
+ else:
+ ud = value
elif name == "username":
username = value
elif name == "userpassword":
@@ -335,7 +405,11 @@ def read_azure_ovf(contents):
elif name == "hostname":
md['local-hostname'] = value
elif name == "dscfg":
- cfg['datasource'] = {DS_NAME: util.load_yaml(value, default={})}
+ if attrs.get('encoding') in (None, "base64"):
+ dscfg = base64.b64decode(''.join(value.split()))
+ else:
+ dscfg = value
+ cfg['datasource'] = {DS_NAME: util.load_yaml(dscfg, default={})}
elif name == "ssh":
cfg['_pubkeys'] = load_azure_ovf_pubkeys(child)
elif name == "disablesshpasswordauthentication":
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 8542fe27..4a74ba57 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -1770,3 +1770,38 @@ def which(program):
return exe_file
return None
+
+
+def log_time(logfunc, msg, func, args=None, kwargs=None, get_uptime=False):
+ if args is None:
+ args = []
+ if kwargs is None:
+ kwargs = {}
+
+ start = time.time()
+
+ ustart = None
+ if get_uptime:
+ try:
+ ustart = float(uptime())
+ except ValueError:
+ pass
+
+ try:
+ ret = func(*args, **kwargs)
+ finally:
+ delta = time.time() - start
+ if ustart is not None:
+ try:
+ udelta = float(uptime()) - ustart
+ except ValueError:
+ udelta = "N/A"
+
+ tmsg = " took %0.3f seconds" % delta
+ if get_uptime:
+ tmsg += "(%0.2f)" % udelta
+ try:
+ logfunc(msg + tmsg)
+ except:
+ pass
+ return ret
diff --git a/doc/examples/cloud-config-datasources.txt b/doc/examples/cloud-config-datasources.txt
index a19353fc..6544448e 100644
--- a/doc/examples/cloud-config-datasources.txt
+++ b/doc/examples/cloud-config-datasources.txt
@@ -45,6 +45,11 @@ datasource:
Azure:
agent_command: [service, walinuxagent, start]
+ set_hostname: True
+ hostname_bounce:
+ interface: eth0
+ policy: on # [can be 'on', 'off' or 'force']
+ }
SmartOS:
# Smart OS datasource works over a serial console interacting with
diff --git a/doc/sources/azure/README.rst b/doc/sources/azure/README.rst
new file mode 100644
index 00000000..8239d1fa
--- /dev/null
+++ b/doc/sources/azure/README.rst
@@ -0,0 +1,134 @@
+================
+Azure Datasource
+================
+
+This datasource finds metadata and user-data from the Azure cloud platform.
+
+Azure Platform
+--------------
+The azure cloud-platform provides initial data to an instance via an attached
+CD formated in UDF. That CD contains a 'ovf-env.xml' file that provides some
+information. Additional information is obtained via interaction with the
+"endpoint". The ip address of the endpoint is advertised to the instance
+inside of dhcp option 245. On ubuntu, that can be seen in
+/var/lib/dhcp/dhclient.eth0.leases as a colon delimited hex value (example:
+``option unknown-245 64:41:60:82;`` is 100.65.96.130)
+
+walinuxagent
+------------
+In order to operate correctly, cloud-init needs walinuxagent to provide much
+of the interaction with azure. In addition to "provisioning" code, walinux
+does the following on the agent is a long running daemon that handles the
+following things:
+- generate a x509 certificate and send that to the endpoint
+
+waagent.conf config
+~~~~~~~~~~~~~~~~~~~
+in order to use waagent.conf with cloud-init, the following settings are recommended. Other values can be changed or set to the defaults.
+
+ ::
+
+ # disabling provisioning turns off all 'Provisioning.*' function
+ Provisioning.Enabled=n
+ # this is currently not handled by cloud-init, so let walinuxagent do it.
+ ResourceDisk.Format=y
+ ResourceDisk.MountPoint=/mnt
+
+
+Userdata
+--------
+Userdata is provided to cloud-init inside the ovf-env.xml file. Cloud-init
+expects that user-data will be provided as base64 encoded value inside the
+text child of a element named ``UserData`` or ``CustomData`` which is a direct
+child of the ``LinuxProvisioningConfigurationSet`` (a sibling to ``UserName``)
+If both ``UserData`` and ``CustomData`` are provided behavior is undefined on
+which will be selected.
+
+In the example below, user-data provided is 'this is my userdata', and the
+datasource config provided is ``{"agent_command": ["start", "walinuxagent"]}``.
+That agent command will take affect as if it were specified in system config.
+
+Example:
+
+.. code::
+
+ <wa:ProvisioningSection>
+ <wa:Version>1.0</wa:Version>
+ <LinuxProvisioningConfigurationSet
+ xmlns="http://schemas.microsoft.com/windowsazure"
+ xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
+ <HostName>myHost</HostName>
+ <UserName>myuser</UserName>
+ <UserPassword/>
+ <CustomData>dGhpcyBpcyBteSB1c2VyZGF0YQ===</CustomData>
+ <dscfg>eyJhZ2VudF9jb21tYW5kIjogWyJzdGFydCIsICJ3YWxpbnV4YWdlbnQiXX0=</dscfg>
+ <DisableSshPasswordAuthentication>true</DisableSshPasswordAuthentication>
+ <SSH>
+ <PublicKeys>
+ <PublicKey>
+ <Fingerprint>6BE7A7C3C8A8F4B123CCA5D0C2F1BE4CA7B63ED7</Fingerprint>
+ <Path>this-value-unused</Path>
+ </PublicKey>
+ </PublicKeys>
+ </SSH>
+ </LinuxProvisioningConfigurationSet>
+ </wa:ProvisioningSection>
+
+Configuration
+-------------
+Configuration for the datasource can be read from the system config's or set
+via the `dscfg` entry in the `LinuxProvisioningConfigurationSet`. Content in
+dscfg node is expected to be base64 encoded yaml content, and it will be
+merged into the 'datasource: Azure' entry.
+
+The '``hostname_bounce: command``' entry can be either the literal string
+'builtin' or a command to execute. The command will be invoked after the
+hostname is set, and will have the 'interface' in its environment. If
+``set_hostname`` is not true, then ``hostname_bounce`` will be ignored.
+
+An example might be:
+ command: ["sh", "-c", "killall dhclient; dhclient $interface"]
+
+.. code::
+
+ datasource:
+ agent_command
+ Azure:
+ agent_command: [service, walinuxagent, start]
+ set_hostname: True
+ hostname_bounce:
+ # the name of the interface to bounce
+ interface: eth0
+ # policy can be 'on', 'off' or 'force'
+ policy: on
+ # the method 'bounce' command.
+ command: "builtin"
+ hostname_command: "hostname"
+ }
+
+hostname
+--------
+When the user launches an instance, they provide a hostname for that instance.
+The hostname is provided to the instance in the ovf-env.xml file as
+``HostName``.
+
+Whatever value the instance provides in its dhcp request will resolve in the
+domain returned in the 'search' request.
+
+The interesting issue is that a generic image will already have a hostname
+configured. The ubuntu cloud images have 'ubuntu' as the hostname of the
+system, and the initial dhcp request on eth0 is not guaranteed to occur after
+the datasource code has been run. So, on first boot, that initial value will
+be sent in the dhcp request and *that* value will resolve.
+
+In order to make the ``HostName`` provided in the ovf-env.xml resolve, a
+dhcp request must be made with the new value. Walinuxagent (in its current
+version) handles this by polling the state of hostname and bouncing ('``ifdown
+eth0; ifup eth0``' the network interface if it sees that a change has been
+made.
+
+cloud-init handles this by setting the hostname in the DataSource's 'get_data'
+method via '``hostname $HostName``', and then bouncing the interface. This
+behavior can be configured or disabled in the datasource config. See
+'Configuration' above.
diff --git a/packages/bddeb b/packages/bddeb
index 7ae07a80..30559870 100755
--- a/packages/bddeb
+++ b/packages/bddeb
@@ -32,11 +32,12 @@ PKG_MP = {
'boto': 'python-boto',
'cheetah': 'python-cheetah',
'configobj': 'python-configobj',
+ 'jsonpatch': 'python-json-patch',
'oauth': 'python-oauth',
'prettytable': 'python-prettytable',
+ 'pyserial': 'python-serial',
'pyyaml': 'python-yaml',
'requests': 'python-requests',
- 'jsonpatch': 'python-json-patch',
}
DEBUILD_ARGS = ["-us", "-S", "-uc", "-d"]
@@ -95,12 +96,20 @@ def main():
default=False,
action='store_true')
+ parser.add_argument("--init-system", dest="init_system",
+ help=("build deb with INIT_SYSTEM=xxx"
+ " (default: %(default)s"),
+ default=os.environ.get("INIT_SYSTEM", "upstart"))
+
+
for ent in DEBUILD_ARGS:
parser.add_argument(ent, dest="debuild_args", action='append_const',
const=ent, help=("pass through '%s' to debuild" % ent))
args = parser.parse_args()
+ os.environ['INIT_SYSTEM'] = args.init_system
+
capture = True
if args.verbose:
capture = False
diff --git a/packages/brpm b/packages/brpm
index 91a0a0ec..8c90a0ab 100755
--- a/packages/brpm
+++ b/packages/brpm
@@ -42,6 +42,7 @@ PKG_MP = {
'jsonpatch': 'python-jsonpatch',
'oauth': 'python-oauth',
'prettytable': 'python-prettytable',
+ 'pyserial': 'pyserial',
'pyyaml': 'PyYAML',
'requests': 'python-requests',
},
@@ -53,6 +54,7 @@ PKG_MP = {
'jsonpatch': 'python-jsonpatch',
'oauth': 'python-oauth',
'prettytable': 'python-prettytable',
+ 'pyserial': 'python-pyserial',
'pyyaml': 'python-yaml',
'requests': 'python-requests',
}
diff --git a/setup.py b/setup.py
index 4aa1a47c..8d18b97e 100755
--- a/setup.py
+++ b/setup.py
@@ -37,8 +37,8 @@ def is_f(p):
INITSYS_FILES = {
- 'sysvinit': [f for f in glob('sysvinit/*') if is_f(f)],
- 'sysvinit_deb': [f for f in glob('sysvinit/*') if is_f(f)],
+ 'sysvinit': [f for f in glob('sysvinit/redhat/*') if is_f(f)],
+ 'sysvinit_deb': [f for f in glob('sysvinit/debian/*') if is_f(f)],
'systemd': [f for f in glob('systemd/*') if is_f(f)],
'upstart': [f for f in glob('upstart/*') if is_f(f)],
}
diff --git a/sysvinit/debian/cloud-config b/sysvinit/debian/cloud-config
new file mode 100644
index 00000000..53322748
--- /dev/null
+++ b/sysvinit/debian/cloud-config
@@ -0,0 +1,64 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: cloud-config
+# Required-Start: cloud-init cloud-init-local
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Cloud init modules --mode config
+# Description: Cloud configuration initialization
+### END INIT INFO
+
+# Authors: Julien Danjou <acid@debian.org>
+# Juerg Haefliger <juerg.haefliger@hp.com>
+# Thomas Goirand <zigo@debian.org>
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Cloud service"
+NAME=cloud-init
+DAEMON=/usr/bin/$NAME
+DAEMON_ARGS="modules --mode config"
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+if init_is_upstart; then
+ case "$1" in
+ stop)
+ exit 0
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+fi
+
+case "$1" in
+start)
+ log_daemon_msg "Starting $DESC" "$NAME"
+ $DAEMON ${DAEMON_ARGS}
+ case "$?" in
+ 0|1) log_end_msg 0 ;;
+ 2) log_end_msg 1 ;;
+ esac
+;;
+stop|restart|force-reload)
+ echo "Error: argument '$1' not supported" >&2
+ exit 3
+;;
+*)
+ echo "Usage: $SCRIPTNAME {start}" >&2
+ exit 3
+;;
+esac
+
+:
diff --git a/sysvinit/debian/cloud-final b/sysvinit/debian/cloud-final
new file mode 100644
index 00000000..55afc8b0
--- /dev/null
+++ b/sysvinit/debian/cloud-final
@@ -0,0 +1,66 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: cloud-final
+# Required-Start: $all cloud-config
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Cloud init modules final jobs
+# Description: This runs the cloud configuration initialization "final" jobs
+# and can be seen as the traditional "rc.local" time for the cloud.
+# It runs after all cloud-config jobs are run
+### END INIT INFO
+
+# Authors: Julien Danjou <acid@debian.org>
+# Juerg Haefliger <juerg.haefliger@hp.com>
+# Thomas Goirand <zigo@debian.org>
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Cloud service"
+NAME=cloud-init
+DAEMON=/usr/bin/$NAME
+DAEMON_ARGS="modules --mode final"
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+if init_is_upstart; then
+ case "$1" in
+ stop)
+ exit 0
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+fi
+
+case "$1" in
+start)
+ log_daemon_msg "Starting $DESC" "$NAME"
+ $DAEMON ${DAEMON_ARGS}
+ case "$?" in
+ 0|1) log_end_msg 0 ;;
+ 2) log_end_msg 1 ;;
+ esac
+;;
+stop|restart|force-reload)
+ echo "Error: argument '$1' not supported" >&2
+ exit 3
+;;
+*)
+ echo "Usage: $SCRIPTNAME {start}" >&2
+ exit 3
+;;
+esac
+
+:
diff --git a/sysvinit/debian/cloud-init b/sysvinit/debian/cloud-init
new file mode 100755
index 00000000..48fa0423
--- /dev/null
+++ b/sysvinit/debian/cloud-init
@@ -0,0 +1,64 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: cloud-init
+# Required-Start: $local_fs $remote_fs $syslog $network cloud-init-local
+# Required-Stop: $remote_fs
+# X-Start-Before: sshd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Cloud init
+# Description: Cloud configuration initialization
+### END INIT INFO
+
+# Authors: Julien Danjou <acid@debian.org>
+# Thomas Goirand <zigo@debian.org>
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Cloud service"
+NAME=cloud-init
+DAEMON=/usr/bin/$NAME
+DAEMON_ARGS="init"
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+if init_is_upstart; then
+ case "$1" in
+ stop)
+ exit 0
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+fi
+
+case "$1" in
+ start)
+ log_daemon_msg "Starting $DESC" "$NAME"
+ $DAEMON ${DAEMON_ARGS}
+ case "$?" in
+ 0|1) log_end_msg 0 ;;
+ 2) log_end_msg 1 ;;
+ esac
+ ;;
+ stop|restart|force-reload)
+ echo "Error: argument '$1' not supported" >&2
+ exit 3
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/sysvinit/debian/cloud-init-local b/sysvinit/debian/cloud-init-local
new file mode 100644
index 00000000..802ee8e9
--- /dev/null
+++ b/sysvinit/debian/cloud-init-local
@@ -0,0 +1,63 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: cloud-init-local
+# Required-Start: $local_fs $remote_fs
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Cloud init local
+# Description: Cloud configuration initialization
+### END INIT INFO
+
+# Authors: Julien Danjou <acid@debian.org>
+# Juerg Haefliger <juerg.haefliger@hp.com>
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Cloud service"
+NAME=cloud-init
+DAEMON=/usr/bin/$NAME
+DAEMON_ARGS="init --local"
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+if init_is_upstart; then
+ case "$1" in
+ stop)
+ exit 0
+ ;;
+ *)
+ exit 1
+ ;;
+ esac
+fi
+
+case "$1" in
+start)
+ log_daemon_msg "Starting $DESC" "$NAME"
+ $DAEMON ${DAEMON_ARGS}
+ case "$?" in
+ 0|1) log_end_msg 0 ;;
+ 2) log_end_msg 1 ;;
+ esac
+;;
+stop|restart|force-reload)
+ echo "Error: argument '$1' not supported" >&2
+ exit 3
+;;
+*)
+ echo "Usage: $SCRIPTNAME {start}" >&2
+ exit 3
+;;
+esac
+
+:
diff --git a/sysvinit/cloud-config b/sysvinit/redhat/cloud-config
index ad8ed831..ad8ed831 100755
--- a/sysvinit/cloud-config
+++ b/sysvinit/redhat/cloud-config
diff --git a/sysvinit/cloud-final b/sysvinit/redhat/cloud-final
index aeae8903..aeae8903 100755
--- a/sysvinit/cloud-final
+++ b/sysvinit/redhat/cloud-final
diff --git a/sysvinit/cloud-init b/sysvinit/redhat/cloud-init
index c1c92ad0..c1c92ad0 100755
--- a/sysvinit/cloud-init
+++ b/sysvinit/redhat/cloud-init
diff --git a/sysvinit/cloud-init-local b/sysvinit/redhat/cloud-init-local
index b53e0db2..b53e0db2 100755
--- a/sysvinit/cloud-init-local
+++ b/sysvinit/redhat/cloud-init-local
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
index 2e8583f9..4cd3f213 100644
--- a/tests/unittests/test_datasource/test_azure.py
+++ b/tests/unittests/test_datasource/test_azure.py
@@ -26,8 +26,15 @@ def construct_valid_ovf_env(data=None, pubkeys=None, userdata=None):
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
"""
- for key, val in data.items():
- content += "<%s>%s</%s>\n" % (key, val, key)
+ for key, dval in data.items():
+ if isinstance(dval, dict):
+ val = dval.get('text')
+ attrs = ' ' + ' '.join(["%s='%s'" % (k, v) for k, v in dval.items()
+ if k != 'text'])
+ else:
+ val = dval
+ attrs = ""
+ content += "<%s%s>%s</%s>\n" % (key, attrs, val, key)
if userdata:
content += "<UserData>%s</UserData>\n" % (base64.b64encode(userdata))
@@ -103,6 +110,9 @@ class TestAzureDataSource(MockerTestCase):
data['iid_from_shared_cfg'] = path
return 'i-my-azure-id'
+ def _apply_hostname_bounce(**kwargs):
+ data['apply_hostname_bounce'] = kwargs
+
if data.get('ovfcontent') is not None:
populate_dir(os.path.join(self.paths.seed_dir, "azure"),
{'ovf-env.xml': data['ovfcontent']})
@@ -118,7 +128,9 @@ class TestAzureDataSource(MockerTestCase):
(mod, 'pubkeys_from_crt_files',
_pubkeys_from_crt_files),
(mod, 'iid_from_shared_config',
- _iid_from_shared_config), ])
+ _iid_from_shared_config),
+ (mod, 'apply_hostname_bounce',
+ _apply_hostname_bounce), ])
dsrc = mod.DataSourceAzureNet(
data.get('sys_cfg', {}), distro=None, paths=self.paths)
@@ -139,10 +151,24 @@ class TestAzureDataSource(MockerTestCase):
self.assertEqual(0700, data['datadir_mode'])
self.assertEqual(dsrc.metadata['instance-id'], 'i-my-azure-id')
+ def test_user_cfg_set_agent_command_plain(self):
+ # set dscfg in via plaintext
+ cfg = {'agent_command': "my_command"}
+ odata = {'HostName': "myhost", 'UserName': "myuser",
+ 'dscfg': {'text': yaml.dump(cfg), 'encoding': 'plain'}}
+ data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
+
+ dsrc = self._get_ds(data)
+ ret = dsrc.get_data()
+ self.assertTrue(ret)
+ self.assertEqual(data['agent_invoked'], cfg['agent_command'])
+
def test_user_cfg_set_agent_command(self):
+ # set dscfg in via base64 encoded yaml
cfg = {'agent_command': "my_command"}
odata = {'HostName': "myhost", 'UserName': "myuser",
- 'dscfg': yaml.dump(cfg)}
+ 'dscfg': {'text': base64.b64encode(yaml.dump(cfg)),
+ 'encoding': 'base64'}}
data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
dsrc = self._get_ds(data)
@@ -218,6 +244,48 @@ class TestAzureDataSource(MockerTestCase):
for mypk in mypklist:
self.assertIn(mypk, dsrc.cfg['_pubkeys'])
+ def test_disabled_bounce(self):
+ pass
+
+ def test_apply_bounce_call_1(self):
+ # hostname needs to get through to apply_hostname_bounce
+ mydata = "FOOBAR"
+ odata = {'HostName': 'my-random-hostname'}
+ data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
+
+ self._get_ds(data).get_data()
+ self.assertIn('hostname', data['apply_hostname_bounce'])
+ self.assertEqual(data['apply_hostname_bounce']['hostname'],
+ odata['HostName'])
+
+ def test_apply_bounce_call_configurable(self):
+ # hostname_bounce should be configurable in datasource cfg
+ cfg = {'hostname_bounce': {'interface': 'eth1', 'policy': 'off',
+ 'command': 'my-bounce-command',
+ 'hostname_command': 'my-hostname-command'}}
+ odata = {'HostName': "xhost",
+ 'dscfg': {'text': base64.b64encode(yaml.dump(cfg)),
+ 'encoding': 'base64'}}
+ data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
+ self._get_ds(data).get_data()
+
+ for k in cfg['hostname_bounce']:
+ self.assertIn(k, data['apply_hostname_bounce'])
+
+ for k, v in cfg['hostname_bounce'].items():
+ self.assertEqual(data['apply_hostname_bounce'][k], v)
+
+ def test_set_hostname_disabled(self):
+ # config specifying set_hostname off should not bounce
+ cfg = {'set_hostname': False}
+ odata = {'HostName': "xhost",
+ 'dscfg': {'text': base64.b64encode(yaml.dump(cfg)),
+ 'encoding': 'base64'}}
+ data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
+ self._get_ds(data).get_data()
+
+ self.assertEqual(data.get('apply_hostname_bounce', "N/A"), "N/A")
+
class TestReadAzureOvf(MockerTestCase):
def test_invalid_xml_raises_non_azure_ds(self):