summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
authorScott Moser <smoser@ubuntu.com>2016-03-23 11:00:37 -0400
committerScott Moser <smoser@ubuntu.com>2016-03-23 11:00:37 -0400
commitd32116c3468e5c394b56d078ceef86d416d83b3a (patch)
tree6c92d8bc5edfd9726106946bd57e9c8acc26f86a /cloudinit
parent6082a5b0c2b1a52ddbf63bfd80331f28f8cdc4fa (diff)
parent5b3cad36be8981cd12cffdf5c5e539b522404000 (diff)
downloadvyos-cloud-init-d32116c3468e5c394b56d078ceef86d416d83b3a.tar.gz
vyos-cloud-init-d32116c3468e5c394b56d078ceef86d416d83b3a.zip
merge from trunk.net1
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/distros/__init__.py2
-rw-r--r--cloudinit/distros/debian.py9
-rw-r--r--cloudinit/net/__init__.py131
-rw-r--r--cloudinit/sources/DataSourceNoCloud.py46
-rw-r--r--cloudinit/sources/__init__.py4
-rw-r--r--cloudinit/stages.py90
6 files changed, 219 insertions, 63 deletions
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 74b484a7..418421b9 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -135,7 +135,7 @@ class Distro(object):
return self._bring_up_interfaces(dev_names)
return False
- def apply_network_config(self, netconfig, bring_up=True):
+ def apply_network_config(self, netconfig, bring_up=False):
# Write it out
dev_names = self._write_network_config(netconfig)
# Now try to bring them up
diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py
index c7a4ba07..b14fa3e2 100644
--- a/cloudinit/distros/debian.py
+++ b/cloudinit/distros/debian.py
@@ -46,7 +46,8 @@ APT_GET_WRAPPER = {
class Distro(distros.Distro):
hostname_conf_fn = "/etc/hostname"
locale_conf_fn = "/etc/default/locale"
- network_conf_fn = "/etc/network/interfaces"
+ network_conf_fn = "/etc/network/interfaces.d/50-cloud-init.cfg"
+ links_prefix = "/etc/systemd/network/50-cloud-init-"
def __init__(self, name, cfg, paths):
distros.Distro.__init__(self, name, cfg, paths)
@@ -79,8 +80,10 @@ class Distro(distros.Distro):
def _write_network_config(self, netconfig):
ns = net.parse_net_config_data(netconfig)
- ns = net.merge_from_cmdline_config(ns)
- net.render_network_state(network_state=ns, target="/")
+ net.render_network_state(target="/", network_state=ns,
+ eni=self.network_conf_fn,
+ links_prefix=self.links_prefix)
+ util.del_file("/etc/network/interfaces.d/eth0.cfg")
return []
def _bring_up_interfaces(self, device_names):
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
index e2dcaee7..0560fa45 100644
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -29,6 +29,7 @@ from . import network_state
LOG = logging.getLogger(__name__)
SYS_CLASS_NET = "/sys/class/net/"
+LINKS_FNAME_PREFIX = "etc/systemd/network/50-cloud-init-"
NET_CONFIG_OPTIONS = [
"address", "netmask", "broadcast", "network", "metric", "gateway",
@@ -46,6 +47,8 @@ NET_CONFIG_BRIDGE_OPTIONS = [
"bridge_hello", "bridge_maxage", "bridge_maxwait", "bridge_stp",
]
+DEFAULT_PRIMARY_INTERFACE = 'eth0'
+
def sys_dev_path(devname, path=""):
return SYS_CLASS_NET + devname + "/" + path
@@ -510,18 +513,126 @@ def render_interfaces(network_state):
return content
-def render_network_state(target, network_state):
- eni = 'etc/network/interfaces'
- netrules = 'etc/udev/rules.d/70-persistent-net.rules'
+def render_network_state(target, network_state, eni="etc/network/interfaces",
+ links_prefix=LINKS_FNAME_PREFIX,
+ netrules='etc/udev/rules.d/70-persistent-net.rules'):
- eni = os.path.sep.join((target, eni,))
- util.ensure_dir(os.path.dirname(eni))
- with open(eni, 'w+') as f:
+ fpeni = os.path.sep.join((target, eni,))
+ util.ensure_dir(os.path.dirname(fpeni))
+ with open(fpeni, 'w+') as f:
f.write(render_interfaces(network_state))
- netrules = os.path.sep.join((target, netrules,))
- util.ensure_dir(os.path.dirname(netrules))
- with open(netrules, 'w+') as f:
- f.write(render_persistent_net(network_state))
+ if netrules:
+ netrules = os.path.sep.join((target, netrules,))
+ util.ensure_dir(os.path.dirname(netrules))
+ with open(netrules, 'w+') as f:
+ f.write(render_persistent_net(network_state))
+
+ if links_prefix:
+ render_systemd_links(target, network_state, links_prefix)
+
+
+def render_systemd_links(target, network_state,
+ links_prefix=LINKS_FNAME_PREFIX):
+ fp_prefix = os.path.sep.join((target, links_prefix))
+ for f in glob.glob(fp_prefix + "*"):
+ os.unlink(f)
+
+ interfaces = network_state.get('interfaces')
+ for iface in interfaces.values():
+ if (iface['type'] == 'physical' and 'name' in iface and
+ 'mac_address' in iface):
+ fname = fp_prefix + iface['name'] + ".link"
+ with open(fname, "w") as fp:
+ fp.write("\n".join([
+ "[Match]",
+ "MACAddress=" + iface['mac_address'],
+ "",
+ "[Link]",
+ "Name=" + iface['name'],
+ ""
+ ]))
+
+
+def is_disabled_cfg(cfg):
+ if not cfg or not isinstance(cfg, dict):
+ return False
+ return cfg.get('config') == "disabled"
+
+
+def generate_fallback_config():
+ """Determine which attached net dev is most likely to have a connection and
+ generate network state to run dhcp on that interface"""
+ # by default use eth0 as primary interface
+ nconf = {'config': [], 'version': 1}
+
+ # get list of interfaces that could have connections
+ invalid_interfaces = set(['lo'])
+ potential_interfaces = set(os.listdir(SYS_CLASS_NET))
+ potential_interfaces = potential_interfaces.difference(invalid_interfaces)
+ # sort into interfaces with carrier, interfaces which could have carrier,
+ # and ignore interfaces that are definitely disconnected
+ connected = []
+ possibly_connected = []
+ for interface in potential_interfaces:
+ try:
+ sysfs_carrier = os.path.join(SYS_CLASS_NET, interface, 'carrier')
+ carrier = int(util.load_file(sysfs_carrier).strip())
+ if carrier:
+ connected.append(interface)
+ continue
+ except OSError:
+ pass
+ # check if nic is dormant or down, as this may make a nick appear to
+ # not have a carrier even though it could acquire one when brought
+ # online by dhclient
+ try:
+ sysfs_dormant = os.path.join(SYS_CLASS_NET, interface, 'dormant')
+ dormant = int(util.load_file(sysfs_dormant).strip())
+ if dormant:
+ possibly_connected.append(interface)
+ continue
+ except OSError:
+ pass
+ try:
+ sysfs_operstate = os.path.join(SYS_CLASS_NET, interface,
+ 'operstate')
+ operstate = util.load_file(sysfs_operstate).strip()
+ if operstate in ['dormant', 'down', 'lowerlayerdown', 'unknown']:
+ possibly_connected.append(interface)
+ continue
+ except OSError:
+ pass
+
+ # don't bother with interfaces that might not be connected if there are
+ # some that definitely are
+ if connected:
+ potential_interfaces = connected
+ else:
+ potential_interfaces = possibly_connected
+ # if there are no interfaces, give up
+ if not potential_interfaces:
+ return
+ # if eth0 exists use it above anything else, otherwise get the interface
+ # that looks 'first'
+ if DEFAULT_PRIMARY_INTERFACE in potential_interfaces:
+ name = DEFAULT_PRIMARY_INTERFACE
+ else:
+ name = sorted(potential_interfaces)[0]
+
+ sysfs_mac = os.path.join(SYS_CLASS_NET, name, 'address')
+ mac = util.load_file(sysfs_mac).strip()
+ target_name = name
+
+ nconf['config'].append(
+ {'type': 'physical', 'name': target_name,
+ 'mac_address': mac, 'subnets': [{'type': 'dhcp4'}]})
+ return nconf
+
+
+def read_kernel_cmdline_config():
+ # FIXME: add implementation here
+ return None
+
# vi: ts=4 expandtab syntax=python
diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py
index 741ef5bc..afd08935 100644
--- a/cloudinit/sources/DataSourceNoCloud.py
+++ b/cloudinit/sources/DataSourceNoCloud.py
@@ -36,7 +36,9 @@ class DataSourceNoCloud(sources.DataSource):
self.dsmode = 'local'
self.seed = None
self.cmdline_id = "ds=nocloud"
- self.seed_dir = os.path.join(paths.seed_dir, 'nocloud')
+ self.seed_dirs = [os.path.join(paths.seed_dir, 'nocloud'),
+ os.path.join(paths.seed_dir, 'nocloud-net')]
+ self.seed_dir = None
self.supported_seed_starts = ("/", "file://")
def __str__(self):
@@ -58,7 +60,7 @@ class DataSourceNoCloud(sources.DataSource):
md = {}
if parse_cmdline_data(self.cmdline_id, md):
found.append("cmdline")
- mydata = _merge_new_seed({'meta-data': md})
+ mydata = _merge_new_seed(mydata, {'meta-data': md})
except:
util.logexc(LOG, "Unable to parse command line data")
return False
@@ -67,15 +69,15 @@ class DataSourceNoCloud(sources.DataSource):
pp2d_kwargs = {'required': ['user-data', 'meta-data'],
'optional': ['vendor-data', 'network-config']}
- try:
- seeded = util.pathprefix2dict(self.seed_dir, **pp2d_kwargs)
- found.append(self.seed_dir)
- LOG.debug("Using seeded data from %s", self.seed_dir)
- except ValueError as e:
- pass
-
- if self.seed_dir in found:
- mydata = _merge_new_seed(mydata, seeded)
+ for path in self.seed_dirs:
+ try:
+ seeded = util.pathprefix2dict(path, **pp2d_kwargs)
+ found.append(path)
+ LOG.debug("Using seeded data from %s", path)
+ mydata = _merge_new_seed(mydata, seeded)
+ break
+ except ValueError as e:
+ pass
# If the datasource config had a 'seedfrom' entry, then that takes
# precedence over a 'seedfrom' that was found in a filesystem
@@ -188,21 +190,19 @@ class DataSourceNoCloud(sources.DataSource):
# if this is the local datasource or 'seedfrom' was used
# and the source of the seed was self.dsmode.
# Then see if there is network config to apply.
+ # note this is obsolete network-interfaces style seeding.
if self.dsmode in ("local", seeded_network):
if mydata['meta-data'].get('network-interfaces'):
LOG.debug("Updating network interfaces from %s", self)
self.distro.apply_network(
mydata['meta-data']['network-interfaces'])
- elif mydata.get('network-config'):
- LOG.debug("Updating network config from %s", self)
- self.distro.apply_network_config(mydata['network-config'],
- bring_up=False)
if mydata['meta-data']['dsmode'] == self.dsmode:
self.seed = ",".join(found)
self.metadata = mydata['meta-data']
self.userdata_raw = mydata['user-data']
self.vendordata_raw = mydata['vendor-data']
+ self._network_config = mydata['network-config']
return True
LOG.debug("%s: not claiming datasource, dsmode=%s", self,
@@ -217,11 +217,15 @@ class DataSourceNoCloud(sources.DataSource):
return None
quick_id = _quick_read_instance_id(cmdline_id=self.cmdline_id,
- dirs=[self.seed_dir])
+ dirs=self.seed_dirs)
if not quick_id:
return None
return quick_id == current
+ @property
+ def network_config(self):
+ return self._network_config
+
def _quick_read_instance_id(cmdline_id, dirs=None):
if dirs is None:
@@ -291,8 +295,12 @@ def parse_cmdline_data(ds_id, fill, cmdline=None):
def _merge_new_seed(cur, seeded):
ret = cur.copy()
- ret['meta-data'] = util.mergemanydict([cur['meta-data'],
- util.load_yaml(seeded['meta-data'])])
+
+ newmd = seeded.get('meta-data', {})
+ if not isinstance(seeded['meta-data'], dict):
+ newmd = util.load_yaml(seeded['meta-data'])
+ ret['meta-data'] = util.mergemanydict([cur['meta-data'], newmd])
+
if seeded.get('network-config'):
ret['network-config'] = util.load_yaml(seeded['network-config'])
@@ -308,7 +316,7 @@ class DataSourceNoCloudNet(DataSourceNoCloud):
DataSourceNoCloud.__init__(self, sys_cfg, distro, paths)
self.cmdline_id = "ds=nocloud-net"
self.supported_seed_starts = ("http://", "https://", "ftp://")
- self.seed_dir = os.path.join(paths.seed_dir, 'nocloud-net')
+ self.seed_dirs = [os.path.join(paths.seed_dir, 'nocloud-net')]
self.dsmode = "net"
diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py
index 28540a7b..c63464b2 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -221,6 +221,10 @@ class DataSource(object):
# quickly (local check only) if self.instance_id is still
return False
+ @property
+ def network_config(self):
+ return None
+
def normalize_pubkey_data(pubkey_data):
keys = []
diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index edad6450..8ebbe6a9 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -43,6 +43,7 @@ from cloudinit import distros
from cloudinit import helpers
from cloudinit import importer
from cloudinit import log as logging
+from cloudinit import net
from cloudinit import sources
from cloudinit import type_utils
from cloudinit import util
@@ -193,40 +194,12 @@ class Init(object):
# We try to restore from a current link and static path
# by using the instance link, if purge_cache was called
# the file wont exist.
- pickled_fn = self.paths.get_ipath_cur('obj_pkl')
- pickle_contents = None
- try:
- pickle_contents = util.load_file(pickled_fn, decode=False)
- except Exception as e:
- if os.path.isfile(pickled_fn):
- LOG.warn("failed loading pickle in %s: %s" % (pickled_fn, e))
- pass
-
- # This is expected so just return nothing
- # successfully loaded...
- if not pickle_contents:
- return None
- try:
- return pickle.loads(pickle_contents)
- except Exception:
- util.logexc(LOG, "Failed loading pickled blob from %s", pickled_fn)
- return None
+ return _pkl_load(self.paths.get_ipath_cur('obj_pkl'))
def _write_to_cache(self):
if self.datasource is NULL_DATA_SOURCE:
return False
- pickled_fn = self.paths.get_ipath_cur("obj_pkl")
- try:
- pk_contents = pickle.dumps(self.datasource)
- except Exception:
- util.logexc(LOG, "Failed pickling datasource %s", self.datasource)
- return False
- try:
- util.write_file(pickled_fn, pk_contents, omode="wb", mode=0o400)
- except Exception:
- util.logexc(LOG, "Failed pickling datasource to %s", pickled_fn)
- return False
- return True
+ return _pkl_store(self.datasource, self.paths.get_ipath_cur("obj_pkl"))
def _get_datasources(self):
# Any config provided???
@@ -595,6 +568,30 @@ class Init(object):
# Run the handlers
self._do_handlers(user_data_msg, c_handlers_list, frequency)
+ def _find_networking_config(self):
+ cmdline_cfg = ('cmdline', net.read_kernel_cmdline_config())
+ dscfg = ('ds', None)
+ if self.datasource and hasattr(self.datasource, 'network_config'):
+ dscfg = ('ds', self.datasource.network_config)
+ sys_cfg = ('system_cfg', self.cfg.get('network'))
+
+ for loc, ncfg in (cmdline_cfg, dscfg, sys_cfg):
+ if net.is_disabled_cfg(ncfg):
+ LOG.debug("network config disabled by %s", loc)
+ return (None, loc)
+ if ncfg:
+ return (ncfg, loc)
+ return (net.generate_fallback_config(), "fallback")
+
+ def apply_network_config(self):
+ netcfg, src = self._find_networking_config()
+ if netcfg is None:
+ LOG.info("network config is disabled by %s", src)
+ return
+
+ LOG.info("Applying network configuration from %s: %s", src, netcfg)
+ return self.distro.apply_network_config(netcfg)
+
class Modules(object):
def __init__(self, init, cfg_files=None, reporter=None):
@@ -796,3 +793,36 @@ def fetch_base_config():
base_cfgs.append(default_cfg)
return util.mergemanydict(base_cfgs)
+
+
+def _pkl_store(obj, fname):
+ try:
+ pk_contents = pickle.dumps(obj)
+ except Exception:
+ util.logexc(LOG, "Failed pickling datasource %s", obj)
+ return False
+ try:
+ util.write_file(fname, pk_contents, omode="wb", mode=0o400)
+ except Exception:
+ util.logexc(LOG, "Failed pickling datasource to %s", fname)
+ return False
+ return True
+
+
+def _pkl_load(fname):
+ pickle_contents = None
+ try:
+ pickle_contents = util.load_file(fname, decode=False)
+ except Exception as e:
+ if os.path.isfile(fname):
+ LOG.warn("failed loading pickle in %s: %s" % (fname, e))
+ pass
+
+ # This is allowed so just return nothing successfully loaded...
+ if not pickle_contents:
+ return None
+ try:
+ return pickle.loads(pickle_contents)
+ except Exception:
+ util.logexc(LOG, "Failed loading pickled blob from %s", fname)
+ return None