# Copyright 2014 Cloudbase Solutions Srl # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import re from oslo_log import log as oslo_logging import six from cloudbaseinit.models import network as network_model LOG = oslo_logging.getLogger(__name__) NAME = "name" MAC = "mac" ADDRESS = "address" ADDRESS6 = "address6" NETMASK = "netmask" NETMASK6 = "netmask6" BROADCAST = "broadcast" GATEWAY = "gateway" GATEWAY6 = "gateway6" DNSNS = "dnsnameservers" # Fields of interest by regexps. FIELDS = { NAME: re.compile(r"iface\s+(?P<{}>\S+)" r"\s+inet6?\s+static".format(NAME)), MAC: re.compile(r"hwaddress\s+ether\s+" r"(?P<{}>\S+)".format(MAC)), ADDRESS: re.compile(r"address\s+" r"(?P<{}>\S+)".format(ADDRESS)), ADDRESS6: re.compile(r"post-up ip -6 addr add (?P<{}>[^/]+)/" r"(\d+) dev".format(ADDRESS6)), NETMASK: re.compile(r"netmask\s+" r"(?P<{}>\S+)".format(NETMASK)), NETMASK6: re.compile(r"post-up ip -6 addr add ([^/]+)/" r"(?P<{}>\d+) dev".format(NETMASK6)), BROADCAST: re.compile(r"broadcast\s+" r"(?P<{}>\S+)".format(BROADCAST)), GATEWAY: re.compile(r"gateway\s+" r"(?P<{}>\S+)".format(GATEWAY)), GATEWAY6: re.compile(r"post-up ip -6 route add default via " r"(?P<{}>.+) dev".format(GATEWAY6)), DNSNS: re.compile(r"dns-nameservers\s+(?P<{}>.+)".format(DNSNS)) } IFACE_TEMPLATE = dict.fromkeys(FIELDS.keys()) # Map IPv6 availability by value index under `NetworkDetails`. V6_PROXY = { ADDRESS: ADDRESS6, NETMASK: NETMASK6, GATEWAY: GATEWAY6 } DETAIL_PREPROCESS = { MAC: lambda value: value.upper(), DNSNS: lambda value: value.strip().split() } def _get_iface_blocks(data): """"Yield interface blocks as pairs of v4 and v6 halves.""" lines, lines6 = [], [] crt_lines = lines for line in data.splitlines(): line = line.strip() if not line or line.startswith("#"): continue if "iface" in line: if "inet6" in line: crt_lines = lines6 continue if lines: yield lines, lines6 lines[:] = [] lines6[:] = [] crt_lines = lines crt_lines.append(line) if lines: yield lines, lines6 def _get_field(line): for field, regex in FIELDS.items(): match = regex.match(line) if match: yield field, match.group(field) def _add_nic(iface, nics): if not iface or iface == IFACE_TEMPLATE: return # no information gathered LOG.debug("Found new interface: %s", iface) # Each missing detail is marked as None. nic = network_model.NetworkDetails(**iface) nics.append(nic) def parse(data): """Parse the received content and obtain network details.""" if not data or not isinstance(data, six.string_types): LOG.error("Invalid Debian config to parse:\n%s", data) return LOG.info("Parsing Debian config...\n%s", data) nics = [] # list of NetworkDetails objects for lines_pair in _get_iface_blocks(data): iface = IFACE_TEMPLATE.copy() for lines, use_proxy in zip(lines_pair, (False, True)): for line in lines: for field, value in _get_field(line): if use_proxy: field = V6_PROXY.get(field) if not field: continue func = DETAIL_PREPROCESS.get(field, lambda value: value) iface[field] = func(value) if value != "None" else None _add_nic(iface, nics) return nics