From ab03692a812c607c03abfc1a2e198216371a8420 Mon Sep 17 00:00:00 2001 From: Scott Hussey Date: Thu, 22 Jun 2017 11:54:56 -0500 Subject: [PATCH] DRYD7 - Bootdata API and promenade integration Add bootdata API for nodes to gather post-boot defintion info Add owner_data support to the maasdriver Machine model Add steps to the DeployNode task to setup bootdata and set owner_data --- drydock_provisioner/control/api.py | 5 +- drydock_provisioner/control/bootdata.py | 93 ++++ .../drivers/node/maasdriver/driver.py | 20 + .../drivers/node/maasdriver/models/machine.py | 19 +- drydock_provisioner/ingester/__init__.py | 11 +- drydock_provisioner/ingester/plugins/yaml.py | 498 +++++++++--------- drydock_provisioner/objects/promenade.py | 45 ++ drydock_provisioner/statemgmt/__init__.py | 36 ++ 8 files changed, 470 insertions(+), 257 deletions(-) create mode 100644 drydock_provisioner/control/bootdata.py create mode 100644 drydock_provisioner/objects/promenade.py diff --git a/drydock_provisioner/control/api.py b/drydock_provisioner/control/api.py index a6d832f9..65d68c34 100644 --- a/drydock_provisioner/control/api.py +++ b/drydock_provisioner/control/api.py @@ -42,7 +42,10 @@ def start_api(state_manager=None, ingester=None, orchestrator=None): ('/designs/{design_id}', DesignResource(state_manager=state_manager, orchestrator=orchestrator)), ('/designs/{design_id}/parts', DesignsPartsResource(state_manager=state_manager, ingester=ingester)), ('/designs/{design_id}/parts/{kind}', DesignsPartsKindsResource(state_manager=state_manager)), - ('/designs/{design_id}/parts/{kind}/{name}', DesignsPartResource(state_manager=state_manager, orchestrator=orchestrator)) + ('/designs/{design_id}/parts/{kind}/{name}', DesignsPartResource(state_manager=state_manager, orchestrator=orchestrator)), + + # API for nodes to discover their bootdata during curtin install + ('/bootdata/{hostname}/{data_key}', BootdataResource(state_manager=state_manager)) ] for path, res in v1_0_routes: diff --git a/drydock_provisioner/control/bootdata.py b/drydock_provisioner/control/bootdata.py new file mode 100644 index 00000000..61152b13 --- /dev/null +++ b/drydock_provisioner/control/bootdata.py @@ -0,0 +1,93 @@ +# 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 falcon +import json + +from .base import StatefulResource + +class BootdataResource(StatefulResource): + + def __init__(self, orchestrator=None, **kwargs): + super(BootdataResource, self).__init__(**kwargs) + self.authorized_roles = ['anyone'] + self.orchestrator = orchestrator + + def on_get(self, req, resp, hostname, data_key): + if data_key == 'systemd': + resp.body = BootdataResource.systemd_definition + resp.content_type = 'text/plain' + return + elif data_key == 'prominit' + resp.boy = BootdataResource.prominit + resp.content_type = 'text/plain' + return + else: + bootdata = self.state_manager.get_boot_data(hostname) + + if bootdata is None: + resp.status = falcon.HTTP_404 + return + elif bootdata.get('key', None) == data_key: + resp.content_type = 'text/plain' + + host_design_id = bootdata.get('design_id', None) + self.orchestrator.get_effective_site(host_design_id) + + host_model = host_design.get_baremetal_node(hostname) + + part_list = [] + + all_parts = self.state_manager.get_promenade_parts('all') + + if all_parts is not None: + part_list.extend(all_parts) + + host_parts = self.state_manager.get_promenade_parts(hostname) + + if host_parts is not None: + part_list.extend(host_parts) + + for t in host_model.tags: + tag_parts = self.state_manager.get_promenade_parts(t) + if t is not None: + part_list.extend(tag_parts) + + resp.body = yaml.dump_all(part_list, explicit_start=True) + return + else: + resp.status = falcon.HTTP_403 + return + + systemd_definition = \ +"""[Unit] +Description=Promenade Initialization Service +Documentation=http://github.com/att-comdev/drydock +After=network.target local-fs.target +ConditionPathExists=!/var/lib/prom.done + +[Service] +Type=simple +Environment=HTTP_PROXY=http://one.proxy.att.com:8080 HTTPS_PROXY=http://one.proxy.att.com:8080 NO_PROXY=127.0.0.1,localhost,135.16.101.87,135.16.101.86,135.16.101.85,135.16.101.84,135.16.101.83,135.16.101.82,135.16.101.81,135.16.101.80,kubernetes +ExecStartPre=echo 4 >/sys/class/net/ens3f0/device/sriov_numvfs +ExecStart=/var/tmp/prom_init.sh /etc/prom_init.yaml + +[Install] +WantedBy=multi-user.target +""" + prom_init = \ +"""!/bin/bash +echo $HTTP_PROXY +echo $NO_PROXY +cat $1 +""" \ No newline at end of file diff --git a/drydock_provisioner/drivers/node/maasdriver/driver.py b/drydock_provisioner/drivers/node/maasdriver/driver.py index 23ae2e85..a0200d9f 100644 --- a/drydock_provisioner/drivers/node/maasdriver/driver.py +++ b/drydock_provisioner/drivers/node/maasdriver/driver.py @@ -15,6 +15,7 @@ import time import logging import traceback import sys +import uuid from oslo_config import cfg @@ -1135,6 +1136,25 @@ class MaasTaskRunner(drivers.DriverTaskRunner): failed = True continue + # Need to create bootdata keys for all the nodes being deployed + # TODO this should be in the orchestrator + node = site_design.get_baremetal_node(n) + data_key = uuid.uuid4() + self.state_manager.set_bootdata_key(n, design_id, data_key) + node.owner_data['bootdata_key'] = data_key + self.logger.debug("Configured bootdata for node %s" % (n)) + + # Set owner data in MaaS + try: + self.logger.info("Setting node %s owner data." % n) + for k,v in node.owner_data.items(): + self.logger.debug("Set owner data %s = %s for node %s" % (k, v, n)) + machine.set_owner_data(k, v) + except Exception as ex: + self.logger.warning("Error setting node %s owner data: %s" % (n, str(ex))) + failed = True + continue + self.logger.info("Deploying node %s" % (n)) try: diff --git a/drydock_provisioner/drivers/node/maasdriver/models/machine.py b/drydock_provisioner/drivers/node/maasdriver/models/machine.py index 0cf456f9..25855a7e 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/machine.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/machine.py @@ -23,7 +23,7 @@ class Machine(model_base.ResourceBase): resource_url = 'machines/{resource_id}/' fields = ['resource_id', 'hostname', 'power_type', 'power_state', 'power_parameters', 'interfaces', - 'boot_interface', 'memory', 'cpu_count', 'tag_names', 'status_name', 'boot_mac'] + 'boot_interface', 'memory', 'cpu_count', 'tag_names', 'status_name', 'boot_mac', 'owner_data'] json_fields = ['hostname', 'power_type'] def __init__(self, api_client, **kwargs): @@ -95,6 +95,23 @@ class Machine(model_base.ResourceBase): detail_config = bson.loads(resp.text) 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 + 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(url, op='set_owner_data', files={key: value}) + + if resp.status_code == 200: + detail_config = bson.loads(resp.text) + return detail_config + def to_dict(self): """ diff --git a/drydock_provisioner/ingester/__init__.py b/drydock_provisioner/ingester/__init__.py index daaff310..b997545e 100644 --- a/drydock_provisioner/ingester/__init__.py +++ b/drydock_provisioner/ingester/__init__.py @@ -26,6 +26,7 @@ import drydock_provisioner.objects.network as network import drydock_provisioner.objects.hwprofile as hwprofile import drydock_provisioner.objects.node as node import drydock_provisioner.objects.hostprofile as hostprofile +import drydock_provisioner.objects.promenade as prom from drydock_provisioner.statemgmt import DesignState @@ -69,6 +70,7 @@ class Ingester(object): def ingest_data(self, plugin_name='', design_state=None, design_id=None, context=None, **kwargs): + if design_state is None: self.logger.error("Ingester:ingest_data called without valid DesignState handler") raise ValueError("Invalid design_state handler") @@ -104,17 +106,12 @@ class Ingester(object): design_data.add_hardware_profile(m) elif type(m) is node.BaremetalNode: design_data.add_baremetal_node(m) + elif type(m) is prom.PromenadeConfig: + design_state.post_promenade_part(m) design_state.put_design(design_data) return design_items else: self.logger.error("Could not find plugin %s to ingest data." % (plugin_name)) raise LookupError("Could not find plugin %s" % plugin_name) - """ - ingest_data - params: plugin_name - Which plugin should be used for ingestion - params: params - A map of parameters that will be passed to the plugin's ingest_data method - - Execute a data ingestion using the named plugin (assuming it is enabled) - """ diff --git a/drydock_provisioner/ingester/plugins/yaml.py b/drydock_provisioner/ingester/plugins/yaml.py index 4ca0b158..e66885ca 100644 --- a/drydock_provisioner/ingester/plugins/yaml.py +++ b/drydock_provisioner/ingester/plugins/yaml.py @@ -79,82 +79,45 @@ class YamlIngester(IngesterPlugin): for d in parsed_data: kind = d.get('kind', '') - if kind != '': - if kind == 'Region': - api_version = d.get('apiVersion', '') + api = d.get('apiVersion', '') + if api.startswith('drydock/'): + (foo, api_version) = api.split('/') + if kind != '': + if kind == 'Region': + if api_version == 'v1': + model = objects.Site() - if api_version == 'v1.0': - model = objects.Site() + metadata = d.get('metadata', {}) - metadata = d.get('metadata', {}) + # Need to add validation logic, we'll assume the input is + # valid for now + model.name = metadata.get('name', '') + model.status = hd_fields.SiteStatus.Unknown + model.source = hd_fields.ModelSource.Designed - # Need to add validation logic, we'll assume the input is - # valid for now - model.name = metadata.get('name', '') - model.status = hd_fields.SiteStatus.Unknown - model.source = hd_fields.ModelSource.Designed + spec = d.get('spec', {}) - spec = d.get('spec', {}) + model.tag_definitions = objects.NodeTagDefinitionList() - model.tag_definitions = objects.NodeTagDefinitionList() + tag_defs = spec.get('tag_definitions', []) - tag_defs = spec.get('tag_definitions', []) + for t in tag_defs: + tag_model = objects.NodeTagDefinition() + tag_model.tag = t.get('tag', '') + tag_model.type = t.get('definition_type', '') + tag_model.definition = t.get('definition', '') - for t in tag_defs: - tag_model = objects.NodeTagDefinition() - tag_model.tag = t.get('tag', '') - tag_model.type = t.get('definition_type', '') - tag_model.definition = t.get('definition', '') + if tag_model.type not in ['lshw_xpath']: + raise ValueError('Unknown definition type in ' \ + 'NodeTagDefinition: %s' % (self.definition_type)) + model.tag_definitions.append(tag_model) - if tag_model.type not in ['lshw_xpath']: - raise ValueError('Unknown definition type in ' \ - 'NodeTagDefinition: %s' % (self.definition_type)) - model.tag_definitions.append(tag_model) - - models.append(model) - else: - raise ValueError('Unknown API version %s of Region kind' %s (api_version)) - elif kind == 'NetworkLink': - api_version = d.get('apiVersion', '') - - if api_version == "v1.0": - model = objects.NetworkLink() - - metadata = d.get('metadata', {}) - spec = d.get('spec', {}) - - model.name = metadata.get('name', '') - model.site = metadata.get('region', '') - - bonding = spec.get('bonding', {}) - model.bonding_mode = bonding.get('mode', - hd_fields.NetworkLinkBondingMode.Disabled) - - # How should we define defaults for CIs not in the input? - if model.bonding_mode == hd_fields.NetworkLinkBondingMode.LACP: - model.bonding_xmit_hash = bonding.get('hash', 'layer3+4') - model.bonding_peer_rate = bonding.get('peer_rate', 'fast') - model.bonding_mon_rate = bonding.get('mon_rate', '100') - model.bonding_up_delay = bonding.get('up_delay', '200') - model.bonding_down_delay = bonding.get('down_delay', '200') - - model.mtu = spec.get('mtu', None) - model.linkspeed = spec.get('linkspeed', None) - - trunking = spec.get('trunking', {}) - model.trunk_mode = trunking.get('mode', hd_fields.NetworkLinkTrunkingMode.Disabled) - model.native_network = trunking.get('default_network', None) - - model.allowed_networks = spec.get('allowed_networks', None) - - models.append(model) - else: - raise ValueError('Unknown API version of object') - elif kind == 'Network': - api_version = d.get('apiVersion', '') - - if api_version == "v1.0": - model = objects.Network() + models.append(model) + else: + raise ValueError('Unknown API version %s of Region kind' %s (api_version)) + elif kind == 'NetworkLink': + if api_version == "v1": + model = objects.NetworkLink() metadata = d.get('metadata', {}) spec = d.get('spec', {}) @@ -162,213 +125,252 @@ class YamlIngester(IngesterPlugin): model.name = metadata.get('name', '') model.site = metadata.get('region', '') - model.cidr = spec.get('cidr', None) - model.allocation_strategy = spec.get('allocation', 'static') - model.vlan_id = spec.get('vlan', None) + bonding = spec.get('bonding', {}) + model.bonding_mode = bonding.get('mode', + hd_fields.NetworkLinkBondingMode.Disabled) + + # How should we define defaults for CIs not in the input? + if model.bonding_mode == hd_fields.NetworkLinkBondingMode.LACP: + model.bonding_xmit_hash = bonding.get('hash', 'layer3+4') + model.bonding_peer_rate = bonding.get('peer_rate', 'fast') + model.bonding_mon_rate = bonding.get('mon_rate', '100') + model.bonding_up_delay = bonding.get('up_delay', '200') + model.bonding_down_delay = bonding.get('down_delay', '200') + model.mtu = spec.get('mtu', None) + model.linkspeed = spec.get('linkspeed', None) - dns = spec.get('dns', {}) - model.dns_domain = dns.get('domain', 'local') - model.dns_servers = dns.get('servers', None) + trunking = spec.get('trunking', {}) + model.trunk_mode = trunking.get('mode', hd_fields.NetworkLinkTrunkingMode.Disabled) + model.native_network = trunking.get('default_network', None) - ranges = spec.get('ranges', []) - model.ranges = [] + model.allowed_networks = spec.get('allowed_networks', None) - for r in ranges: - model.ranges.append({'type': r.get('type', None), - 'start': r.get('start', None), - 'end': r.get('end', None), - }) - - routes = spec.get('routes', []) - model.routes = [] - - for r in routes: - model.routes.append({'subnet': r.get('subnet', None), - 'gateway': r.get('gateway', None), - 'metric': r.get('metric', None), - }) models.append(model) - elif kind == 'HardwareProfile': - api_version = d.get('apiVersion', '') - - if api_version == 'v1.0': - metadata = d.get('metadata', {}) - spec = d.get('spec', {}) - - model = objects.HardwareProfile() - - # Need to add validation logic, we'll assume the input is - # valid for now - model.name = metadata.get('name', '') - model.site = metadata.get('region', '') - model.source = hd_fields.ModelSource.Designed - - model.vendor = spec.get('vendor', None) - model.generation = spec.get('generation', None) - model.hw_version = spec.get('hw_version', None) - model.bios_version = spec.get('bios_version', None) - model.boot_mode = spec.get('boot_mode', None) - model.bootstrap_protocol = spec.get('bootstrap_protocol', None) - model.pxe_interface = spec.get('pxe_interface', None) - - model.devices = objects.HardwareDeviceAliasList() - - device_aliases = spec.get('device_aliases', {}) - - for d in device_aliases: - dev_model = objects.HardwareDeviceAlias() - dev_model.source = hd_fields.ModelSource.Designed - dev_model.alias = d.get('alias', None) - dev_model.bus_type = d.get('bus_type', None) - dev_model.dev_type = d.get('dev_type', None) - dev_model.address = d.get('address', None) - model.devices.append(dev_model) - - models.append(model) - elif kind == 'HostProfile' or kind == 'BaremetalNode': - api_version = d.get('apiVersion', '') - - if api_version == "v1.0": - model = None - - if kind == 'HostProfile': - model = objects.HostProfile() else: - model = objects.BaremetalNode() + raise ValueError('Unknown API version of object') + elif kind == 'Network': + if api_version == "v1": + model = objects.Network() - metadata = d.get('metadata', {}) - spec = d.get('spec', {}) + metadata = d.get('metadata', {}) + spec = d.get('spec', {}) - model.name = metadata.get('name', '') - model.site = metadata.get('region', '') - model.source = hd_fields.ModelSource.Designed + model.name = metadata.get('name', '') + model.site = metadata.get('region', '') - model.parent_profile = spec.get('host_profile', None) - model.hardware_profile = spec.get('hardware_profile', None) + 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) - oob = spec.get('oob', {}) + dns = spec.get('dns', {}) + model.dns_domain = dns.get('domain', 'local') + model.dns_servers = dns.get('servers', None) - model.oob_parameters = {} - for k,v in oob.items(): - if k == 'type': - model.oob_type = oob.get('type', None) - else: - model.oob_parameters[k] = v + ranges = spec.get('ranges', []) + model.ranges = [] - storage = spec.get('storage', {}) - model.storage_layout = storage.get('layout', 'lvm') + for r in ranges: + model.ranges.append({'type': r.get('type', None), + 'start': r.get('start', None), + 'end': r.get('end', None), + }) - bootdisk = storage.get('bootdisk', {}) - model.bootdisk_device = bootdisk.get('device', None) - model.bootdisk_root_size = bootdisk.get('root_size', None) - model.bootdisk_boot_size = bootdisk.get('boot_size', None) + routes = spec.get('routes', []) + model.routes = [] - partitions = storage.get('partitions', []) - model.partitions = objects.HostPartitionList() + for r in routes: + model.routes.append({'subnet': r.get('subnet', None), + 'gateway': r.get('gateway', None), + 'metric': r.get('metric', None), + }) + models.append(model) + elif kind == 'HardwareProfile': + if api_version == 'v1': + metadata = d.get('metadata', {}) + spec = d.get('spec', {}) - for p in partitions: - part_model = objects.HostPartition() + model = objects.HardwareProfile() - part_model.name = p.get('name', None) - part_model.source = hd_fields.ModelSource.Designed - part_model.device = p.get('device', None) - part_model.part_uuid = p.get('part_uuid', None) - part_model.size = p.get('size', None) - part_model.mountpoint = p.get('mountpoint', None) - part_model.fstype = p.get('fstype', 'ext4') - part_model.mount_options = p.get('mount_options', 'defaults') - part_model.fs_uuid = p.get('fs_uuid', None) - part_model.fs_label = p.get('fs_label', None) + # Need to add validation logic, we'll assume the input is + # valid for now + model.name = metadata.get('name', '') + model.site = metadata.get('region', '') + model.source = hd_fields.ModelSource.Designed - model.partitions.append(part_model) - - interfaces = spec.get('interfaces', []) - model.interfaces = objects.HostInterfaceList() - - for i in interfaces: - int_model = objects.HostInterface() - - int_model.device_name = i.get('device_name', None) - int_model.network_link = i.get('device_link', None) - - int_model.hardware_slaves = [] - slaves = i.get('slaves', []) - - for s in slaves: - int_model.hardware_slaves.append(s) - - int_model.networks = [] - networks = i.get('networks', []) - - for n in networks: - int_model.networks.append(n) - - model.interfaces.append(int_model) - - platform = spec.get('platform', {}) - - model.image = platform.get('image', None) - model.kernel = platform.get('kernel', None) - - model.kernel_params = {} - for k,v in platform.get('kernel_params', {}).items(): - model.kernel_params[k] = v - - model.primary_network = spec.get('primary_network', None) + model.vendor = spec.get('vendor', None) + model.generation = spec.get('generation', None) + model.hw_version = spec.get('hw_version', None) + model.bios_version = spec.get('bios_version', None) + model.boot_mode = spec.get('boot_mode', None) + model.bootstrap_protocol = spec.get('bootstrap_protocol', None) + model.pxe_interface = spec.get('pxe_interface', None) - node_metadata = spec.get('metadata', {}) - metadata_tags = node_metadata.get('tags', []) - model.tags = [] + model.devices = objects.HardwareDeviceAliasList() - for t in metadata_tags: - model.tags.append(t) + device_aliases = spec.get('device_aliases', {}) - owner_data = node_metadata.get('owner_data', {}) - model.owner_data = {} + for d in device_aliases: + dev_model = objects.HardwareDeviceAlias() + dev_model.source = hd_fields.ModelSource.Designed + dev_model.alias = d.get('alias', None) + dev_model.bus_type = d.get('bus_type', None) + dev_model.dev_type = d.get('dev_type', None) + dev_model.address = d.get('address', None) + model.devices.append(dev_model) - for k, v in owner_data.items(): - model.owner_data[k] = v + models.append(model) + elif kind == 'HostProfile' or kind == 'BaremetalNode': + if api_version == "v1": + model = None - model.rack = node_metadata.get('rack', None) + if kind == 'HostProfile': + model = objects.HostProfile() + else: + model = objects.BaremetalNode() - if kind == 'BaremetalNode': - model.boot_mac = node_metadata.get('boot_mac', None) + metadata = d.get('metadata', {}) + spec = d.get('spec', {}) - addresses = spec.get('addressing', []) + model.name = metadata.get('name', '') + model.site = metadata.get('region', '') + model.source = hd_fields.ModelSource.Designed - if len(addresses) == 0: - raise ValueError('BaremetalNode needs at least' \ - ' 1 assigned address') + model.parent_profile = spec.get('host_profile', None) + model.hardware_profile = spec.get('hardware_profile', None) - model.addressing = objects.IpAddressAssignmentList() - - for a in addresses: - assignment = objects.IpAddressAssignment() + oob = spec.get('oob', {}) - address = a.get('address', '') - if address == 'dhcp': - assignment.type = 'dhcp' - assignment.address = None - assignment.network = a.get('network') - - model.addressing.append(assignment) - elif address != '': - assignment.type = 'static' - assignment.address = a.get('address') - assignment.network = a.get('network') - - model.addressing.append(assignment) + model.oob_parameters = {} + for k,v in oob.items(): + if k == 'type': + model.oob_type = oob.get('type', None) else: - self.log.error("Invalid address assignment %s on Node %s" - % (address, self.name)) - models.append(model) - else: - raise ValueError('Unknown API version %s of Kind HostProfile' % (api_version)) - else: - self.log.error( - "Error processing document in %s, no kind field" - % (f)) - continue + model.oob_parameters[k] = v + storage = spec.get('storage', {}) + model.storage_layout = storage.get('layout', 'lvm') + + bootdisk = storage.get('bootdisk', {}) + model.bootdisk_device = bootdisk.get('device', None) + model.bootdisk_root_size = bootdisk.get('root_size', None) + model.bootdisk_boot_size = bootdisk.get('boot_size', None) + + partitions = storage.get('partitions', []) + model.partitions = objects.HostPartitionList() + + for p in partitions: + part_model = objects.HostPartition() + + part_model.name = p.get('name', None) + part_model.source = hd_fields.ModelSource.Designed + part_model.device = p.get('device', None) + part_model.part_uuid = p.get('part_uuid', None) + part_model.size = p.get('size', None) + part_model.mountpoint = p.get('mountpoint', None) + part_model.fstype = p.get('fstype', 'ext4') + part_model.mount_options = p.get('mount_options', 'defaults') + part_model.fs_uuid = p.get('fs_uuid', None) + part_model.fs_label = p.get('fs_label', None) + + model.partitions.append(part_model) + + interfaces = spec.get('interfaces', []) + model.interfaces = objects.HostInterfaceList() + + for i in interfaces: + int_model = objects.HostInterface() + + int_model.device_name = i.get('device_name', None) + int_model.network_link = i.get('device_link', None) + + int_model.hardware_slaves = [] + slaves = i.get('slaves', []) + + for s in slaves: + int_model.hardware_slaves.append(s) + + int_model.networks = [] + networks = i.get('networks', []) + + for n in networks: + int_model.networks.append(n) + + model.interfaces.append(int_model) + + platform = spec.get('platform', {}) + + model.image = platform.get('image', None) + model.kernel = platform.get('kernel', None) + + model.kernel_params = {} + for k,v in platform.get('kernel_params', {}).items(): + model.kernel_params[k] = v + + model.primary_network = spec.get('primary_network', None) + + node_metadata = spec.get('metadata', {}) + metadata_tags = node_metadata.get('tags', []) + model.tags = [] + + for t in metadata_tags: + model.tags.append(t) + + owner_data = node_metadata.get('owner_data', {}) + model.owner_data = {} + + for k, v in owner_data.items(): + model.owner_data[k] = v + + model.rack = node_metadata.get('rack', None) + + if kind == 'BaremetalNode': + model.boot_mac = node_metadata.get('boot_mac', None) + + addresses = spec.get('addressing', []) + + if len(addresses) == 0: + raise ValueError('BaremetalNode needs at least' \ + ' 1 assigned address') + + model.addressing = objects.IpAddressAssignmentList() + + for a in addresses: + assignment = objects.IpAddressAssignment() + + address = a.get('address', '') + if address == 'dhcp': + assignment.type = 'dhcp' + assignment.address = None + assignment.network = a.get('network') + + model.addressing.append(assignment) + elif address != '': + assignment.type = 'static' + assignment.address = a.get('address') + assignment.network = a.get('network') + + model.addressing.append(assignment) + else: + self.log.error("Invalid address assignment %s on Node %s" + % (address, self.name)) + models.append(model) + else: + raise ValueError('Unknown API version %s of Kind HostProfile' % (api_version)) + else: + self.log.error( + "Error processing document in %s, no kind field" + % (f)) + continue + elif api.startswith('promenade/'): + (foo, api_version) = api.split('/') + if api_version == 'v1': + metadata = d.get('metadata', {}) + + target = metadata.get('target', 'all') + name = metadata.get('name', None) + + model = objects.PromenadeConfig(target=target, name=name, kind=kind, document=yaml.dump(d)) + models.append(model) return models diff --git a/drydock_provisioner/objects/promenade.py b/drydock_provisioner/objects/promenade.py new file mode 100644 index 00000000..cd86b1a4 --- /dev/null +++ b/drydock_provisioner/objects/promenade.py @@ -0,0 +1,45 @@ +# 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. +# +from copy import deepcopy + +from oslo_versionedobjects import fields as ovo_fields + +import drydock_provisioner.objects as objects +import drydock_provisioner.objects.base as base +import drydock_provisioner.objects.fields as hd_fields + +@base.DrydockObjectRegistry.register +class PromenadeConfig(base.DrydockPersistentObject, base.DrydockObject): + + VERSION = '1.0' + + fields = { + 'target': ovo_fields.StringField(), + 'name': ovo_fields.StringField(nullable=True), + 'kind': ovo_fields.StringField(), + 'document': ovo_fields.StringField(), + } + + def __init__(self, **kwargs): + super(PromenadeConfig, self).__init__(**kwargs) + + return + + # HardwareProfile keyed on name + def get_id(self): + return self.get_name() + + def get_name(self): + return self.name diff --git a/drydock_provisioner/statemgmt/__init__.py b/drydock_provisioner/statemgmt/__init__.py index a6be0d0c..744a4394 100644 --- a/drydock_provisioner/statemgmt/__init__.py +++ b/drydock_provisioner/statemgmt/__init__.py @@ -29,12 +29,18 @@ class DesignState(object): self.designs = {} self.designs_lock = Lock() + self.promenade = {} + self.promenade_lock = Lock() + self.builds = [] self.builds_lock = Lock() self.tasks = [] self.tasks_lock = Lock() + self.bootdata = {} + self.bootdata_lock = Lock() + return # TODO Need to lock a design base or change once implementation @@ -207,4 +213,34 @@ class DesignState(object): else: raise StateError("Could not acquire lock") + def post_promenade_part(self, part): + my_lock = self.promenade_lock.acquire(blocking=True, timeout=10) + if my_lock: + if self.promenade.get(target, None) is not None: + self.promenade[part.target].append(part.obj_to_primitive()) + else: + self.promenade[target] = [part.obj_to_primitive()] + self.promenade_lock.release() + return None + else: + raise StateError("Could not acquire lock") + + def get_promenade_parts(self, target): + parts = self.promenade.get(target, None) + if parts is not None: + return [p.obj_to_primitive() for p in parts] + else: + return None + + def set_bootdata_key(self, hostname, design_id, data_key): + my_lock = self.bootdata_lock.acquire(blocking=True, timeout=10) + if my_lock: + self.bootdata[hostname] = {'design_id': design_id, 'key': data_key} + self.bootdata_lock.release() + return None + else: + raise StateError("Could not acquire lock") + + def get_bootdata_key(self, hostname): + return self.bootdata.get(hostname, None) \ No newline at end of file