[390773] Support SRIOV fields in definition profiles

- Add hugepages and cpu_sets stanzas to HardwareProfile as the
  size and count of hugepages and the exact CPUs to pin for SRIOV
  are dependent on hardware.
- Add sriov stanza to a node interface to specify vf_count and
  trustedmode. These will be passthrough values as Drydock doesn't
  configure SRIOV.
- Add sriov information to the bootaction context so it can be written
  to disk on a deployed node if needed
- Allow an interface configuration to be skipped when an interface has
  no defined network_link for things like SR-IOV interfaces.
- Add kernel parameter reference support to access hardware profile
  information
- Add unit tests
- Update topology documentation for usage of HardwareProfile
  and kernel parameter references

Change-Id: Iefd326f5c6fad19dbd21300ee249019a3dfd4848
This commit is contained in:
Scott Hussey 2018-03-10 15:20:44 -06:00
parent c27c8e6c9b
commit b628a1bfce
18 changed files with 539 additions and 49 deletions

View File

@ -1 +1,2 @@
.tox .tox
**/build

View File

@ -75,10 +75,19 @@ ifeq ($(PUSH_IMAGE), true)
docker push $(IMAGE) docker push $(IMAGE)
endif endif
.PHONY: docs
docs: clean drydock_docs
.PHONY: drydock_docs
drydock_docs:
tox -e docs
.PHONY: clean .PHONY: clean
clean: clean:
rm -rf build rm -rf build
rm -rf docs/build
rm -rf charts/drydock/charts
rm -rf charts/drydock/requirements.lock
.PHONY: pep8 .PHONY: pep8
pep8: pep8:

View File

