Source code for COT.platforms.platform

# September 2016, Glenn F. Matthews
# Copyright (c) 2013-2017 the COT project developers.
# See the COPYRIGHT.txt file at the top-level directory of this distribution
# and at https://github.com/glennmatthews/cot/blob/master/COPYRIGHT.txt.
#
# This file is part of the Common OVF Tool (COT) project.
# It is subject to the license terms in the LICENSE.txt file found in the
# top-level directory of this distribution and at
# https://github.com/glennmatthews/cot/blob/master/LICENSE.txt. No part
# of COT, including this file, may be copied, modified, propagated, or
# distributed except according to the terms contained in the LICENSE.txt file.

"""API and generic implementation of platform-specific logic."""

import logging
from collections import defaultdict
from enum import Enum

from COT.data_validation import (
    validate_int, ValueUnsupportedError, NIC_TYPES, ValidRange,
)

logger = logging.getLogger(__name__)


[docs]class Hardware(Enum): """Enumeration of hardware types that have integer quantities.""" # The actual numbers don't matter at all cpus = 1 memory = 2 nic_count = 3 serial_count = 4
[docs]class Platform(object): """Generic class for operations that depend on guest platform. To be used whenever the guest is unrecognized or does not need special handling. """ PLATFORM_NAME = "(unrecognized platform, generic)" """String used as a descriptive label for this class of Platform.""" CONFIG_TEXT_FILE = 'config.txt' """When embedding a primary configuration text file, use this filename. .. seealso:: :attr:`COT.inject_config.COTInjectConfig.config_file` """ SECONDARY_CONFIG_TEXT_FILE = None """When embedding a secondary configuration text file, use this filename. Most platforms do not support a secondary configuration file. .. seealso:: :attr:`COT.inject_config.COTInjectConfig.secondary_config_file` """ LITERAL_CLI_STRING = 'config' """Key prefix for converting text config to OVF environment properties. Most platforms do not support configuration properties in the environment, and so should define this attribute to ``None``. .. seealso:: :meth:`~COT.vm_description.VMDescription.config_file_to_properties` """ BOOTSTRAP_DISK_TYPE = 'cdrom' """Type of disk (cdrom/harddisk) to use for bootstrap configuration. Most platforms use a CD-ROM for this purpose. """ SUPPORTED_NIC_TYPES = NIC_TYPES """List of NIC device types supported by this platform.""" PRODUCT_PLATFORM_MAP = {} """Mapping of product strings to product classes.""" HARDWARE_LIMITS = { Hardware.cpus: ValidRange(1, None), Hardware.memory: ValidRange(1, None), Hardware.nic_count: ValidRange(0, None), Hardware.serial_count: ValidRange(0, None), } """Range of valid values for various hardware properties."""
[docs] @classmethod def for_product_string(cls, product_string): """Get the class of Platform corresponding to a product string. Args: product_string (str): String such as 'com.cisco.iosxrv' Returns: Platform: Instance of Platform or the appropriate subclass. Examples: :: >>> Platform.for_product_string("com.cisco.n9k") <class 'COT.platforms.cisco_nexus_9000v.Nexus9000v'> >>> Platform.for_product_string(None) <class 'COT.platforms.platform.Platform'> >>> Platform.for_product_string("frobozz") <class 'COT.platforms.platform.Platform'> """ if product_string is None: logger.notice("No product class given. Treating this as" " a generic platform.") return Platform() if product_string in cls.PRODUCT_PLATFORM_MAP: return cls.PRODUCT_PLATFORM_MAP[product_string]() logger.notice("Unrecognized product class '%s'. Treating this as" " a generic platform.", product_string) logger.verbose("Known product classes are %s", cls.PRODUCT_PLATFORM_MAP.keys()) return Platform()
[docs] def __init__(self): """Create an instance of this class.""" self._already_validated = defaultdict(dict) """Cache of values already validated. :: _already_validated[Hardware.cpus][value] = True Used to avoid raising the same ValueError over and over from various points in the code. """
def __str__(self): """String representation - same as :attr:`PLATFORM_NAME`.""" return self.__class__.PLATFORM_NAME # Some of these methods are semi-abstract, so: # pylint: disable=unused-argument, no-self-use
[docs] def controller_type_for_device(self, device_type): """Get the default controller type for the given device type. Args: device_type (str): 'harddisk', 'cdrom', etc. Returns: str: 'ide' unless overridden by subclass. """ # For most platforms IDE is the correct default. return 'ide'
[docs] def guess_nic_name(self, nic_number): """Guess the name of the Nth NIC for this platform. .. note:: This method counts from 1, not from 0! Args: nic_number (int): Nth NIC to name. Returns: str: "Ethernet1", "Ethernet2", etc. unless overridden by subclass. """ return "Ethernet" + str(nic_number)
[docs] def validate_cpu_count(self, cpus): """Throw an error if the number of CPUs is not a supported value. Args: cpus (int): Number of CPUs Raises: ValueTooLowError: if ``cpus`` is less than the minimum required by this platform ValueTooHighError: if ``cpus`` exceeds the maximum supported by this platform """ if cpus not in self._already_validated[Hardware.cpus]: self._already_validated[Hardware.cpus][cpus] = True validate_int(cpus, *self.HARDWARE_LIMITS[Hardware.cpus], label="CPUs for platform {0}".format(self))
[docs] def validate_memory_amount(self, mebibytes): """Throw an error if the amount of RAM is not supported. Args: mebibytes (int): RAM, in MiB. Raises: ValueTooLowError: if ``mebibytes`` is less than the minimum required by this platform ValueTooHighError: if ``mebibytes`` is more than the maximum supported by this platform """ if mebibytes not in self._already_validated[Hardware.memory]: self._already_validated[Hardware.memory][mebibytes] = True validate_int(mebibytes, *self.HARDWARE_LIMITS[Hardware.memory], label="MiB of RAM for platform {0}".format(self))
[docs] def validate_nic_count(self, count): """Throw an error if the number of NICs is not supported. Args: count (int): Number of NICs. Raises: ValueTooLowError: if ``count`` is less than the minimum required by this platform ValueTooHighError: if ``count`` is more than the maximum supported by this platform """ if count not in self._already_validated[Hardware.nic_count]: self._already_validated[Hardware.nic_count][count] = True validate_int(count, *self.HARDWARE_LIMITS[Hardware.nic_count], label="NIC count for platform {0}".format(self))
[docs] def validate_nic_type(self, type_string): """Throw an error if the NIC type string is not supported. .. seealso:: - :func:`COT.data_validation.canonicalize_nic_subtype` - :data:`COT.data_validation.NIC_TYPES` Args: type_string (str): See :data:`COT.data_validation.NIC_TYPES` Raises: ValueUnsupportedError: if ``type_string`` is not in :const:`SUPPORTED_NIC_TYPES` """ if type_string not in self.SUPPORTED_NIC_TYPES: raise ValueUnsupportedError( "NIC type for {0}".format(self), type_string, self.SUPPORTED_NIC_TYPES)
[docs] def validate_nic_types(self, type_list): """Throw an error if any NIC type string in the list is unsupported. Args: type_list (list): See :data:`COT.data_validation.NIC_TYPES` Raises: ValueUnsupportedError: if any value in ``type_list`` is not in :const:`SUPPORTED_NIC_TYPES` """ for type_string in type_list: self.validate_nic_type(type_string)
[docs] def validate_serial_count(self, count): """Throw an error if the number of serial ports is not supported. Args: count (int): Number of serial ports. Raises: ValueTooLowError: if ``count`` is less than the minimum required by this platform ValueTooHighError: if ``count`` is more than the maximum supported by this platform """ if count not in self._already_validated[Hardware.serial_count]: self._already_validated[Hardware.serial_count][count] = True validate_int(count, *self.HARDWARE_LIMITS[Hardware.serial_count], label="serial port count for platform {0}" .format(self))
Platform.PRODUCT_PLATFORM_MAP[None] = Platform