From 8844ffb5988bcfbb8cfbe57d9139c3dcb8b429cc Mon Sep 17 00:00:00 2001 From: Sankar Tanguturi Date: Wed, 18 Nov 2015 16:03:15 -0800 Subject: Add Image Customization Parser for VMware vSphere Hypervisor Support. This is the first changeset submitted as a part of project to add cloud-init support for VMware vSphere Hypervisor. This changeset contains _only_ the changes for a simple python parser for a Image Customization Specification file pushed by VMware vSphere hypervisor into the guest VMs. In a later changeset, will be submitting another patch to actually detect the underlying VMware vSphere hypervisor and do the necessary customization. --- cloudinit/sources/helpers/vmware/imc/config.py | 125 +++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 cloudinit/sources/helpers/vmware/imc/config.py (limited to 'cloudinit/sources/helpers/vmware/imc/config.py') diff --git a/cloudinit/sources/helpers/vmware/imc/config.py b/cloudinit/sources/helpers/vmware/imc/config.py new file mode 100644 index 00000000..ea0873fb --- /dev/null +++ b/cloudinit/sources/helpers/vmware/imc/config.py @@ -0,0 +1,125 @@ +from cloudinit.sources.helpers.vmware.imc.nic import Nic + + +class Config: + DNS = 'DNS|NAMESERVER|' + SUFFIX = 'DNS|SUFFIX|' + PASS = 'PASSWORD|-PASS' + TIMEZONE = 'DATETIME|TIMEZONE' + UTC = 'DATETIME|UTC' + HOSTNAME = 'NETWORK|HOSTNAME' + OMAINNAME = 'NETWORK|DOMAINNAME' + + def __init__(self, configFile): + self._configFile = configFile + + # Retrieves hostname. + # + # Args: + # None + # Results: + # string: hostname + # Throws: + # None + @property + def hostName(self): + return self._configFile.get(Config.HOSTNAME, None) + + # Retrieves domainName. + # + # Args: + # None + # Results: + # string: domainName + # Throws: + # None + @property + def domainName(self): + return self._configFile.get(Config.DOMAINNAME, None) + + # Retrieves timezone. + # + # Args: + # None + # Results: + # string: timezone + # Throws: + # None + @property + def timeZone(self): + return self._configFile.get(Config.TIMEZONE, None) + + # Retrieves whether to set time to UTC or Local. + # + # Args: + # None + # Results: + # boolean: True for yes/YES, True for no/NO, otherwise - None + # Throws: + # None + @property + def utc(self): + return self._configFile.get(Config.UTC, None) + + # Retrieves root password to be set. + # + # Args: + # None + # Results: + # string: base64-encoded root password or None + # Throws: + # None + @property + def adminPassword(self): + return self._configFile.get(Config.PASS, None) + + # Retrieves DNS Servers. + # + # Args: + # None + # Results: + # integer: count or 0 + # Throws: + # None + @property + def nameServers(self): + res = [] + for i in range(1, self._configFile.getCnt(Config.DNS) + 1): + key = Config.DNS + str(i) + res.append(self._configFile[key]) + + return res + + # Retrieves DNS Suffixes. + # + # Args: + # None + # Results: + # integer: count or 0 + # Throws: + # None + @property + def dnsSuffixes(self): + res = [] + for i in range(1, self._configFile.getCnt(Config.SUFFIX) + 1): + key = Config.SUFFIX + str(i) + res.append(self._configFile[key]) + + return res + + # Retrieves NICs. + # + # Args: + # None + # Results: + # integer: count + # Throws: + # None + @property + def nics(self): + res = [] + nics = self._configFile['NIC-CONFIG|NICS'] + for nic in nics.split(','): + res.append(Nic(nic, self._configFile)) + + return res -- cgit v1.2.3 From 8d9e5bd7fcda8f56a4fe087150db1456af738335 Mon Sep 17 00:00:00 2001 From: Sankar Tanguturi Date: Tue, 5 Jan 2016 12:05:11 -0800 Subject: Fixed all the styling nits. Used proper naming convention for the methods. Added proper documentation. Checked pep8 and flake8 output and no issues were reported. --- cloudinit/sources/helpers/vmware/imc/boot_proto.py | 28 +- cloudinit/sources/helpers/vmware/imc/config.py | 116 +++---- .../sources/helpers/vmware/imc/config_file.py | 372 +++++++++------------ .../sources/helpers/vmware/imc/config_namespace.py | 22 +- .../sources/helpers/vmware/imc/config_source.py | 21 ++ cloudinit/sources/helpers/vmware/imc/ipv4_mode.py | 74 ++-- cloudinit/sources/helpers/vmware/imc/nic.py | 254 ++++++++------ 7 files changed, 448 insertions(+), 439 deletions(-) (limited to 'cloudinit/sources/helpers/vmware/imc/config.py') diff --git a/cloudinit/sources/helpers/vmware/imc/boot_proto.py b/cloudinit/sources/helpers/vmware/imc/boot_proto.py index 6c3b070a..abfffd75 100644 --- a/cloudinit/sources/helpers/vmware/imc/boot_proto.py +++ b/cloudinit/sources/helpers/vmware/imc/boot_proto.py @@ -1,11 +1,25 @@ -# from enum import Enum +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + class BootProto: + """Specifies the NIC Boot Settings.""" + DHCP = 'dhcp' STATIC = 'static' - -# def __eq__(self, other): -# return self.name == other.name and self.value == other.value -# -# def __ne__(self, other): -# return not self.__eq__(other) diff --git a/cloudinit/sources/helpers/vmware/imc/config.py b/cloudinit/sources/helpers/vmware/imc/config.py index ea0873fb..7eee47a5 100644 --- a/cloudinit/sources/helpers/vmware/imc/config.py +++ b/cloudinit/sources/helpers/vmware/imc/config.py @@ -1,122 +1,90 @@ -from cloudinit.sources.helpers.vmware.imc.nic import Nic +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from .nic import Nic class Config: + """ + Stores the Contents specified in the Customization + Specification file. + """ + DNS = 'DNS|NAMESERVER|' SUFFIX = 'DNS|SUFFIX|' PASS = 'PASSWORD|-PASS' TIMEZONE = 'DATETIME|TIMEZONE' UTC = 'DATETIME|UTC' HOSTNAME = 'NETWORK|HOSTNAME' - OMAINNAME = 'NETWORK|DOMAINNAME' + DOMAINNAME = 'NETWORK|DOMAINNAME' def __init__(self, configFile): self._configFile = configFile - # Retrieves hostname. - # - # Args: - # None - # Results: - # string: hostname - # Throws: - # None @property - def hostName(self): + def host_name(self): + """Return the hostname.""" return self._configFile.get(Config.HOSTNAME, None) - # Retrieves domainName. - # - # Args: - # None - # Results: - # string: domainName - # Throws: - # None @property - def domainName(self): + def domain_name(self): + """Return the domain name.""" return self._configFile.get(Config.DOMAINNAME, None) - # Retrieves timezone. - # - # Args: - # None - # Results: - # string: timezone - # Throws: - # None @property - def timeZone(self): + def timezone(self): + """Return the timezone.""" return self._configFile.get(Config.TIMEZONE, None) - # Retrieves whether to set time to UTC or Local. - # - # Args: - # None - # Results: - # boolean: True for yes/YES, True for no/NO, otherwise - None - # Throws: - # None @property def utc(self): + """Retrieves whether to set time to UTC or Local.""" return self._configFile.get(Config.UTC, None) - # Retrieves root password to be set. - # - # Args: - # None - # Results: - # string: base64-encoded root password or None - # Throws: - # None @property - def adminPassword(self): + def admin_password(self): + """Return the root password to be set.""" return self._configFile.get(Config.PASS, None) - # Retrieves DNS Servers. - # - # Args: - # None - # Results: - # integer: count or 0 - # Throws: - # None @property - def nameServers(self): + def name_servers(self): + """Return the list of DNS servers.""" res = [] - for i in range(1, self._configFile.getCnt(Config.DNS) + 1): + for i in range(1, self._configFile.get_count(Config.DNS) + 1): key = Config.DNS + str(i) res.append(self._configFile[key]) return res - # Retrieves DNS Suffixes. - # - # Args: - # None - # Results: - # integer: count or 0 - # Throws: - # None @property - def dnsSuffixes(self): + def dns_suffixes(self): + """Return the list of DNS Suffixes.""" res = [] - for i in range(1, self._configFile.getCnt(Config.SUFFIX) + 1): + for i in range(1, self._configFile.get_count(Config.SUFFIX) + 1): key = Config.SUFFIX + str(i) res.append(self._configFile[key]) return res - # Retrieves NICs. - # - # Args: - # None - # Results: - # integer: count - # Throws: - # None @property def nics(self): + """Return the list of associated NICs.""" res = [] nics = self._configFile['NIC-CONFIG|NICS'] for nic in nics.split(','): diff --git a/cloudinit/sources/helpers/vmware/imc/config_file.py b/cloudinit/sources/helpers/vmware/imc/config_file.py index 3f9938da..e08a2a9a 100644 --- a/cloudinit/sources/helpers/vmware/imc/config_file.py +++ b/cloudinit/sources/helpers/vmware/imc/config_file.py @@ -1,221 +1,151 @@ -import logging -import re - -from cloudinit.sources.helpers.vmware.imc.config_source import ConfigSource - -logger = logging.getLogger(__name__) - - -class ConfigFile(ConfigSource): - def __init__(self): - self._configData = {} - - def __getitem__(self, key): - return self._configData[key] - - def get(self, key, default=None): - return self._configData.get(key, default) - - # Removes all the properties. - # - # Args: - # None - # Results: - # None - # Throws: - # None - def clear(self): - self._configData.clear() - - # Inserts k/v pair. - # - # Does not do any key/cross-key validation. - # - # Args: - # key: string: key - # val: string: value - # Results: - # None - # Throws: - # None - def _insertKey(self, key, val): - # cleaning up on all "input" path - - # remove end char \n (chomp) - key = key.strip() - val = val.strip() - - if key.startswith('-') or '|-' in key: - canLog = 0 - else: - canLog = 1 - - # "sensitive" settings shall not be logged - if canLog: - logger.debug("ADDED KEY-VAL :: '%s' = '%s'" % (key, val)) - else: - logger.debug("ADDED KEY-VAL :: '%s' = '*****************'" % key) - - self._configData[key] = val - - # Determines properties count. - # - # Args: - # None - # Results: - # integer: properties count - # Throws: - # None - def size(self): - return len(self._configData) - - # Parses properties from a .cfg file content. - # - # Any previously available properties will be removed. - # - # Sensitive data will not be logged in case key starts from '-'. - # - # Args: - # content: string: e.g. content of config/cust.cfg - # Results: - # None - # Throws: - # None - def loadConfigContent(self, content): - self.clear() - - # remove end char \n (chomp) - for line in content.split('\n'): - # TODO validate against allowed characters (not done in Perl) - - # spaces at the end are not allowed, things like passwords must be - # at least base64-encoded - line = line.strip() - - # "sensitive" settings shall not be logged - if line.startswith('-'): - canLog = 0 - else: - canLog = 1 - - if canLog: - logger.debug("Processing line: '%s'" % line) - else: - logger.debug("Processing line: '***********************'") - - if not line: - logger.debug("Empty line. Ignored.") - continue - - if line.startswith('#'): - logger.debug("Comment found. Line ignored.") - continue - - matchObj = re.match(r'\[(.+)\]', line) - if matchObj: - category = matchObj.group(1) - logger.debug("FOUND CATEGORY = '%s'" % category) - else: - # POSIX.2 regex doesn't support non-greedy like in (.+?)=(.*) - # key value pair (non-eager '=' for base64) - matchObj = re.match(r'([^=]+)=(.*)', line) - if matchObj: - # cleaning up on all "input" paths - key = category + "|" + matchObj.group(1).strip() - val = matchObj.group(2).strip() - - self._insertKey(key, val) - else: - # TODO document - raise Exception("Unrecognizable line: '%s'" % line) - - self.validate() - - # Parses properties from a .cfg file - # - # Any previously available properties will be removed. - # - # Sensitive data will not be logged in case key starts from '-'. - # - # Args: - # filename: string: full path to a .cfg file - # Results: - # None - # Throws: - # None - def loadConfigFile(self, filename): - logger.info("Opening file name %s." % filename) - # TODO what throws? - with open(filename, "r") as myfile: - self.loadConfigContent(myfile.read()) - - # Determines whether a property with a given key exists. - # - # Args: - # key: string: key - # Results: - # boolean: True if such property exists, otherwise - False. - # Throws: - # None - def hasKey(self, key): - return key in self._configData - - # Determines whether a value for a property must be kept. - # - # If the property is missing, it's treated as it should be not changed by - # the engine. - # - # Args: - # key: string: key - # Results: - # boolean: True if property must be kept, otherwise - False. - # Throws: - # None - def keepCurrentValue(self, key): - # helps to distinguish from "empty" value which is used to indicate - # "removal" - return not self.hasKey(key) - - # Determines whether a value for a property must be removed. - # - # If the property is empty, it's treated as it should be removed by the - # engine. - # - # Args: - # key: string: key - # Results: - # boolean: True if property must be removed, otherwise - False. - # Throws: - # None - def removeCurrentValue(self, key): - # helps to distinguish from "missing" value which is used to indicate - # "keeping unchanged" - if self.hasKey(key): - return not bool(self._configData[key]) - else: - return False - - # TODO - def getCnt(self, prefix): - res = 0 - for key in self._configData.keys(): - if key.startswith(prefix): - res += 1 - - return res - - # TODO - # TODO pass base64 - # Throws: - # Dies in case timezone is present but empty. - # Dies in case password is present but empty. - # Dies in case hostname is present but empty or greater than 63 chars. - # Dies in case UTC is present, but is not yes/YES or no/NO. - # Dies in case NICS is not present. - def validate(self): - # TODO must log all the errors - keyValidators = {'NIC1|IPv6GATEWAY|': None} - crossValidators = {} - - for key in self._configData.keys(): - pass +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import logging + +try: + import configparser +except ImportError: + import ConfigParser as configparser + +from .config_source import ConfigSource + +logger = logging.getLogger(__name__) + + +class ConfigFile(ConfigSource, dict): + """ConfigFile module to load the content from a specified source.""" + + def __init__(self): + pass + + def _insertKey(self, key, val): + """ + Inserts a Key Value pair. + + Keyword arguments: + key -- The key to insert + val -- The value to insert for the key + + """ + key = key.strip() + val = val.strip() + + if key.startswith('-') or '|-' in key: + canLog = 0 + else: + canLog = 1 + + # "sensitive" settings shall not be logged + if canLog: + logger.debug("ADDED KEY-VAL :: '%s' = '%s'" % (key, val)) + else: + logger.debug("ADDED KEY-VAL :: '%s' = '*****************'" % key) + + self[key] = val + + def size(self): + """Return the number of properties present.""" + return len(self) + + def loadConfigFile(self, filename): + """ + Parses properties from the specified config file. + + Any previously available properties will be removed. + Sensitive data will not be logged in case the key starts + from '-'. + + Keyword arguments: + filename - The full path to the config file. + """ + logger.info('Parsing the config file %s.' % filename) + + config = configparser.ConfigParser() + config.optionxform = str + config.read(filename) + + self.clear() + + for category in config.sections(): + logger.debug("FOUND CATEGORY = '%s'" % category) + + for (key, value) in config.items(category): + # "sensitive" settings shall not be logged + if key.startswith('-'): + canLog = 0 + else: + canLog = 1 + + if canLog: + logger.debug("Processing key, value: '%s':'%s'" % + (key, value)) + else: + logger.debug("Processing key, value : " + "'*********************'") + + self._insertKey(category + '|' + key, value) + + def keep_current_value(self, key): + """ + Determines whether a value for a property must be kept. + + If the propery is missing, it is treated as it should be not + changed by the engine. + + Keyword arguments: + key -- The key to search for. + """ + # helps to distinguish from "empty" value which is used to indicate + # "removal" + return not key in self + + def remove_current_value(self, key): + """ + Determines whether a value for the property must be removed. + + If the specified key is empty, it is treated as it should be + removed by the engine. + + Return true if the value can be removed, false otherwise. + + Keyword arguments: + key -- The key to search for. + """ + # helps to distinguish from "missing" value which is used to indicate + # "keeping unchanged" + if key in self: + return not bool(self[key]) + else: + return False + + def get_count(self, prefix): + """ + Return the total number of keys that start with the + specified prefix. + + Keyword arguments: + prefix -- prefix of the key + """ + res = 0 + for key in self.keys(): + if key.startswith(prefix): + res += 1 + + return res diff --git a/cloudinit/sources/helpers/vmware/imc/config_namespace.py b/cloudinit/sources/helpers/vmware/imc/config_namespace.py index 7f76ac8b..7266b699 100644 --- a/cloudinit/sources/helpers/vmware/imc/config_namespace.py +++ b/cloudinit/sources/helpers/vmware/imc/config_namespace.py @@ -1,5 +1,25 @@ -from cloudinit.sources.helpers.vmware.imc.config_source import ConfigSource +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from .config_source import ConfigSource class ConfigNamespace(ConfigSource): + """Specifies the Config Namespace.""" pass diff --git a/cloudinit/sources/helpers/vmware/imc/config_source.py b/cloudinit/sources/helpers/vmware/imc/config_source.py index fad3a389..a367e476 100644 --- a/cloudinit/sources/helpers/vmware/imc/config_source.py +++ b/cloudinit/sources/helpers/vmware/imc/config_source.py @@ -1,2 +1,23 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + class ConfigSource: + """Specifies a source for the Config Content.""" pass diff --git a/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py b/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py index 66b4fad7..28544e4f 100644 --- a/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py +++ b/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py @@ -1,29 +1,45 @@ -# from enum import Enum - - -# The IPv4 configuration mode which directly represents the user's goal. -# -# This mode effectively acts as a contract of the inguest customization engine. -# It must be set based on what the user has requested via VMODL/generators API -# and should not be changed by those layers. It's up to the in-guest engine to -# interpret and materialize the user's request. -# -# Also defined in linuxconfiggenerator.h. -class Ipv4Mode: - # The legacy mode which only allows dhcp/static based on whether IPv4 - # addresses list is empty or not - IPV4_MODE_BACKWARDS_COMPATIBLE = 'BACKWARDS_COMPATIBLE' - # IPv4 must use static address. Reserved for future use - IPV4_MODE_STATIC = 'STATIC' - # IPv4 must use DHCPv4. Reserved for future use - IPV4_MODE_DHCP = 'DHCP' - # IPv4 must be disabled - IPV4_MODE_DISABLED = 'DISABLED' - # IPv4 settings should be left untouched. Reserved for future use - IPV4_MODE_AS_IS = 'AS_IS' - - # def __eq__(self, other): - # return self.name == other.name and self.value == other.value - # - # def __ne__(self, other): - # return not self.__eq__(other) +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +class Ipv4Mode: + """ + The IPv4 configuration mode which directly represents the user's goal. + + This mode effectively acts as a contract of the in-guest customization + engine. It must be set based on what the user has requested and should + not be changed by those layers. It's up to the in-guest engine to + interpret and materialize the user's request. + """ + + # The legacy mode which only allows dhcp/static based on whether IPv4 + # addresses list is empty or not + IPV4_MODE_BACKWARDS_COMPATIBLE = 'BACKWARDS_COMPATIBLE' + + # IPv4 must use static address. Reserved for future use + IPV4_MODE_STATIC = 'STATIC' + + # IPv4 must use DHCPv4. Reserved for future use + IPV4_MODE_DHCP = 'DHCP' + + # IPv4 must be disabled + IPV4_MODE_DISABLED = 'DISABLED' + + # IPv4 settings should be left untouched. Reserved for future use + IPV4_MODE_AS_IS = 'AS_IS' diff --git a/cloudinit/sources/helpers/vmware/imc/nic.py b/cloudinit/sources/helpers/vmware/imc/nic.py index b90a5640..bb45a9e6 100644 --- a/cloudinit/sources/helpers/vmware/imc/nic.py +++ b/cloudinit/sources/helpers/vmware/imc/nic.py @@ -1,107 +1,147 @@ -from cloudinit.sources.helpers.vmware.imc.boot_proto import BootProto - - -class Nic: - def __init__(self, name, configFile): - self._name = name - self._configFile = configFile - - def _get(self, what): - return self._configFile.get(self.name + what, None) - - def _getCnt(self, prefix): - return self._configFile.getCnt(self.name + prefix) - - @property - def name(self): - return self._name - - @property - def mac(self): - return self._get('|MACADDR').lower() - - @property - def bootProto(self): - return self._get('|BOOTPROTO').lower() - - @property - def ipv4(self): - # TODO implement NONE - if self.bootProto == BootProto.STATIC: - return StaticIpv4Conf(self) - - return DhcpIpv4Conf(self) - - @property - def ipv6(self): - # TODO implement NONE - cnt = self._getCnt("|IPv6ADDR|") - - if cnt != 0: - return StaticIpv6Conf(self) - - return DhcpIpv6Conf(self) - - -class DhcpIpv4Conf: - def __init__(self, nic): - self._nic = nic - - -class StaticIpv4Addr: - def __init__(self, nic): - self._nic = nic - - @property - def ip(self): - return self._nic._get('|IPADDR') - - @property - def netmask(self): - return self._nic._get('|NETMASK') - - @property - def gateway(self): - return self._nic._get('|GATEWAY') - - -class StaticIpv4Conf(DhcpIpv4Conf): - @property - def addrs(self): - return [StaticIpv4Addr(self._nic)] - - -class DhcpIpv6Conf: - def __init__(self, nic): - self._nic = nic - - -class StaticIpv6Addr: - def __init__(self, nic, index): - self._nic = nic - self._index = index - - @property - def ip(self): - return self._nic._get("|IPv6ADDR|" + str(self._index)) - - @property - def prefix(self): - return self._nic._get("|IPv6NETMASK|" + str(self._index)) - - @property - def gateway(self): - return self._nic._get("|IPv6GATEWAY|" + str(self._index)) - - -class StaticIpv6Conf(DhcpIpv6Conf): - @property - def addrs(self): - cnt = self._nic._getCnt("|IPv6ADDR|") - - res = [] - - for i in range(1, cnt + 1): - res.append(StaticIpv6Addr(self._nic, i)) - - return res +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from .boot_proto import BootProto + + +class Nic: + """ + Holds the information about each NIC specified + in the customization specification file + """ + + def __init__(self, name, configFile): + self._name = name + self._configFile = configFile + + def _get(self, what): + return self._configFile.get(self.name + what, None) + + def _get_count(self, prefix): + return self._configFile.get_count(self.name + prefix) + + @property + def name(self): + return self._name + + @property + def mac(self): + return self._get('|MACADDR').lower() + + @property + def bootProto(self): + return self._get('|BOOTPROTO').lower() + + @property + def ipv4(self): + """ + Retrieves the DHCP or Static IPv6 configuration + based on the BOOTPROTO property associated with the NIC + """ + if self.bootProto == BootProto.STATIC: + return StaticIpv4Conf(self) + + return DhcpIpv4Conf(self) + + @property + def ipv6(self): + cnt = self._get_count("|IPv6ADDR|") + + if cnt != 0: + return StaticIpv6Conf(self) + + return DhcpIpv6Conf(self) + + +class DhcpIpv4Conf: + """DHCP Configuration Setting.""" + + def __init__(self, nic): + self._nic = nic + + +class StaticIpv4Addr: + """Static IPV4 Setting.""" + + def __init__(self, nic): + self._nic = nic + + @property + def ip(self): + return self._nic._get('|IPADDR') + + @property + def netmask(self): + return self._nic._get('|NETMASK') + + @property + def gateway(self): + return self._nic._get('|GATEWAY') + + +class StaticIpv4Conf(DhcpIpv4Conf): + """Static IPV4 Configuration.""" + + @property + def addrs(self): + """Return the list of associated IPv4 addresses.""" + return [StaticIpv4Addr(self._nic)] + + +class DhcpIpv6Conf: + """DHCP IPV6 Configuration.""" + + def __init__(self, nic): + self._nic = nic + + +class StaticIpv6Addr: + """Static IPV6 Address.""" + + def __init__(self, nic, index): + self._nic = nic + self._index = index + + @property + def ip(self): + return self._nic._get("|IPv6ADDR|" + str(self._index)) + + @property + def prefix(self): + return self._nic._get("|IPv6NETMASK|" + str(self._index)) + + @property + def gateway(self): + return self._nic._get("|IPv6GATEWAY|" + str(self._index)) + + +class StaticIpv6Conf(DhcpIpv6Conf): + """Static IPV6 Configuration.""" + + @property + def addrs(self): + """Return the list Associated IPV6 addresses.""" + cnt = self._nic._get_count("|IPv6ADDR|") + + res = [] + + for i in range(1, cnt + 1): + res.append(StaticIpv6Addr(self._nic, i)) + + return res -- cgit v1.2.3 From 415c45a2b9b66603e672e8ea54cee8f40a19abd1 Mon Sep 17 00:00:00 2001 From: Sankar Tanguturi Date: Tue, 19 Jan 2016 18:24:54 -0800 Subject: Fixed all the review comments from Daniel. Added a new file i.e. nic_base.py which will be used a base calls for all NIC related configuration. Modified some code in nic.py. --- cloudinit/sources/helpers/vmware/imc/boot_proto.py | 2 +- cloudinit/sources/helpers/vmware/imc/config.py | 6 +- .../sources/helpers/vmware/imc/config_file.py | 40 ++---- cloudinit/sources/helpers/vmware/imc/ipv4_mode.py | 2 +- cloudinit/sources/helpers/vmware/imc/nic.py | 118 +++++++--------- cloudinit/sources/helpers/vmware/imc/nic_base.py | 154 +++++++++++++++++++++ 6 files changed, 222 insertions(+), 100 deletions(-) create mode 100644 cloudinit/sources/helpers/vmware/imc/nic_base.py (limited to 'cloudinit/sources/helpers/vmware/imc/config.py') diff --git a/cloudinit/sources/helpers/vmware/imc/boot_proto.py b/cloudinit/sources/helpers/vmware/imc/boot_proto.py index abfffd75..faba5887 100644 --- a/cloudinit/sources/helpers/vmware/imc/boot_proto.py +++ b/cloudinit/sources/helpers/vmware/imc/boot_proto.py @@ -18,7 +18,7 @@ # along with this program. If not, see . -class BootProto: +class BootProtoEnum: """Specifies the NIC Boot Settings.""" DHCP = 'dhcp' diff --git a/cloudinit/sources/helpers/vmware/imc/config.py b/cloudinit/sources/helpers/vmware/imc/config.py index 7eee47a5..aebc12a0 100644 --- a/cloudinit/sources/helpers/vmware/imc/config.py +++ b/cloudinit/sources/helpers/vmware/imc/config.py @@ -66,7 +66,8 @@ class Config: def name_servers(self): """Return the list of DNS servers.""" res = [] - for i in range(1, self._configFile.get_count(Config.DNS) + 1): + cnt = self._configFile.get_count_with_prefix(Config.DNS) + for i in range(1, cnt + 1): key = Config.DNS + str(i) res.append(self._configFile[key]) @@ -76,7 +77,8 @@ class Config: def dns_suffixes(self): """Return the list of DNS Suffixes.""" res = [] - for i in range(1, self._configFile.get_count(Config.SUFFIX) + 1): + cnt = self._configFile.get_count_with_prefix(Config.SUFFIX) + for i in range(1, cnt + 1): key = Config.SUFFIX + str(i) res.append(self._configFile[key]) diff --git a/cloudinit/sources/helpers/vmware/imc/config_file.py b/cloudinit/sources/helpers/vmware/imc/config_file.py index e08a2a9a..7c47d14c 100644 --- a/cloudinit/sources/helpers/vmware/imc/config_file.py +++ b/cloudinit/sources/helpers/vmware/imc/config_file.py @@ -32,7 +32,8 @@ logger = logging.getLogger(__name__) class ConfigFile(ConfigSource, dict): """ConfigFile module to load the content from a specified source.""" - def __init__(self): + def __init__(self, filename): + self._loadConfigFile(filename) pass def _insertKey(self, key, val): @@ -48,9 +49,9 @@ class ConfigFile(ConfigSource, dict): val = val.strip() if key.startswith('-') or '|-' in key: - canLog = 0 + canLog = False else: - canLog = 1 + canLog = True # "sensitive" settings shall not be logged if canLog: @@ -64,7 +65,7 @@ class ConfigFile(ConfigSource, dict): """Return the number of properties present.""" return len(self) - def loadConfigFile(self, filename): + def _loadConfigFile(self, filename): """ Parses properties from the specified config file. @@ -87,22 +88,9 @@ class ConfigFile(ConfigSource, dict): logger.debug("FOUND CATEGORY = '%s'" % category) for (key, value) in config.items(category): - # "sensitive" settings shall not be logged - if key.startswith('-'): - canLog = 0 - else: - canLog = 1 - - if canLog: - logger.debug("Processing key, value: '%s':'%s'" % - (key, value)) - else: - logger.debug("Processing key, value : " - "'*********************'") - self._insertKey(category + '|' + key, value) - def keep_current_value(self, key): + def should_keep_current_value(self, key): """ Determines whether a value for a property must be kept. @@ -114,9 +102,9 @@ class ConfigFile(ConfigSource, dict): """ # helps to distinguish from "empty" value which is used to indicate # "removal" - return not key in self + return key not in self - def remove_current_value(self, key): + def should_remove_current_value(self, key): """ Determines whether a value for the property must be removed. @@ -135,17 +123,11 @@ class ConfigFile(ConfigSource, dict): else: return False - def get_count(self, prefix): + def get_count_with_prefix(self, prefix): """ - Return the total number of keys that start with the - specified prefix. + Return the total count of keys that start with the specified prefix. Keyword arguments: prefix -- prefix of the key """ - res = 0 - for key in self.keys(): - if key.startswith(prefix): - res += 1 - - return res + return len([key for key in self if key.startswith(prefix)]) diff --git a/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py b/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py index 28544e4f..33f88726 100644 --- a/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py +++ b/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py @@ -18,7 +18,7 @@ # along with this program. If not, see . -class Ipv4Mode: +class Ipv4ModeEnum: """ The IPv4 configuration mode which directly represents the user's goal. diff --git a/cloudinit/sources/helpers/vmware/imc/nic.py b/cloudinit/sources/helpers/vmware/imc/nic.py index bb45a9e6..a7594874 100644 --- a/cloudinit/sources/helpers/vmware/imc/nic.py +++ b/cloudinit/sources/helpers/vmware/imc/nic.py @@ -17,10 +17,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .boot_proto import BootProto +from .boot_proto import BootProtoEnum +from .nic_base import NicBase, StaticIpv4Base, StaticIpv6Base -class Nic: +class Nic(NicBase): """ Holds the information about each NIC specified in the customization specification file @@ -31,10 +32,10 @@ class Nic: self._configFile = configFile def _get(self, what): - return self._configFile.get(self.name + what, None) + return self._configFile.get(self.name + '|' + what, None) - def _get_count(self, prefix): - return self._configFile.get_count(self.name + prefix) + def _get_count_with_prefix(self, prefix): + return self._configFile.get_count_with_prefix(self.name + prefix) @property def name(self): @@ -42,41 +43,52 @@ class Nic: @property def mac(self): - return self._get('|MACADDR').lower() + return self._get('MACADDR').lower() @property - def bootProto(self): - return self._get('|BOOTPROTO').lower() + def primary(self): + value = self._get('PRIMARY').lower() + return value == 'yes' or value == 'true' @property - def ipv4(self): - """ - Retrieves the DHCP or Static IPv6 configuration - based on the BOOTPROTO property associated with the NIC - """ - if self.bootProto == BootProto.STATIC: - return StaticIpv4Conf(self) + def onboot(self): + value = self._get('ONBOOT').lower() + return value == 'yes' or value == 'true' - return DhcpIpv4Conf(self) + @property + def bootProto(self): + return self._get('BOOTPROTO').lower() @property - def ipv6(self): - cnt = self._get_count("|IPv6ADDR|") + def ipv4_mode(self): + return self._get('IPv4_MODE').lower() - if cnt != 0: - return StaticIpv6Conf(self) + @property + def staticIpv4(self): + """ + Checks the BOOTPROTO property and returns StaticIPv4Addr + configuration object if STATIC configuration is set. + """ + if self.bootProto == BootProtoEnum.STATIC: + return [StaticIpv4Addr(self)] + else: + return None - return DhcpIpv6Conf(self) + @property + def staticIpv6(self): + cnt = self._get_count_with_prefix('|IPv6ADDR|') + if not cnt: + return None -class DhcpIpv4Conf: - """DHCP Configuration Setting.""" + result = [] + for index in range(1, cnt + 1): + result.append(StaticIpv6Addr(self, index)) - def __init__(self, nic): - self._nic = nic + return result -class StaticIpv4Addr: +class StaticIpv4Addr(StaticIpv4Base): """Static IPV4 Setting.""" def __init__(self, nic): @@ -84,34 +96,22 @@ class StaticIpv4Addr: @property def ip(self): - return self._nic._get('|IPADDR') + return self._nic._get('IPADDR') @property def netmask(self): - return self._nic._get('|NETMASK') + return self._nic._get('NETMASK') @property - def gateway(self): - return self._nic._get('|GATEWAY') + def gateways(self): + value = self._nic._get('GATEWAY') + if value: + return [x.strip() for x in value.split(',')] + else: + return None -class StaticIpv4Conf(DhcpIpv4Conf): - """Static IPV4 Configuration.""" - - @property - def addrs(self): - """Return the list of associated IPv4 addresses.""" - return [StaticIpv4Addr(self._nic)] - - -class DhcpIpv6Conf: - """DHCP IPV6 Configuration.""" - - def __init__(self, nic): - self._nic = nic - - -class StaticIpv6Addr: +class StaticIpv6Addr(StaticIpv6Base): """Static IPV6 Address.""" def __init__(self, nic, index): @@ -120,28 +120,12 @@ class StaticIpv6Addr: @property def ip(self): - return self._nic._get("|IPv6ADDR|" + str(self._index)) + return self._nic._get('IPv6ADDR|' + str(self._index)) @property - def prefix(self): - return self._nic._get("|IPv6NETMASK|" + str(self._index)) + def netmask(self): + return self._nic._get('IPv6NETMASK|' + str(self._index)) @property def gateway(self): - return self._nic._get("|IPv6GATEWAY|" + str(self._index)) - - -class StaticIpv6Conf(DhcpIpv6Conf): - """Static IPV6 Configuration.""" - - @property - def addrs(self): - """Return the list Associated IPV6 addresses.""" - cnt = self._nic._get_count("|IPv6ADDR|") - - res = [] - - for i in range(1, cnt + 1): - res.append(StaticIpv6Addr(self._nic, i)) - - return res + return self._nic._get('IPv6GATEWAY|' + str(self._index)) diff --git a/cloudinit/sources/helpers/vmware/imc/nic_base.py b/cloudinit/sources/helpers/vmware/imc/nic_base.py new file mode 100644 index 00000000..030ba311 --- /dev/null +++ b/cloudinit/sources/helpers/vmware/imc/nic_base.py @@ -0,0 +1,154 @@ +# vi: ts=4 expandtab +# +# Copyright (C) 2015 Canonical Ltd. +# Copyright (C) 2015 VMware Inc. +# +# Author: Sankar Tanguturi +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +class NicBase: + """ + Define what are expected of each nic. + The following properties should be provided in an implementation class. + """ + + @property + def mac(self): + """ + Retrieves the mac address of the nic + @return (str) : the MACADDR setting + """ + raise NotImplementedError('MACADDR') + + @property + def primary(self): + """ + Retrieves whether the nic is the primary nic + Indicates whether NIC will be used to define the default gateway. + If none of the NICs is configured to be primary, default gateway won't + be set. + @return (bool): the PRIMARY setting + """ + raise NotImplementedError('PRIMARY') + + @property + def onboot(self): + """ + Retrieves whether the nic should be up at the boot time + @return (bool) : the ONBOOT setting + """ + raise NotImplementedError('ONBOOT') + + @property + def bootProto(self): + """ + Retrieves the boot protocol of the nic + @return (str): the BOOTPROTO setting, valid values: dhcp and static. + """ + raise NotImplementedError('BOOTPROTO') + + @property + def ipv4_mode(self): + """ + Retrieves the IPv4_MODE + @return (str): the IPv4_MODE setting, valid values: + backwards_compatible, static, dhcp, disabled, as_is + """ + raise NotImplementedError('IPv4_MODE') + + @property + def staticIpv4(self): + """ + Retrieves the static IPv4 configuration of the nic + @return (StaticIpv4Base list): the static ipv4 setting + """ + raise NotImplementedError('Static IPv4') + + @property + def staticIpv6(self): + """ + Retrieves the IPv6 configuration of the nic + @return (StaticIpv6Base list): the static ipv6 setting + """ + raise NotImplementedError('Static Ipv6') + + def validate(self): + """ + Validate the object + For example, the staticIpv4 property is required and should not be + empty when ipv4Mode is STATIC + """ + raise NotImplementedError('Check constraints on properties') + + +class StaticIpv4Base: + """ + Define what are expected of a static IPv4 setting + The following properties should be provided in an implementation class. + """ + + @property + def ip(self): + """ + Retrieves the Ipv4 address + @return (str): the IPADDR setting + """ + raise NotImplementedError('Ipv4 Address') + + @property + def netmask(self): + """ + Retrieves the Ipv4 NETMASK setting + @return (str): the NETMASK setting + """ + raise NotImplementedError('Ipv4 NETMASK') + + @property + def gateways(self): + """ + Retrieves the gateways on this Ipv4 subnet + @return (str list): the GATEWAY setting + """ + raise NotImplementedError('Ipv4 GATEWAY') + + +class StaticIpv6Base: + """Define what are expected of a static IPv6 setting + The following properties should be provided in an implementation class. + """ + + @property + def ip(self): + """ + Retrieves the Ipv6 address + @return (str): the IPv6ADDR setting + """ + raise NotImplementedError('Ipv6 Address') + + @property + def netmask(self): + """ + Retrieves the Ipv6 NETMASK setting + @return (str): the IPv6NETMASK setting + """ + raise NotImplementedError('Ipv6 NETMASK') + + @property + def gateway(self): + """ + Retrieves the Ipv6 GATEWAY setting + @return (str): the IPv6GATEWAY setting + """ + raise NotImplementedError('Ipv6 GATEWAY') -- cgit v1.2.3