@ -227,6 +227,90 @@ reference to the particular physical node. The ``BaremetalNode`` definition will
reference a ``HostProfile`` and can then extend or override any of the reference a ``HostProfile`` and can then extend or override any of the
configuration values. configuration values.
Hardware Profile
----------------
The hardware profile is used to convert some abstractions in the HostProfile documents
into concrete configurations based a particular hardware build. A host profile will
designate how the bootdisk should be configured, but the hardware profile will
designate which exact device is used for the bootdisk. This allows a heterogeneous mix
of hardware in a site without duplicating definitions of how that hardware should
be configured.
An example HardwareProfile document:
.. code:: yaml
---
schema: 'drydock/HardwareProfile/v1'
metadata:
schema: 'metadata/Document/v1'
name: AcmeServer
storagePolicy: 'cleartext'
labels:
application: 'drydock'
data:
vendor: HP
generation: '8'
hw_version: '3'
bios_version: '2.2.3'
boot_mode: bios
bootstrap_protocol: pxe
pxe_interface: 0
device_aliases:
prim_nic01:
address: '0000:00:03.0'
dev_type: '82540EM Gigabit Ethernet Controller'
bus_type: 'pci'
prim_nic02:
address: '0000:00:04.0'
dev_type: '82540EM Gigabit Ethernet Controller'
bus_type: 'pci'
primary_boot:
address: '2:0.0.0'
dev_type: 'VBOX HARDDISK'
bus_type: 'scsi'
cpu_sets:
sriov: '2,4'
hugepages:
sriov:
size: '1G'
count: 300
dpdk:
size: '2M'
count: 530000
Device Aliases
~~~~~~~~~~~~~~
Device aliases are a way of mapping a particular device bus address
to an alias. In the example above we map the PCI address ``0000:00:03.0``
to the alias ``prim_nic01``. A host profile or baremetal node definition
can then provide a configuration using ``prim_nic01`` and Drydock will
translate that to the correct operating system device name for the NIC device
at PCI address ``0000.00.03.0``. Currently device aliases are supported
for network interface slave devices and storage physical devices.
Kernel Parameter References
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some kernel parameters specified in a host profile rely on particular hardware
builds, such as ``isolcpus``. To support the greatest flexibility in building
host profiles, you can specify a few values in a hardware profile that will then
be sourced when needed by a host profile or baremetal node definition.
* ``cpu_sets``: Each key should have a value of a comma-separated list of CPUs/cores/hyperthreads
that would be appropriate for the ``isolcpus`` kernel parameters. A host profile can
then select any one of these CPU sets for a host.
* ``hugepages``: Each key should have a value of a mapping containing two keys: ``size`` and
``count``. Again, a host profile can then select these values when defining kernel parameters
for a host. Note the ``size`` field is a string and will be used as-is, so the format must
be usable by the kernel.
Host Profiles and Baremetal Nodes
---------------------------------
Example ``HostProfile`` and ``BaremetalNode`` configuration: Example ``HostProfile`` and ``BaremetalNode`` configuration:
.. code:: yaml .. code:: yaml
@ -273,7 +357,7 @@ adopted from *defaults*) and can then again override or append any
configuration that is specific to that node. configuration that is specific to that node.
Defining Node Interfaces and Network Addressing Defining Node Interfaces and Network Addressing
=============================================== ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Node network attachment can be described in a ``HostProfile`` or a Node network attachment can be described in a ``HostProfile`` or a
``BaremetalNode`` document. Node addressing is allowed only in a ``BaremetalNode`` document. Node addressing is allowed only in a
@ -286,7 +370,7 @@ Once the interface attachments to networks is defined, ``HostProfile`` and
which network the node should use as the primary route. which network the node should use as the primary route.
Interfaces Interfaces
---------- **********
Interfaces for a node can be described in either a ``HostProfile`` or Interfaces for a node can be described in either a ``HostProfile`` or
``BaremetalNode`` definition. This will attach a defined NetworkLink to a host ``BaremetalNode`` definition. This will attach a defined NetworkLink to a host
@ -333,7 +417,7 @@ that interface for an inherited configuration.
have trunking enabled or the design validation will fail. have trunking enabled or the design validation will fail.
Addressing Addressing
---------- **********
Addressing for a node can only be defined in a ``BaremetalNode`` definition. The 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 ``addressing`` stanza simply defines a static IP address or ``dhcp`` for each
@ -358,7 +442,7 @@ Example ``addressing`` YAML schema:
Defining Node Storage Defining Node Storage
===================== ~~~~~~~~~~~~~~~~~~~~~
Storage can be defined in the ``storage`` stanza of either a HostProfile or Storage can be defined in the ``storage`` stanza of either a HostProfile or
BaremetalNode document. The storage configuration can describe the creation of BaremetalNode document. The storage configuration can describe the creation of
@ -405,13 +489,13 @@ Example YAML schema of the ``storage`` stanza:
mount_options: 'defaults' mount_options: 'defaults'
Schema Schema
------ ******
The ``storage`` stanza can contain two top-level keys: ``physical_devices`` and The ``storage`` stanza can contain two top-level keys: ``physical_devices`` and
``volume_groups``. The latter is optional. ``volume_groups``. The latter is optional.
Physical Devices and Partitions Physical Devices and Partitions
------------------------------- *******************************
A physical device can either be carved up in partitions (including a single 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 partition consuming the entire device) or added to a volume group as a physical
@ -429,7 +513,7 @@ mapping with the following keys
volume. Incompatible with the ``partitions`` specification. volume. Incompatible with the ``partitions`` specification.
Partition Partition
~~~~~~~~~ ^^^^^^^^^
A partition mapping describes a GPT partition on a physical disk. It can be left A partition mapping describes a GPT partition on a physical disk. It can be left
as a raw block device or formatted and mounted as a filesystem. as a raw block device or formatted and mounted as a filesystem.
@ -451,7 +535,7 @@ as a raw block device or formatted and mounted as a filesystem.
* ``fs_label``: A filesystem label to assign to the filesystem. Optional. * ``fs_label``: A filesystem label to assign to the filesystem. Optional.
Size Format Size Format
~~~~~~~~~~~ ^^^^^^^^^^^
The size specification for a partition or logical volume is formed from three The size specification for a partition or logical volume is formed from three
parts: parts:
@ -468,7 +552,7 @@ parts:
* %: The percentage of total device or volume group space * %: The percentage of total device or volume group space
Volume Groups and Logical Volumes Volume Groups and Logical Volumes
--------------------------------- *********************************
Logical volumes can be used to create RAID-0 volumes spanning multiple physical 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 disks or partitions. Each key in the ``volume_groups`` mapping is a name
@ -482,7 +566,7 @@ invalid. Each mapping value is another mapping describing the volume group.
created in the volume group created in the volume group
Logical Volume Logical Volume
~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^
A logical volume is a RAID-0 volume. Using logical volumes for ``/`` and A logical volume is a RAID-0 volume. Using logical volumes for ``/`` and
``/boot`` is supported ``/boot`` is supported
@ -494,3 +578,41 @@ A logical volume is a RAID-0 volume. Using logical volumes for ``/`` and
* ``filesystem``: A mapping specifying how the logical volume should be * ``filesystem``: A mapping specifying how the logical volume should be
formatted and mounted. See the *Partition* section above for filesystem formatted and mounted. See the *Partition* section above for filesystem
details. details.
Platform Configuration
----------------------
In the ``platform`` stanza you can define the operating system ``image``
and ``kernel`` to use as well as customize the kernel configuration with
``kernel_params``.
Image and Kernel Selection
**************************
The valid ``image`` and ``kernel`` values are dependent on what is supported
by your node provisioner. In the example of Canonical MaaS using the 16.04 LTS
image, the values would be ``image: 'xenial'`` and ``kernel: 'ga-16.04'`` for the
LTS kernel or ``kernel: hwe-16.04`` for the hardware-enablement kernel.
Kernel Parameters
*****************
The ``kernel_params`` configuration is a mapping. Each key should either be a string
or boolean value. For boolean ``true`` values, the key will be added to the kernel
parameter list as a flag. For string values, the key:value pair will be added to the
kernel parameter list as ``key=value``.
Parameter References
^^^^^^^^^^^^^^^^^^^^
One special case is supported for values that match a hardware profile reference.
When the parameter is rendered for a particular node, the value included in the
kernel parameter list will be sourced from the effective HardwareProfile assigned
to the node.
* ``hardwareprofile:cpuset.<name>``: Sourced from the hardware profile ``cpu_sets.<name>``
value.
* ``hardwareprofile.hugepages.<name>.size``: Source from the hardware profile
``hugepages.<name>.size`` value.
* ``hardwareprofile.hugepages.<name>.count``: Source from the hardware profile
``hugepages.<name>.count`` value.

