summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSankar Tanguturi <stanguturi@stanguturi-rhel>2015-11-18 16:03:15 -0800
committerSankar Tanguturi <stanguturi@stanguturi-rhel>2015-11-18 16:03:15 -0800
commit8844ffb5988bcfbb8cfbe57d9139c3dcb8b429cc (patch)
treef96479c4a3e21504af9be515970e0279e6deefdc
parent97fbe6e44fc09d7522001deb7e3984272e9c26b7 (diff)
downloadvyos-cloud-init-8844ffb5988bcfbb8cfbe57d9139c3dcb8b429cc.tar.gz
vyos-cloud-init-8844ffb5988bcfbb8cfbe57d9139c3dcb8b429cc.zip
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.
-rw-r--r--cloudinit/sources/helpers/vmware/__init__.py13
-rw-r--r--cloudinit/sources/helpers/vmware/imc/__init__.py13
-rw-r--r--cloudinit/sources/helpers/vmware/imc/boot_proto.py11
-rw-r--r--cloudinit/sources/helpers/vmware/imc/config.py125
-rw-r--r--cloudinit/sources/helpers/vmware/imc/config_file.py221
-rw-r--r--cloudinit/sources/helpers/vmware/imc/config_namespace.py5
-rw-r--r--cloudinit/sources/helpers/vmware/imc/config_source.py2
-rw-r--r--cloudinit/sources/helpers/vmware/imc/ipv4_mode.py29
-rw-r--r--cloudinit/sources/helpers/vmware/imc/nic.py107
9 files changed, 526 insertions, 0 deletions
diff --git a/cloudinit/sources/helpers/vmware/__init__.py b/cloudinit/sources/helpers/vmware/__init__.py
new file mode 100644
index 00000000..386225d5
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/__init__.py
@@ -0,0 +1,13 @@
+# vi: ts=4 expandtab
+#
+# 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 <http://www.gnu.org/licenses/>.
diff --git a/cloudinit/sources/helpers/vmware/imc/__init__.py b/cloudinit/sources/helpers/vmware/imc/__init__.py
new file mode 100644
index 00000000..386225d5
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/__init__.py
@@ -0,0 +1,13 @@
+# vi: ts=4 expandtab
+#
+# 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 <http://www.gnu.org/licenses/>.
diff --git a/cloudinit/sources/helpers/vmware/imc/boot_proto.py b/cloudinit/sources/helpers/vmware/imc/boot_proto.py
new file mode 100644
index 00000000..6c3b070a
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/boot_proto.py
@@ -0,0 +1,11 @@
+# from enum import Enum
+
+class BootProto:
+ 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
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
diff --git a/cloudinit/sources/helpers/vmware/imc/config_file.py b/cloudinit/sources/helpers/vmware/imc/config_file.py
new file mode 100644
index 00000000..3f9938da
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/config_file.py
@@ -0,0 +1,221 @@
+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
diff --git a/cloudinit/sources/helpers/vmware/imc/config_namespace.py b/cloudinit/sources/helpers/vmware/imc/config_namespace.py
new file mode 100644
index 00000000..7f76ac8b
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/config_namespace.py
@@ -0,0 +1,5 @@
+from cloudinit.sources.helpers.vmware.imc.config_source import ConfigSource
+
+
+class ConfigNamespace(ConfigSource):
+ pass
diff --git a/cloudinit/sources/helpers/vmware/imc/config_source.py b/cloudinit/sources/helpers/vmware/imc/config_source.py
new file mode 100644
index 00000000..fad3a389
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/config_source.py
@@ -0,0 +1,2 @@
+class ConfigSource:
+ pass
diff --git a/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py b/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py
new file mode 100644
index 00000000..66b4fad7
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/ipv4_mode.py
@@ -0,0 +1,29 @@
+# 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)
diff --git a/cloudinit/sources/helpers/vmware/imc/nic.py b/cloudinit/sources/helpers/vmware/imc/nic.py
new file mode 100644
index 00000000..b90a5640
--- /dev/null
+++ b/cloudinit/sources/helpers/vmware/imc/nic.py
@@ -0,0 +1,107 @@
+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