← Back to team overview

cloud-init-dev team mailing list archive

[Merge] lp:~harlowja/cloud-init/cloud-init-net-refactor into lp:cloud-init

 

Joshua Harlow has proposed merging lp:~harlowja/cloud-init/cloud-init-net-refactor into lp:cloud-init.

Requested reviews:
  cloud init development team (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~harlowja/cloud-init/cloud-init-net-refactor/+merge/293957
-- 
Your team cloud init development team is requested to review the proposed merge of lp:~harlowja/cloud-init/cloud-init-net-refactor into lp:cloud-init.
=== modified file 'cloudinit/net/__init__.py'
--- cloudinit/net/__init__.py	2016-04-15 20:21:05 +0000
+++ cloudinit/net/__init__.py	2016-05-06 00:03:46 +0000
@@ -262,7 +262,7 @@
 
 
 def parse_net_config_data(net_config):
-    """Parses the config, returns NetworkState dictionary
+    """Parses the config, returns NetworkState object
 
     :param net_config: curtin network config dict
     """

=== modified file 'cloudinit/net/network_state.py'
--- cloudinit/net/network_state.py	2016-03-23 16:05:22 +0000
+++ cloudinit/net/network_state.py	2016-05-06 00:03:46 +0000
@@ -15,6 +15,10 @@
 #   You should have received a copy of the GNU Affero General Public License
 #   along with Curtin.  If not, see <http://www.gnu.org/licenses/>.
 
+import copy
+
+import six
+
 from cloudinit import log as logging
 from cloudinit import util
 from cloudinit.util import yaml_dumps as dump_config
@@ -32,34 +36,75 @@
     state = util.read_conf(state_file)
     network_state = NetworkState()
     network_state.load(state)
-
     return network_state
 
 
-class NetworkState:
+class InvalidCommand(Exception):
+    pass
+
+
+def ensure_command_keys(required_keys):
+    required_keys = frozenset(required_keys)
+
+    def extract_missing(command):
+        missing_keys = set()
+        for key in required_keys:
+            if key not in command:
+                missing_keys.add(key)
+        return missing_keys
+
+    def wrapper(func):
+
+        @six.wraps(func)
+        def decorator(self, command, *args, **kwargs):
+            if required_keys:
+                missing_keys = extract_missing(command)
+                if missing_keys:
+                    raise InvalidCommand("Command missing %s of required"
+                                         " keys %s" % (missing_keys,
+                                                       required_keys))
+            return func(self, command, *args, **kwargs)
+
+        return decorator
+
+    return wrapper
+
+
+class CommandHandlerMeta(type):
+    """Metaclass that dynamically creates a 'command_handlers' attribute.
+
+    This will scan the to-be-created class for methods that start with
+    'handle_' and on finding those will populate a class attribute mapping
+    so that those methods can be quickly located and called.
+    """
+    def __new__(cls, name, parents, dct):
+        command_handlers = {}
+        for attr_name, attr in six.iteritems(dct):
+            if six.callable(attr) and attr_name.startswith('handle_'):
+                handles_what = attr_name[len('handle_'):]
+                if handles_what:
+                    command_handlers[handles_what] = attr
+        dct['command_handlers'] = command_handlers
+        return super(CommandHandlerMeta, cls).__new__(cls, name,
+                                                      parents, dct)
+
+
+@six.add_metaclass(CommandHandlerMeta)
+class NetworkState(object):
+
+    initial_network_state = {
+        'interfaces': {},
+        'routes': [],
+        'dns': {
+            'nameservers': [],
+            'search': [],
+        }
+    }
+
     def __init__(self, version=NETWORK_STATE_VERSION, config=None):
         self.version = version
         self.config = config
-        self.network_state = {
-            'interfaces': {},
-            'routes': [],
-            'dns': {
-                'nameservers': [],
-                'search': [],
-            }
-        }
-        self.command_handlers = self.get_command_handlers()
-
-    def get_command_handlers(self):
-        METHOD_PREFIX = 'handle_'
-        methods = filter(lambda x: callable(getattr(self, x)) and
-                         x.startswith(METHOD_PREFIX),  dir(self))
-        handlers = {}
-        for m in methods:
-            key = m.replace(METHOD_PREFIX, '')
-            handlers[key] = getattr(self, m)
-
-        return handlers
+        self.network_state = copy.deepcopy(self.initial_network_state)
 
     def dump(self):
         state = {
@@ -83,24 +128,30 @@
         # v1 - direct attr mapping, except version
         for key in [k for k in required_keys if k not in ['version']]:
             setattr(self, key, state[key])
-        self.command_handlers = self.get_command_handlers()
 
     def dump_network_state(self):
         return dump_config(self.network_state)
 
-    def parse_config(self):
+    def parse_config(self, skip_broken=True):
         # rebuild network state
         for command in self.config:
-            handler = self.command_handlers.get(command['type'])
-            handler(command)
-
-    def valid_command(self, command, required_keys):
-        if not required_keys:
-            return False
-
-        found_keys = [key for key in command.keys() if key in required_keys]
-        return len(found_keys) == len(required_keys)
-
+            command_type = command['type']
+            try:
+                handler = self.command_handlers[command_type]
+            except KeyError:
+                raise RuntimeError("No handler found for"
+                                   " command '%s'" % command_type)
+            try:
+                handler(command)
+            except InvalidCommand:
+                if not skip_broken:
+                    raise
+                else:
+                    LOG.warn("Skipping invalid command: %s", command,
+                             exc_info=True)
+                    LOG.debug(self.dump_network_state())
+
+    @ensure_command_keys(['name'])
     def handle_physical(self, command):
         '''
         command = {
@@ -112,13 +163,6 @@
              ]
         }
         '''
-        required_keys = [
-            'name',
-        ]
-        if not self.valid_command(command, required_keys):
-            LOG.warn('Skipping Invalid command: {}'.format(command))
-            LOG.debug(self.dump_network_state())
-            return
 
         interfaces = self.network_state.get('interfaces')
         iface = interfaces.get(command['name'], {})
@@ -149,6 +193,7 @@
         self.network_state['interfaces'].update({command.get('name'): iface})
         self.dump_network_state()
 
+    @ensure_command_keys(['name', 'vlan_id', 'vlan_link'])
     def handle_vlan(self, command):
         '''
             auto eth0.222
@@ -158,16 +203,6 @@
                     hwaddress ether BC:76:4E:06:96:B3
                     vlan-raw-device eth0
         '''
-        required_keys = [
-            'name',
-            'vlan_link',
-            'vlan_id',
-        ]
-        if not self.valid_command(command, required_keys):
-            print('Skipping Invalid command: {}'.format(command))
-            print(self.dump_network_state())
-            return
-
         interfaces = self.network_state.get('interfaces')
         self.handle_physical(command)
         iface = interfaces.get(command.get('name'), {})
@@ -175,6 +210,7 @@
         iface['vlan_id'] = command.get('vlan_id')
         interfaces.update({iface['name']: iface})
 
+    @ensure_command_keys(['name', 'bond_interfaces', 'params'])
     def handle_bond(self, command):
         '''
     #/etc/network/interfaces
@@ -200,15 +236,6 @@
          bond-updelay 200
          bond-lacp-rate 4
         '''
-        required_keys = [
-            'name',
-            'bond_interfaces',
-            'params',
-        ]
-        if not self.valid_command(command, required_keys):
-            print('Skipping Invalid command: {}'.format(command))
-            print(self.dump_network_state())
-            return
 
         self.handle_physical(command)
         interfaces = self.network_state.get('interfaces')
@@ -236,6 +263,7 @@
                 bond_if.update({param: val})
             self.network_state['interfaces'].update({ifname: bond_if})
 
+    @ensure_command_keys(['name', 'bridge_interfaces', 'params'])
     def handle_bridge(self, command):
         '''
             auto br0
@@ -263,15 +291,6 @@
             "bridge_waitport",
         ]
         '''
-        required_keys = [
-            'name',
-            'bridge_interfaces',
-            'params',
-        ]
-        if not self.valid_command(command, required_keys):
-            print('Skipping Invalid command: {}'.format(command))
-            print(self.dump_network_state())
-            return
 
         # find one of the bridge port ifaces to get mac_addr
         # handle bridge_slaves
@@ -295,15 +314,8 @@
 
         interfaces.update({iface['name']: iface})
 
+    @ensure_command_keys(['address'])
     def handle_nameserver(self, command):
-        required_keys = [
-            'address',
-        ]
-        if not self.valid_command(command, required_keys):
-            print('Skipping Invalid command: {}'.format(command))
-            print(self.dump_network_state())
-            return
-
         dns = self.network_state.get('dns')
         if 'address' in command:
             addrs = command['address']
@@ -318,15 +330,8 @@
             for path in paths:
                 dns['search'].append(path)
 
+    @ensure_command_keys(['destination'])
     def handle_route(self, command):
-        required_keys = [
-            'destination',
-        ]
-        if not self.valid_command(command, required_keys):
-            print('Skipping Invalid command: {}'.format(command))
-            print(self.dump_network_state())
-            return
-
         routes = self.network_state.get('routes')
         network, cidr = command['destination'].split("/")
         netmask = cidr2mask(int(cidr))
@@ -376,71 +381,3 @@
         return ipv4mask2cidr(mask)
     else:
         return mask
-
-
-if __name__ == '__main__':
-    import sys
-    import random
-    from cloudinit import net
-
-    def load_config(nc):
-        version = nc.get('version')
-        config = nc.get('config')
-        return (version, config)
-
-    def test_parse(network_config):
-        (version, config) = load_config(network_config)
-        ns1 = NetworkState(version=version, config=config)
-        ns1.parse_config()
-        random.shuffle(config)
-        ns2 = NetworkState(version=version, config=config)
-        ns2.parse_config()
-        print("----NS1-----")
-        print(ns1.dump_network_state())
-        print()
-        print("----NS2-----")
-        print(ns2.dump_network_state())
-        print("NS1 == NS2 ?=> {}".format(
-            ns1.network_state == ns2.network_state))
-        eni = net.render_interfaces(ns2.network_state)
-        print(eni)
-        udev_rules = net.render_persistent_net(ns2.network_state)
-        print(udev_rules)
-
-    def test_dump_and_load(network_config):
-        print("Loading network_config into NetworkState")
-        (version, config) = load_config(network_config)
-        ns1 = NetworkState(version=version, config=config)
-        ns1.parse_config()
-        print("Dumping state to file")
-        ns1_dump = ns1.dump()
-        ns1_state = "/tmp/ns1.state"
-        with open(ns1_state, "w+") as f:
-            f.write(ns1_dump)
-
-        print("Loading state from file")
-        ns2 = from_state_file(ns1_state)
-        print("NS1 == NS2 ?=> {}".format(
-            ns1.network_state == ns2.network_state))
-
-    def test_output(network_config):
-        (version, config) = load_config(network_config)
-        ns1 = NetworkState(version=version, config=config)
-        ns1.parse_config()
-        random.shuffle(config)
-        ns2 = NetworkState(version=version, config=config)
-        ns2.parse_config()
-        print("NS1 == NS2 ?=> {}".format(
-            ns1.network_state == ns2.network_state))
-        eni_1 = net.render_interfaces(ns1.network_state)
-        eni_2 = net.render_interfaces(ns2.network_state)
-        print(eni_1)
-        print(eni_2)
-        print("eni_1 == eni_2 ?=> {}".format(
-            eni_1 == eni_2))
-
-    y = util.read_conf(sys.argv[1])
-    network_config = y.get('network')
-    test_parse(network_config)
-    test_dump_and_load(network_config)
-    test_output(network_config)

=== modified file 'cloudinit/sources/DataSourceConfigDrive.py'
--- cloudinit/sources/DataSourceConfigDrive.py	2016-04-29 13:04:36 +0000
+++ cloudinit/sources/DataSourceConfigDrive.py	2016-05-06 00:03:46 +0000
@@ -61,7 +61,7 @@
         mstr += "[source=%s]" % (self.source)
         return mstr
 
-    def get_data(self):
+    def get_data(self, skip_first_boot=False):
         found = None
         md = {}
         results = {}
@@ -119,7 +119,8 @@
         # instance-id
         prev_iid = get_previous_iid(self.paths)
         cur_iid = md['instance-id']
-        if prev_iid != cur_iid and self.dsmode == "local":
+        if prev_iid != cur_iid and \
+           self.dsmode == "local" and not skip_first_boot:
             on_first_boot(results, distro=self.distro)
 
         # dsmode != self.dsmode here if:
@@ -163,7 +164,8 @@
     def network_config(self):
         if self._network_config is None:
             if self.network_json is not None:
-                self._network_config = convert_network_data(self.network_json)
+                self._network_config = openstack.convert_net_json(
+                    self.network_json)
         return self._network_config
 
 
@@ -303,122 +305,3 @@
 # Return a list of data sources that match this set of dependencies
 def get_datasource_list(depends):
     return sources.list_from_depends(depends, datasources)
-
-
-# Convert OpenStack ConfigDrive NetworkData json to network_config yaml
-def convert_network_data(network_json=None):
-    """Return a dictionary of network_config by parsing provided
-       OpenStack ConfigDrive NetworkData json format
-
-    OpenStack network_data.json provides a 3 element dictionary
-      - "links" (links are network devices, physical or virtual)
-      - "networks" (networks are ip network configurations for one or more
-                    links)
-      -  services (non-ip services, like dns)
-
-    networks and links are combined via network items referencing specific
-    links via a 'link_id' which maps to a links 'id' field.
-
-    To convert this format to network_config yaml, we first iterate over the
-    links and then walk the network list to determine if any of the networks
-    utilize the current link; if so we generate a subnet entry for the device
-
-    We also need to map network_data.json fields to network_config fields. For
-    example, the network_data links 'id' field is equivalent to network_config
-    'name' field for devices.  We apply more of this mapping to the various
-    link types that we encounter.
-
-    There are additional fields that are populated in the network_data.json
-    from OpenStack that are not relevant to network_config yaml, so we
-    enumerate a dictionary of valid keys for network_yaml and apply filtering
-    to drop these superflous keys from the network_config yaml.
-    """
-    if network_json is None:
-        return None
-
-    # dict of network_config key for filtering network_json
-    valid_keys = {
-        'physical': [
-            'name',
-            'type',
-            'mac_address',
-            'subnets',
-            'params',
-        ],
-        'subnet': [
-            'type',
-            'address',
-            'netmask',
-            'broadcast',
-            'metric',
-            'gateway',
-            'pointopoint',
-            'mtu',
-            'scope',
-            'dns_nameservers',
-            'dns_search',
-            'routes',
-        ],
-    }
-
-    links = network_json.get('links', [])
-    networks = network_json.get('networks', [])
-    services = network_json.get('services', [])
-
-    config = []
-    for link in links:
-        subnets = []
-        cfg = {k: v for k, v in link.items()
-               if k in valid_keys['physical']}
-        cfg.update({'name': link['id']})
-        for network in [net for net in networks
-                        if net['link'] == link['id']]:
-            subnet = {k: v for k, v in network.items()
-                      if k in valid_keys['subnet']}
-            if 'dhcp' in network['type']:
-                t = 'dhcp6' if network['type'].startswith('ipv6') else 'dhcp4'
-                subnet.update({
-                    'type': t,
-                })
-            else:
-                subnet.update({
-                    'type': 'static',
-                    'address': network.get('ip_address'),
-                })
-            subnets.append(subnet)
-        cfg.update({'subnets': subnets})
-        if link['type'] in ['ethernet', 'vif', 'ovs', 'phy']:
-            cfg.update({
-                'type': 'physical',
-                'mac_address': link['ethernet_mac_address']})
-        elif link['type'] in ['bond']:
-            params = {}
-            for k, v in link.items():
-                if k == 'bond_links':
-                    continue
-                elif k.startswith('bond'):
-                    params.update({k: v})
-            cfg.update({
-                'bond_interfaces': copy.deepcopy(link['bond_links']),
-                'params': params,
-            })
-        elif link['type'] in ['vlan']:
-            cfg.update({
-                'name': "%s.%s" % (link['vlan_link'],
-                                   link['vlan_id']),
-                'vlan_link': link['vlan_link'],
-                'vlan_id': link['vlan_id'],
-                'mac_address': link['vlan_mac_address'],
-            })
-        else:
-            raise ValueError(
-                'Unknown network_data link type: %s' % link['type'])
-
-        config.append(cfg)
-
-    for service in services:
-        cfg = service
-        cfg.update({'type': 'nameserver'})
-        config.append(cfg)
-
-    return {'version': 1, 'config': config}

=== modified file 'cloudinit/sources/helpers/openstack.py'
--- cloudinit/sources/helpers/openstack.py	2016-03-23 19:17:10 +0000
+++ cloudinit/sources/helpers/openstack.py	2016-05-06 00:03:46 +0000
@@ -474,6 +474,127 @@
                                                retries=self.retries)
 
 
+def convert_net_json(network_json):
+    """Return a dictionary of network_config by parsing provided
+       OpenStack ConfigDrive NetworkData json format
+
+    OpenStack network_data.json provides a 3 element dictionary
+      - "links" (links are network devices, physical or virtual)
+      - "networks" (networks are ip network configurations for one or more
+                    links)
+      -  services (non-ip services, like dns)
+
+    networks and links are combined via network items referencing specific
+    links via a 'link_id' which maps to a links 'id' field.
+
+    To convert this format to network_config yaml, we first iterate over the
+    links and then walk the network list to determine if any of the networks
+    utilize the current link; if so we generate a subnet entry for the device
+
+    We also need to map network_data.json fields to network_config fields. For
+    example, the network_data links 'id' field is equivalent to network_config
+    'name' field for devices.  We apply more of this mapping to the various
+    link types that we encounter.
+
+    There are additional fields that are populated in the network_data.json
+    from OpenStack that are not relevant to network_config yaml, so we
+    enumerate a dictionary of valid keys for network_yaml and apply filtering
+    to drop these superflous keys from the network_config yaml.
+    """
+
+    # Dict of network_config key for filtering network_json
+    valid_keys = {
+        'physical': [
+            'name',
+            'type',
+            'mac_address',
+            'subnets',
+            'params',
+        ],
+        'subnet': [
+            'type',
+            'address',
+            'netmask',
+            'broadcast',
+            'metric',
+            'gateway',
+            'pointopoint',
+            'mtu',
+            'scope',
+            'dns_nameservers',
+            'dns_search',
+            'routes',
+        ],
+    }
+
+    links = network_json.get('links', [])
+    networks = network_json.get('networks', [])
+    services = network_json.get('services', [])
+
+    config = []
+    for link in links:
+        subnets = []
+        cfg = {k: v for k, v in link.items()
+               if k in valid_keys['physical']}
+        cfg.update({'name': link['id']})
+        for network in [net for net in networks
+                        if net['link'] == link['id']]:
+            subnet = {k: v for k, v in network.items()
+                      if k in valid_keys['subnet']}
+            if 'dhcp' in network['type']:
+                t = 'dhcp6' if network['type'].startswith('ipv6') else 'dhcp4'
+                subnet.update({
+                    'type': t,
+                })
+            else:
+                subnet.update({
+                    'type': 'static',
+                    'address': network.get('ip_address'),
+                })
+            subnets.append(subnet)
+        cfg.update({'subnets': subnets})
+        if link['type'] in ['ethernet', 'vif', 'ovs', 'phy']:
+            cfg.update({
+                'type': 'physical',
+                'mac_address': link['ethernet_mac_address']})
+        elif link['type'] in ['bond']:
+            params = {}
+            for k, v in link.items():
+                if k == 'bond_links':
+                    continue
+                elif k.startswith('bond'):
+                    params.update({k: v})
+            cfg.update({
+                'bond_interfaces': copy.deepcopy(link['bond_links']),
+                'params': params,
+            })
+        elif link['type'] in ['vlan']:
+            cfg.update({
+                'name': "%s.%s" % (link['vlan_link'],
+                                   link['vlan_id']),
+                'vlan_link': link['vlan_link'],
+                'vlan_id': link['vlan_id'],
+                'mac_address': link['vlan_mac_address'],
+            })
+        elif link['type'] in ['bridge']:
+            cfg.update({
+                'type': 'bridge',
+                'mac_address': link['ethernet_mac_address'],
+                'mtu': link['mtu']})
+        else:
+            raise ValueError(
+                'Unknown network_data link type: %s' % link['type'])
+
+        config.append(cfg)
+
+    for service in services:
+        cfg = copy.deepcopy(service)
+        cfg.update({'type': 'nameserver'})
+        config.append(cfg)
+
+    return {'version': 1, 'config': config}
+
+
 def convert_vendordata_json(data, recurse=True):
     """ data: a loaded json *object* (strings, arrays, dicts).
     return something suitable for cloudinit vendordata_raw.

=== modified file 'tests/unittests/test_datasource/test_configdrive.py'
--- tests/unittests/test_datasource/test_configdrive.py	2016-03-23 18:32:23 +0000
+++ tests/unittests/test_datasource/test_configdrive.py	2016-05-06 00:03:46 +0000
@@ -355,6 +355,14 @@
         self.assertEqual(myds.get_public_ssh_keys(),
                          [OSTACK_META['public_keys']['mykey']])
 
+
+class TestNetJson(TestCase):
+    def setUp(self):
+        super(TestNetJson, self).setUp()
+        self.tmp = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.tmp)
+        self.maxDiff = None
+
     def test_network_data_is_found(self):
         """Verify that network_data is present in ds in config-drive-v2."""
         populate_dir(self.tmp, CFG_DRIVE_FILES_V2)
