← Back to team overview

cloud-init-dev team mailing list archive

[Merge] lp:~smoser/cloud-init/lp1020695 into lp:cloud-init

 

Scott Moser has proposed merging lp:~smoser/cloud-init/lp1020695 into lp:cloud-init.

Requested reviews:
  cloud init development team (cloud-init-dev)
Related bugs:
  Bug #1020695 in cloud-init: "Add variable for local IP address to /etc/hosts manager"
  https://bugs.launchpad.net/cloud-init/+bug/1020695

For more details, see:
https://code.launchpad.net/~smoser/cloud-init/lp1020695/+merge/163216

Improve the data available in /etc/hosts namagement

Previously, cloud-config like:
   manage_etc_hosts: template

would render /etc/hosts file from /etc/cloud/templates/.  However, there were not many variables available for substitution.
This change does the following things:

 * moves the 'manage_etc_hosts' key to:
   | etc_hosts:
   |   manage_mode: template
   but supports it in the old place
 * allows users to reference things from metadata:
    $public_ipv4
    $private_ipv4
    $public_hostname
    $private_hostname
 * allow reference in template to:
    $devaddr_<devname> ($devaddr_eth0)  ip address of the device
    $devaddr6_<devname> ($devaddr6_eth0) ipv6 addr on the device

 * adds configuration of 'etc_hosts' like this:
   | etc_hosts:
   |    template_from_metadata:
   |      public-ipv4: '0.0.0.0'
   |      public-hostname: 'undefined-public-hostname'
   |      private-ipv4: '0.0.0.0'
   |      private-hostname: 'undefined-private-hostname'
   |    default_addr: '0.0.0.0',
   |    default_addr6: '::1',
   |    manage_mode: False

   'template_from_metadata' is 'field-name': 'default' pairs.
   default_addr and default_addr6 are used if the interface referenced
   does not have an address or is not present.

   Anything in 'template_from_metadata' will be loaded from the
   metadata for the cloud. ie: metadata['public-ipv4']. and placed
   into the template (with - replaced with _)

-- 
https://code.launchpad.net/~smoser/cloud-init/lp1020695/+merge/163216
Your team cloud init development team is requested to review the proposed merge of lp:~smoser/cloud-init/lp1020695 into lp:cloud-init.
=== modified file 'cloudinit/config/cc_update_etc_hosts.py'
--- cloudinit/config/cc_update_etc_hosts.py	2013-01-15 21:34:51 +0000
+++ cloudinit/config/cc_update_etc_hosts.py	2013-05-09 20:12:38 +0000
@@ -18,6 +18,7 @@
 #    You should have received a copy of the GNU General Public License
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from cloudinit import netinfo
 from cloudinit import templater
 from cloudinit import util
 
@@ -26,14 +27,44 @@
 frequency = PER_ALWAYS
 
 
+def get_template_parms(hostname, fqdn, cfg, metadata):
+    # metadata is a cloud metadata
+    # cfg is a 'etc_hosts' entry in cloud-config
+    # etc_hosts
+    #   template_from_metadata:
+    #     public-ipv4: '0.0.0.0'
+    #     public-hostname: 'undefined-public-hostname'
+    #     private-ipv4: '0.0.0.0'
+    #     private-hostname: 'undefined-private-hostname'
+    #   default_addr: '0.0.0.0'
+    #   manage_mode: 'template'
+    parms = {'hostname': hostname, 'fqdn': fqdn}
+
+    if metadata:
+        for field, default in cfg.get('template_from_metadata', {}).iteritems():
+            parms[field.replace("-","_")] = metadata.get(field, default)
+
+    dev_default = cfg.get('default_addr', "0.0.0.0")
+    dev_default6 = cfg.get('default_addr6', "0.0.0.0")
+    devs = netinfo.netdev_info()
+    for dev, info in devs.items():
+        parms['devaddr_' + dev] = info.get('addr') or dev_default
+        parms['devaddr6_' + dev] = info.get('addr6') or dev_default6
+
+    return parms
+
+
 def handle(name, cfg, cloud, log, _args):
-    manage_hosts = util.get_cfg_option_str(cfg, "manage_etc_hosts", False)
+
+    # copy the old 'manage_etc_hosts' value to new location
+    hostscfg = cfg.get('etc_hosts', {})
+    if manage_hosts:
+        if 'manage_etc_hosts' in cfg:
+            hostscfg['manage_mode'] = cfg['manage_etc_hosts']
+    
+    manage_hosts = hostscfg['manage_mode']
+
     if util.translate_bool(manage_hosts, addons=['template']):
-        (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
-        if not hostname:
-            log.warn(("Option 'manage_etc_hosts' was set,"
-                     " but no hostname was found"))
-            return
 
         # Render from a template file
         tpl_fn_name = cloud.get_template_filename("hosts.%s" %
@@ -43,8 +74,16 @@
                                 " found for distro %s") %
                                 (cloud.distro.osfamily))
 
-        templater.render_to_file(tpl_fn_name, '/etc/hosts',
-                                {'hostname': hostname, 'fqdn': fqdn})
+        (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)
+        if not hostname:
+            log.warn(("Option 'manage_etc_hosts' was set,"
+                      " but no hostname was found"))
+            return
+
+        parms = get_template_parms(hostname, fqdn, hostscfg,
+                                   cloud.datasource.metadata)
+
+        templater.render_to_file(tpl_fn_name, '/etc/hosts', parms)
 
     elif manage_hosts == "localhost":
         (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud)

