35
36
DEFAULT_METADATA = {
36
37
"instance-id": DEFAULT_IID,
38
VALID_DSMODES = ("local", "net", "pass", "disabled")
39
39
FS_TYPES = ('vfat', 'iso9660')
40
40
LABEL_TYPES = ('config-2',)
41
41
POSSIBLE_MOUNTS = ('sr', 'cd')
47
47
def __init__(self, sys_cfg, distro, paths):
48
48
super(DataSourceConfigDrive, self).__init__(sys_cfg, distro, paths)
51
50
self.seed_dir = os.path.join(paths.seed_dir, 'config_drive')
52
51
self.version = None
53
52
self.ec2_metadata = None
54
53
self._network_config = None
55
54
self.network_json = None
55
self.network_eni = None
99
99
md = results.get('metadata', {})
100
100
md = util.mergemanydict([md, DEFAULT_METADATA])
101
user_dsmode = results.get('dsmode', None)
102
if user_dsmode not in VALID_DSMODES + (None,):
103
LOG.warn("User specified invalid mode: %s", user_dsmode)
106
dsmode = get_ds_mode(cfgdrv_ver=results['version'],
107
ds_cfg=self.ds_cfg.get('dsmode'),
110
if dsmode == "disabled":
111
# most likely user specified
102
self.dsmode = self._determine_dsmode(
103
[results.get('dsmode'), self.ds_cfg.get('dsmode'),
104
sources.DSMODE_PASS if results['version'] == 1 else None])
106
if self.dsmode == sources.DSMODE_DISABLED:
114
# TODO(smoser): fix this, its dirty.
115
# we want to do some things (writing files and network config)
116
# only on first boot, and even then, we want to do so in the
117
# local datasource (so they happen earlier) even if the configured
118
# dsmode is 'net' or 'pass'. To do this, we check the previous
109
# This is legacy and sneaky. If dsmode is 'pass' then write
110
# 'injected files' and apply legacy ENI network format.
120
111
prev_iid = get_previous_iid(self.paths)
121
112
cur_iid = md['instance-id']
122
if prev_iid != cur_iid and self.dsmode == "local":
113
if prev_iid != cur_iid and self.dsmode == sources.DSMODE_PASS:
123
114
on_first_boot(results, distro=self.distro)
125
# dsmode != self.dsmode here if:
126
# * dsmode = "pass", pass means it should only copy files and then
127
# pass to another datasource
128
# * dsmode = "net" and self.dsmode = "local"
129
# so that user boothooks would be applied with network, the
130
# local datasource just gets out of the way, and lets the net claim
131
if dsmode != self.dsmode:
132
LOG.debug("%s: not claiming datasource, dsmode=%s", self, dsmode)
115
LOG.debug("%s: not claiming datasource, dsmode=%s", self,
135
119
self.source = found
147
131
LOG.warn("Invalid content in vendor-data: %s", e)
148
132
self.vendordata_raw = None
151
self.network_json = results.get('networkdata')
152
except ValueError as e:
153
LOG.warn("Invalid content in network-data: %s", e)
154
self.network_json = None
134
# network_config is an /etc/network/interfaces formated file and is
135
# obsolete compared to networkdata (from network_data.json) but both
137
self.network_eni = results.get("network_config")
138
self.network_json = results.get('networkdata')
158
141
def check_instance_id(self, sys_cfg):
163
146
def network_config(self):
164
147
if self._network_config is None:
165
148
if self.network_json is not None:
149
LOG.debug("network config provided via network_json")
166
150
self._network_config = convert_network_data(self.network_json)
151
elif self.network_eni is not None:
152
self._network_config = net.convert_eni_data(self.network_eni)
153
LOG.debug("network config provided via converted eni data")
155
LOG.debug("no network configuration available")
167
156
return self._network_config
170
class DataSourceConfigDriveNet(DataSourceConfigDrive):
171
def __init__(self, sys_cfg, distro, paths):
172
DataSourceConfigDrive.__init__(self, sys_cfg, distro, paths)
176
def get_ds_mode(cfgdrv_ver, ds_cfg=None, user=None):
177
"""Determine what mode should be used.
178
valid values are 'pass', 'disabled', 'local', 'net'
180
# user passed data trumps everything
184
if ds_cfg is not None:
187
# at config-drive version 1, the default behavior was pass. That
188
# meant to not use use it as primary data source, but expect a ec2 metadata
189
# source. for version 2, we default to 'net', which means
190
# the DataSourceConfigDriveNet, would be used.
192
# this could change in the future. If there was definitive metadata
193
# that indicated presense of an openstack metadata service, then
194
# we could change to 'pass' by default also. The motivation for that
195
# would be 'cloud-init query' as the web service could be more dynamic
201
159
def read_config_drive(source_dir):
202
160
reader = openstack.ConfigDriveReader(source_dir)
232
190
net_conf = data.get("network_config", '')
233
191
if net_conf and distro:
234
LOG.debug("Updating network interfaces from config drive")
192
LOG.warn("Updating network interfaces from config drive")
235
193
distro.apply_network(net_conf)
236
files = data.get('files', {})
194
write_injected_files(data.get('files'))
197
def write_injected_files(files):
238
199
LOG.debug("Writing %s injected files", len(files))
239
200
for (filename, content) in files.items():
296
# Used to match classes to dependencies
298
(DataSourceConfigDrive, (sources.DEP_FILESYSTEM, )),
299
(DataSourceConfigDriveNet, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)),
303
# Return a list of data sources that match this set of dependencies
304
def get_datasource_list(depends):
305
return sources.list_from_depends(depends, datasources)
308
257
# Convert OpenStack ConfigDrive NetworkData json to network_config yaml
309
def convert_network_data(network_json=None):
258
def convert_network_data(network_json=None, known_macs=None):
310
259
"""Return a dictionary of network_config by parsing provided
311
260
OpenStack ConfigDrive NetworkData json format
371
320
cfg = {k: v for k, v in link.items()
372
321
if k in valid_keys['physical']}
373
cfg.update({'name': link['id']})
374
for network in [net for net in networks
375
if net['link'] == link['id']]:
322
# 'name' is not in openstack spec yet, but we will support it if it is
323
# present. The 'id' in the spec is currently implemented as the host
324
# nic's name, meaning something like 'tap-adfasdffd'. We do not want
325
# to name guest devices with such ugly names.
327
cfg['name'] = link['name']
329
for network in [n for n in networks
330
if n['link'] == link['id']]:
376
331
subnet = {k: v for k, v in network.items()
377
332
if k in valid_keys['subnet']}
378
333
if 'dhcp' in network['type']:
388
343
subnets.append(subnet)
389
344
cfg.update({'subnets': subnets})
390
if link['type'] in ['ethernet', 'vif', 'ovs', 'phy']:
345
if link['type'] in ['ethernet', 'vif', 'ovs', 'phy', 'bridge']:
392
347
'type': 'physical',
393
348
'mac_address': link['ethernet_mac_address']})
417
372
config.append(cfg)
374
need_names = [d for d in config
375
if d.get('type') == 'physical' and 'name' not in d]
378
if known_macs is None:
379
known_macs = net.get_interfaces_by_mac()
382
mac = d.get('mac_address')
384
raise ValueError("No mac_address or name entry for %s" % d)
385
if mac not in known_macs:
386
raise ValueError("Unable to find a system nic for %s" % d)
387
d['name'] = known_macs[mac]
419
389
for service in services:
421
391
cfg.update({'type': 'nameserver'})
422
392
config.append(cfg)
424
394
return {'version': 1, 'config': config}
397
# Legacy: Must be present in case we load an old pkl object
398
DataSourceConfigDriveNet = DataSourceConfigDrive
400
# Used to match classes to dependencies
402
(DataSourceConfigDrive, (sources.DEP_FILESYSTEM, )),
406
# Return a list of data sources that match this set of dependencies
407
def get_datasource_list(depends):
408
return sources.list_from_depends(depends, datasources)