diff --git a/drydock_provisioner/ingester/ingester.py b/drydock_provisioner/ingester/ingester.py index 2ce198e9..e0356d02 100644 --- a/drydock_provisioner/ingester/ingester.py +++ b/drydock_provisioner/ingester/ingester.py @@ -25,6 +25,7 @@ import drydock_provisioner.objects.node as node import drydock_provisioner.objects.hostprofile as hostprofile import drydock_provisioner.objects.promenade as prom import drydock_provisioner.objects.rack as rack +import drydock_provisioner.objects.bootaction as bootaction class Ingester(object): @@ -119,4 +120,6 @@ class Ingester(object): design_data.add_promenade_config(m) elif type(m) is rack.Rack: design_data.add_rack(m) + elif type(m) is bootaction.BootAction: + design_data.add_bootaction(m) return status, design_data diff --git a/drydock_provisioner/ingester/plugins/yaml.py b/drydock_provisioner/ingester/plugins/yaml.py index 98bdf9b5..d23ff12e 100644 --- a/drydock_provisioner/ingester/plugins/yaml.py +++ b/drydock_provisioner/ingester/plugins/yaml.py @@ -382,6 +382,55 @@ class YamlIngester(IngesterPlugin): return model + def process_drydock_bootaction(self, name, data): + """Process the data/spec section of a BootAction document. + + :param name: the document name attribute + :Param data: the dictionary of the parsed data/spec section + """ + model = objects.BootAction() + model.name = name + model.source = hd_fields.ModelSource.Designed + + assets = data.get('assets') + + model.asset_list = objects.BootActionAssetList() + + for a in assets: + ba = self.process_bootaction_asset(a) + model.asset_list.append(ba) + + node_filter = data.get('node_filter', None) + + if node_filter is not None: + nfs = self.process_bootaction_nodefilter(node_filter) + model.node_filter = nfs + + return model + + def process_bootaction_asset(self, asset_dict): + """Process a dictionary representing a BootAction Data Asset. + + :param asset_dict: dictionary representing the bootaction asset + """ + model = objects.BootActionAsset(**asset_dict) + return model + + def process_bootaction_nodefilter(self, nf): + """Process a dictionary representing a BootAction NodeFilter Set. + + :param nf: dictionary representing the bootaction nodefilter set. + """ + model = objects.NodeFilterSet() + model.filter_set_type = nf.get('filter_set_type', None) + model.filter_set = [] + + for nf in nf.get('filter_set', []): + nf_model = objects.NodeFilter(**nf) + model.filter_set.append(nf_model) + + return model + def process_drydock_node(self, name, data): """Process the data/spec section of a BaremetalNode document. @@ -610,4 +659,5 @@ class YamlIngester(IngesterPlugin): 'HardwareProfile': process_drydock_hwprofile, 'HostProfile': process_drydock_hostprofile, 'BaremetalNode': process_drydock_node, + 'BootAction': process_drydock_bootaction, } diff --git a/drydock_provisioner/objects/__init__.py b/drydock_provisioner/objects/__init__.py index 8d0958ba..77fb3665 100644 --- a/drydock_provisioner/objects/__init__.py +++ b/drydock_provisioner/objects/__init__.py @@ -29,6 +29,7 @@ def register_all(): importlib.import_module('drydock_provisioner.objects.site') importlib.import_module('drydock_provisioner.objects.promenade') importlib.import_module('drydock_provisioner.objects.rack') + importlib.import_module('drydock_provisioner.objects.bootaction') importlib.import_module('drydock_provisioner.objects.task') diff --git a/drydock_provisioner/objects/bootaction.py b/drydock_provisioner/objects/bootaction.py new file mode 100644 index 00000000..bdd0bc8f --- /dev/null +++ b/drydock_provisioner/objects/bootaction.py @@ -0,0 +1,117 @@ +# 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. +"""Object models for BootActions.""" + +import oslo_versionedobjects.fields as ovo_fields + +import drydock_provisioner.objects.base as base +import drydock_provisioner.objects.fields as hd_fields + + +@base.DrydockObjectRegistry.register +class BootAction(base.DrydockPersistentObject, base.DrydockObject): + + VERSION = '1.0' + + fields = { + 'name': + ovo_fields.StringField(), + 'source': + hd_fields.ModelSourceField(nullable=False), + 'asset_list': + ovo_fields.ObjectField('BootActionAssetList', nullable=False), + 'node_filter': + ovo_fields.ObjectField('NodeFilterSet', nullable=True), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # NetworkLink keyed by name + def get_id(self): + return self.get_name() + + def get_name(self): + return self.name + + +@base.DrydockObjectRegistry.register +class BootActionList(base.DrydockObjectListBase, base.DrydockObject): + + VERSION = '1.0' + + fields = { + 'objects': ovo_fields.ListOfObjectsField('BootAction'), + } + + +@base.DrydockObjectRegistry.register +class BootActionAsset(base.DrydockObject): + + VERSION = '1.0' + + fields = { + 'type': ovo_fields.StringField(nullable=True), + 'path': ovo_fields.StringField(nullable=True), + 'location': ovo_fields.StringField(nullable=True), + 'data': ovo_fields.StringField(nullable=True), + 'location_pipeline': ovo_fields.ListOfStringsField(nullable=True), + 'data_pipeline': ovo_fields.ListOfStringsField(nullable=True), + 'permissions': ovo_fields.IntegerField(nullable=True), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +@base.DrydockObjectRegistry.register +class BootActionAssetList(base.DrydockObjectListBase, base.DrydockObject): + + VERSION = '1.0' + + fields = { + 'objects': ovo_fields.ListOfObjectsField('BootActionAsset'), + } + + +@base.DrydockObjectRegistry.register +class NodeFilterSet(base.DrydockObject): + + VERSION = '1.0' + + fields = { + 'filter_set_type': ovo_fields.StringField(nullable=False), + 'filter_set': ovo_fields.ListOfObjectsField('NodeFilter'), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +@base.DrydockObjectRegistry.register +class NodeFilter(base.DrydockObject): + + VERSION = '1.0' + + fields = { + 'filter_type': ovo_fields.StringField(nullable=False), + 'node_names': ovo_fields.ListOfStringsField(nullable=True), + 'node_tags': ovo_fields.ListOfStringsField(nullable=True), + 'node_labels': ovo_fields.DictOfStringsField(nullable=True), + 'rack_names': ovo_fields.ListOfStringsField(nullable=True), + 'rack_labels': ovo_fields.DictOfStringsField(nullable=True), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) diff --git a/drydock_provisioner/objects/site.py b/drydock_provisioner/objects/site.py index 507974a6..baccbb5c 100644 --- a/drydock_provisioner/objects/site.py +++ b/drydock_provisioner/objects/site.py @@ -148,6 +148,8 @@ class SiteDesign(base.DrydockPersistentObject, base.DrydockObject): ovo_fields.ObjectField('PromenadeConfigList', nullable=True), 'racks': ovo_fields.ObjectField('RackList', nullable=True), + 'bootactions': + ovo_fields.ObjectField('BootActionList', nullable=True), } def __init__(self, **kwargs): @@ -218,6 +220,27 @@ class SiteDesign(base.DrydockPersistentObject, base.DrydockObject): raise errors.DesignError( "Rack %s not found in design state" % rack_key) + def add_bootaction(self, new_ba): + """Add a bootaction definition to this site design. + + :param new_ba: instance of BootAction to add to the design + """ + if self.bootactions is None: + self.bootactions = objects.BootActionList() + + self.bootactions.append(new_ba) + + def get_bootaction(self, ba_key): + """Select a boot action from this site design with the matchkey key. + + :param ba_key: Value should match the ``get_id()`` value of the BootAction returned + """ + for ba in self.bootactions: + if ba.get_id() == ba_key: + return ba + raise errors.DesignError( + "BootAction %s not found in design state" % ba_key) + def add_host_profile(self, new_host_profile): if new_host_profile is None: raise errors.DesignError("Invalid HostProfile model") diff --git a/drydock_provisioner/schemas/bootaction.yaml b/drydock_provisioner/schemas/bootaction.yaml new file mode 100644 index 00000000..acdfcbe8 --- /dev/null +++ b/drydock_provisioner/schemas/bootaction.yaml @@ -0,0 +1,89 @@ +--- +schema: 'deckhand/DataSchema/v1' +metadata: + schema: metadata/Control/v1 + name: drydock/BootAction/v1 + labels: + application: drydock +data: + $schema: 'http://json-schema.org/schema#' + id: 'http://att.com/att-comdev/drydock/bootaction.yaml' + type: 'object' + additionalProperties: false + properties: + assets: + type: 'array' + items: + type: 'object' + additionalProperties: false + properties: + path: + type: 'string' + pattern: '^/.+' + location: + type: 'string' + type: + type: 'string' + enum: + - 'unit' + - 'file' + - 'pkg_list' + data: + type: 'string' + location_pipeline: + type: 'array' + items: + type: 'string' + enum: + - 'template' + data_pipeline: + type: 'array' + items: + type: 'string' + enum: + - 'base64_encode' + - 'template' + - 'base64_decode' + permissions: + type: 'integer' + required: + - 'type' + node_filter: + type: 'object' + additionalProperties: false + properties: + filter_set_type: + type: 'string' + enum: + - 'intersection' + - 'union' + filter_set: + type: 'array' + items: + type: 'object' + additionalProperties: false + properties: + filter_type: + type: 'string' + enum: + - 'intersection' + - 'union' + node_names: + type: 'array' + items: + type: 'string' + node_tags: + type: 'array' + items: + type: 'string' + node_labels: + type: 'object' + additionalProperties: true + rack_names: + type: 'array' + items: + type: 'string' + rack_labels: + type: 'object' + additionalProperties: true +... diff --git a/tests/yaml_samples/bootaction.yaml b/tests/yaml_samples/bootaction.yaml new file mode 100644 index 00000000..3eb3b6db --- /dev/null +++ b/tests/yaml_samples/bootaction.yaml @@ -0,0 +1,26 @@ +--- +apiVersion: 'drydock/v1' +kind: BootAction +metadata: + name: helloworld + region: sitename + date: 17-FEB-2017 + author: Scott Hussey +spec: + assets: + - path: /var/tmp/hello.sh + type: file + permissions: 555 + data: | + IyEvYmluL2Jhc2gKCmVjaG8gJ0hlbGxvIFdvcmxkIScK + data_pipeline: + - base64_decode + - path: /lib/systemd/system/hello.service + type: unit + data: | + W1VuaXRdCkRlc2NyaXB0aW9uPUhlbGxvIFdvcmxkCgpbU2VydmljZV0KVHlwZT1vbmVzaG90CkV4 + ZWNTdGFydD0vdmFyL3RtcC9oZWxsby5zaAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIu + dGFyZ2V0Cg== + data_pipeline: + - base64_decode +... diff --git a/tests/yaml_samples/invalid_bootaction.yaml b/tests/yaml_samples/invalid_bootaction.yaml new file mode 100644 index 00000000..70798bef --- /dev/null +++ b/tests/yaml_samples/invalid_bootaction.yaml @@ -0,0 +1,18 @@ +data: + assets: + - path: /var/tmp/hello.sh + type: file + permissions: 555 + data: | + IyEvYmluL2Jhc2gKCmVjaG8gJ0hlbGxvIFdvcmxkIScK + data_pipeline: + - foo + - path: hello.service + type: unit + data: | + W1VuaXRdCkRlc2NyaXB0aW9uPUhlbGxvIFdvcmxkCgpbU2VydmljZV0KVHlwZT1vbmVzaG90CkV4 + ZWNTdGFydD0vdmFyL3RtcC9oZWxsby5zaAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIu + dGFyZ2V0Cg== + data_pipeline: + - base64_decode +