@@ -365,31 +373,112 @@
         """Verify that network_data is converted and present on ds object."""
         populate_dir(self.tmp, CFG_DRIVE_FILES_V2)
         myds = cfg_ds_from_dir(self.tmp)
-        network_config = ds.convert_network_data(NETWORK_DATA)
+        network_config = openstack.convert_net_json(NETWORK_DATA)
         self.assertEqual(myds.network_config, network_config)
 
+    def test_network_config_conversions(self):
+        """Tests a bunch of input network json and checks the expected conversions."""
+        in_datas = [
+            NETWORK_DATA,
+            {
+                'services': [{'type': 'dns', 'address': '172.19.0.12'}],
+                'networks': [
+                    {'network_id': 'dacd568d-5be6-4786-91fe-750c374b78b4',
+                     'type': 'ipv4', 'netmask': '255.255.252.0', 
+                     'link': 'tap1a81968a-79', 
+                     'routes': [
+                        {
+                            'netmask': '0.0.0.0',
+                            'network': '0.0.0.0', 
+                            'gateway': '172.19.3.254'
+                        },
+                      ],
+                      'ip_address': '172.19.1.34', 
+                      'id': 'network0',
+                }],
+                'links': [
+                    {'type': 'bridge',
+                     'vif_id': '1a81968a-797a-400f-8a80-567f997eb93f', 
+                     'ethernet_mac_address': 'fa:16:3e:ed:9a:59', 
+                     'id': 'tap1a81968a-79', 'mtu': None}]
+            },
+        ]
+        out_datas = [
+            {
+                'version': 1,
+                'config': [
+                    {
+                        'subnets': [{'type': 'dhcp4'}],
+                        'type': 'physical',
+                        'mac_address': 'fa:16:3e:69:b0:58',
+                        'name': 'tap2ecc7709-b3',
+                    },
+                    {
+                        'subnets': [{'type': 'dhcp4'}],
+                        'type': 'physical',
+                        'mac_address': 'fa:16:3e:d4:57:ad',
+                        'name': 'tap2f88d109-5b',
+                    },
+                    {
+                        'subnets': [{'type': 'dhcp4'}],
+                        'type': 'physical',
+                        'mac_address': 'fa:16:3e:05:30:fe',
+                        'name': 'tap1a5382f8-04',
+                    },
+                    {
+                        'type': 'nameserver',
+                        'address': '199.204.44.24',
+                    },
+                    {
+                        'type': 'nameserver',
+                        'address': '199.204.47.54',
+                    }
+                ],
+
+            },
+            {
+                'version': 1,
+                'config': [
+                    {
+                        'name': 'tap1a81968a-79',
+                        'mac_address': 'fa:16:3e:ed:9a:59',
+                        'mtu': None,
+                        'type': 'bridge',
+                        'subnets': [
+                            {
+                                'address': '172.19.1.34',
+                                'netmask': '255.255.252.0',
+                                'type': 'static',
+                                'routes': [{
+                                    'gateway': '172.19.3.254',
+                                    'netmask': '0.0.0.0',
+                                    'network': '0.0.0.0',
+                                }],
+                            }
+                        ]
+                    },
+                    {
+                        'type': 'nameserver',
+                        'address': '172.19.0.12',
+                    }
+                ],
+            },
+        ]
+        for in_data, out_data in zip(in_datas, out_datas):
+            self.assertEqual(openstack.convert_net_json(in_data),
+                             out_data)
+
 
 def cfg_ds_from_dir(seed_d):
-    found = ds.read_config_drive(seed_d)
     cfg_ds = ds.DataSourceConfigDrive(settings.CFG_BUILTIN, None,
                                       helpers.Paths({}))
-    populate_ds_from_read_config(cfg_ds, seed_d, found)
+    cfg_ds.seed_dir = seed_d
+    if not cfg_ds.get_data(skip_first_boot=True):
+        raise RuntimeError("Data source did not extract itself from"
+                           " seed directory %s" % seed_d)
     return cfg_ds
 
 
-def populate_ds_from_read_config(cfg_ds, source, results):
-    """Patch the DataSourceConfigDrive from the results of
-    read_config_drive_dir hopefully in line with what it would have
-    if cfg_ds.get_data had been successfully called"""
-    cfg_ds.source = source
-    cfg_ds.metadata = results.get('metadata')
-    cfg_ds.ec2_metadata = results.get('ec2-metadata')
-    cfg_ds.userdata_raw = results.get('userdata')
-    cfg_ds.version = results.get('version')
-    cfg_ds.network_json = results.get('networkdata')
-    cfg_ds._network_config = ds.convert_network_data(cfg_ds.network_json)
-
-
 def populate_dir(seed_dir, files):
     for (name, content) in files.items():
         path = os.path.join(seed_dir, name)
@@ -400,7 +489,6 @@
             mode = "w"
         else:
             mode = "wb"
-
         with open(path, mode) as fp:
             fp.write(content)
 

=== modified file 'tests/unittests/test_net.py'
--- tests/unittests/test_net.py	2016-04-15 19:13:07 +0000
+++ tests/unittests/test_net.py	2016-05-06 00:03:46 +0000
@@ -121,6 +121,7 @@
         self.assertEqual(found, self.simple_cfg)
 
 
+
 def _gzip_data(data):
     with io.BytesIO() as iobuf:
         gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf)


Follow ups