View File

@ -31,7 +31,8 @@ class AuthMiddleware(object):
ctx.set_policy_engine(policy.policy_engine) ctx.set_policy_engine(policy.policy_engine)
self.logger.debug("Request with headers: %s" % ','.join(req.headers.keys())) self.logger.debug(
"Request with headers: %s" % ','.join(req.headers.keys()))
auth_status = req.get_header('X-SERVICE-IDENTITY-STATUS') auth_status = req.get_header('X-SERVICE-IDENTITY-STATUS')
service = True service = True
@ -101,7 +102,9 @@ class LoggingMiddleware(object):
'req_id': req.context.request_id, 'req_id': req.context.request_id,
'external_ctx': req.context.external_marker, 'external_ctx': req.context.external_marker,
} }
self.logger.info("Request: %s %s %s" % (req.method, req.uri, req.query_string), extra=extra) self.logger.info(
"Request: %s %s %s" % (req.method, req.uri, req.query_string),
extra=extra)
def process_response(self, req, resp, resource, req_succeeded): def process_response(self, req, resp, resource, req_succeeded):
ctx = req.context ctx = req.context
@ -111,4 +114,6 @@ class LoggingMiddleware(object):
'external_ctx': ctx.external_marker, 'external_ctx': ctx.external_marker,
} }
resp.append_header('X-Drydock-Req', ctx.request_id) resp.append_header('X-Drydock-Req', ctx.request_id)
self.logger.info("Response: %s %s - %s" % (req.method, req.uri, resp.status), extra=extra) self.logger.info(
"Response: %s %s - %s" % (req.method, req.uri, resp.status),
extra=extra)

View File