=== modified file 'cloudinit/settings.py'
--- cloudinit/settings.py	2012-08-20 05:28:14 +0000
+++ cloudinit/settings.py	2013-05-09 20:12:38 +0000
@@ -49,6 +49,17 @@
         },
         'distro': 'ubuntu',
     },
+    'etc_hosts': {
+        'template_from_metadata': {
+            'public-ipv4': '0.0.0.0',
+            'public-hostname': 'undefined-public-hostname',
+            'private-ipv4': '0.0.0.0',
+            'private-hostname': 'undefined-private-hostname',
+        },
+        'default_addr': '0.0.0.0',
+        'default_addr6': '::1',
+        'manage_mode': False
+    }
 }
 
 # Valid frequencies of handlers/modules

=== modified file 'doc/examples/cloud-config.txt'
--- doc/examples/cloud-config.txt	2013-04-03 22:29:32 +0000
+++ doc/examples/cloud-config.txt	2013-05-09 20:12:38 +0000
@@ -440,10 +440,25 @@
 #    true or 'template':
 #      on every boot, /etc/hosts will be re-written from 
 #      /etc/cloud/templates/hosts.tmpl.
-#      The strings '$hostname' and '$fqdn' are replaced in the template
-#      with the appropriate values.
 #      To make modifications persistant across a reboot, you must make 
-#      modificatoins to /etc/cloud/templates/hosts.tmpl
+#      modifications to /etc/cloud/templates/hosts.tmpl
+#
+#      You can use the following values in the template:
+#        $hostname: hostname
+#        $fqdn: fully qualified hostname
+#      You can reference a specific device's ipv4 or ipv6 address with:
+#        $devaddr_<name> (example: devaddr_eth0)
+#        $devaddr6_<name> (example: devaddr6_eth0)
+#      Be careful referencing addresses, as the network device might not be
+#      configured at the point when the update is done.
+#     
+#      Other values can be referenced directly from the metadata service:
+#      By default that list, and default value (if not present in metadata) are:
+#         template_name      metadata_name   default
+#         $public_ipv4       public-ipv4     0.0.0.0
+#         $public_hostname   public-hostname undefined-private-hostname
+#         $private_ipv4      public-ipv4     0.0.0.0
+#         $private_hostname  public-hostname undefined-private-hostname
 #
 #    localhost:
 #      This option ensures that an entry is present for fqdn as described in

=== added file 'tests/unittests/test_handler/test_handler_etc_hosts.py'
--- tests/unittests/test_handler/test_handler_etc_hosts.py	1970-01-01 00:00:00 +0000
+++ tests/unittests/test_handler/test_handler_etc_hosts.py	2013-05-09 20:12:38 +0000
@@ -0,0 +1,53 @@
+from mocker import MockerTestCase
+
+from cloudinit import netinfo
+from cloudinit.config.cc_update_etc_hosts import get_template_parms
+
+NETDEV_INFO = {
+    'eth0': {'addr': '', 'bcast': '', 'hwaddr': 'e8:9a:8f:66:13:ca',
+             'mask': '', 'up': True},
+    'lo': {'addr': '127.0.0.1', 'addr6': '::1/128', 'bcast': '',
+           'hwaddr': '', 'mask': '255.0.0.0', 'up': True},
+    'lxcbr0': {'addr': '10.0.3.1', 'addr6': 'fe80::8061:d9ff:fed6:deca/64',
+               'bcast': '10.0.3.255', 'hwaddr': '00:00:00:00:00:00',
+               'mask': '255.255.255.0', 'up': True},
+    'wlan0': {'addr': '10.155.36.209', 'addr6': 'fe80::d2df:9aff:fe1a:8fda/64',
+              'bcast': '10.155.63.255', 'hwaddr': 'd0:df:9a:1a:8f:da',
+              'mask': '255.255.224.0', 'up': True}
+}
+
+
+class TestTemplateParms(MockerTestCase):
+    def test_basic(self):
+        ndinfo = self.mocker.replace(netinfo.netdev_info,
+                            passthrough=False)
+        ndinfo()
+        self.mocker.result(NETDEV_INFO)
+        self.mocker.replay()
+
+        parms = get_template_parms("myhost", "myfqdn", {}, {})
+
+        self.assertEqual(parms['devaddr_wlan0'], '10.155.36.209')
+
+    def test_md_values(self):
+        ndinfo = self.mocker.replace(netinfo.netdev_info,
+                            passthrough=False)
+        ndinfo()
+        self.mocker.result(NETDEV_INFO)
+        self.mocker.replay()
+
+        nohost = 'NOHOSTHERE'
+        mycfg = {
+            'template_from_metadata': {
+                'public-ipv4': '0.0.0.0',
+                'public-hostname': nohost,
+            }
+        }
+        md = {'public-ipv4': '10.0.1.2'}
+        parms = get_template_parms("myhost", "myfqdn", mycfg, md)
+
+        self.assertEqual(parms['devaddr_wlan0'], '10.155.36.209')
+        self.assertEqual(parms['public_ipv4'], '10.0.1.2')
+        self.assertEqual(parms['public_hostname'], nohost)
+
+# vi: ts=4 expandtab