From ace0e477627b332ac86fd5c8708ca31ce9428023 Mon Sep 17 00:00:00 2001 From: Scott Hussey Date: Thu, 2 Mar 2017 17:04:57 -0600 Subject: [PATCH] Make YAML ingester not AIC specific Add test case for federated design Fix issues with inheritance --- .../ingester/plugins/{aicyaml.py => yaml.py} | 77 +++--- helm_drydock/model/__init__.py | 110 +++++++-- helm_drydock/orchestrator/designdata.py | 11 +- tests/test_design_inheritance.py | 22 +- tests/test_ingester.py | 33 ++- ...ester_aicyaml.py => test_ingester_yaml.py} | 8 +- .../fullsite.yaml | 0 tests/yaml_samples/fullsite_networks.yaml | 228 ++++++++++++++++++ tests/yaml_samples/fullsite_nodes.yaml | 70 ++++++ tests/yaml_samples/fullsite_profiles.yaml | 197 +++++++++++++++ .../invalid.yaml | 0 .../multidoc.yaml | 0 .../singledoc.yaml | 0 .../unknown_kind.yaml | 0 14 files changed, 674 insertions(+), 82 deletions(-) rename helm_drydock/ingester/plugins/{aicyaml.py => yaml.py} (58%) rename tests/{test_ingester_aicyaml.py => test_ingester_yaml.py} (86%) rename tests/{aicyaml_samples => yaml_samples}/fullsite.yaml (100%) create mode 100644 tests/yaml_samples/fullsite_networks.yaml create mode 100644 tests/yaml_samples/fullsite_nodes.yaml create mode 100644 tests/yaml_samples/fullsite_profiles.yaml rename tests/{aicyaml_samples => yaml_samples}/invalid.yaml (100%) rename tests/{aicyaml_samples => yaml_samples}/multidoc.yaml (100%) rename tests/{aicyaml_samples => yaml_samples}/singledoc.yaml (100%) rename tests/{aicyaml_samples => yaml_samples}/unknown_kind.yaml (100%) diff --git a/helm_drydock/ingester/plugins/aicyaml.py b/helm_drydock/ingester/plugins/yaml.py similarity index 58% rename from helm_drydock/ingester/plugins/aicyaml.py rename to helm_drydock/ingester/plugins/yaml.py index 2da7bba2..44664c8c 100644 --- a/helm_drydock/ingester/plugins/aicyaml.py +++ b/helm_drydock/ingester/plugins/yaml.py @@ -22,7 +22,7 @@ import logging import helm_drydock.model as model from helm_drydock.ingester.plugins import IngesterPlugin -class AicYamlIngester(IngesterPlugin): +class YamlIngester(IngesterPlugin): kind_map = { "Region": model.Site, @@ -34,10 +34,10 @@ class AicYamlIngester(IngesterPlugin): } def __init__(self): - super(AicYamlIngester, self).__init__() + super(YamlIngester, self).__init__() def get_name(self): - return "aic_yaml" + return "yaml" """ AIC YAML ingester params @@ -48,6 +48,8 @@ class AicYamlIngester(IngesterPlugin): """ def ingest_data(self, **kwargs): + models = [] + if 'filenames' in kwargs: # TODO validate filenames is array for f in kwargs.get('filenames'): @@ -55,45 +57,52 @@ class AicYamlIngester(IngesterPlugin): file = open(f,'rt') contents = file.read() file.close() + models.extend(self.parse_docs(contents)) except OSError as err: self.log.error( "Error opening input file %s for ingestion: %s" % (filename, err)) continue - try: - parsed_data = yaml.load_all(contents) - except yaml.YAMLError as err: - self.log.error("Error parsing YAML in %s: %s" % (f, err)) - continue - - models = [] - for d in parsed_data: - kind = d.get('kind', '') - if kind != '': - if kind in AicYamlIngester.kind_map: - try: - model = AicYamlIngester.kind_map[kind](**d) - models.append(model) - except Exception as err: - self.log.error("Error building model %s: %s" - % (kind, str(err))) - else: - self.log.error( - "Error processing document, unknown kind %s" - % (kind)) - continue - else: - self.log.error( - "Error processing document in %s, no kind field" - % (f)) - continue - - return models + + elif 'content' in kwargs: + models.extend(self.parse_docs(kwargs.get('content'))) else: raise ValueError('Missing parameter "filename"') - return processed_data - + return models + """ + Translate a YAML string into the internal Drydock model + """ + def parse_docs(self, yaml_string): + models = [] + try: + parsed_data = yaml.load_all(yaml_string) + except yaml.YAMLError as err: + raise ValueError("Error parsing YAML in %s: %s" % (f,err)) + + for d in parsed_data: + kind = d.get('kind', '') + if kind != '': + if kind in YamlIngester.kind_map: + try: + model = YamlIngester.kind_map[kind](**d) + models.append(model) + except Exception as err: + self.log.error("Error building model %s: %s" + % (kind, str(err))) + continue + else: + self.log.error( + "Error processing document, unknown kind %s" + % (kind)) + continue + else: + self.log.error( + "Error processing document in %s, no kind field" + % (f)) + continue + + return models diff --git a/helm_drydock/model/__init__.py b/helm_drydock/model/__init__.py index e5cec4bf..05bb67d9 100644 --- a/helm_drydock/model/__init__.py +++ b/helm_drydock/model/__init__.py @@ -14,12 +14,16 @@ # # Models for helm_drydock # +import logging + from copy import deepcopy class HardwareProfile(object): def __init__(self, **kwargs): + self.log = logging.Logger('model') + self.api_version = kwargs.get('apiVersion', '') if self.api_version == "v1.0": @@ -55,14 +59,19 @@ class HardwareProfile(object): self.devices.append( HardwareDeviceAlias(self.api_version, **d)) else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') return def resolve_alias(self, alias_type, alias): + selector = {} for d in self.devices: if d.alias == alias and d.bus_type == alias_type: - return deepcopy(d) + selector['address'] = d.address + selector['device_type'] = d.type + return selector return None @@ -70,6 +79,8 @@ class HardwareProfile(object): class HardwareDeviceAlias(object): def __init__(self, api_version, **kwargs): + self.log = logging.Logger('model') + self.api_version = api_version if self.api_version == "v1.0": @@ -78,12 +89,16 @@ class HardwareDeviceAlias(object): self.alias = kwargs.get('alias', None) self.type = kwargs.get('type', None) else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') class Site(object): def __init__(self, **kwargs): + self.log = logging.Logger('model') + self.api_version = kwargs.get('apiVersion', '') if self.api_version == "v1.0": @@ -100,6 +115,8 @@ class Site(object): self.baremetal_nodes = [] else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') def get_network(self, network_name): @@ -140,6 +157,8 @@ class Site(object): class NetworkLink(object): def __init__(self, **kwargs): + self.log = logging.Logger('model') + self.api_version = kwargs.get('apiVersion', '') if self.api_version == "v1.0": @@ -152,13 +171,13 @@ class NetworkLink(object): bonding = spec.get('bonding', {}) self.bonding_mode = bonding.get('mode', 'none') - # TODO How should we define defaults for CIs not in the input? + # How should we define defaults for CIs not in the input? if self.bonding_mode == '802.3ad': self.bonding_xmit_hash = bonding.get('hash', 'layer3+4') self.bonding_peer_rate = bonding.get('peer_rate', 'fast') - self.bonding_mon_rate = bonding.get('mon_rate', '') - self.bonding_up_delay = bonding.get('up_delay', '') - self.bonding_down_delay = bonding.get('down_delay', '') + self.bonding_mon_rate = bonding.get('mon_rate', '100') + self.bonding_up_delay = bonding.get('up_delay', '200') + self.bonding_down_delay = bonding.get('down_delay', '200') self.mtu = spec.get('mtu', 1500) self.linkspeed = spec.get('linkspeed', 'auto') @@ -168,12 +187,16 @@ class NetworkLink(object): self.native_network = spec.get('default_network', '') else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') class Network(object): def __init__(self, **kwargs): + self.log = logging.Logger('model') + self.api_version = kwargs.get('apiVersion', '') if self.api_version == "v1.0": @@ -204,12 +227,16 @@ class Network(object): for r in routes: self.routes.append(NetworkRoute(self.api_version, **r)) else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') class NetworkAddressRange(object): def __init__(self, api_version, **kwargs): + self.log = logging.Logger('model') + self.api_version = api_version if self.api_version == "v1.0": @@ -217,12 +244,16 @@ class NetworkAddressRange(object): self.start = kwargs.get('start', None) self.end = kwargs.get('end', None) else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') class NetworkRoute(object): def __init__(self, api_version, **kwargs): + self.log = logging.Logger('model') + self.api_version = api_version if self.api_version == "v1.0": @@ -230,12 +261,16 @@ class NetworkRoute(object): self.start = kwargs.get('gateway', None) self.end = kwargs.get('metric', 100) else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') class HostProfile(object): def __init__(self, **kwargs): + self.log = logging.Logger('model') + self.api_version = kwargs.get('apiVersion', '') if self.api_version == "v1.0": @@ -291,6 +326,8 @@ class HostProfile(object): self.rack = metadata.get('rack', None) else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') def apply_inheritance(self, site): @@ -304,7 +341,8 @@ class HostProfile(object): parent = site.get_host_profile(self.parent_profile) if parent is None: - return self_copy + raise NameError("Cannot find parent profile %s for %s" + % (self.parent_profile, self.name)) parent = parent.apply_inheritance(site) @@ -338,6 +376,8 @@ class HostProfile(object): class HostInterface(object): def __init__(self, api_version, **kwargs): + self.log = logging.Logger('model') + self.api_version = api_version if self.api_version == "v1.0": @@ -355,8 +395,9 @@ class HostInterface(object): for n in networks: self.networks.append(n) - else: + self.log.error("Unknown API version %s of %s" % + (self.api_version, self.__class__)) raise ValueError('Unknown API version of object') # The device attribute may be hardware alias that translates to a @@ -365,16 +406,24 @@ class HostInterface(object): # apply_hardware_profile method is called on the parent Node of this # device, the selector will be decided and applied - def add_selector(self, sel_type, selector): + def add_selector(self, sel_type, address='', dev_type=''): if getattr(self, 'selectors', None) is None: self.selectors = [] new_selector = {} new_selector['selector_type'] = sel_type - new_selector['selector'] = selector + new_selector['address'] = address + new_selector['device_type'] = dev_type self.selectors.append(new_selector) + def get_slave_selectors(self): + return self.selectors + + # Return number of slaves for this interface + def get_slave_count(self): + return len(self.hardware_slaves) + """ Merge two lists of HostInterface models with child_list taking priority when conflicts. If a member of child_list has a device_name @@ -394,13 +443,16 @@ class HostInterface(object): continue else: effective_list.append(deepcopy(i)) + return effective_list parent_interfaces = [] for i in parent_list: parent_name = i.device_name parent_interfaces.append(parent_name) + add = True for j in child_list: if j.device_name == ("!" + parent_name): + add = False break elif j.device_name == parent_name: m = HostInterface(j.api_version) @@ -425,9 +477,15 @@ class HostInterface(object): m.networks = n effective_list.append(m) + add = False + break + + if add: + effective_list.append(deepcopy(i)) for j in child_list: - if j.device_name not in parent_list: + if (j.device_name not in parent_interfaces + and not j.device_name.startswith("!")): effective_list.append(deepcopy(j)) return effective_list @@ -457,10 +515,17 @@ class HostPartition(object): # apply_hardware_profile method is called on the parent Node of this # device, the selector will be decided and applied - def set_selector(self, sel_type, selector): - self.selector_type = sel_type + def set_selector(self, sel_type, address='', dev_type=''): + selector = {} + selector['type'] = sel_type + selector['address'] = address + selector['device_type'] = dev_type + self.selector = selector + def get_selector(self): + return self.selector + """ Merge two lists of HostPartition models with child_list taking priority when conflicts. If a member of child_list has a name @@ -489,8 +554,10 @@ class HostPartition(object): for i in parent_list: parent_name = i.name parent_partitions.append(parent_name) + add = True for j in child_list: if j.name == ("!" + parent_name): + add = False break elif j.name == parent_name: p = HostPartition(j.api_version) @@ -500,8 +567,10 @@ class HostPartition(object): setattr(p, Utils.apply_field_inheritance(getattr(j, f), getattr(i, f)) ) - + add = False effective_list.append(p) + if add: + effective_list.append(deepcopy(i)) for j in child_list: if j.name not in parent_list: @@ -534,16 +603,18 @@ class BaremetalNode(HostProfile): for s in i.hardware_slaves: selector = hw_profile.resolve_alias("pci", s) if selector is None: - i.add_selector("name", s) + i.add_selector("name", address=p.device) else: - i.add_selector("address", selector) + i.add_selector("address", address=selector['address'], + dev_type=selector['device_type']) for p in self_copy.partitions: selector = hw_profile.resolve_alias("scsi", p.device) if selector is None: - p.set_selector("name", p.device) + p.set_selector("name", address=p.device) else: - p.set_selector("address", selector) + p.set_selector("address", address=selector['address'], + dev_type=selector['device_type']) hardware = {"vendor": getattr(hw_profile, 'vendor', None), @@ -561,6 +632,11 @@ class BaremetalNode(HostProfile): return self_copy + def get_interface(self, iface_name): + for i in self.interfaces: + if i.device_name == iface_name: + return i + return None # Utility class for calculating inheritance diff --git a/helm_drydock/orchestrator/designdata.py b/helm_drydock/orchestrator/designdata.py index 2ec2cf65..a26ebb1d 100644 --- a/helm_drydock/orchestrator/designdata.py +++ b/helm_drydock/orchestrator/designdata.py @@ -31,13 +31,8 @@ class DesignStateClient(object): return a Site model populated with all components from the design state """ - def load_design_data(self, design_state=None): - if len(design_state.get_sites()) != 1: - self.log.error("Invalid design state, should only have 1 Site model") - raise Exception("Invalid design state, should only have 1 Site model") - - site = design_state.get_sites()[0] - site_name = site.name + def load_design_data(self, site_name, design_state=None): + site = design_state.get_site(site_name) networks = design_state.get_networks() @@ -96,5 +91,3 @@ class DesignStateClient(object): site_copy.baremetal_nodes = effective_nodes return site_copy - - diff --git a/tests/test_design_inheritance.py b/tests/test_design_inheritance.py index 991289bb..8691646d 100644 --- a/tests/test_design_inheritance.py +++ b/tests/test_design_inheritance.py @@ -21,7 +21,7 @@ from copy import deepcopy import pytest import shutil import os -import helm_drydock.ingester.plugins.aicyaml +import helm_drydock.ingester.plugins.yaml import yaml class TestClass(object): @@ -33,15 +33,23 @@ class TestClass(object): def test_design_inheritance(self, loaded_design): client = DesignStateClient() - design_data = client.load_design_data(design_state=loaded_design) + design_data = client.load_design_data("sitename", design_state=loaded_design) design_data = client.compute_model_inheritance(design_data) - print(yaml.dump(design_data, default_flow_style=False)) - node = design_data.get_baremetal_node("controller01") + print(yaml.dump(node, default_flow_style=False)) + assert node.hardware_profile == 'HPGen9v3' + iface = node.get_interface('bond0') + + assert iface.get_slave_count() == 2 + + iface = node.get_interface('pxe') + + assert iface.get_slave_count() == 1 + @pytest.fixture(scope='module') def loaded_design(self, input_files): input_file = input_files.join("fullsite.yaml") @@ -49,8 +57,8 @@ class TestClass(object): module_design_state = DesignState() ingester = Ingester() - ingester.enable_plugins([helm_drydock.ingester.plugins.aicyaml.AicYamlIngester]) - ingester.ingest_data(plugin_name='aic_yaml', design_state=module_design_state, filenames=[str(input_file)]) + ingester.enable_plugins([helm_drydock.ingester.plugins.yaml.YamlIngester]) + ingester.ingest_data(plugin_name='yaml', design_state=module_design_state, filenames=[str(input_file)]) return module_design_state @@ -59,7 +67,7 @@ class TestClass(object): @pytest.fixture(scope='module') def input_files(self, tmpdir_factory, request): tmpdir = tmpdir_factory.mktemp('data') - samples_dir = os.path.dirname(str(request.fspath)) + "/aicyaml_samples" + samples_dir = os.path.dirname(str(request.fspath)) + "/yaml_samples" samples = os.listdir(samples_dir) for f in samples: diff --git a/tests/test_ingester.py b/tests/test_ingester.py index edece137..2081521b 100644 --- a/tests/test_ingester.py +++ b/tests/test_ingester.py @@ -18,26 +18,42 @@ from helm_drydock.statemgmt import DesignState import pytest import shutil import os -import helm_drydock.ingester.plugins.aicyaml +import helm_drydock.ingester.plugins.yaml class TestClass(object): def setup_method(self, method): print("Running test {0}".format(method.__name__)) - def test_ingest_full_site(self, design_state,input_files): + def test_ingest_full_site(self, input_files): input_file = input_files.join("fullsite.yaml") + design_state = DesignState() ingester = Ingester() - ingester.enable_plugins([helm_drydock.ingester.plugins.aicyaml.AicYamlIngester]) - ingester.ingest_data(plugin_name='aic_yaml', design_state=design_state, filenames=[str(input_file)]) + ingester.enable_plugins([helm_drydock.ingester.plugins.yaml.YamlIngester]) + ingester.ingest_data(plugin_name='yaml', design_state=design_state, filenames=[str(input_file)]) + + assert len(design_state.get_host_profiles()) == 3 + assert len(design_state.get_baremetal_nodes()) == 2 + + def test_ingest_federated_design(self, input_files): + profiles_file = input_files.join("fullsite_profiles.yaml") + networks_file = input_files.join("fullsite_networks.yaml") + nodes_file = input_files.join("fullsite_nodes.yaml") + + design_state = DesignState() + + ingester = Ingester() + ingester.enable_plugins([helm_drydock.ingester.plugins.yaml.YamlIngester]) + ingester.ingest_data(plugin_name='yaml', design_state=design_state, + filenames=[str(profiles_file), str(networks_file), str(nodes_file)]) assert len(design_state.host_profiles) == 3 @pytest.fixture(scope='module') def input_files(self, tmpdir_factory, request): tmpdir = tmpdir_factory.mktemp('data') - samples_dir = os.path.dirname(str(request.fspath)) + "/aicyaml_samples" + samples_dir = os.path.dirname(str(request.fspath)) + "/yaml_samples" samples = os.listdir(samples_dir) for f in samples: @@ -45,9 +61,4 @@ class TestClass(object): dst_file = str(tmpdir) + "/" + f shutil.copyfile(src_file, dst_file) - return tmpdir - - @pytest.fixture(scope='session') - def design_state(self): - design_state = DesignState() - return design_state \ No newline at end of file + return tmpdir \ No newline at end of file diff --git a/tests/test_ingester_aicyaml.py b/tests/test_ingester_yaml.py similarity index 86% rename from tests/test_ingester_aicyaml.py rename to tests/test_ingester_yaml.py index 0827bc8b..081a006b 100644 --- a/tests/test_ingester_aicyaml.py +++ b/tests/test_ingester_yaml.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from helm_drydock.ingester.plugins.aicyaml import AicYamlIngester +from helm_drydock.ingester.plugins.yaml import YamlIngester import pytest import shutil import os @@ -25,7 +25,7 @@ class TestClass(object): def test_ingest_singledoc(self, input_files): input_file = input_files.join("singledoc.yaml") - ingester = AicYamlIngester() + ingester = YamlIngester() models = ingester.ingest_data(filenames=[str(input_file)]) @@ -34,7 +34,7 @@ class TestClass(object): def test_ingest_multidoc(self, input_files): input_file = input_files.join("multidoc.yaml") - ingester = AicYamlIngester() + ingester = YamlIngester() models = ingester.ingest_data(filenames=[str(input_file)]) @@ -43,7 +43,7 @@ class TestClass(object): @pytest.fixture(scope='module') def input_files(self, tmpdir_factory, request): tmpdir = tmpdir_factory.mktemp('data') - samples_dir = os.path.dirname(str(request.fspath)) + "/aicyaml_samples" + samples_dir = os.path.dirname(str(request.fspath)) + "/yaml_samples" samples = os.listdir(samples_dir) for f in samples: diff --git a/tests/aicyaml_samples/fullsite.yaml b/tests/yaml_samples/fullsite.yaml similarity index 100% rename from tests/aicyaml_samples/fullsite.yaml rename to tests/yaml_samples/fullsite.yaml diff --git a/tests/yaml_samples/fullsite_networks.yaml b/tests/yaml_samples/fullsite_networks.yaml new file mode 100644 index 00000000..54770ac5 --- /dev/null +++ b/tests/yaml_samples/fullsite_networks.yaml @@ -0,0 +1,228 @@ +# 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. +#################### +# +# bootstrap_seed.yaml - Site server design definition for physical layer +# +#################### +# version the schema in this file so consumers can rationally parse it +--- +apiVersion: 'v1.0' +kind: NetworkLink +metadata: + name: oob + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 1 attributes. Primary key is 'name'. These settings will generally be things the switch and server have to agree on +spec: + bonding: + mode: none + mtu: 1500 + linkspeed: 100full + trunking: + mode: none + default_network: oob +--- +# pxe is a bit of 'magic' indicating the link config used when PXE booting +# a node. All other links indicate network configs applied when the node +# is deployed. +apiVersion: 'v1.0' +kind: NetworkLink +metadata: + name: pxe + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 1 attributes. Primary key is 'name'. These settings will generally be things the switch and server have to agree on +spec: + bonding: + mode: none + mtu: 1500 + linkspeed: auto + # Is this link supporting multiple layer 2 networks? + # none is a port-based VLAN identified by default_network + # tagged is is using 802.1q VLAN tagging. Untagged packets will default to default_netwokr + trunking: + mode: none + # use name, will translate to VLAN ID + default_network: pxe +--- +apiVersion: 'v1.0' +kind: NetworkLink +metadata: + name: gp + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 1 attributes. These CIs will generally be things the switch and server have to agree on + # pxe is a bit of 'magic' indicating the link config used when PXE booting + # a node. All other links indicate network configs applied when the node + # is deployed. +spec: + # If this link is a bond of physical links, how is it configured + # 802.3ad + # active-backup + # balance-rr + # Can add support for others down the road + bonding: + mode: 802.3ad + # For LACP (802.3ad) xmit hashing policy: layer2, layer2+3, layer3+4, encap3+4 + hash: layer3+4 + # 802.3ad specific options + peer_rate: slow + mon_rate: default + up_delay: default + down_delay: default + mtu: 9000 + linkspeed: auto + # Is this link supporting multiple layer 2 networks? + trunking: + mode: tagged + default_network: mgmt +--- +apiVersion: 'v1.0' +kind: Network +metadata: + name: oob + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + allocation: static + cidr: 172.16.100.0/24 + ranges: + - type: static + start: 172.16.100.15 + end: 172.16.100.254 + dns: + domain: ilo.sitename.att.com + servers: 172.16.100.10 +--- +apiVersion: 'v1.0' +kind: Network +metadata: + name: pxe + region: sitename + date: 17-FEB-2017 + 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' + # How are addresses assigned? + allocation: dhcp + # MTU for this VLAN interface, if not specified it will be inherited from the link + mtu: 1500 + # Network address + cidr: 172.16.0.0/24 + # Desribe IP address ranges + ranges: + - type: dhcp + start: 172.16.0.5 + end: 172.16.0.254 + # DNS settings for this network + dns: + # Domain addresses on this network will be registered under + domain: admin.sitename.att.com + # DNS servers that a server using this network as its default gateway should use + servers: 172.16.0.10 +--- +apiVersion: 'v1.0' +kind: Network +metadata: + name: mgmt + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + vlan_id: '100' + # How are addresses assigned? + allocation: static + # Allow MTU to be inherited from link the network rides on + mtu: 1500 + # Network address + cidr: 172.16.1.0/24 + # Desribe IP address ranges + ranges: + - type: static + start: 172.16.1.15 + end: 172.16.1.254 + # Static routes to be added for this network + routes: + - subnet: 0.0.0.0/0 + # A blank gateway would leave to a static route specifying + # only the interface as a source + gateway: 172.16.1.1 + metric: 10 + # DNS settings for this network + dns: + # Domain addresses on this network will be registered under + domain: mgmt.sitename.example.com + # DNS servers that a server using this network as its default gateway should use + servers: 172.16.1.9,172.16.1.10 +--- +apiVersion: 'v1.0' +kind: Network +metadata: + name: private + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + vlan_id: '101' + allocation: static + mtu: 9000 + cidr: 172.16.2.0/24 + # Desribe IP address ranges + ranges: + # Type can be reserved (not used for baremetal), static (all explicit + # assignments should fall here), dhcp (will be used by a DHCP server on this network) + - type: static + start: 172.16.2.15 + end: 172.16.2.254 + dns: + domain: priv.sitename.example.com + servers: 172.16.2.9,172.16.2.10 +--- +apiVersion: 'v1.0' +kind: Network +metadata: + name: public + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + vlan_id: '102' + # How are addresses assigned? + allocation: static + # MTU size for the VLAN interface + mtu: 1500 + cidr: 172.16.3.0/24 + # Desribe IP address ranges + ranges: + - type: static + start: 172.16.3.15 + end: 172.16.3.254 + routes: + - subnet: 0.0.0.0/0 + gateway: 172.16.3.1 + metric: 9 + dns: + domain: sitename.example.com + servers: 8.8.8.8 \ No newline at end of file diff --git a/tests/yaml_samples/fullsite_nodes.yaml b/tests/yaml_samples/fullsite_nodes.yaml new file mode 100644 index 00000000..05625e0e --- /dev/null +++ b/tests/yaml_samples/fullsite_nodes.yaml @@ -0,0 +1,70 @@ +# 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. +#################### +# +# bootstrap_seed.yaml - Site server design definition for physical layer +# +#################### +# version the schema in this file so consumers can rationally parse it +--- +apiVersion: 'v1.0' +kind: BaremetalNode +metadata: + name: controller01 + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + host_profile: k8-node-public + # the hostname for a server, could be used in multiple DNS domains to + # represent different interfaces + interfaces: + - device_name: bond0 + networks: + # '!' prefix for the value of the primary key indicates a record should be removed + - '!private' + # Addresses assigned to network interfaces + addressing: + # Which network the address applies to. If a network appears in addressing + # that isn't assigned to an interface, design validation will fail + - network: pxe + # The address assigned. Either a explicit IPv4 or IPv6 address + # or dhcp or slaac + address: dhcp + - network: mgmt + address: 172.16.1.20 + - network: public + address: 172.16.3.20 + metadata: + roles: os_ctl + rack: rack01 +--- +apiVersion: 'v1.0' +kind: BaremetalNode +metadata: + name: compute01 + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + host_profile: k8-node + addressing: + - network: pxe + address: dhcp + - network: mgmt + address: 172.16.1.21 + - network: private + address: 172.16.2.21 diff --git a/tests/yaml_samples/fullsite_profiles.yaml b/tests/yaml_samples/fullsite_profiles.yaml new file mode 100644 index 00000000..7ad9a73d --- /dev/null +++ b/tests/yaml_samples/fullsite_profiles.yaml @@ -0,0 +1,197 @@ +# 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. +#################### +# +# bootstrap_seed.yaml - Site server design definition for physical layer +# +#################### +# version the schema in this file so consumers can rationally parse it +--- +apiVersion: 'v1.0' +kind: Region +metadata: + name: sitename + date: 17-FEB-2017 + description: Sample site design + author: sh8121@att.com +spec: + # Not sure if we have site wide data that doesn't fall into another 'Kind' +--- +apiVersion: 'v1.0' +kind: HostProfile +metadata: + name: defaults + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces + # No magic to this host_profile, it just provides a way to specify + # sitewide settings. If it is absent from a node's inheritance chain + # then these values will NOT be applied +spec: + # OOB (iLO, iDRAC, etc...) settings. Should prefer open standards such + # as IPMI over vender-specific when possible. + oob: + type: ipmi + # OOB networking should be preconfigured, but we can include a network + # definition for validation or enhancement (DNS registration) + network: oob + account: admin + 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 + # Platform (Operating System) settings + platform: + image: ubuntu_16.04_hwe + kernel_params: default + # Additional metadata to apply to a node + metadata: + # Base URL of the introspection service - may go in curtin data + introspection_url: http://172.16.1.10:9090 +--- +apiVersion: 'v1.0' +kind: HostProfile +metadata: + name: k8-node + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + # host_profile inheritance allows for deduplication of common CIs + # Inheritance is additive for CIs that are lists of multiple items + # To remove an inherited list member, prefix the primary key value + # with '!'. + host_profile: defaults + # Hardware profile will map hardware specific details to the abstract + # names uses in the host profile as well as specify hardware specific + # configs. A viable model should be to build a host profile without a + # hardware_profile and then for each node inherit the host profile and + # specify a hardware_profile to map that node's hardware to the abstract + # settings of the host_profile + hardware_profile: HPGen9v3 + # Network interfaces. + 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 + network_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 + metadata: + # Explicit tag assignment + tags: + - 'test' + # MaaS supports key/value pairs. Not sure of the use yet + owner_data: + foo: bar +--- +apiVersion: 'v1.0' +kind: HostProfile +metadata: + name: k8-node-public + region: sitename + date: 17-FEB-2017 + author: sh8121@att.com + description: Describe layer 2/3 attributes. Primarily CIs used for configuring server interfaces +spec: + host_profile: k8-node + interfaces: + - device_name: bond0 + networks: + # This is additive, so adds a network to those defined in the host_profile + # inheritance chain + - public +--- +apiVersion: 'v1.0' +kind: HardwareProfile +metadata: + name: HPGen9v3 + region: sitename + date: 17-FEB-2017 + author: Scott Hussey +spec: + # Vendor of the server chassis + vendor: HP + # Generation of the chassis model + generation: '8' + # Version of the chassis model within its generation - not version of the hardware definition + hw_version: '3' + # The certified version of the chassis BIOS + bios_version: '2.2.3' + # Mode of the default boot of hardware - bios, uefi + boot_mode: bios + # Protocol of boot of the hardware - pxe, usb, hdd + bootstrap_protocol: pxe + # Which interface to use for network booting within the OOB manager, not OS device + pxe_interface: 0 + # Map hardware addresses to aliases/roles to allow a mix of hardware configs + # in a site to result in a consistent configuration + device_aliases: + pci: + - address: pci@0000:00:03.0 + alias: prim_nic01 + # type could identify expected hardware - used for hardware manifest validation + type: '82540EM Gigabit Ethernet Controller' + - address: pci@0000:00:04.0 + alias: prim_nic02 + type: '82540EM Gigabit Ethernet Controller' + scsi: + - address: scsi@2:0.0.0 + alias: primary_boot + type: 'VBOX HARDDISK' \ No newline at end of file diff --git a/tests/aicyaml_samples/invalid.yaml b/tests/yaml_samples/invalid.yaml similarity index 100% rename from tests/aicyaml_samples/invalid.yaml rename to tests/yaml_samples/invalid.yaml diff --git a/tests/aicyaml_samples/multidoc.yaml b/tests/yaml_samples/multidoc.yaml similarity index 100% rename from tests/aicyaml_samples/multidoc.yaml rename to tests/yaml_samples/multidoc.yaml diff --git a/tests/aicyaml_samples/singledoc.yaml b/tests/yaml_samples/singledoc.yaml similarity index 100% rename from tests/aicyaml_samples/singledoc.yaml rename to tests/yaml_samples/singledoc.yaml diff --git a/tests/aicyaml_samples/unknown_kind.yaml b/tests/yaml_samples/unknown_kind.yaml similarity index 100% rename from tests/aicyaml_samples/unknown_kind.yaml rename to tests/yaml_samples/unknown_kind.yaml