← Back to team overview

cloud-init-dev team mailing list archive

[Merge] lp:~utlemming/cloud-init/lp1375252 into lp:cloud-init

 

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/247438

Allow the system hostname and the fabric name to be separate per bug #137525. This change also brings the Azure datasource closer in behavior to other Datasources. 
-- 
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 16:09:58 +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,7 +156,7 @@
 
         # handle the hostname 'publishing'
         try:
-            handle_set_hostname(mycfg.get('set_hostname'),
+            handle_set_dhcp_hostname(mycfg.get('set_hostname'),
                                 self.metadata.get('local-hostname'),
                                 mycfg['hostname_bounce'])
         except Exception as e:
@@ -298,7 +299,45 @@
     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):
+                    current_hostname = None
+
+                    if current_hostname == hostname:
+                        LOG.debug("dhcpclient is already configured for"
+                                  " current hostname '{}'".format(hostname))
+                        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):
+    if not set_dhcp_hostname(hostname):
+        LOG.info("dchpclient hostname matches the fabric name")
+        return
+
     if not util.is_true(enabled):
         return
 
@@ -308,38 +347,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 16:09:58 +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 = {}