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
This commit is contained in:
parent
864f0d35a5
commit
48df97e133
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
"""
|
||||
|
@ -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.
|
||||
|
@ -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({
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user