summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
authorAkihiko Ota <skywalker.37th@gmail.com>2017-12-13 23:46:02 +0900
committerScott Moser <smoser@ubuntu.com>2018-01-24 09:42:07 -0500
commit8a9421421497b3e7c05589c62389745d565c6633 (patch)
tree68760d6a99eb60e1848d7b27b540a01f03ef487f /cloudinit
parent32a6a1764e902c31dd3af9b674cea14cd6501187 (diff)
downloadvyos-cloud-init-8a9421421497b3e7c05589c62389745d565c6633.tar.gz
vyos-cloud-init-8a9421421497b3e7c05589c62389745d565c6633.zip
OpenNebula: Improve network configuration support.
Network configuration in OpenNebula would only work if the host correctly guessed the names of the devices in the guest. OpenNebula provided data in its context.sh like 'ETH0_NETWORK', but if the guest named devices differently then results were not predictable. This would occur with Predictable Network Interface Names. To address this, newer versions (of OpenNebula provide the mac address ETH0_MAC. This function is present in 4.14 and documented officially in 5.0 docs. This provides support for reading the mac addresses from the context.sh. It also fixes cases where context.sh provided a field (ETH0_NETWORK or ETH0_MASK) with a empty string. Previously the empty string would be used rather than falling back to the default. LP: #1719157, #1716397, #1736750
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/net/__init__.py4
-rw-r--r--cloudinit/sources/DataSourceOpenNebula.py112
2 files changed, 61 insertions, 55 deletions
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
index a1b0db10..c015e793 100644
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -18,7 +18,7 @@ SYS_CLASS_NET = "/sys/class/net/"
DEFAULT_PRIMARY_INTERFACE = 'eth0'
-def _natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
+def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
"""Sorting for Humans: natural sort order. Can be use as the key to sort
functions.
This will sort ['eth0', 'ens3', 'ens10', 'ens12', 'ens8', 'ens0'] as
@@ -224,7 +224,7 @@ def find_fallback_nic(blacklist_drivers=None):
# if eth0 exists use it above anything else, otherwise get the interface
# that we can read 'first' (using the sorted defintion of first).
- names = list(sorted(potential_interfaces, key=_natural_sort_key))
+ names = list(sorted(potential_interfaces, key=natural_sort_key))
if DEFAULT_PRIMARY_INTERFACE in names:
names.remove(DEFAULT_PRIMARY_INTERFACE)
names.insert(0, DEFAULT_PRIMARY_INTERFACE)
diff --git a/cloudinit/sources/DataSourceOpenNebula.py b/cloudinit/sources/DataSourceOpenNebula.py
index f66c95d7..ce47b6bd 100644
--- a/cloudinit/sources/DataSourceOpenNebula.py
+++ b/cloudinit/sources/DataSourceOpenNebula.py
@@ -12,6 +12,7 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
+import collections
import os
import pwd
import re
@@ -19,6 +20,7 @@ import string
from cloudinit import log as logging
from cloudinit import net
+from cloudinit.net import eni
from cloudinit import sources
from cloudinit import util
@@ -89,11 +91,18 @@ class DataSourceOpenNebula(sources.DataSource):
return False
self.seed = seed
- self.network_eni = results.get("network_config")
+ self.network_eni = results.get('network-interfaces')
self.metadata = md
self.userdata_raw = results.get('userdata')
return True
+ @property
+ def network_config(self):
+ if self.network_eni is not None:
+ return eni.convert_eni_data(self.network_eni)
+ else:
+ return None
+
def get_hostname(self, fqdn=False, resolve_ip=None):
if resolve_ip is None:
if self.dsmode == sources.DSMODE_NETWORK:
@@ -116,58 +125,53 @@ class OpenNebulaNetwork(object):
self.context = context
if system_nics_by_mac is None:
system_nics_by_mac = get_physical_nics_by_mac()
- self.ifaces = system_nics_by_mac
+ self.ifaces = collections.OrderedDict(
+ [k for k in sorted(system_nics_by_mac.items(),
+ key=lambda k: net.natural_sort_key(k[1]))])
+
+ # OpenNebula 4.14+ provide macaddr for ETHX in variable ETH_MAC.
+ # context_devname provides {mac.lower():ETHX, mac2.lower():ETHX}
+ self.context_devname = {}
+ for k, v in context.items():
+ m = re.match(r'^(.+)_MAC$', k)
+ if m:
+ self.context_devname[v.lower()] = m.group(1)
def mac2ip(self, mac):
- components = mac.split(':')[2:]
- return [str(int(c, 16)) for c in components]
+ return '.'.join([str(int(c, 16)) for c in mac.split(':')[2:]])
- def get_ip(self, dev, components):
- var_name = dev.upper() + '_IP'
- if var_name in self.context:
- return self.context[var_name]
- else:
- return '.'.join(components)
+ def mac2network(self, mac):
+ return self.mac2ip(mac).rpartition(".")[0] + ".0"
- def get_mask(self, dev):
- var_name = dev.upper() + '_MASK'
- if var_name in self.context:
- return self.context[var_name]
- else:
- return '255.255.255.0'
+ def get_dns(self, dev):
+ return self.get_field(dev, "dns", "").split()
- def get_network(self, dev, components):
- var_name = dev.upper() + '_NETWORK'
- if var_name in self.context:
- return self.context[var_name]
- else:
- return '.'.join(components[:-1]) + '.0'
+ def get_domain(self, dev):
+ return self.get_field(dev, "domain")
+
+ def get_ip(self, dev, mac):
+ return self.get_field(dev, "ip", self.mac2ip(mac))
def get_gateway(self, dev):
- var_name = dev.upper() + '_GATEWAY'
- if var_name in self.context:
- return self.context[var_name]
- else:
- return None
+ return self.get_field(dev, "gateway")
- def get_dns(self, dev):
- var_name = dev.upper() + '_DNS'
- if var_name in self.context:
- return self.context[var_name]
- else:
- return None
+ def get_mask(self, dev):
+ return self.get_field(dev, "mask", "255.255.255.0")
- def get_domain(self, dev):
- var_name = dev.upper() + '_DOMAIN'
- if var_name in self.context:
- return self.context[var_name]
- else:
- return None
+ def get_network(self, dev, mac):
+ return self.get_field(dev, "network", self.mac2network(mac))
+
+ def get_field(self, dev, name, default=None):
+ """return the field name in context for device dev.
+
+ context stores <dev>_<NAME> (example: eth0_DOMAIN).
+ an empty string for value will return default."""
+ val = self.context.get('_'.join((dev, name,)).upper())
+ # allow empty string to return the default.
+ return default if val in (None, "") else val
def gen_conf(self):
- global_dns = []
- if 'DNS' in self.context:
- global_dns.append(self.context['DNS'])
+ global_dns = self.context.get('DNS', "").split()
conf = []
conf.append('auto lo')
@@ -175,29 +179,31 @@ class OpenNebulaNetwork(object):
conf.append('')
for mac, dev in self.ifaces.items():
- ip_components = self.mac2ip(mac)
+ mac = mac.lower()
+
+ # c_dev stores name in context 'ETHX' for this device.
+ # dev stores the current system name.
+ c_dev = self.context_devname.get(mac, dev)
conf.append('auto ' + dev)
conf.append('iface ' + dev + ' inet static')
- conf.append(' address ' + self.get_ip(dev, ip_components))
- conf.append(' network ' + self.get_network(dev, ip_components))
- conf.append(' netmask ' + self.get_mask(dev))
+ conf.append(' #hwaddress %s' % mac)
+ conf.append(' address ' + self.get_ip(c_dev, mac))
+ conf.append(' network ' + self.get_network(c_dev, mac))
+ conf.append(' netmask ' + self.get_mask(c_dev))
- gateway = self.get_gateway(dev)
+ gateway = self.get_gateway(c_dev)
if gateway:
conf.append(' gateway ' + gateway)
- domain = self.get_domain(dev)
+ domain = self.get_domain(c_dev)
if domain:
conf.append(' dns-search ' + domain)
# add global DNS servers to all interfaces
- dns = self.get_dns(dev)
+ dns = self.get_dns(c_dev)
if global_dns or dns:
- all_dns = global_dns
- if dns:
- all_dns.append(dns)
- conf.append(' dns-nameservers ' + ' '.join(all_dns))
+ conf.append(' dns-nameservers ' + ' '.join(global_dns + dns))
conf.append('')