From 48df97e133b99b9fe3b42fc087aa333e67bc0773 Mon Sep 17 00:00:00 2001 From: Scott Hussey Date: Tue, 12 Sep 2017 16:35:02 -0500 Subject: [PATCH] DRYD-50 Drydock support of NIC bonding Implement interface bonding via the MaaS API - Documentation on writing topology definition of networking and host network attachment - Adjust topology YAML schema for interface definition - Add MaaS API support for create_bond - Fix some bugs from Gerrit #377818 - Update MaaS API client to support multi-select options Change-Id: I1c42300ede3f67595ebc8029b0f375622b459254 --- docs/topology.rst | 336 ++++++++++++++++-- .../drivers/node/maasdriver/api_client.py | 32 +- .../drivers/node/maasdriver/driver.py | 54 ++- .../node/maasdriver/models/blockdev.py | 4 +- .../node/maasdriver/models/interface.py | 133 ++++++- .../drivers/node/maasdriver/models/machine.py | 65 ++-- .../drivers/node/maasdriver/models/vlan.py | 4 +- .../node/maasdriver/models/volumegroup.py | 11 + drydock_provisioner/drydock_client/session.py | 6 +- drydock_provisioner/ingester/plugins/yaml.py | 21 +- drydock_provisioner/orchestrator/readme.md | 32 +- tests/unit/test_maasdriver_calculate_bytes.py | 65 ++-- tests/yaml_samples/fullsite.yaml | 22 +- tests/yaml_samples/fullsite_profiles.yaml | 103 +++--- 14 files changed, 686 insertions(+), 202 deletions(-) diff --git a/docs/topology.rst b/docs/topology.rst index db405868..3135cb40 100644 --- a/docs/topology.rst +++ b/docs/topology.rst @@ -12,17 +12,279 @@ metadata. The best source for a sample of the YAML schema for a topology is the unit test input source_ /tests/yaml_samples/fullsite.yaml in tests/yaml_samples/fullsite.yaml. +Defining Networking +=================== + +Network definitions in the topology are described by two document types: NetworkLink and +Network. NetworkLink describes a physical or logical link between a node and switch. It +is concerned with attributes that must be agreed upon by both endpoints: bonding, media +speed, trunking, etc. A Network describes the layer 2 and layer 3 networks accessible +over a link. + +Network Links +------------- + +The NetworkLink document defines layer 1 and layer 2 attributes that should be in-sync +between the node and the switch. Each link can support a single untagged VLAN and 0 or more +tagged VLANs. + +Example YAML schema of the NetworkLink spec:: + + spec: + bonding: + mode: 802.3ad + hash: layer3+4 + peer_rate: slow + mtu: 9000 + linkspeed: auto + trunking: + mode: 802.1q + allowed_networks: + - public + - mgmt + +``bonding`` describes combining multiple physical links into a single logical link (aka LAG +or link aggregation group). + +* ``mode``: What bonding mode to configure + + * ``disabled``: Do not configure a bond + * ``802.3ad``: Use 802.3ad dynamic aggregation (aka LACP) + * ``active-backup``: Use static active/standby bonding + * ``balanced-rr``: Use static round-robin bonding + +For a ``mode`` of ``802.3ad`` the below attributes are available, but optional. + +* ``hash``: The link selection hash. Supported values are ``layer3+4``, ``layer2+3``, ``layer2``. Default is ``layer3+4`` +* ``peer_rate``: How frequently to send LACP control frames. Supported values are ``fast`` and ``slow``. Default is ``fast`` +* ``mon_rate``: Interval between checking link state in milliseconds. Default is ``100`` +* ``up_delay``: Delay in milliseconds between a link coming up and being marked up in the bond. Must be greater than ``mon_rate``. Default is ``200`` +* ``down_delay``: Delay in milliseconds between a link going down and being marked down in the bond. Must be greater than ``mon_rate``. Default is ``200`` + +``mtu`` is the maximum transmission unit for the link. It must be equal or greater than the MTU of any VLAN interfaces +using the link. Default is ``1500``. + +``linkspeed`` is the physical layer speed and duplex. Recommended to always be ``auto`` + +``trunking`` describes how multiple layer 2 networks will be multiplexed on the link. + + * ``mode``: Can be ``disabled`` for no trunking or ``802.1q`` for standard VLAN tagging + * ``default_network``: For ``mode: disabled``, this is the single network on the link. For ``mode: 802.1q`` this is optionally the network accessed by untagged frames. + +``allowed_networks`` is a sequence of network names listing all networks allowed on this link. Each Network can +be listed on one and only one NetworkLink. + +Network +------- + +The Network document defines the layer 2 and layer 3 networks nodes will access. Each Network is accessible over +exactly one NetworkLink. However that NetworkLink can be attached to different interfaces on different nodes +to support changing hardware configurations. + +Example YAML schema of the Network spec:: + + spec: + vlan: '102' + mtu: 1500 + cidr: 172.16.3.0/24 + ranges: + - type: static + start: 172.16.3.15 + end: 172.16.3.200 + - type: dhcp + start: 172.16.3.201 + end: 172.16.3.254 + routes: + - subnet: 0.0.0.0/0 + gateway: 172.16.3.1 + metric: 10 + dns: + domain: sitename.example.com + servers: 8.8.8.8 + +If a Network is accessible over a NetworkLink using 802.1q VLAN tagging, the ``vlan`` attribute +specified the VLAN tag for this Network. It should be omitted for non-tagged Networks. + +``mtu`` is the maximum transmission unit for this Network. Must be equal or less than the ``mtu`` +defined for the hosting NetworkLink. Can be omitted to default to the NetworkLink ``mtu``. + +``cidr`` is the classless inter-domain routing address for the network. + +``ranges`` defines a sequence of IP addresses within the defined ``cidr``. Ranges cannot overlap. + +* ``type``: The type of address range. + + * ``static``: A range used for static, explicit address assignments for nodes. + * ``dhcp``: A range used for assigning DHCP addresses. Note that a network being used for PXE booting must have a DHCP range defined. + * ``reserved``: A range of addresses that will not be used by MaaS. + +* ``start``: The starting IP of the range, inclusive. +* ``end``: The last IP of the range, inclusive + +*NOTE: Static routes is not currently implemented beyond specifying a route for 0.0.0.0/0 for default route* +``routes`` defines a list of static routes to be configured on nodes attached to this network. + +* ``subnet``: Destination CIDR for the route +* ``gateway``: The gateway IP on this Network to use for accessing the destination +* ``metric``: The metric or weight for this route + +``dns`` is used for specifying the list of DNS servers to use if this network +is the priamry network for the node. + +* ``servers``: A comma-separated list of IP addresses to use for DNS resolution +* ``domain``: A domain that can be used for automated registeration of IP addresses assigned from this Network + +DHCP Relay +~~~~~~~~~~ + +DHCP relaying is used when a DHCP server is not attached to the same layer 2 broadcast domain as nodes that +are being PXE booted. The DHCP requests from the node are consumed by the relay (generally configured on a +top-of-rack switch) which then enscapsulates the request in layer 3 routing and sends it to an upstream DHCP +server. The Network spec supports a ``dhcp_relay`` key for Networks that should relay DHCP requests. + +* The Network must have a configured DHCP relay, this is *not* configured by Drydock or MaaS. +* The ``upstream_target`` IP address must be a host IP address for a MaaS rack controller +* The Network must have a defined DHCP address range. +* The upstream target network must have a defined DHCP address range. + +The ``dhcp_relay`` stanza:: + + dhcp_relay: + upstream_target: 172.16.4.100 + +Defining Node Configuration +=========================== + +Node configuration is defined in three documents: HostProfile, HardwareProfile and BaremetalNode. HardwareProfile +defines attributes directly related to hardware configuration such as card-slot layout and firmware levels. HostProfile +is a generic definition for how a node should be configured such that many nodes can reference a single HostProfile +and each will be configured identically. A BaremetalNode is a concrete reference to particular physical node. +The BaremetalNode definition will reference a HostProfile and can then extend or override any of the configuration values. + +Example HostProfile and BaremetalNode configuration:: + + --- + apiVersion: 'drydock/v1' + kind: HostProfile + metadata: + name: defaults + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + spec: + # configuration values + --- + apiVersion: 'drydock/v1' + kind: HostProfile + metadata: + name: compute_node + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + spec: + host_profile: defaults + # compute_node customizations to defaults + --- + apiVersion: 'drydock/v1' + kind: BaremetalNode + metadata: + name: compute01 + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + spec: + host_profile: compute_node + # configuration customization specific to single node compute01 + ... + +In the above example, the ``compute_node`` HostProfile adopts all values from the ``defaults`` +HostProfile and can then override defined values or append additional values. BaremetalNode +``compute01`` then adopts all values from the ``compute_node`` HostProfile (which includes all +the configuration items it adopted from ``defaults``) and can then again override or append any +configuration that is specific to that node. + +Defining Node Interfaces and Network Addressing +=============================================== + +Node network attachment can be described in a HostProfile or a BaremetalNode document. Node addressing +is allowed only in a BaremetalNode document. If a HostProfile or BaremetalNode needs to remove a defined +interface from an inherited configuration, it can set the mapping value for the interface name to ``null``. + +Once the interface attachments to networks is defined, HostProfile and BaremetalNode specs must define a +``primary_network`` attribute to denote which network the node should use a the primary route. This designation + +Interfaces +---------- + +Interfaces for a node can be described in either a HostProfile or BaremetalNode definition. This will attach +a defined NetworkLink to a host interface and define which Networks should be configured to use that interface. + +Example interface definition YAML schema:: + + interfaces: + pxe: + device_link: pxe + labels: + pxe: true + slaves: + - prim_nic01 + networks: + - pxe + bond0: + device_link: gp + slaves: + - prim_nic01 + - prim_nic02 + networks: + - mgmt + - private + +Each key in the interfaces mapping is a defined interface. The key is the name that will be used +on the deployed node for the interface. The value must be a mapping defining the interface configuration +or ``null`` to denote removal of that interface for an inherited configuration. + +* ``device_link``: The name of the defined NetworkLink that will be attached to this interface. The NetworkLink + definition includes part of the interface configuration such as bonding. +* ``labels``: Metadata for describing this interface. +* ``slaves``: The list of hardware interfaces used for creating this interface. This value can be a device alias + defined in the HardwareProfile or the kernel name of the hardware interface. For bonded interfaces, this would + list all the slaves. For non-bonded interfaces, this should list the single hardware interface used. +* ``networks``: This is the list of networks to enable on this interface. If multiple networks are listed, the + NetworkLink attached to this interface must have trunking enabled or the design validation will fail. + +Addressing +---------- + +Addressing for a node can only be defined in a BaremetalNode definition. The ``addressing`` stanza simply +defines a static IP address or ``dhcp`` for each network a node should have a configured layer 3 interface on. It +is a valid design to omit networks from the ``addressing`` stanza, in that case the interface attached to the omitted +network will be configured as link up with no address. + +Example ``addressing`` YAML schema:: + + addressing: + - network: pxe + address: dhcp + - network: mgmt + address: 172.16.1.21 + - network: private + address: 172.16.2.21 + - network: oob + address: 172.16.100.21 + + Defining Node Storage ===================== -Storage can be defined in the `storage` stanza of either a HostProfile or BaremetalNode +Storage can be defined in the ``storage`` stanza of either a HostProfile or BaremetalNode document. The storage configuration can describe creation of partitions on physical disks, the assignment of physical disks and/or partitions to volume groups, and the creation of logical volumes. Drydock will make a best effort to parse out system-level storage such as the root filesystem or boot filesystem and take appropriate steps to configure them in -the active node provisioning driver. +the active node provisioning driver. At a minimum the storage configuration *must* contain +a root filesystem partition. -Example YAML schema of the `storage` stanza:: +Example YAML schema of the ``storage`` stanza:: storage: physical_devices: @@ -58,23 +320,21 @@ Example YAML schema of the `storage` stanza:: Schema ------ -The `storage` stanza can contain two top level keys: `physical_devices` and -`volume_groups`. The latter is optional. +The ``storage`` stanza can contain two top level keys: ``physical_devices`` and +``volume_groups``. The latter is optional. Physical Devices and Partitions ------------------------------- A physical device can either be carved up in partitions (including a single partition consuming the entire device) or added to a volume group as a physical volume. Each -key in the `physical_devices` mapping represents a device on a node. The key should either +key in the ``physical_devices`` mapping represents a device on a node. The key should either be a device alias defined in the HardwareProfile or the name of the device published by the OS. The value of each key must be a mapping with the following keys -* `labels`: A mapping of key/value strings providing generic labels for the device -* `partitions`: A sequence of mappings listing the partitions to be created on the device. -The mapping is described below. Incompatible with the `volume_group` specification. -* `volume_group`: A volume group name to add the device to as a physical volume. Incompatible -with the `partitions` specification. +* ``labels``: A mapping of key/value strings providing generic labels for the device +* ``partitions``: A sequence of mappings listing the partitions to be created on the device. The mapping is described below. Incompatible with the ``volume_group`` specification. +* ``volume_group``: A volume group name to add the device to as a physical volume. Incompatible with the ``partitions`` specification. Partition ~~~~~~~~~ @@ -82,51 +342,51 @@ Partition A partition mapping describes a GPT partition on a physical disk. It can left as a raw block device or formatted and mounted as a filesystem -* `name`: Metadata describing the partition in the topology -* `size`: The size of the partition. See the *Size Format* section below -* `bootable`: Boolean whether this partition should be the bootable device -* `part_uuid`: A UUID4 formatted UUID to assign to the partition. If not specified one will be generated -* `filesystem`: A optional mapping describing how the partition should be formatted and mounted - * `mountpoint`: Where the filesystem should be mounted. If not specified the partition will be left as a raw deice - * `fstype`: The format of the filesyste. Defaults to ext4 - * `mount_options`: fstab style mount options. Default is 'defaults' - * `fs_uuid`: A UUID4 formatted UUID to assign to the filesystem. If not specified one will be generated - * `fs_label`: A filesystem label to assign to the filesystem. Optional. +* ``name``: Metadata describing the partition in the topology +* ``size``: The size of the partition. See the *Size Format* section below +* ``bootable``: Boolean whether this partition should be the bootable device +* ``part_uuid``: A UUID4 formatted UUID to assign to the partition. If not specified one will be generated +* ``filesystem``: A optional mapping describing how the partition should be formatted and mounted + + * ``mountpoint``: Where the filesystem should be mounted. If not specified the partition will be left as a raw deice + * ``fstype``: The format of the filesyste. Defaults to ext4 + * ``mount_options``: fstab style mount options. Default is 'defaults' + * ``fs_uuid``: A UUID4 formatted UUID to assign to the filesystem. If not specified one will be generated + * ``fs_label``: A filesystem label to assign to the filesystem. Optional. Size Format ~~~~~~~~~~~ The size specification for a partition or logical volume is formed from three parts -* The first character can optionally be `>` indicating that the size specified is a minimum and the -calculated size should be at least the minimum and should take the rest of the available space on -the physical device or volume group. +* The first character can optionally be ``>`` indicating that the size specified is a minimum and the calculated size should be at least the minimum and should take the rest of the available space on the physical device or volume group. * The second part is the numeric portion and must be an integer * The third part is a label - * `m`\|`M`\|`mb`\|`MB`: Megabytes or 10^6 * the numeric - * `g`\|`G`\|`gb`\|`GB`: Gigabytes or 10^9 * the numeric - * `t`\|`T`\|`tb`\|`TB`: Terabytes or 10^12 * the numeric - * `%`: The percentage of total device or volume group space + + * ``m``\|``M``\|``mb``\|``MB``: Megabytes or 10^6 * the numeric + * ``g``\|``G``\|``gb``\|``GB``: Gigabytes or 10^9 * the numeric + * ``t``\|``T``\|``tb``\|``TB``: Terabytes or 10^12 * the numeric + * ``%``: The percentage of total device or volume group space Volume Groups and Logical Volumes --------------------------------- Logical volumes can be used to create RAID-0 volumes spanning multiple physical disks or partitions. -Each key in the `volume_groups` mapping is a name assigned to a volume group. This name must be specified -as the `volume_group` attribute on one or more physical devices or partitions, or the configuration is invalid. +Each key in the ``volume_groups`` mapping is a name assigned to a volume group. This name must be specified +as the ``volume_group`` attribute on one or more physical devices or partitions, or the configuration is invalid. Each mapping value is another mapping describing the volume group. -* `vg_uuid`: A UUID4 format uuid applied to the volume group. If not specified, one is generated -* `logical_volumes`: A sequence of mappings listing the logical volumes to be created in the volume group +* ``vg_uuid``: A UUID4 format uuid applied to the volume group. If not specified, one is generated +* ``logical_volumes``: A sequence of mappings listing the logical volumes to be created in the volume group Logical Volume ~~~~~~~~~~~~~~ -A logical volume is a RAID-0 volume. Using logical volumes for `/` and `/boot` is supported +A logical volume is a RAID-0 volume. Using logical volumes for ``/`` and ``/boot`` is supported + +* ``name``: Required field. Used as the logical volume name. +* ``size``: The logical volume size. See *Size Format* above for details. +* ``lv_uuid``: A UUID4 format uuid applied to the logical volume: If not specified, one is generated +* ``filesystem``: A mapping specifying how the logical volume should be formatted and mounted. See the *Partition* section above for filesystem details. -* `name`: Required field. Used as the logical volume name. -* `size`: The logical volume size. See *Size Format* above for details. -* `lv_uuid`: A UUID4 format uuid applied to the logical volume: If not specified, one is generated -* `filesystem`: A mapping specifying how the logical volume should be formatted and mounted. See the -*Partition* section above for filesystem details. diff --git a/drydock_provisioner/drivers/node/maasdriver/api_client.py b/drydock_provisioner/drivers/node/maasdriver/api_client.py index 61f0660f..55f63f4a 100644 --- a/drydock_provisioner/drivers/node/maasdriver/api_client.py +++ b/drydock_provisioner/drivers/node/maasdriver/api_client.py @@ -76,7 +76,7 @@ class MaasRequestFactory(object): def test_connectivity(self): try: resp = self.get('version/') - except requests.Timeout as ex: + except requests.Timeout: raise errors.TransientDriverError("Timeout connection to MaaS") if resp.status_code in [500, 503]: @@ -122,24 +122,26 @@ class MaasRequestFactory(object): if kwargs.get('files', None) is not None: files = kwargs.pop('files') - files_tuples = {} + files_tuples = [] for (k, v) in files.items(): if v is None: continue - files_tuples[k] = ( - None, - base64.b64encode(str(v).encode('utf-8')).decode('utf-8'), - 'text/plain; charset="utf-8"', { - 'Content-Transfer-Encoding': 'base64' - }) - - # elif isinstance(v, str): - # files_tuples[k] = (None, base64.b64encode(v.encode('utf-8')).decode('utf-8'), 'text/plain; charset="utf-8"', {'Content-Transfer-Encoding': 'base64'}) - # elif isinstance(v, int) or isinstance(v, bool): - # if isinstance(v, bool): - # v = int(v) - # files_tuples[k] = (None, base64.b64encode(v.to_bytes(2, byteorder='big')), 'application/octet-stream', {'Content-Transfer-Encoding': 'base64'}) + elif isinstance(v, list): + for i in v: + files_tuples.append( + (k, (None, base64.b64encode( + str(i).encode('utf-8')).decode('utf-8'), + 'text/plain; charset="utf-8"', { + 'Content-Transfer-Encoding': 'base64' + }))) + else: + files_tuples.append((k, (None, base64.b64encode( + str(v).encode('utf-8')).decode('utf-8'), + 'text/plain; charset="utf-8"', { + 'Content-Transfer-Encoding': + 'base64' + }))) kwargs['files'] = files_tuples diff --git a/drydock_provisioner/drivers/node/maasdriver/driver.py b/drydock_provisioner/drivers/node/maasdriver/driver.py index 3977b17f..475cdaf2 100644 --- a/drydock_provisioner/drivers/node/maasdriver/driver.py +++ b/drydock_provisioner/drivers/node/maasdriver/driver.py @@ -1419,6 +1419,9 @@ class MaasTaskRunner(drivers.DriverTaskRunner): "Located node %s in MaaS, starting interface configuration" % (n)) + machine.reset_network_config() + machine.refresh() + for i in node.interfaces: nl = site_design.get_network_link( i.network_link) @@ -1439,9 +1442,51 @@ class MaasTaskRunner(drivers.DriverTaskRunner): failed = True continue - # TODO(sh8121att): HardwareProfile device alias integration - iface = machine.get_network_interface( - i.device_name) + if nl.bonding_mode != hd_fields.NetworkLinkBondingMode.Disabled: + if len(i.get_hw_slaves()) > 1: + msg = "Building node %s interface %s as a bond." % ( + n, i.device_name) + self.logger.debug(msg) + result_detail['detail'].append(msg) + hw_iface_list = i.get_hw_slaves() + iface = machine.interfaces.create_bond( + device_name=i.device_name, + parent_names=hw_iface_list, + mtu=nl.mtu, + fabric=fabric.resource_id, + mode=nl.bonding_mode, + monitor_interval=nl. + bonding_mon_rate, + downdelay=nl.bonding_down_delay, + updelay=nl.bonding_up_delay, + lacp_rate=nl.bonding_peer_rate, + hash_policy=nl.bonding_xmit_hash) + else: + msg = "Network link %s indicates bonding, interface %s has less than 2 slaves." % \ + (nl.name, i.device_name) + self.logger.warning(msg) + result_detail['detail'].append(msg) + continue + else: + if len(i.get_hw_slaves()) > 1: + msg = "Network link %s disables bonding, interface %s has multiple slaves." % \ + (nl.name, i.device_name) + self.logger.warning(msg) + result_detail['detail'].append(msg) + continue + elif len(i.get_hw_slaves()) == 0: + msg = "Interface %s has 0 slaves." % ( + i.device_name) + self.logger.warning(msg) + result_detail['detail'].append(msg) + else: + msg = "Configuring interface %s on node %s" % ( + i.device_name, n) + self.logger.debug(msg) + hw_iface = i.get_hw_slaves()[0] + # TODO(sh8121att): HardwareProfile device alias integration + iface = machine.get_network_interface( + hw_iface) if iface is None: self.logger.warning( @@ -1950,7 +1995,8 @@ class MaasTaskRunner(drivers.DriverTaskRunner): maas_volgroup.refresh() for lv in v.logical_volumes: - calc_size = MaasTaskRunner.calculate_bytes(size_str=lv.size, context=maas_volgroup) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=lv.size, context=maas_volgroup) bd_id = maas_volgroup.create_lv( name=lv.name, uuid_str=lv.lv_uuid, diff --git a/drydock_provisioner/drivers/node/maasdriver/models/blockdev.py b/drydock_provisioner/drivers/node/maasdriver/models/blockdev.py index 806709db..8169307c 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/blockdev.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/blockdev.py @@ -56,7 +56,7 @@ class BlockDevice(model_base.ResourceBase): def __init__(self, api_client, **kwargs): super().__init__(api_client, **kwargs) - if getattr(self, 'resource_id', None) is not None: + if hasattr(self, 'resource_id') and hasattr(self, 'system_id'): try: self.partitions = maas_partition.Partitions( api_client, @@ -116,7 +116,7 @@ class BlockDevice(model_base.ResourceBase): (self.name)) return - if self.filesystem.get('mount_pount', None) is not None: + if self.filesystem.get('mount_point', None) is not None: self.unmount() url = self.interpolate_url() diff --git a/drydock_provisioner/drivers/node/maasdriver/models/interface.py b/drydock_provisioner/drivers/node/maasdriver/models/interface.py index 224c4483..6605ef5d 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/interface.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/interface.py @@ -37,13 +37,13 @@ class Interface(model_base.ResourceBase): 'effective_mtu', 'fabric_id', 'mtu', + 'parents', ] json_fields = [ 'name', 'type', 'mac_address', 'vlan', - 'links', 'mtu', ] @@ -98,12 +98,32 @@ class Interface(model_base.ResourceBase): self.update() def is_linked(self, subnet_id): + """Check if this interface is linked to the given subnet. + + :param subnet_id: MaaS resource id of the subnet + """ for l in self.links: if l.get('subnet_id', None) == subnet_id: return True return False + def disconnect(self): + """Disconnect this interface from subnets and VLANs.""" + url = self.interpolate_url() + + self.logger.debug("Disconnecting interface %s from networks." % + (self.name)) + resp = self.api_client.post(url, op='disconnect') + + if not resp.ok: + self.logger.warning( + "Could not disconnect interface, MaaS error: %s - %s" % + (resp.status_code, resp.text)) + raise errors.DriverError( + "Could not disconnect interface, MaaS error: %s - %s" % + (resp.status_code, resp.text)) + def unlink_subnet(self, subnet_id): for l in self.links: if l.get('subnet_id', None) == subnet_id: @@ -216,7 +236,7 @@ class Interface(model_base.ResourceBase): return False def set_mtu(self, new_mtu): - """Set interface MTU + """Set interface MTU. :param new_mtu: integer of the new MTU size for this inteface """ @@ -284,14 +304,6 @@ class Interfaces(model_base.ResourceCollectionBase): raise errors.DriverError("Cannot locate parent interface %s" % (parent_name)) - if parent_iface.type != 'physical': - self.logger.error( - "Cannot create VLAN interface on parent of type %s" % - (parent_iface.type)) - raise errors.DriverError( - "Cannot create VLAN interface on parent of type %s" % - (parent_iface.type)) - if parent_iface.vlan is None: self.logger.error( "Cannot create VLAN interface on disconnected parent %s" % @@ -352,3 +364,104 @@ class Interfaces(model_base.ResourceCollectionBase): self.refresh() return + + def create_bond(self, + device_name=None, + parent_names=[], + mtu=None, + mac_address=None, + tags=[], + fabric=None, + mode=None, + monitor_interval=None, + downdelay=None, + updelay=None, + lacp_rate=None, + hash_policy=None): + """Create a new bonded interface on this node. + + Slaves will be disconnected from networks. + + :param device_name: What the bond interface should be named + :param parent_names: The names of interfaces to use as slaves + :param mtu: Optional configuration of the interface MTU + :param mac_address: String of valid 48-bit mac address, colon separated + :param tags: Optional list of string tags to apply to the bonded interface + :param fabric: Fabric (MaaS resource id) to attach the new bond to. + :param mode: The bonding mode + :param monitor_interval: The frequency of checking slave status in milliseconds + :param downdelay: The delay in disabling a down slave in milliseconds + :param updelay: The delay in enabling a recovered slave in milliseconds + :param lacp_rate: Rate LACP control units are emitted - 'fast' or 'slow' + :param hash_policy: Link selection hash policy + """ + self.refresh() + + parent_ifaces = [] + + for n in parent_names: + parent_iface = self.singleton({'name': n}) + if parent_iface is not None: + parent_ifaces.append(parent_iface) + else: + self.logger.error("Cannot locate slave interface %s" % (n)) + + if len(parent_ifaces) != len(parent_names): + self.logger.error("Missing slave interfaces.") + raise errors.DriverError("Missing slave interfaces.") + + for i in parent_ifaces: + if mtu: + i.set_mtu(mtu) + i.disconnect() + i.attach_fabric(fabric_id=fabric) + + url = self.interpolate_url() + + options = { + 'name': device_name, + 'tags': tags, + 'parents': [x.resource_id for x in parent_ifaces], + } + + if mtu is not None: + options['mtu'] = mtu + + if mac_address is not None: + options['mac_address'] = mac_address + + if mode is not None: + options['bond_mode'] = mode + + if monitor_interval is not None: + options['bond_miimon'] = monitor_interval + + if downdelay is not None: + options['bond_downdelay'] = downdelay + + if updelay is not None: + options['bond_updelay'] = updelay + + if lacp_rate is not None: + options['bond_lacp_rate'] = lacp_rate + + if hash_policy is not None: + options['bond_xmit_hash_policy'] = hash_policy + + resp = self.api_client.post(url, op='create_bond', files=options) + + if resp.status_code == 200: + resp_json = resp.json() + bond_iface = Interface.from_dict(self.api_client, resp_json) + self.logger.debug("Created bond interface %s with slaves %s" % + (bond_iface.resource_id, ','.join(parent_names))) + bond_iface.attach_fabric(fabric_id=fabric) + self.refresh() + return bond_iface + else: + self.logger.error( + "Error creating bond interface on system %s - MaaS response %s: %s" + % (self.system_id, resp.status_code, resp.text)) + raise errors.DriverError( + "Error creating bond interface on system %s - MaaS response %s" + % (self.system_id, resp.status_code)) diff --git a/drydock_provisioner/drivers/node/maasdriver/models/machine.py b/drydock_provisioner/drivers/node/maasdriver/models/machine.py index 06603a27..0ed57883 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/machine.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/machine.py @@ -48,7 +48,7 @@ class Machine(model_base.ResourceBase): super(Machine, self).__init__(api_client, **kwargs) # Replace generic dicts with interface collection model - if getattr(self, 'resource_id', None) is not None: + if hasattr(self, 'resource_id'): self.interfaces = maas_interface.Interfaces( api_client, system_id=self.resource_id) self.interfaces.refresh() @@ -56,14 +56,14 @@ class Machine(model_base.ResourceBase): self.block_devices = maas_blockdev.BlockDevices( api_client, system_id=self.resource_id) self.block_devices.refresh() - except Exception as ex: + except Exception: self.logger.warning("Failed loading node %s block devices." % (self.resource_id)) try: self.volume_groups = maas_vg.VolumeGroups( api_client, system_id=self.resource_id) self.volume_groups.refresh() - except Exception as ex: + except Exception: self.logger.warning("Failed load node %s volume groups." % (self.resource_id)) else: @@ -84,6 +84,7 @@ class Machine(model_base.ResourceBase): return None def get_power_params(self): + """Load power parameters for this node from MaaS.""" url = self.interpolate_url() resp = self.api_client.get(url, op='power_parameters') @@ -91,6 +92,21 @@ class Machine(model_base.ResourceBase): if resp.status_code == 200: self.power_parameters = resp.json() + def reset_network_config(self): + """Reset the node networking configuration.""" + self.logger.info("Resetting networking configuration on node %s" % + (self.resource_id)) + + url = self.interpolate_url() + + resp = self.api_client.post(url, op='restore_networking_configuration') + + if not resp.ok: + msg = "Error resetting network on node %s: %s - %s" \ + % (self.resource_id, resp.status_code, resp.text) + self.logger.error(msg) + raise errors.DriverError(msg) + def reset_storage_config(self): """Reset storage config on this machine. @@ -186,6 +202,10 @@ class Machine(model_base.ResourceBase): raise errors.DriverError(msg) def commission(self, debug=False): + """Start the MaaS commissioning process. + + :param debug: If true, enable ssh on the node and leave it power up after commission + """ url = self.interpolate_url() # If we want to debug this node commissioning, enable SSH @@ -206,6 +226,12 @@ class Machine(model_base.ResourceBase): resp.status_code) def deploy(self, user_data=None, platform=None, kernel=None): + """Start the MaaS deployment process. + + :param user_data: cloud-init user data + :param platform: Which image to install + :param kernel: Which kernel to enable + """ deploy_options = {} if user_data is not None: @@ -247,14 +273,14 @@ class Machine(model_base.ResourceBase): return detail_config def set_owner_data(self, key, value): - """ - Add/update/remove node owner data. If the machine is not currently allocated to a user + """Add/update/remove node owner data. + + If the machine is not currently allocated to a user it cannot have owner data :param key: Key of the owner data :param value: Value of the owner data. If None, the key is removed """ - url = self.interpolate_url() resp = self.api_client.post( @@ -270,8 +296,9 @@ class Machine(model_base.ResourceBase): resp.status_code) def to_dict(self): - """ - Serialize this resource instance into a dict matching the + """Serialize this resource instance into a dict. + + The dict format matches the MAAS representation of the resource """ data_dict = {} @@ -287,9 +314,9 @@ class Machine(model_base.ResourceBase): @classmethod def from_dict(cls, api_client, obj_dict): - """ - Create a instance of this resource class based on a dict - of MaaS type attributes + """Create a instance of this resource class based on a dict. + + Dict format matches MaaS type attributes Customized for Machine due to use of system_id instead of id as resource key @@ -297,7 +324,6 @@ class Machine(model_base.ResourceBase): :param api_client: Instance of api_client.MaasRequestFactory for accessing MaaS API :param obj_dict: Python dict as parsed from MaaS API JSON representing this resource type """ - refined_dict = {k: obj_dict.get(k, None) for k in cls.fields} if 'system_id' in obj_dict.keys(): @@ -327,12 +353,10 @@ class Machines(model_base.ResourceCollectionBase): v.get_power_params() def acquire_node(self, node_name): - """ - Acquire a commissioned node fro deployment + """Acquire a commissioned node fro deployment. :param node_name: The hostname of a node to acquire """ - self.refresh() node = self.singleton({'hostname': node_name}) @@ -364,7 +388,8 @@ class Machines(model_base.ResourceCollectionBase): return node def identify_baremetal_node(self, node_model, update_name=True): - """ + """Find MaaS node resource matching Drydock BaremetalNode. + Search all the defined MaaS Machines and attempt to match one against the provided Drydock BaremetalNode model. Update the MaaS instance with the correct hostname @@ -372,7 +397,6 @@ class Machines(model_base.ResourceCollectionBase): :param node_model: Instance of objects.node.BaremetalNode to search MaaS for matching resource :param update_name: Whether Drydock should update the MaaS resource name to match the Drydock design """ - maas_node = None if node_model.oob_type == 'ipmi': @@ -390,7 +414,7 @@ class Machines(model_base.ResourceCollectionBase): 'power_params.power_address': node_oob_ip }) - except ValueError as ve: + except ValueError: self.logger.warn( "Error locating matching MaaS resource for OOB IP %s" % (node_oob_ip)) @@ -438,10 +462,11 @@ class Machines(model_base.ResourceCollectionBase): return result def add(self, res): - """ - Create a new resource in this collection in MaaS + """Create a new resource in this collection in MaaS. Customize as Machine resources use 'system_id' instead of 'id' + + :param res: A instance of the Machine model """ data_dict = res.to_dict() url = self.interpolate_url() diff --git a/drydock_provisioner/drivers/node/maasdriver/models/vlan.py b/drydock_provisioner/drivers/node/maasdriver/models/vlan.py index 33edc19e..201826e7 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/vlan.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/vlan.py @@ -108,7 +108,7 @@ class Vlans(model_base.ResourceCollectionBase): 'name': res.name, 'description': getattr(res, 'description', None), } - + if getattr(res, 'vid', None) is None: min_fields['vid'] = 0 else: @@ -124,7 +124,7 @@ class Vlans(model_base.ResourceCollectionBase): # Submit PUT for additonal fields res.update() return res - + raise errors.DriverError("Failed updating MAAS url %s - return code %s\n%s" % (url, resp.status_code, resp.text)) """ diff --git a/drydock_provisioner/drivers/node/maasdriver/models/volumegroup.py b/drydock_provisioner/drivers/node/maasdriver/models/volumegroup.py index 32470589..aaa9ea11 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/volumegroup.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/volumegroup.py @@ -115,6 +115,17 @@ class VolumeGroup(model_base.ResourceBase): self.logger.error(msg) raise errors.DriverError(msg) + def delete(self): + """Delete this volume group. + + Override the default delete so that logical volumes can be + removed first. + """ + for lv in self.logical_volumes: + self.delete_lv(lv_name=lv) + + super().delete() + @classmethod def from_dict(cls, api_client, obj_dict): """Instantiate this model from a dictionary. diff --git a/drydock_provisioner/drydock_client/session.py b/drydock_provisioner/drydock_client/session.py index 975e9226..a26e54fb 100644 --- a/drydock_provisioner/drydock_client/session.py +++ b/drydock_provisioner/drydock_client/session.py @@ -25,11 +25,7 @@ class DrydockSession(object): :param string marker: (optional) external context marker """ - def __init__(self, - host, - port=None, - scheme='http', - token=None, + def __init__(self, host, port=None, scheme='http', token=None, marker=None): self.__session = requests.Session() self.__session.headers.update({ diff --git a/drydock_provisioner/ingester/plugins/yaml.py b/drydock_provisioner/ingester/plugins/yaml.py index 85d6f8ef..2708b165 100644 --- a/drydock_provisioner/ingester/plugins/yaml.py +++ b/drydock_provisioner/ingester/plugins/yaml.py @@ -225,8 +225,6 @@ class YamlIngester(IngesterPlugin): model.metalabels.append(l) model.cidr = spec.get('cidr', None) - model.allocation_strategy = spec.get( - 'allocation', 'static') model.vlan_id = spec.get('vlan', None) model.mtu = spec.get('mtu', None) @@ -414,25 +412,30 @@ class YamlIngester(IngesterPlugin): vg.logical_volumes.append(lv) - interfaces = spec.get('interfaces', []) + interfaces = spec.get('interfaces', {}) model.interfaces = objects.HostInterfaceList() - for i in interfaces: + for k, v in interfaces.items(): int_model = objects.HostInterface() - int_model.device_name = i.get( - 'device_name', None) - int_model.network_link = i.get( + # A null value indicates this interface should be removed + # from any parent profiles + if v is None: + int_model.device_name = '!' + k + continue + + int_model.device_name = k + int_model.network_link = v.get( 'device_link', None) int_model.hardware_slaves = [] - slaves = i.get('slaves', []) + slaves = v.get('slaves', []) for s in slaves: int_model.hardware_slaves.append(s) int_model.networks = [] - networks = i.get('networks', []) + networks = v.get('networks', []) for n in networks: int_model.networks.append(n) diff --git a/drydock_provisioner/orchestrator/readme.md b/drydock_provisioner/orchestrator/readme.md index e76755d0..30c5f1fc 100644 --- a/drydock_provisioner/orchestrator/readme.md +++ b/drydock_provisioner/orchestrator/readme.md @@ -25,19 +25,27 @@ is compatible with the physical state of the site. #### Validations #### * Networking -** No static IP assignments are duplicated -** No static IP assignments are outside of the network they are targetted for -** All IP assignments are within declared ranges on the network -** Networks assigned to each node's interface are within the set of of the attached link's allowed\_networks -** No network is allowed on multiple network links -** Network MTU is equal or less than NetworkLink MTU -** MTU values are sane + * No static IP assignments are duplicated + * No static IP assignments are outside of the network they are targetted for + * All IP assignments are within declared ranges on the network + * No network is allowed on multiple network links + * Network MTU is equal or less than NetworkLink MTU + * MTU values are sane + * NetworkLink bond mode is compatible with other bond options + * NetworkLink with more than one allowed network supports trunking * Storage -** Boot drive is above minimum size -** Root drive is above minimum size -** No physical device specifies a target VG and a partition list -** No partition specifies a target VG and a filesystem - + * Boot drive is above minimum size + * Root drive is above minimum size + * No physical device specifies a target VG and a partition list + * No partition specifies a target VG and a filesystem + * All defined VGs have at least one defined PV (partition or physical device) + * Partition and LV sizing is sane + * Percentages don't sum to above 100% + * If percentages sum to 100%, no other partitions or LVs are defined +* Node + * Root filesystem is defined on a partition or LV + * Networks assigned to each node's interface are within the set of of the attached link's allowed\_networks + * Inter ### VerifySite ### Verify site-wide resources are in a useful state diff --git a/tests/unit/test_maasdriver_calculate_bytes.py b/tests/unit/test_maasdriver_calculate_bytes.py index 36e79947..d2a4f9e4 100644 --- a/tests/unit/test_maasdriver_calculate_bytes.py +++ b/tests/unit/test_maasdriver_calculate_bytes.py @@ -23,7 +23,6 @@ from drydock_provisioner.drivers.node.maasdriver.models.blockdev import BlockDev from drydock_provisioner.drivers.node.maasdriver.models.volumegroup import VolumeGroup - class TestCalculateBytes(): def test_calculate_m_label(self): '''Convert megabyte labels to x * 10^6 bytes.''' @@ -32,7 +31,8 @@ class TestCalculateBytes(): drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 @@ -42,7 +42,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 @@ -52,7 +53,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 @@ -62,7 +64,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 @@ -72,7 +75,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 @@ -82,7 +86,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 @@ -92,7 +97,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 @@ -102,7 +108,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 @@ -112,7 +119,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 * 1000 @@ -122,7 +130,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 * 1000 @@ -132,7 +141,8 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 * 1000 @@ -142,55 +152,60 @@ class TestCalculateBytes(): drive_size = 20 * 1000 * 1000 * 1000 * 1000 drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == 15 * 1000 * 1000 * 1000 * 1000 def test_calculate_percent_blockdev(self): '''Convert a percent of total blockdev space to explicit byte count.''' - drive_size = 20 * 1000 * 1000 # 20 mb drive - part_size = math.floor(.2 * drive_size) # calculate 20% of drive size + drive_size = 20 * 1000 * 1000 # 20 mb drive + part_size = math.floor(.2 * drive_size) # calculate 20% of drive size size_str = '20%' drive = BlockDevice(None, size=drive_size, available_size=drive_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=drive) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=drive) assert calc_size == part_size def test_calculate_percent_vg(self): '''Convert a percent of total blockdev space to explicit byte count.''' - vg_size = 20 * 1000 * 1000 # 20 mb drive - lv_size = math.floor(.2 * vg_size) # calculate 20% of drive size + vg_size = 20 * 1000 * 1000 # 20 mb drive + lv_size = math.floor(.2 * vg_size) # calculate 20% of drive size size_str = '20%' vg = VolumeGroup(None, size=vg_size, available_size=vg_size) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=vg) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=vg) assert calc_size == lv_size def test_calculate_overprovision(self): '''When calculated space is higher than available space, raise an exception.''' - vg_size = 20 * 1000 * 1000 # 20 mb drive + vg_size = 20 * 1000 * 1000 # 20 mb drive vg_available = 10 # 10 bytes available - lv_size = math.floor(.8 * vg_size) # calculate 80% of drive size + lv_size = math.floor(.8 * vg_size) # calculate 80% of drive size size_str = '80%' vg = VolumeGroup(None, size=vg_size, available_size=vg_available) with pytest.raises(error.NotEnoughStorage): - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=vg) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=vg) def test_calculate_min_label(self): '''Adding the min marker '>' should provision all available space.''' - vg_size = 20 * 1000 * 1000 # 20 mb drive + vg_size = 20 * 1000 * 1000 # 20 mb drive vg_available = 15 * 1000 * 1000 - lv_size = math.floor(.1 * vg_size) # calculate 20% of drive size + lv_size = math.floor(.1 * vg_size) # calculate 20% of drive size size_str = '>10%' vg = VolumeGroup(None, size=vg_size, available_size=vg_available) - calc_size = MaasTaskRunner.calculate_bytes(size_str=size_str, context=vg) + calc_size = MaasTaskRunner.calculate_bytes( + size_str=size_str, context=vg) assert calc_size == vg_available diff --git a/tests/yaml_samples/fullsite.yaml b/tests/yaml_samples/fullsite.yaml index be0607b0..7a2ff54a 100644 --- a/tests/yaml_samples/fullsite.yaml +++ b/tests/yaml_samples/fullsite.yaml @@ -170,8 +170,6 @@ metadata: author: sh8121@att.com description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces spec: - # Layer 2 VLAN segment id, could support other segmentations. Optional - vlan_id: '99' # If this network utilizes a dhcp relay, where does it forward DHCPDISCOVER requests to? dhcp_relay: # Required if Drydock is configuring a switch with the relay @@ -203,7 +201,7 @@ metadata: author: sh8121@att.com description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces spec: - vlan_id: '100' + vlan: '100' # Allow MTU to be inherited from link the network rides on mtu: 1500 # Network address @@ -236,7 +234,7 @@ metadata: author: sh8121@att.com description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces spec: - vlan_id: '101' + vlan: '101' mtu: 9000 cidr: 172.16.2.0/24 # Desribe IP address ranges @@ -259,7 +257,7 @@ metadata: author: sh8121@att.com description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces spec: - vlan_id: '102' + vlan: '102' # MTU size for the VLAN interface mtu: 1500 cidr: 172.16.3.0/24 @@ -369,18 +367,20 @@ spec: primary_network: mgmt interfaces: # Keyed on device_name - # pxe is a special marker indicating which device should be used for pxe boot - - device_name: pxe + pxe: # The network link attached to this device_link: pxe - # Slaves will specify aliases from hwdefinition.yaml + labels: + # this interface will be used only for PXE booting during deploy + noconfig: true + # Slaves will specify aliases from hwdefinition.yaml or kernel device names slaves: - prim_nic01 # Which networks will be configured on this interface networks: - pxe - - device_name: bond0 - network_link: gp + bond0: + device_link: gp # If multiple slaves are specified, but no bonding config # is applied to the link, design validation will fail slaves: @@ -409,7 +409,7 @@ spec: # the hostname for a server, could be used in multiple DNS domains to # represent different interfaces interfaces: - - device_name: bond0 + bond0: networks: # '!' prefix for the value of the primary key indicates a record should be removed - '!private' diff --git a/tests/yaml_samples/fullsite_profiles.yaml b/tests/yaml_samples/fullsite_profiles.yaml index 23cdedc0..8bbe18aa 100644 --- a/tests/yaml_samples/fullsite_profiles.yaml +++ b/tests/yaml_samples/fullsite_profiles.yaml @@ -61,33 +61,36 @@ spec: credential: admin # Specify storage layout of base OS. Ceph out of scope storage: - # How storage should be carved up: lvm (logical volumes), flat - # (single partition) - layout: lvm - # Info specific to the boot and root disk/partitions - bootdisk: - # Device will specify an alias defined in hwdefinition.yaml - device: primary_boot - # For LVM, the size of the partition added to VG as a PV - # For flat, the size of the partition formatted as ext4 - root_size: 50g - # The /boot partition. If not specified, /boot will in root - boot_size: 2g - # Info for additional partitions. Need to balance between - # flexibility and complexity - partitions: - - name: logs - device: primary_boot - # Partition uuid if needed - part_uuid: 84db9664-f45e-11e6-823d-080027ef795a - size: 10g - # Optional, can carve up unformatted block devices - mountpoint: /var/log - fstype: ext4 - mount_options: defaults - # Filesystem UUID or label can be specified. UUID recommended - fs_uuid: cdb74f1c-9e50-4e51-be1d-068b0e9ff69e - fs_label: logs + physical_devices: + sda: + labels: + role: rootdisk + partitions: + - name: root + size: 20g + bootable: true + filesystem: + mountpoint: '/' + fstype: 'ext4' + mount_options: 'defaults' + - name: boot + size: 1g + bootable: false + filesystem: + mountpoint: '/boot' + fstype: 'ext4' + mount_options: 'defaults' + sdb: + volume_group: 'log_vg' + volume_groups: + log_vg: + logical_volumes: + - name: 'log_lv' + size: '500m' + filesystem: + mountpoint: '/var/log' + fstype: 'xfs' + mount_options: 'defaults' # Platform (Operating System) settings platform: image: ubuntu_16.04 @@ -128,28 +131,30 @@ spec: primary_network: mgmt interfaces: # Keyed on device_name - # pxe is a special marker indicating which device should be used for pxe boot - - device_name: pxe - # The network link attached to this - device_link: pxe - # Slaves will specify aliases from hwdefinition.yaml - slaves: - - prim_nic01 - # Which networks will be configured on this interface - networks: - - pxe - - device_name: bond0 - network_link: gp - # If multiple slaves are specified, but no bonding config - # is applied to the link, design validation will fail - slaves: - - prim_nic01 - - prim_nic02 - # If multiple networks are specified, but no trunking - # config is applied to the link, design validation will fail - networks: - - mgmt - - private + pxe: + # The network link attached to this + device_link: pxe + labels: + # this interface will be used only for PXE booting during deploy + noconfig: true + # Slaves will specify aliases from hwdefinition.yaml or kernel device names + slaves: + - prim_nic01 + # Which networks will be configured on this interface + networks: + - pxe + bond0: + device_link: gp + # If multiple slaves are specified, but no bonding config + # is applied to the link, design validation will fail + slaves: + - prim_nic01 + - prim_nic02 + # If multiple networks are specified, but no trunking + # config is applied to the link, design validation will fail + networks: + - mgmt + - private metadata: # Explicit tag assignment tags: