summaryrefslogtreecommitdiff
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
parent6082a5b0c2b1a52ddbf63bfd80331f28f8cdc4fa (diff)
parent5b3cad36be8981cd12cffdf5c5e539b522404000 (diff)
downloadvyos-cloud-init-d32116c3468e5c394b56d078ceef86d416d83b3a.tar.gz
vyos-cloud-init-d32116c3468e5c394b56d078ceef86d416d83b3a.zip
merge from trunk.net1
-rwxr-xr-xbin/cloud-init20
-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
-rwxr-xr-xsetup.py3
-rwxr-xr-xsystemd/cloud-init-generator3
-rw-r--r--systemd/cloud-init-local.service2
-rw-r--r--tests/unittests/test_distros/test_netconfig.py5
-rw-r--r--udev/79-cloud-init-net-wait.rules10
-rwxr-xr-xudev/cloud-init-wait68
13 files changed, 321 insertions, 72 deletions
diff --git a/bin/cloud-init b/bin/cloud-init
index 11cc0237..341359e3 100755
--- a/bin/cloud-init
+++ b/bin/cloud-init
@@ -263,6 +263,10 @@ def main_init(name, args):
return (None, [])
else:
return (None, ["No instance datasource found."])
+
+ if args.local:
+ init.apply_network_config()
+
# Stage 6
iid = init.instancify()
LOG.debug("%s will now be targeting instance id: %s", name, iid)
@@ -325,7 +329,7 @@ def main_modules(action_name, args):
init.read_cfg(extract_fns(args))
# Stage 2
try:
- init.fetch()
+ init.fetch(existing="trust")
except sources.DataSourceNotFoundException:
# There was no datasource found, theres nothing to do
msg = ('Can not apply stage %s, no datasource found! Likely bad '
@@ -379,7 +383,7 @@ def main_single(name, args):
init.read_cfg(extract_fns(args))
# Stage 2
try:
- init.fetch()
+ init.fetch(existing="trust")
except sources.DataSourceNotFoundException:
# There was no datasource found,
# that might be bad (or ok) depending on
@@ -432,20 +436,24 @@ def main_single(name, args):
return 0
-def atomic_write_json(path, data):
+def atomic_write_file(path, content, mode='w'):
tf = None
try:
tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(path),
- delete=False)
- tf.write(util.encode_text(json.dumps(data, indent=1) + "\n"))
+ delete=False, mode=mode)
+ tf.write(content)
tf.close()
os.rename(tf.name, path)
except Exception as e:
if tf is not None:
- util.del_file(tf.name)
+ os.unlink(tf.name)
raise e
+def atomic_write_json(path, data):
+ return atomic_write_file(path, json.dumps(data, indent=1) + "\n")
+
+
def status_wrapper(name, args, data_d=None, link_d=None):
if data_d is None:
data_d = os.path.normpath("/var/lib/cloud/data")
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
diff --git a/setup.py b/setup.py
index 0b261dfe..f86727b2 100755
--- a/setup.py
+++ b/setup.py
@@ -183,7 +183,8 @@ else:
[f for f in glob('doc/examples/*') if is_f(f)]),
(USR + '/share/doc/cloud-init/examples/seed',
[f for f in glob('doc/examples/seed/*') if is_f(f)]),
- (LIB + '/udev/rules.d', ['udev/66-azure-ephemeral.rules']),
+ (LIB + '/udev/rules.d', [f for f in glob('udev/*.rules')]),
+ (LIB + '/udev', ['udev/cloud-init-wait']),
]
# Use a subclass for install that handles
# adding on the right init system configuration files
diff --git a/systemd/cloud-init-generator b/systemd/cloud-init-generator
index 2d319695..ae286d58 100755
--- a/systemd/cloud-init-generator
+++ b/systemd/cloud-init-generator
@@ -107,6 +107,9 @@ main() {
"ln $CLOUD_SYSTEM_TARGET $link_path"
fi
fi
+ # this touches /run/cloud-init/enabled, which is read by
+ # udev/cloud-init-wait. If not present, it will exit quickly.
+ touch "$LOG_D/$ENABLE"
elif [ "$result" = "$DISABLE" ]; then
if [ -f "$link_path" ]; then
if rm -f "$link_path"; then
diff --git a/systemd/cloud-init-local.service b/systemd/cloud-init-local.service
index 475a2e11..b19eeaee 100644
--- a/systemd/cloud-init-local.service
+++ b/systemd/cloud-init-local.service
@@ -2,6 +2,7 @@
Description=Initial cloud-init job (pre-networking)
DefaultDependencies=no
Wants=local-fs.target
+Wants=network-pre.target
After=local-fs.target
Conflicts=shutdown.target
Before=network-pre.target
@@ -10,6 +11,7 @@ Before=shutdown.target
[Service]
Type=oneshot
ExecStart=/usr/bin/cloud-init init --local
+ExecStart=/bin/touch /run/cloud-init/network-config-ready
RemainAfterExit=yes
TimeoutSec=0
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 6d30c5b8..2c2a424d 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -109,8 +109,9 @@ class TestNetCfgDistro(TestCase):
ub_distro.apply_network(BASE_NET_CFG, False)
self.assertEquals(len(write_bufs), 1)
- self.assertIn('/etc/network/interfaces', write_bufs)
- write_buf = write_bufs['/etc/network/interfaces']
+ eni_name = '/etc/network/interfaces.d/50-cloud-init.cfg'
+ self.assertIn(eni_name, write_bufs)
+ write_buf = write_bufs[eni_name]
self.assertEquals(str(write_buf).strip(), BASE_NET_CFG.strip())
self.assertEquals(write_buf.mode, 0o644)
diff --git a/udev/79-cloud-init-net-wait.rules b/udev/79-cloud-init-net-wait.rules
new file mode 100644
index 00000000..8344222a
--- /dev/null
+++ b/udev/79-cloud-init-net-wait.rules
@@ -0,0 +1,10 @@
+# cloud-init cold/hot-plug blocking mechanism
+# this file blocks further processing of network events
+# until cloud-init local has had a chance to read and apply network
+SUBSYSTEM!="net", GOTO="cloudinit_naming_end"
+ACTION!="add", GOTO="cloudinit_naming_end"
+
+IMPORT{program}="/lib/udev/cloud-init-wait"
+
+LABEL="cloudinit_naming_end"
+# vi: ts=4 expandtab syntax=udevrules
diff --git a/udev/cloud-init-wait b/udev/cloud-init-wait
new file mode 100755
index 00000000..7d53dee4
--- /dev/null
+++ b/udev/cloud-init-wait
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+CI_NET_READY="/run/cloud-init/network-config-ready"
+LOG="/run/cloud-init/${0##*/}.log"
+LOG_INIT=0
+DEBUG=0
+
+block_until_ready() {
+ local fname="$1"
+ local naplen="$2" max="$3" n=0
+ while ! [ -f "$fname" ]; do
+ n=$(($n+1))
+ [ "$n" -ge "$max" ] && return 1
+ sleep $naplen
+ done
+}
+
+log() {
+ [ -n "${LOG}" ] || return
+ [ "${DEBUG:-0}" = "0" ] && return
+
+ if [ $LOG_INIT = 0 ]; then
+ if [ -d "${LOG%/*}" ] || mkdir -p "${LOG%/*}"; then
+ LOG_INIT=1
+ else
+ echo "${0##*/}: WARN: log init to ${LOG%/*}" 1>&2
+ return
+ fi
+ elif [ "$LOG_INIT" = "-1" ]; then
+ return
+ fi
+ local info="$$ $INTERFACE"
+ if [ "$DEBUG" -gt 1 ]; then
+ local up idle
+ read up idle < /proc/uptime
+ info="$$ $INTERFACE $up"
+ fi
+ echo "[$info]" "$@" >> "$LOG"
+}
+
+main() {
+ local name="" readyfile="$CI_NET_READY"
+ local info="INTERFACE=${INTERFACE} ID_NET_NAME=${ID_NET_NAME}"
+ info="$info ID_NET_NAME_PATH=${ID_NET_NAME_PATH}"
+ info="$info MAC_ADDRESS=${MAC_ADDRESS}"
+ log "$info"
+
+ ## Check to see if cloud-init.target is set. If cloud-init is
+ ## disabled we do not want to do anything.
+ if [ ! -f "/run/cloud-init/enabled" ]; then
+ log "cloud-init disabled"
+ return 0
+ fi
+
+ if [ "${INTERFACE#lo}" != "$INTERFACE" ]; then
+ return 0
+ fi
+
+ block_until_ready "$readyfile" .1 600 ||
+ { log "failed waiting for ready on $INTERFACE"; return 1; }
+
+ log "net config ready"
+}
+
+main "$@"
+exit
+
+# vi: ts=4 expandtab