Ben Howard has proposed merging lp:~utlemming/cloud-init/lp1375252 into lp:cloud-init.
Requested reviews: cloud init development team (cloud-init-dev) Related bugs: Bug #1375252 in walinuxagent (Ubuntu): "Hostname change is not preserved across reboot on Azure Ubuntu VMs" https://bugs.launchpad.net/ubuntu/+source/walinuxagent/+bug/1375252 For more details, see: https://code.launchpad.net/~utlemming/cloud-init/lp1375252/+merge/247494 Change handling of setting the fabric hostname via dhcp configuration instead of using hostname. This allows for instances to use a different name for the hostname and brings hostname setting inline w/ other datasources (LP: #1375252). -- Your team cloud init development team is requested to review the proposed merge of lp:~utlemming/cloud-init/lp1375252 into lp:cloud-init.
=== modified file 'cloudinit/sources/DataSourceAzure.py' --- cloudinit/sources/DataSourceAzure.py 2014-08-26 18:50:11 +0000 +++ cloudinit/sources/DataSourceAzure.py 2015-01-23 22:51:53 +0000 @@ -21,6 +21,7 @@ import fnmatch import os import os.path +import re import time from xml.dom import minidom @@ -46,7 +47,6 @@ 'interface': 'eth0', 'policy': True, 'command': BOUNCE_COMMAND, - 'hostname_command': 'hostname', }, 'disk_aliases': {'ephemeral0': '/dev/sdb'}, } @@ -64,6 +64,7 @@ DS_CFG_PATH = ['datasource', DS_NAME] DEF_EPHEMERAL_LABEL = 'Temporary Storage' +DHCPCLIENT_CONFIGS = ["/etc/dhcp/dhclient.conf", "/etc/dhcp3/dhclient.conf"] class DataSourceAzureNet(sources.DataSource): @@ -155,9 +156,9 @@ # handle the hostname 'publishing' try: - handle_set_hostname(mycfg.get('set_hostname'), - self.metadata.get('local-hostname'), - mycfg['hostname_bounce']) + handle_set_dhcp_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") @@ -298,7 +299,49 @@ return mod_list -def handle_set_hostname(enabled, hostname, cfg): +def set_dhcp_hostname(hostname): + try: + for dhcp_fns in DHCPCLIENT_CONFIGS: + if not os.path.exists(dhcp_fns): + continue + + LOG.debug("reading current DHCP configuration from {}".format( + dhcp_fns)) + content = util.load_file(dhcp_fns) + + host_re = re.compile(r'^send\s*host-name.*') + new_content = [] + for line in content.splitlines(): + if host_re.match(line): + chname = re.search(r'^send\shost-name = (.*|\".*\").*', + line).group(1).replace('"','') + chname = re.sub('(\"|;)', '', chname) + LOG.debug("dchp hostname is configured as '{}'".format( + chname)) + + if hostname == chname: + LOG.info("dchpclient hostname matches the fabric name") + return False + + line = 'send host-name = "{}";'.format(hostname) + new_content.append(line) + + LOG.debug("setting 'send host-name = \"{}\";' in {}".format( + hostname, dhcp_fns)) + util.write_file(dhcp_fns, "\n".join(new_content)) + return True + + except IOError, e: + LOG.warn("unable to set DHCP hostname.\n{}".format(e.message)) + return False + + +def handle_set_dhcp_hostname(enabled, hostname, cfg): + # only continue if the dhcp hostname configuration has been updated + if not set_dhcp_hostname(hostname): + return + + # only bounce the interface if we are told to do su if not util.is_true(enabled): return @@ -308,38 +351,25 @@ 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)) + command=cfg['command']) + + +def apply_hostname_bounce(hostname, policy, interface, command): + msg = ("fabric_name={} policy={} interface={}".format(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) + LOG.debug("pubhname: policy false, skipping [{}]".format(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) + LOG.debug("pubhname: publishing hostname [{}]".format(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", === modified file 'tests/unittests/test_datasource/test_azure.py' --- tests/unittests/test_datasource/test_azure.py 2014-08-26 18:50:11 +0000 +++ tests/unittests/test_datasource/test_azure.py 2015-01-23 22:51:53 +0000 @@ -1,5 +1,5 @@ from cloudinit import helpers -from cloudinit.util import load_file +from cloudinit.util import (load_file, write_file) from cloudinit.sources import DataSourceAzure from ..helpers import populate_dir @@ -76,6 +76,10 @@ self.paths = helpers.Paths({'cloud_dir': self.tmp}) self.waagent_d = os.path.join(self.tmp, 'var', 'lib', 'waagent') + # spoof a dhcp configuration + self.dhcp_cfg = os.path.join(self.tmp, 'etc', 'dhcp', 'dhclient.conf') + write_file(self.dhcp_cfg, "send host-name = gethostname();") + self.unapply = [] super(TestAzureDataSource, self).setUp() @@ -116,6 +120,7 @@ mod = DataSourceAzure mod.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d + mod.DHCPCLIENT_CONFIGS = [self.dhcp_cfg] self.apply_patches([(mod, 'list_possible_azure_ds_devs', dsdevs)]) @@ -268,11 +273,13 @@ self.assertEqual(data['apply_hostname_bounce']['hostname'], odata['HostName']) + new_dhcp_cfg = load_file(self.dhcp_cfg) + self.assertIn('my-random-hostname', new_dhcp_cfg) + 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'}} + 'command': 'my-bounce-command'}} odata = {'HostName': "xhost", 'dscfg': {'text': base64.b64encode(yaml.dump(cfg)), 'encoding': 'base64'}} @@ -285,6 +292,9 @@ for k, v in cfg['hostname_bounce'].items(): self.assertEqual(data['apply_hostname_bounce'][k], v) + new_dhcp_cfg = load_file(self.dhcp_cfg) + self.assertIn('xhost', new_dhcp_cfg) + def test_set_hostname_disabled(self): # config specifying set_hostname off should not bounce cfg = {'set_hostname': False} @@ -296,6 +306,9 @@ self.assertEqual(data.get('apply_hostname_bounce', "N/A"), "N/A") + new_dhcp_cfg = load_file(self.dhcp_cfg) + self.assertIn('xhost', new_dhcp_cfg) + def test_default_ephemeral(self): # make sure the ephemeral device works odata = {}
_______________________________________________ Mailing list: https://launchpad.net/~cloud-init-dev Post to : cloud-init-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~cloud-init-dev More help : https://help.launchpad.net/ListHelp