@ -968,6 +968,12 @@ class ApplyNodeNetworking(BaseMaasAction):
machine.refresh() machine.refresh()
for i in n.interfaces: for i in n.interfaces:
if not i.network_link:
self.logger.debug(
"Interface %s has no network link, skipping configuration."
% (i.device_name))
continue
nl = site_design.get_network_link(i.network_link) nl = site_design.get_network_link(i.network_link)
if nl.metalabels is not None: if nl.metalabels is not None:
@ -1130,7 +1136,8 @@ class ApplyNodeNetworking(BaseMaasAction):
link_iface = machine.interfaces.create_vlan( link_iface = machine.interfaces.create_vlan(
**vlan_options) **vlan_options)
except errors.DriverError as ex: except errors.DriverError as ex:
msg = "Error creating interface: %s" % str(ex) msg = "Error creating interface: %s" % str(
ex)
self.logger.info(msg) self.logger.info(msg)
self.task.add_status_msg( self.task.add_status_msg(
msg=msg, msg=msg,

View File

@ -63,6 +63,37 @@ class UnsupportedDocumentType(DesignError):
pass pass
class HugepageConfNotFound(DesignError):
"""
**Message:** *Hugepage configuration not found*.
**Troubleshoot:** *Define the hugepage configuration in the HardwareProfile*.
"""
pass
class CpuSetNotFound(DesignError):
"""
**Message:** *CPU set not found*.
**Troubleshoot:** *Define the CPU set in the HardwareProfile*.
"""
pass
class InvalidParameterReference(DesignError):
"""
**Message:** *Invalid configuration specified*.
**Troubleshoot:** *Only ``cpuset`` and ``hugepages`` can be referenced*.
**Message:** *Invalid field specified*.
**Troubleshoot:** *For hugepages, only fields ``size`` and ``count`` are valid*.
"""
pass
class StateError(Exception): class StateError(Exception):
pass pass

View File

@ -372,6 +372,15 @@ class DeckhandIngester(IngesterPlugin):
dev_model.address = v.get('address', None) dev_model.address = v.get('address', None)
model.devices.append(dev_model) model.devices.append(dev_model)
model.cpu_sets = data.get('cpu_sets', None) or dict()
model.hugepages_confs = objects.HugepagesConfList()
for c, d in data.get('hugepages', {}).items():
conf = objects.HugepagesConf(
name=c, size=d.get('size'), count=d.get('count'))
model.hugepages_confs.append(conf)
return model return model
def process_drydock_hostprofile(self, name, data): def process_drydock_hostprofile(self, name, data):
@ -535,6 +544,12 @@ class DeckhandIngester(IngesterPlugin):
for n in networks: for n in networks:
int_model.networks.append(n) int_model.networks.append(n)
if 'sriov' in v:
int_model.sriov = True
int_model.vf_count = v.get('sriov', {}).get('vf_count', 0)
int_model.trustedmode = v.get('sriov', {}).get(
'trustedmode', False)
model.interfaces.append(int_model) model.interfaces.append(int_model)
platform = data.get('platform', {}) platform = data.get('platform', {})

View File

@ -134,31 +134,8 @@ class BootActionAsset(base.DrydockObject):
:param action_id: a 128-bit ULID boot action id :param action_id: a 128-bit ULID boot action id
:param design_ref: The design ref this bootaction was initiated under :param design_ref: The design ref this bootaction was initiated under
""" """
node = site_design.get_baremetal_node(nodename) tpl_ctx = self._get_template_context(nodename, site_design, action_id,
design_ref)
tpl_ctx = {
'node': {
'hostname': nodename,
'tags': [t for t in node.tags],
'labels': {k: v
for (k, v) in node.owner_data.items()},
'network': {},
},
'action': {
'key': ulid2.ulid_to_base32(action_id),
'report_url': config.config_mgr.conf.bootactions.report_url,
'design_ref': design_ref,
}
}
for a in node.addressing:
if a.address is not None:
tpl_ctx['node']['network'][a.network] = dict()
tpl_ctx['node']['network'][a.network]['ip'] = a.address
network = site_design.get_network(a.network)
tpl_ctx['node']['network'][a.network]['cidr'] = network.cidr
tpl_ctx['node']['network'][a.network][
'dns_suffix'] = network.dns_domain
if self.location is not None: if self.location is not None:
rendered_location = self.execute_pipeline( rendered_location = self.execute_pipeline(
@ -174,6 +151,78 @@ class BootActionAsset(base.DrydockObject):
value = value.encode('utf-8') value = value.encode('utf-8')
self.rendered_bytes = value self.rendered_bytes = value
def _get_template_context(self, nodename, site_design, action_id,
design_ref):
"""Create a context to be used for template rendering.
:param nodename: The name of the node for the bootaction
:param site_design: The full site design
:param action_id: the ULID assigned to the boot action using this context
:param design_ref: The design reference representing ``site_design``
"""
return dict(
node=self._get_node_context(nodename, site_design),
action=self._get_action_context(action_id, design_ref))
def _get_action_context(self, action_id, design_ref):
"""Create the action-specific context items for template rendering.
:param action_id: ULID of this boot action
:param design_ref: Design reference representing the site design
"""
return dict(
key=ulid2.ulid_to_base32(action_id),
report_url=config.config_mgr.conf.bootactions.report_url,
design_ref=design_ref)
def _get_node_context(self, nodename, site_design):
"""Create the node-specific context items for template rendering.
:param nodename: name of the node this boot action targets
:param site_design: full site design
"""
node = site_design.get_baremetal_node(nodename)
return dict(
hostname=nodename,
tags=[t for t in node.tags],
labels={k: v
for (k, v) in node.owner_data.items()},
network=self._get_node_network_context(node, site_design),
interfaces=self._get_node_interface_context(node))
def _get_node_network_context(self, node, site_design):
"""Create a node's network configuration context.
:param node: node object
:param site_design: full site design
"""
network_context = dict()
for a in node.addressing:
if a.address is not None:
network = site_design.get_network(a.network)
network_context[a.network] = dict(
ip=a.address,
cidr=network.cidr,
dns_suffix=network.dns_domain)
if a.network == node.primary_network:
network_context['default'] = network_context[a.network]
return network_context
def _get_node_interface_context(self, node):
"""Create a node's network interface context.
:param node: the node object
"""
interface_context = dict()
for i in node.interfaces:
interface_context[i.device_name] = dict(sriov=i.sriov)
if i.sriov:
interface_context[i.device_name]['vf_count'] = i.vf_count
interface_context[i.device_name]['trustedmode'] = i.trustedmode
return interface_context
def resolve_asset_location(self, asset_url): def resolve_asset_location(self, asset_url):
"""Retrieve the data asset from the url. """Retrieve the data asset from the url.

View File

@ -169,6 +169,14 @@ class HostInterface(base.DrydockObject):
obj_fields.ObjectField('HardwareDeviceSelectorList', nullable=True), obj_fields.ObjectField('HardwareDeviceSelectorList', nullable=True),
'networks': 'networks':
obj_fields.ListOfStringsField(nullable=True), obj_fields.ListOfStringsField(nullable=True),
'sriov':
obj_fields.BooleanField(default=False),
# SRIOV virtual functions
'vf_count':
obj_fields.IntegerField(nullable=True),
# SRIOV VF trusted mode
'trustedmode':
obj_fields.BooleanField(nullable=True),
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):

View File

@ -16,6 +16,7 @@
from oslo_versionedobjects import fields as ovo_fields from oslo_versionedobjects import fields as ovo_fields
import drydock_provisioner.objects as objects import drydock_provisioner.objects as objects
import drydock_provisioner.error as errors
import drydock_provisioner.objects.base as base import drydock_provisioner.objects.base as base
import drydock_provisioner.objects.fields as hd_fields import drydock_provisioner.objects.fields as hd_fields
@ -48,6 +49,10 @@ class HardwareProfile(base.DrydockPersistentObject, base.DrydockObject):
ovo_fields.StringField(nullable=True), ovo_fields.StringField(nullable=True),
'devices': 'devices':
ovo_fields.ObjectField('HardwareDeviceAliasList', nullable=True), ovo_fields.ObjectField('HardwareDeviceAliasList', nullable=True),
'cpu_sets':
ovo_fields.DictOfStringsField(nullable=True),
'hugepages_confs':
ovo_fields.ObjectField('HugepagesConfList', nullable=True),
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -62,6 +67,29 @@ class HardwareProfile(base.DrydockPersistentObject, base.DrydockObject):
def get_name(self): def get_name(self):
return self.name return self.name
def get_hugepage_conf(self, conf_name):
"""Return the hugepages conf matching ``conf_name``"""
if not self.hugepages_confs:
raise errors.HugepageConfNotFound(
"Hugepage configuration %s not found." % conf_name)
for c in self.hugepages_confs:
if c.name == conf_name:
return c
raise errors.HugepageConfNotFound(
"Hugepage configuration %s not found." % conf_name)
def get_cpu_set(self, set_name):
"""Return the cpu set matching ``set_name``"""
if not self.cpu_sets:
raise errors.CpuSetNotFound("CPU set %s not found." % set_name)
if set_name in self.cpu_sets:
return self.cpu_sets[set_name]
raise errors.CpuSetNotFound("CPU set %s not found." % set_name)
def resolve_alias(self, alias_type, alias): def resolve_alias(self, alias_type, alias):
for d in self.devices: for d in self.devices:
if d.alias == alias and d.bus_type == alias_type: if d.alias == alias and d.bus_type == alias_type:
@ -82,6 +110,26 @@ class HardwareProfileList(base.DrydockObjectListBase, base.DrydockObject):
fields = {'objects': ovo_fields.ListOfObjectsField('HardwareProfile')} fields = {'objects': ovo_fields.ListOfObjectsField('HardwareProfile')}
@base.DrydockObjectRegistry.register
class HugepagesConf(base.DrydockObject):
VERSION = '1.0'
fields = {
'name': ovo_fields.StringField(),
'size': ovo_fields.StringField(),
'count': ovo_fields.NonNegativeIntegerField(),
}
@base.DrydockObjectRegistry.register
class HugepagesConfList(base.DrydockObjectListBase, base.DrydockObject):
VERSION = '1.0'
fields = {'objects': ovo_fields.ListOfObjectsField('HugepagesConf')}
@base.DrydockObjectRegistry.register @base.DrydockObjectRegistry.register
class HardwareDeviceAlias(base.DrydockObject): class HardwareDeviceAlias(base.DrydockObject):

View File

@ -20,6 +20,7 @@ from defusedxml.ElementTree import fromstring
import logging import logging
from oslo_versionedobjects import fields as ovo_fields from oslo_versionedobjects import fields as ovo_fields
import drydock_provisioner.error as errors
import drydock_provisioner.config as config import drydock_provisioner.config as config
import drydock_provisioner.objects as objects import drydock_provisioner.objects as objects
import drydock_provisioner.objects.hostprofile import drydock_provisioner.objects.hostprofile
@ -49,9 +50,14 @@ class BaremetalNode(drydock_provisioner.objects.hostprofile.HostProfile):
# Compile the applied version of this model sourcing referenced # Compile the applied version of this model sourcing referenced
# data from the passed site design # data from the passed site design
def compile_applied_model(self, site_design, state_manager): def compile_applied_model(self, site_design, state_manager):
self.logger.debug("Applying host profile to node %s" % self.name)
self.apply_host_profile(site_design) self.apply_host_profile(site_design)
self.logger.debug("Applying hardware profile to node %s" % self.name)
self.apply_hardware_profile(site_design) self.apply_hardware_profile(site_design)
self.source = hd_fields.ModelSource.Compiled self.source = hd_fields.ModelSource.Compiled
self.logger.debug("Resolving kernel parameters on node %s" % self.name)
self.resolve_kernel_params(site_design)
self.logger.debug("Resolving device aliases on node %s" % self.name)
self.apply_logicalnames(site_design, state_manager) self.apply_logicalnames(site_design, state_manager)
return return
@ -87,6 +93,67 @@ class BaremetalNode(drydock_provisioner.objects.hostprofile.HostProfile):
return return
def resolve_kernel_params(self, site_design):
"""Check if any kernel parameter values are supported references."""
if not self.hardware_profile:
raise ValueError("Hardware profile not set.")
hwprof = site_design.get_hardware_profile(self.hardware_profile)
if not hwprof:
raise ValueError("Hardware profile not found.")
resolved_params = dict()
for p, v in self.kernel_params.items():
try:
rv = self.get_kernel_param_value(v, hwprof)
resolved_params[p] = rv
except (errors.InvalidParameterReference, errors.CpuSetNotFound,
errors.HugepageConfNotFound) as ex:
resolved_params[p] = v
msg = ("Error resolving parameter reference on node %s: %s" %
(self.name, str(ex)))
self.logger.warning(msg)
self.kernel_params = resolved_params
def get_kernel_param_value(self, value, hwprof):
"""If ``value`` is a reference, resolve it otherwise return ``value``
Support some referential values to extract data from the HardwareProfile
hardwareprofile:cpuset.<setname>
hardwareprofile:hugepages.<confname>.size
hardwareprofile:hugepages.<confname>.count
If ``value`` matches none of the above forms, just return the value as passed.
:param value: the value string as specified in the node definition
:param hwprof: the assigned HardwareProfile for this node
"""
if value.startswith('hardwareprofile:'):
(_, ref) = value.split(':', 1)
if ref:
(ref_type, ref_val) = ref.split('.', 1)
if ref_type == 'cpuset':
return hwprof.get_cpu_set(ref_val)
elif ref_type == 'hugepages':
(conf, field) = ref_val.split('.', 1)
hp_conf = hwprof.get_hugepage_conf(conf)
if field in ['size', 'count']:
return getattr(hp_conf, field)
else:
raise errors.InvalidParameterReference(
"Invalid field %s specified." % field)
else:
raise errors.InvalidParameterReference(
"Invalid configuration %s specified." % ref_type)
else:
return value
else:
return value
def get_applied_interface(self, iface_name): def get_applied_interface(self, iface_name):
for i in getattr(self, 'interfaces', []): for i in getattr(self, 'interfaces', []):
if i.get_name() == iface_name: if i.get_name() == iface_name:

View File

@ -247,11 +247,22 @@ class Orchestrator(object):
Given a fully populated Site model, compute the effective Given a fully populated Site model, compute the effective
design by applying inheritance and references design by applying inheritance and references
""" """
node_failed = []
try: try:
nodes = site_design.baremetal_nodes nodes = site_design.baremetal_nodes
for n in nodes or []: for n in nodes or []:
n.compile_applied_model( try:
site_design, state_manager=self.state_manager) n.compile_applied_model(
site_design, state_manager=self.state_manager)
except Exception as ex:
node_failed.append(n)
self.logger.error(
"Failed to build applied model for node %s." % n.name)
if node_failed:
raise errors.DesignError(
"Failed to build applied model for %s" % ",".join(
[x.name for x in node_failed]))
except AttributeError: except AttributeError:
self.logger.debug( self.logger.debug(
"Model inheritance skipped, no node definitions in site design." "Model inheritance skipped, no node definitions in site design."

View File

@ -34,4 +34,17 @@ data:
device_aliases: device_aliases:
type: 'object' type: 'object'
additionalProperties: true additionalProperties: true
cpu_sets:
type: 'object'
additionalProperties:
type: 'string'
hugepages:
type: 'object'
additionalProperties:
type: 'object'
propertes:
size:
type: 'string'
count:
type: 'number'
additionalProperties: false additionalProperties: false

View File

@ -151,5 +151,12 @@ data:
type: 'array' type: 'array'
items: items:
type: 'string' type: 'string'
sriov:
type: 'object'
properties:
vf_count:
type: 'number'
trustmode:
type: 'boolean'
additionalProperties: false additionalProperties: false
... ...

View File

@ -282,8 +282,8 @@ class DrydockState(object):
""" """
try: try:
conn = self.db_engine.connect() conn = self.db_engine.connect()
query = self.tasks_tbl.insert().values(**( query = self.tasks_tbl.insert().values(
task.to_db(include_id=True))) **(task.to_db(include_id=True)))
conn.execute(query) conn.execute(query)
conn.close() conn.close()
return True return True

View File

@ -16,15 +16,12 @@
import ulid2 import ulid2
from drydock_provisioner.statemgmt.state import DrydockState from drydock_provisioner.statemgmt.state import DrydockState
import drydock_provisioner.objects as objects
class TestClass(object): class TestBootactionRenderAction(object):
def test_bootaction_render_nodename(self, input_files, deckhand_ingester, def test_bootaction_render_nodename(self, input_files, deckhand_ingester,
setup): setup):
"""Test the bootaction render routine provides expected output.""" """Test the bootaction render routine provides expected output."""
objects.register_all()
input_file = input_files.join("deckhand_fullsite.yaml") input_file = input_files.join("deckhand_fullsite.yaml")
design_state = DrydockState() design_state = DrydockState()
@ -43,8 +40,6 @@ class TestClass(object):
def test_bootaction_render_design_ref(self, input_files, deckhand_ingester, def test_bootaction_render_design_ref(self, input_files, deckhand_ingester,
setup): setup):
"""Test the bootaction render routine provides expected output.""" """Test the bootaction render routine provides expected output."""
objects.register_all()
input_file = input_files.join("deckhand_fullsite.yaml") input_file = input_files.join("deckhand_fullsite.yaml")
design_state = DrydockState() design_state = DrydockState()
@ -60,3 +55,38 @@ class TestClass(object):
assert 'deckhand_fullsite.yaml' in assets[2].rendered_bytes.decode( assert 'deckhand_fullsite.yaml' in assets[2].rendered_bytes.decode(
'utf-8') 'utf-8')
def test_bootaction_network_context(self, input_files,
deckhand_orchestrator, setup):
"""Test that a boot action creates proper network context."""
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)
design_status, design_data = deckhand_orchestrator.get_effective_site(
design_ref)
ba = design_data.get_bootaction('helloworld')
node = design_data.get_baremetal_node('compute01')
net_ctx = ba.asset_list[0]._get_node_network_context(node, design_data)
assert 'mgmt' in net_ctx
assert net_ctx['mgmt'].get('ip', None) == '172.16.1.21'
def test_bootaction_interface_context(self, input_files,
deckhand_orchestrator, setup):
"""Test that a boot action creates proper network context."""
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)
design_status, design_data = deckhand_orchestrator.get_effective_site(
design_ref)
ba = design_data.get_bootaction('helloworld')
node = design_data.get_baremetal_node('compute01')
iface_ctx = ba.asset_list[0]._get_node_interface_context(node)
assert 'bond0' in iface_ctx
assert iface_ctx['bond0'].get('sriov')
assert iface_ctx['bond0'].get('vf_count') == 2

View File

@ -0,0 +1,50 @@
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from drydock_provisioner.statemgmt.state import DrydockState
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
LOG = logging.getLogger(__name__)
class TestKernelParameterReferences(object):
def test_valid_param_reference(self, deckhand_ingester, input_files,
setup):
input_file = input_files.join("deckhand_fullsite.yaml")
design_state = DrydockState()
design_ref = "file://%s" % str(input_file)
orchestrator = Orchestrator(
state_manager=design_state, ingester=deckhand_ingester)
design_status, design_data = orchestrator.get_effective_site(
design_ref)
assert len(design_data.baremetal_nodes) == 2
node = design_data.get_baremetal_node("compute01")
assert node.hardware_profile == 'HPGen9v3'
isolcpu = node.kernel_params.get('isolcpus', None)
# '2,4' is defined in the HardwareProfile as cpu_sets.sriov
assert isolcpu == '2,4'
hugepagesz = node.kernel_params.get('hugepagesz', None)
assert hugepagesz == '1G'

View File

@ -302,6 +302,9 @@ data:
networks: networks:
- mgmt - mgmt
- private - private
sriov:
vf_count: 2
trustedmode: false
metadata: metadata:
tags: tags:
- 'test' - 'test'
@ -349,6 +352,11 @@ data:
address: 172.16.2.21 address: 172.16.2.21
- network: oob - network: oob
address: 172.16.100.21 address: 172.16.100.21
platform:
kernel_params:
isolcpus: hardwareprofile:cpuset.sriov
hugepagesz: hardwareprofile:hugepages.sriov.size
hugepages: hardwareprofile:hugepages.sriov.count
metadata: metadata:
rack: rack2 rack: rack2
--- ---
@ -380,6 +388,15 @@ data:
address: '2:0.0.0' address: '2:0.0.0'
dev_type: 'VBOX HARDDISK' dev_type: 'VBOX HARDDISK'
bus_type: 'scsi' bus_type: 'scsi'
cpu_sets:
sriov: '2,4'
hugepages:
sriov:
size: '1G'
count: 300
dpdk:
size: '2M'
count: 530000
--- ---
schema: 'drydock/BootAction/v1' schema: 'drydock/BootAction/v1'
metadata: metadata: