From 1a502b9151fadb93ae6fdc667a72f978133ed9e5 Mon Sep 17 00:00:00 2001 From: John Kung Date: Thu, 17 Jan 2019 21:55:19 -0500 Subject: [PATCH] Create DataNetworks modelling in System Configuration Introduce the DataNetwork api, client, model to allow modelling of the physical data network, with the following attributes: datanetwork_name mtu datanetwork_type (flat, vlan, vxlan) VxLAN specific attributes - port_number - multicast_group - ttl - mode ('dynamic' default, or 'static') The system data network may then be assigned to interface. This is part of the Story to "Move neutron provider network modelling to system configuration". The interface api is currently made compatible with current usage to allow specifying datanetwork (formerly providernetwork). The following new CLI commands and corresponding api are exposed: datanetwork-add Add a datanetwork. datanetwork-delete Delete a datanetwork. datanetwork-list List datanetworks. datanetwork-modify Modify a datanetwork. datanetwork-show Show datanetwork details. interface-datanetwork-assign Assign a datanetwork to an interface. interface-datanetwork-list List datanetwork interfaces. interface-datanetwork-remove Remove an assigned datanetwork from an interface. interface-datanetwork-show Show interface datanetwork details. 'system datanetwork-add' must be run where 'neutron providernetwork-create'. Tests Performed: AIO Sanity Sanity 2-controller, 2-compute Sanity Storage lab Interface Profile create and apply Containers deployment Change-Id: I630f90768647dbb414a60978bf8f8f641496afd5 Story: 2004455 Task: 28324 Signed-off-by: John Kung --- sysinv/cgts-client/centos/build_srpm.data | 2 +- .../cgts-client/cgtsclient/v1/client.py | 5 + .../cgts-client/cgtsclient/v1/datanetwork.py | 78 +++ .../cgtsclient/v1/datanetwork_shell.py | 129 +++++ .../cgts-client/cgtsclient/v1/iinterface.py | 2 +- .../cgtsclient/v1/iinterface_shell.py | 44 +- .../cgtsclient/v1/interface_datanetwork.py | 58 +++ .../v1/interface_datanetwork_shell.py | 102 ++++ .../cgtsclient/v1/iprofile_shell.py | 2 +- .../cgts-client/cgtsclient/v1/shell.py | 4 + sysinv/sysinv/centos/build_srpm.data | 2 +- .../sysinv/etc/sysinv/profileSchema.xsd | 8 +- .../sysinv/etc/sysinv/sampleProfile.xml | 16 +- .../sysinv/api/controllers/v1/__init__.py | 25 + .../sysinv/api/controllers/v1/datanetwork.py | 365 ++++++++++++++ .../sysinv/sysinv/api/controllers/v1/host.py | 106 ++-- .../sysinv/api/controllers/v1/interface.py | 462 ++++++++++++------ .../controllers/v1/interface_datanetwork.py | 286 +++++++++++ .../sysinv/api/controllers/v1/profile.py | 8 +- .../api/controllers/v1/profile_utils.py | 16 +- .../sysinv/sysinv/sysinv/common/constants.py | 40 +- .../sysinv/sysinv/sysinv/common/exception.py | 54 ++ sysinv/sysinv/sysinv/sysinv/common/utils.py | 13 +- .../sysinv/sysinv/conductor/openstack.py | 59 ++- sysinv/sysinv/sysinv/sysinv/db/api.py | 1 - .../sysinv/sysinv/sysinv/db/sqlalchemy/api.py | 295 ++++++++++- .../versions/084_data_networks.py | 144 ++++++ .../sysinv/sysinv/db/sqlalchemy/models.py | 113 +++-- sysinv/sysinv/sysinv/sysinv/helm/neutron.py | 51 +- .../sysinv/sysinv/sysinv/objects/__init__.py | 6 + .../sysinv/sysinv/objects/datanetwork.py | 42 ++ .../sysinv/sysinv/sysinv/objects/interface.py | 17 +- .../sysinv/objects/interface_datanetwork.py | 110 +++++ .../sysinv/objects/interface_ethernet.py | 2 - .../sysinv/sysinv/sysinv/puppet/interface.py | 63 ++- sysinv/sysinv/sysinv/sysinv/puppet/neutron.py | 14 +- sysinv/sysinv/sysinv/sysinv/puppet/nova.py | 19 +- sysinv/sysinv/sysinv/sysinv/puppet/ovs.py | 39 +- .../sysinv/sysinv/tests/api/test_interface.py | 202 ++++---- .../tests/db/sqlalchemy/test_migrations.py | 9 +- sysinv/sysinv/sysinv/sysinv/tests/db/utils.py | 71 ++- 41 files changed, 2624 insertions(+), 460 deletions(-) create mode 100644 sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork.py create mode 100644 sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork_shell.py create mode 100755 sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork.py create mode 100755 sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork_shell.py create mode 100644 sysinv/sysinv/sysinv/sysinv/api/controllers/v1/datanetwork.py create mode 100644 sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_datanetwork.py create mode 100644 sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/084_data_networks.py create mode 100644 sysinv/sysinv/sysinv/sysinv/objects/datanetwork.py create mode 100644 sysinv/sysinv/sysinv/sysinv/objects/interface_datanetwork.py diff --git a/sysinv/cgts-client/centos/build_srpm.data b/sysinv/cgts-client/centos/build_srpm.data index f4cf7d398b..c2971c945f 100644 --- a/sysinv/cgts-client/centos/build_srpm.data +++ b/sysinv/cgts-client/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="cgts-client" -TIS_PATCH_VER=61 +TIS_PATCH_VER=62 diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py index e337974531..e702ace96c 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/client.py @@ -25,6 +25,7 @@ from cgtsclient.v1 import ceph_mon from cgtsclient.v1 import certificate from cgtsclient.v1 import cluster from cgtsclient.v1 import controller_fs +from cgtsclient.v1 import datanetwork from cgtsclient.v1 import drbdconfig from cgtsclient.v1 import ethernetport from cgtsclient.v1 import fernet @@ -42,6 +43,7 @@ from cgtsclient.v1 import iinterface from cgtsclient.v1 import ilvg from cgtsclient.v1 import imemory from cgtsclient.v1 import inode +from cgtsclient.v1 import interface_datanetwork from cgtsclient.v1 import interface_network from cgtsclient.v1 import intp from cgtsclient.v1 import iprofile @@ -132,6 +134,9 @@ class Client(http.HTTPClient): self.load = load.LoadManager(self) self.upgrade = upgrade.UpgradeManager(self) self.network = network.NetworkManager(self) + self.datanetwork = datanetwork.DataNetworkManager(self) + self.interface_datanetwork = \ + interface_datanetwork.InterfaceDataNetworkManager(self) self.interface_network = interface_network.InterfaceNetworkManager(self) self.service_parameter = service_parameter.ServiceParameterManager(self) self.cluster = cluster.ClusterManager(self) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork.py new file mode 100644 index 0000000000..840b53a49a --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork.py @@ -0,0 +1,78 @@ +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# -*- encoding: utf-8 -*- +# + +from cgtsclient.common import base +from cgtsclient.common import utils +from cgtsclient import exc + + +CREATION_ATTRIBUTES = [ + 'network_type', 'name', 'description', 'mtu', + 'multicast_group', 'port_num', 'ttl', 'mode'] + + +class DataNetwork(base.Resource): + def __repr__(self): + return "" % self._info + + +class DataNetworkManager(base.Manager): + resource_class = DataNetwork + + def list(self): + path = '/v1/datanetworks' + return self._list(path, "datanetworks") + + def get(self, datanetwork_id): + path = '/v1/datanetworks/%s' % datanetwork_id + try: + return self._list(path)[0] + except IndexError: + return None + + def create(self, **kwargs): + path = '/v1/datanetworks' + new = {} + for (key, value) in kwargs.items(): + if key in CREATION_ATTRIBUTES: + new[key] = value + else: + raise exc.InvalidAttribute('%s' % key) + return self._create(path, new) + + def update(self, datanetwork_id, patch): + path = '/v1/datanetworks/%s' % datanetwork_id + return self._update(path, patch) + + def delete(self, datanetwork_id): + path = '/v1/datanetworks/%s' % datanetwork_id + return self._delete(path) + + +def _find_datanetwork(cc, datanetwork): + if datanetwork.isdigit() and not utils.is_uuid_like(datanetwork): + datanetwork_list = cc.datanetwork.list() + for n in datanetwork_list: + if str(n.id) == datanetwork: + return n + else: + raise exc.CommandError('datanetwork not found: %s' % datanetwork) + elif utils.is_uuid_like(datanetwork): + try: + h = cc.datanetwork.get(datanetwork) + except exc.HTTPNotFound: + raise exc.CommandError('datanetwork not found: %s' % datanetwork) + else: + return h + else: + datanetwork_list = cc.datanetwork.list() + for n in datanetwork_list: + if n.name == datanetwork: + return n + else: + raise exc.CommandError('datanetwork not found: %s' % datanetwork) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork_shell.py new file mode 100644 index 0000000000..834a5eafd5 --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/datanetwork_shell.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# All Rights Reserved. +# + +from cgtsclient.common import utils +from cgtsclient import exc + +DATANETWORK_TYPE_VXLAN = "vxlan" + + +def _print_datanetwork_show(obj): + fields = ['id', 'uuid', 'name', 'network_type', 'mtu', + 'description'] + + if obj.network_type == DATANETWORK_TYPE_VXLAN: + fields.append('multicast_group') + fields.append('port_num') + fields.append('ttl') + fields.append('mode') + + data = [(f, getattr(obj, f, '')) for f in fields] + utils.print_tuple_list(data) + + +@utils.arg('datanetwork_id', + metavar='', + help="UUID or name of datanetwork") +def do_datanetwork_show(cc, args): + """Show datanetwork details.""" + + datanetwork = cc.datanetwork.get(args.datanetwork_id) + _print_datanetwork_show(datanetwork) + + +def do_datanetwork_list(cc, args): + """List datanetworks.""" + + labels = ['uuid', 'name', 'network_type', 'mtu'] + fields = ['uuid', 'name', 'network_type', 'mtu'] + datanetworks = cc.datanetwork.list() + utils.print_list(datanetworks, fields, labels, sortby=1) + + +@utils.arg('name', + metavar='', + help="Name of the datanetwork [REQUIRED]") +@utils.arg('network_type', + metavar='', + choices=['flat', 'vlan', 'vxlan'], + help="Type of the datanetwork [REQUIRED]") +@utils.arg('-d', '--description', + metavar='', + help='User description of the datanetwork') +@utils.arg('-m', '--mtu', + metavar='', + default=1500, + help='MTU of the datanetwork') +@utils.arg('-p', '--port_num', + metavar='', + help='port_num of the datanetwork') +@utils.arg('-g', '--multicast_group', + metavar='', + help='multicast_group of the datanetwork') +@utils.arg('-t', '--ttl', + metavar='', + help='time-to-live of the datanetwork') +@utils.arg('-M', '--mode', + metavar='', + choices=['dynamic', 'static'], + default='dynamic', + help='mode of the datanetwork') +def do_datanetwork_add(cc, args): + """Add a datanetwork.""" + + field_list = ['name', 'network_type', 'mtu', 'description', + 'multicast_group', 'port_num', 'ttl', 'mode'] + + # Prune input fields down to required/expected values + data = dict((k, v) for (k, v) in vars(args).items() + if k in field_list and not (v is None)) + + datanetwork = cc.datanetwork.create(**data) + uuid = getattr(datanetwork, 'uuid', '') + try: + datanetwork = cc.datanetwork.get(uuid) + except exc.HTTPNotFound: + raise exc.CommandError('Created DataNetwork UUID not found: %s' % uuid) + _print_datanetwork_show(datanetwork) + + +@utils.arg('datanetwork_id', + metavar='', + help="Name of the datanetwork [REQUIRED]") +@utils.arg('-m', '--mtu', + metavar='', + help='MTU of the datanetwork') +@utils.arg('-d', '--description', + metavar='', + help='User description of the datanetwork') +def do_datanetwork_modify(cc, args): + """Modify a datanetwork.""" + + rwfields = ['mtu', 'description'] + + user_specified_fields = dict((k, v) for (k, v) in vars(args).items() + if k in rwfields and not (v is None)) + + patch = [] + for (k, v) in user_specified_fields.items(): + patch.append({'op': 'replace', 'path': '/' + k, 'value': v}) + + datanetwork = cc.datanetwork.update(args.datanetwork_id, patch) + _print_datanetwork_show(datanetwork) + + +@utils.arg('datanetwork_uuid', + metavar='', + help="UUID of datanetwork entry") +def do_datanetwork_delete(cc, args): + """Delete a datanetwork.""" + + cc.datanetwork.delete(args.datanetwork_uuid) + print('Deleted DataNetwork: %s' % args.datanetwork_uuid) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface.py index 54f31950c1..9206a2a5d4 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface.py @@ -14,7 +14,7 @@ from cgtsclient.v1 import port CREATION_ATTRIBUTES = ['ifname', 'iftype', 'ihost_uuid', 'imtu', 'ifclass', 'networks', 'network_uuid', 'networktype', 'aemode', 'txhashpolicy', - 'providernetworks', 'providernetworksdict', 'ifcapabilities', 'ports', 'imac', + 'providernetworks', 'datanetworks', 'ifcapabilities', 'ports', 'imac', 'vlan_id', 'uses', 'used_by', 'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool', 'sriov_numvfs'] diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface_shell.py index 7e243aed75..0b8dd6f8ee 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iinterface_shell.py @@ -17,7 +17,7 @@ from cgtsclient.v1 import network as network_utils def _print_iinterface_show(cc, iinterface): - fields = ['ifname', 'iftype', 'ports', 'providernetworks', + fields = ['ifname', 'iftype', 'ports', 'datanetworks', 'imac', 'imtu', 'ifclass', 'networks', 'aemode', 'schedpolicy', 'txhashpolicy', 'uuid', 'ihost_uuid', @@ -95,9 +95,12 @@ def do_host_if_list(cc, args): attr_str = "%s,accelerated=True" % attr_str setattr(i, 'attrs', attr_str) - field_labels = ['uuid', 'name', 'class', 'type', 'vlan id', 'ports', 'uses i/f', 'used by i/f', 'attributes', 'provider networks'] - fields = ['uuid', 'ifname', 'ifclass', 'iftype', 'vlan_id', 'ports', 'uses', 'used_by', 'attrs', 'providernetworks'] - utils.print_list(iinterfaces, fields, field_labels, sortby=0, no_wrap_fields=['ports']) + field_labels = ['uuid', 'name', 'class', 'type', 'vlan id', 'ports', + 'uses i/f', 'used by i/f', 'attributes', 'data networks'] + fields = ['uuid', 'ifname', 'ifclass', 'iftype', 'vlan_id', 'ports', + 'uses', 'used_by', 'attrs', 'datanetworks'] + utils.print_list( + iinterfaces, fields, field_labels, sortby=0, no_wrap_fields=['ports']) @utils.arg('hostnameorid', @@ -125,11 +128,11 @@ def do_host_if_delete(cc, args): choices=['ae', 'vlan'], nargs='?', help="Type of the interface") -@utils.arg('providernetworks', - metavar='', +@utils.arg('datanetworks', + metavar='', nargs='?', default=None, - help=('The provider network attached to the interface ' + help=('The data network attached to the interface ' '(default: %(default)s) ' '[REQUIRED when interface class is data or pci-passthrough')) @utils.arg('-a', '--aemode', @@ -175,7 +178,7 @@ def do_host_if_add(cc, args): """Add an interface.""" field_list = ['ifname', 'iftype', 'imtu', 'ifclass', 'networks', 'aemode', - 'txhashpolicy', 'providernetworks', 'vlan_id', + 'txhashpolicy', 'datanetworks', 'vlan_id', 'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool'] ihost = ihost_utils._find_ihost(cc, args.hostnameorid) @@ -194,10 +197,10 @@ def do_host_if_add(cc, args): user_specified_fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) - if 'providernetworks' in user_specified_fields.keys(): - user_specified_fields['providernetworks'] = user_specified_fields['providernetworks'].replace(" ", "") - if 'none' in user_specified_fields['providernetworks']: - del user_specified_fields['providernetworks'] + if 'datanetworks' in user_specified_fields.keys(): + user_specified_fields['datanetworks'] = \ + user_specified_fields['datanetworks'].split(',') + if 'networks' in user_specified_fields.keys(): network = network_utils._find_network(cc, args.networks) user_specified_fields['networks'] = [str(network.id)] @@ -230,7 +233,10 @@ def do_host_if_add(cc, args): help='The MTU of the interface') @utils.arg('-p', '--providernetworks', metavar='', - help='The provider network attached to the interface [REQUIRED]') + help='[DEPRECATED] The provider network attached to the interface') +@utils.arg('-d', '--datanetworks', + metavar='', + help='The data network attached to the interface') @utils.arg('-a', '--aemode', metavar='', choices=['balanced', 'active_standby', '802.3ad'], @@ -267,7 +273,7 @@ def do_host_if_modify(cc, args): """Modify interface attributes.""" rwfields = ['iftype', 'ifname', 'imtu', 'aemode', 'txhashpolicy', - 'providernetworks', 'ports', 'ifclass', 'networks', + 'datanetworks', 'providernetworks', 'ports', 'ifclass', 'networks', 'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool', 'sriov_numvfs'] @@ -277,7 +283,13 @@ def do_host_if_modify(cc, args): if k in rwfields and not (v is None)) if 'providernetworks' in user_specified_fields.keys(): - user_specified_fields['providernetworks'] = user_specified_fields['providernetworks'].replace(" ", "") + user_specified_fields['datanetworks'] = \ + user_specified_fields['providernetworks'] + del user_specified_fields['providernetworks'] + + elif 'datanetworks' in user_specified_fields.keys(): + user_specified_fields['datanetworks'] = \ + user_specified_fields['datanetworks'] interface = _find_interface(cc, ihost, args.ifnameoruuid) fields = interface.__dict__ @@ -297,7 +309,7 @@ def do_host_if_modify(cc, args): user_specified_fields['ifname'] = p break if interface.ifclass == 'data': - user_specified_fields['providernetworks'] = 'none' + user_specified_fields['datanetworks'] = 'none' patch = [] for (k, v) in user_specified_fields.items(): diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork.py new file mode 100755 index 0000000000..36f6fc9302 --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork.py @@ -0,0 +1,58 @@ +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# -*- encoding: utf-8 -*- +# + +from cgtsclient.common import base +from cgtsclient import exc + + +CREATION_ATTRIBUTES = [ + 'interface_uuid', 'datanetwork_uuid' +] + + +class InterfaceDataNetwork(base.Resource): + def __repr__(self): + return "" % self._info + + +class InterfaceDataNetworkManager(base.Manager): + resource_class = InterfaceDataNetwork + + def list(self): + path = '/v1/interface_datanetworks' + return self._list(path, "interface_datanetworks") + + def list_by_host(self, host_uuid): + path = '/v1/ihosts/%s/interface_datanetworks' % host_uuid + return self._list(path, "interface_datanetworks") + + def list_by_interface(self, interface_uuid): + path = '/v1/iinterfaces/%s/interface_datanetworks' % interface_uuid + return self._list(path, "interface_datanetworks") + + def get(self, interface_datanetwork_uuid): + path = '/v1/interface_datanetworks/%s' % interface_datanetwork_uuid + try: + return self._list(path)[0] + except IndexError: + return None + + def assign(self, **kwargs): + path = '/v1/interface_datanetworks' + new = {} + for (key, value) in kwargs.items(): + if key in CREATION_ATTRIBUTES: + new[key] = value + else: + raise exc.InvalidAttribute('%s' % key) + return self._create(path, new) + + def remove(self, interface_datanetwork_uuid): + path = '/v1/interface_datanetworks/%s' % interface_datanetwork_uuid + return self._delete(path) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork_shell.py new file mode 100755 index 0000000000..7189dca4f0 --- /dev/null +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/interface_datanetwork_shell.py @@ -0,0 +1,102 @@ +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# -*- encoding: utf-8 -*- +# + +from cgtsclient.common import utils +from cgtsclient import exc +from cgtsclient.v1 import datanetwork as datanetwork_utils +from cgtsclient.v1 import ihost as ihost_utils +from cgtsclient.v1 import iinterface as iinterface_utils + + +def _print_interface_datanetwork_show(cc, obj): + fields = ['hostname', 'uuid', 'ifname', 'datanetwork_name'] + # Add a hostname column using the forihostid field + host_id = str(getattr(obj, 'forihostid', '')) + ihost = ihost_utils._find_ihost(cc, host_id) + setattr(obj, 'hostname', ihost.hostname) + data = [(f, getattr(obj, f, '')) for f in fields] + utils.print_tuple_list(data) + + +@utils.arg('hostnameorid', + metavar='', + help="Name or ID of host") +@utils.arg('ifnameoruuid', + metavar='', + nargs='?', + help="Name or UUID of interface") +def do_interface_datanetwork_list(cc, args): + """List datanetwork interfaces.""" + fields = ['hostname', 'uuid', 'ifname', 'datanetwork_name'] + ihost = ihost_utils._find_ihost(cc, args.hostnameorid) + if args.ifnameoruuid is None: + interface_datanetworks = \ + cc.interface_datanetwork.list_by_host(ihost.uuid) + else: + interface = \ + iinterface_utils._find_interface(cc, ihost, args.ifnameoruuid) + interface_datanetworks = \ + cc.interface_datanetwork.list_by_interface(interface.uuid) + # Add a hostname column using the forihostid field + for i in interface_datanetworks[:]: + host_id = str(getattr(i, 'forihostid', '')) + ihost = ihost_utils._find_ihost(cc, host_id) + setattr(i, 'hostname', ihost.hostname) + utils.print_list(interface_datanetworks, fields, fields, sortby=1) + + +@utils.arg('interface_datanetwork_uuid', + metavar='', + help="UUID of interface datanetwork entry") +def do_interface_datanetwork_show(cc, args): + """Show interface datanetwork details.""" + interface_datanetwork = \ + cc.interface_datanetwork.get(args.interface_datanetwork_uuid) + _print_interface_datanetwork_show(cc, interface_datanetwork) + + +@utils.arg('hostnameorid', + metavar='', + help="Name or ID of host [REQUIRED]") +@utils.arg('ifnameoruuid', + metavar='', + help="Name or UUID of interface [REQUIRED]") +@utils.arg('datanetnameoruuid', + metavar='', + help="Name of UUID of datanetwork [REQUIRED]") +def do_interface_datanetwork_assign(cc, args): + """Assign a datanetwork to an interface.""" + # Determine host, interface, and datanetwork using the given arguments + ihost = ihost_utils._find_ihost(cc, args.hostnameorid) + interface = \ + iinterface_utils._find_interface(cc, ihost, args.ifnameoruuid) + datanetwork = \ + datanetwork_utils._find_datanetwork(cc, args.datanetnameoruuid) + + data = dict() + data['interface_uuid'] = interface.uuid + data['datanetwork_uuid'] = datanetwork.uuid + + interface_datanetwork = cc.interface_datanetwork.assign(**data) + uuid = getattr(interface_datanetwork, 'uuid', '') + try: + interface_datanetwork = cc.interface_datanetwork.get(uuid) + except exc.HTTPNotFound: + raise exc.CommandError('Created Interface DataNetwork ' + 'UUID not found: %s' % uuid) + _print_interface_datanetwork_show(cc, interface_datanetwork) + + +@utils.arg('interface_datanetwork_uuid', + metavar='', + help="UUID of interface datanetwork entry") +def do_interface_datanetwork_remove(cc, args): + """Remove an assigned datanetwork from an interface.""" + cc.interface_datanetwork.remove(args.interface_datanetwork_uuid) + print('Deleted Interface DataNetwork: %s' % args.interface_datanetwork_uuid) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iprofile_shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iprofile_shell.py index e812b7e7e8..5544bb5bee 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/iprofile_shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/iprofile_shell.py @@ -63,7 +63,7 @@ def get_interfaceconfig(iprofile): for interface in iprofile.interfaces: istr = istr + "%s: %s" % (interface.ifname, interface.networktype) if interface.networktype == 'data': - istr = istr + "( %s )" % interface.providernetworks + istr = istr + "( %s )" % interface.datanetworks _get_interface_ports_interfaces(iprofile, interface) if interface.ports: istr = istr + " | %s | PORTS = %s" % (interface.iftype, interface.ports) diff --git a/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py b/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py index f742f3d7c4..2521930b59 100644 --- a/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py +++ b/sysinv/cgts-client/cgts-client/cgtsclient/v1/shell.py @@ -13,6 +13,7 @@ from cgtsclient.v1 import ceph_mon_shell from cgtsclient.v1 import certificate_shell from cgtsclient.v1 import cluster_shell from cgtsclient.v1 import controller_fs_shell +from cgtsclient.v1 import datanetwork_shell from cgtsclient.v1 import drbdconfig_shell from cgtsclient.v1 import ethernetport_shell from cgtsclient.v1 import firewallrules_shell @@ -29,6 +30,7 @@ from cgtsclient.v1 import iinfra_shell from cgtsclient.v1 import iinterface_shell from cgtsclient.v1 import ilvg_shell from cgtsclient.v1 import imemory_shell +from cgtsclient.v1 import interface_datanetwork_shell from cgtsclient.v1 import interface_network_shell from cgtsclient.v1 import intp_shell from cgtsclient.v1 import iprofile_shell @@ -100,6 +102,8 @@ COMMAND_MODULES = [ upgrade_shell, network_shell, interface_network_shell, + datanetwork_shell, + interface_datanetwork_shell, service_parameter_shell, cluster_shell, lldp_agent_shell, diff --git a/sysinv/sysinv/centos/build_srpm.data b/sysinv/sysinv/centos/build_srpm.data index c17ad79259..fb9c4ef5cd 100644 --- a/sysinv/sysinv/centos/build_srpm.data +++ b/sysinv/sysinv/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="sysinv" -TIS_PATCH_VER=295 +TIS_PATCH_VER=296 diff --git a/sysinv/sysinv/sysinv/etc/sysinv/profileSchema.xsd b/sysinv/sysinv/sysinv/etc/sysinv/profileSchema.xsd index cbf7eb07a0..25c5ce351e 100644 --- a/sysinv/sysinv/sysinv/etc/sysinv/profileSchema.xsd +++ b/sysinv/sysinv/sysinv/etc/sysinv/profileSchema.xsd @@ -133,7 +133,7 @@ - + @@ -200,7 +200,7 @@ - + @@ -237,7 +237,7 @@ - + @@ -262,7 +262,7 @@ - + diff --git a/sysinv/sysinv/sysinv/etc/sysinv/sampleProfile.xml b/sysinv/sysinv/sysinv/etc/sysinv/sampleProfile.xml index 25e2d7dcc6..e75382c757 100644 --- a/sysinv/sysinv/sysinv/etc/sysinv/sampleProfile.xml +++ b/sysinv/sysinv/sysinv/etc/sysinv/sampleProfile.xml @@ -213,8 +213,8 @@ be combined. --> - - + + @@ -232,7 +232,7 @@ linkLocal, and static--> - + @@ -267,13 +267,13 @@ - + - + @@ -322,8 +322,8 @@ - - @@ -336,7 +336,7 @@ - + diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py index e1cd7f2b61..70a67b8ad7 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/__init__.py @@ -29,6 +29,8 @@ from sysinv.api.controllers.v1 import community from sysinv.api.controllers.v1 import controller_fs from sysinv.api.controllers.v1 import cpu from sysinv.api.controllers.v1 import disk +from sysinv.api.controllers.v1 import datanetwork +from sysinv.api.controllers.v1 import interface_datanetwork from sysinv.api.controllers.v1 import dns from sysinv.api.controllers.v1 import drbdconfig from sysinv.api.controllers.v1 import ethernet_port @@ -193,6 +195,12 @@ class V1(base.APIBase): networks = [link.Link] "Links to the network resource" + datanetworks = [link.Link] + "Links to the datanetwork resource" + + interface_datanetworks = [link.Link] + "Links to the interface datanetwork resource" + interface_networks = [link.Link] "Links to the network interface resource" @@ -751,6 +759,21 @@ class V1(base.APIBase): 'apps', '', bookmark=True)] + v1.datanetworks = [link.Link.make_link('self', pecan.request.host_url, + 'datanetworks', ''), + link.Link.make_link('bookmark', + pecan.request.host_url, + 'datanetworks', '', + bookmark=True)] + + v1.interface_datanetworks = [ + link.Link.make_link('self', pecan.request.host_url, + 'interface_datanetworks', ''), + link.Link.make_link('bookmark', + pecan.request.host_url, + 'interface_datanetworks', '', + bookmark=True)] + return v1 @@ -817,6 +840,8 @@ class Controller(rest.RestController): labels = label.LabelController() fernet_repo = fernet_repo.FernetKeyController() apps = kube_app.KubeAppController() + datanetworks = datanetwork.DataNetworkController() + interface_datanetworks = interface_datanetwork.InterfaceDataNetworkController() @wsme_pecan.wsexpose(V1) def get(self): diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/datanetwork.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/datanetwork.py new file mode 100644 index 0000000000..7d225aa174 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/datanetwork.py @@ -0,0 +1,365 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 UnitedStack Inc. +# All 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. +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + + +import jsonpatch +import pecan +from pecan import rest +import six +import wsme +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from sysinv.api.controllers.v1 import base +from sysinv.api.controllers.v1 import collection +from sysinv.api.controllers.v1 import types +from sysinv.api.controllers.v1 import utils +from sysinv.common import constants +from sysinv.common import exception +from sysinv.common import utils as cutils +from sysinv import objects +from sysinv.openstack.common import log +from sysinv.openstack.common.gettextutils import _ + +LOG = log.getLogger(__name__) + + +ALLOWED_DATANETWORK_TYPES = [ + constants.DATANETWORK_TYPE_FLAT, + constants.DATANETWORK_TYPE_VLAN, + constants.DATANETWORK_TYPE_VXLAN, +] + +VXLAN_DYNAMIC_REQUIRED_PARAMS = ['multicast_group', 'port_num', 'ttl'] +VXLAN_STATIC_REQUIRED_PARAMS = ['port_num', 'ttl'] + + +class DataNetworkPatchType(types.JsonPatchType): + @staticmethod + def mandatory_attrs(): + return [] + + +class DataNetwork(base.APIBase): + """API representation of an datanetwork. + + This class enforces type checking and value constraints, and converts + between the internal object model and the API representation of a + datanetwork. + """ + + id = int + "Unique ID for this datanetwork" + + uuid = types.uuid + "Unique UUID for this datanetwork" + + network_type = wtypes.text + "Represent the datanetwork type for the datanetwork" + + name = wtypes.text + "Unique name for this datanetwork" + + description = wtypes.text + "Represent the user description for the datanetwork" + + mtu = int + "Represent the MTU size (bytes) of the datanetwork" + + multicast_group = wtypes.text + "Multicast group for this datanetwork. VxLan only" + + port_num = int + "Vxlan Port for this datanetwork. VxLan only" + + ttl = int + "Time To Live for this datanetwork. VxLan only" + + mode = wtypes.text + "Mode for this datanetwork. VxLan only" + + def __init__(self, **kwargs): + self.fields = objects.datanetwork.fields.keys() + for k in self.fields: + if not hasattr(self, k): + continue + setattr(self, k, kwargs.get(k, wtypes.Unset)) + + @classmethod + def convert_with_links(cls, rpc_datanetwork, expand=True): + datanetwork = DataNetwork(**rpc_datanetwork.as_dict()) + if not expand: + datanetwork.unset_fields_except( + ['id', 'uuid', 'network_type', 'name', + 'description', 'mtu', + 'multicast_group', 'port_num', 'ttl', 'mode']) + + return datanetwork + + def _validate_network_type(self): + if self.network_type not in ALLOWED_DATANETWORK_TYPES: + raise ValueError(_("DataNetwork type %s not supported") % + self.network_type) + + def validate_syntax(self): + """ + Validates the syntax of each field. + """ + self._validate_network_type() + + +class DataNetworkCollection(collection.Collection): + """API representation of a collection of datanetworks.""" + + datanetworks = [DataNetwork] + "A list containing DataNetwork objects" + + def __init__(self, **kwargs): + self._type = 'datanetworks' + + @classmethod + def convert_with_links(cls, rpc_datanetworks, limit, url=None, + expand=False, **kwargs): + collection = DataNetworkCollection() + collection.datanetworks = [DataNetwork.convert_with_links(n, expand) + for n in rpc_datanetworks] + collection.next = collection.get_next(limit, url=url, **kwargs) + return collection + + +LOCK_NAME = 'DataNetworkController' + + +class DataNetworkController(rest.RestController): + """REST controller for DataNetworks.""" + + def __init__(self, parent=None, **kwargs): + self._parent = parent + + def _get_datanetwork_collection( + self, marker=None, limit=None, sort_key=None, + sort_dir=None, expand=False, resource_url=None): + + limit = utils.validate_limit(limit) + sort_dir = utils.validate_sort_dir(sort_dir) + marker_obj = None + + if marker: + marker_obj = objects.datanetwork.get_by_uuid( + pecan.request.context, marker) + + datanetworks = pecan.request.dbapi.datanetworks_get_all( + limit=limit, marker=marker_obj, + sort_key=sort_key, sort_dir=sort_dir) + + return DataNetworkCollection.convert_with_links( + datanetworks, limit, url=resource_url, expand=expand, + sort_key=sort_key, sort_dir=sort_dir) + + def _get_one(self, datanetwork_uuid): + rpc_datanetwork = objects.datanetwork.get_by_uuid( + pecan.request.context, datanetwork_uuid) + return DataNetwork.convert_with_links(rpc_datanetwork) + + @staticmethod + def _check_network_type(datanetwork): + if 'network_type' not in datanetwork: + raise wsme.exc.ClientSideError( + _('DataNetwork network_type is required.')) + + network_type = datanetwork['network_type'] + if network_type not in ALLOWED_DATANETWORK_TYPES: + raise ValueError(_("DataNetwork type %s is not supported") % + network_type) + + @staticmethod + def _check_datanetwork_name(datanetwork): + if 'name' not in datanetwork: + raise wsme.exc.ClientSideError( + _('DataNetwork name is required.')) + + name = datanetwork['name'] + if name.lower() == constants.DATANETWORK_TYPE_NONE: + raise ValueError(_("DataNetwork name '%s' is not allowed") % name) + + @staticmethod + def _check_new_datanetwork_mtu_or_set_default(datanetwork): + if 'mtu' not in datanetwork: + datanetwork['mtu'] = constants.DEFAULT_MTU + utils.validate_mtu(datanetwork['mtu']) + + @staticmethod + def _check_datanetwork_vxlan(datanetwork): + if datanetwork['network_type'] != constants.DATANETWORK_TYPE_VXLAN: + return + + mode = datanetwork.get('mode', constants.DATANETWORK_MODE_DYNAMIC) + if mode == constants.DATANETWORK_MODE_STATIC: + required_vxlan_params = VXLAN_STATIC_REQUIRED_PARAMS + else: + required_vxlan_params = VXLAN_DYNAMIC_REQUIRED_PARAMS + + missing = set(required_vxlan_params).difference(datanetwork.keys()) + if missing: + raise wsme.exc.ClientSideError( + _("VxLan parameters '%s' are required for '%s' mode.") % + (list(missing), mode)) + + multicast_group = datanetwork.get('multicast_group') + if mode == constants.DATANETWORK_MODE_STATIC: + if multicast_group: + raise wsme.exc.ClientSideError( + _('VxLan of mode %s does not support multicast_group.') % + mode) + else: + if not cutils.validate_ip_multicast_address(multicast_group): + raise wsme.exc.ClientSideError( + _("multicast group '%s' is not a valid " + "multicast ip address.") % + multicast_group) + + def _check_datanetwork(self, datanetwork): + self._check_network_type(datanetwork) + self._check_datanetwork_name(datanetwork) + self._check_new_datanetwork_mtu_or_set_default(datanetwork) + self._check_datanetwork_vxlan(datanetwork) + + @staticmethod + def _check_update_mtu(rpc_datanetwork): + # Check interfaces using this datanetwork + ifdns = pecan.request.dbapi.interface_datanetwork_get_by_datanetwork( + rpc_datanetwork.uuid) + + for ifdn in ifdns: + interface_obj = pecan.request.dbapi.iinterface_get( + ifdn.interface_uuid) + if interface_obj.imtu < rpc_datanetwork.mtu: + msg = _("The datanetwork MTU '%s' must be smaller than " + "assigned interface MTU '%s'." % + (rpc_datanetwork.mtu, interface_obj.imtu)) + raise wsme.exc.ClientSideError(msg) + + def _create_datanetwork(self, datanetwork): + # Perform syntactic validation + datanetwork.validate_syntax() + + # Perform semantic validation + datanetwork = datanetwork.as_dict() + self._check_datanetwork(datanetwork) + + result = pecan.request.dbapi.datanetwork_create(datanetwork) + + return DataNetwork.convert_with_links(result) + + @wsme_pecan.wsexpose(DataNetworkCollection, + types.uuid, int, wtypes.text, wtypes.text) + def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): + """Retrieve a list of DataNetworks.""" + + return self._get_datanetwork_collection(marker, limit, + sort_key=sort_key, + sort_dir=sort_dir) + + @wsme_pecan.wsexpose(DataNetwork, wtypes.text) + def get_one(self, datanetwork_id): + """Retrieve a single DataNetwork.""" + + return self._get_one(datanetwork_id) + + @cutils.synchronized(LOCK_NAME) + @wsme_pecan.wsexpose(DataNetwork, body=DataNetwork) + def post(self, datanetwork): + """Create a new Data Network.""" + + return self._create_datanetwork(datanetwork) + + @cutils.synchronized(LOCK_NAME) + @wsme.validate(six.text_type, [DataNetworkPatchType]) + @wsme_pecan.wsexpose(DataNetwork, six.text_type, + body=[DataNetworkPatchType]) + def patch(self, datanetwork_id, patch): + """Update an existing datanetwork.""" + + rpc_datanetwork = \ + objects.datanetwork.get_by_uuid( + pecan.request.context, datanetwork_id) + + utils.validate_patch(patch) + patch_obj = jsonpatch.JsonPatch(patch) + LOG.info("datanetwork patch_obj=%s" % patch_obj) + + try: + datanetwork = DataNetwork(**jsonpatch.apply_patch( + rpc_datanetwork.as_dict(), patch_obj)) + + except utils.JSONPATCH_EXCEPTIONS as e: + raise exception.PatchError(patch=patch, reason=e) + + LOG.info("rpc_datanetwork=%s datanetwork=%s" % + (rpc_datanetwork.as_dict(), datanetwork)) + + fields = objects.datanetwork.fields + + for field in fields: + if (field in rpc_datanetwork and + rpc_datanetwork[field] != getattr(datanetwork, field)): + rpc_datanetwork[field] = getattr(datanetwork, field) + + delta = rpc_datanetwork.obj_what_changed() + if not delta: + return DataNetwork.convert_with_links(rpc_datanetwork) + + delta_list = list(delta) + + allowed_updates = ['mtu', 'description'] + if not set(delta_list).issubset(allowed_updates): + extra = set(allowed_updates).difference(delta_list) + raise wsme.exc.ClientSideError( + _("DataNetwork '%s' attributes '%s' may not be modified ") % + (rpc_datanetwork.uuid, extra)) + + values = {} + if 'mtu' in delta_list: + self._check_update_mtu(rpc_datanetwork) + values.update({'mtu': rpc_datanetwork.mtu}) + + if 'description' in delta_list: + values.update({'description': rpc_datanetwork.description}) + + rpc_datanetwork.save() + + return DataNetwork.convert_with_links(rpc_datanetwork) + + @cutils.synchronized(LOCK_NAME) + @wsme_pecan.wsexpose(None, types.uuid, status_code=204) + def delete(self, datanetwork_uuid): + """Delete a Data Network.""" + + # Only allow delete if there are no associated interfaces + ifdns = pecan.request.dbapi.interface_datanetwork_get_by_datanetwork( + datanetwork_uuid) + if ifdns: + raise wsme.exc.ClientSideError( + _("DataNetwork '%s' is still assigned to interfaces. " + "Check interface-datanetwork.") % datanetwork_uuid) + + pecan.request.dbapi.datanetwork_destroy(datanetwork_uuid) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py index 78ada95f2d..3c943e9d42 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/host.py @@ -80,6 +80,7 @@ from sysinv.api.controllers.v1 import state from sysinv.api.controllers.v1 import types from sysinv.api.controllers.v1 import utils from sysinv.api.controllers.v1 import interface_network +from sysinv.api.controllers.v1 import interface_datanetwork from sysinv.api.controllers.v1 import vim_api from sysinv.api.controllers.v1 import patch_api @@ -1081,6 +1082,10 @@ class HostController(rest.RestController): parent="ihosts") "Expose interface_networks as a sub-element of ihosts" + interface_datanetworks = interface_datanetwork.InterfaceDataNetworkController( + parent="ihosts") + "Expose interface_datanetworks as a sub-element of ihosts" + _custom_actions = { 'detail': ['GET'], 'bulk_add': ['POST'], @@ -3179,62 +3184,61 @@ class HostController(rest.RestController): raise wsme.exc.ClientSideError(msg) @staticmethod - def _semantic_check_interface_providernets(ihost, interface): + def _semantic_check_interface_datanets(interface): """ - Perform provider network semantics on a specific interface to ensure - that any provider networks that have special requirements on the - interface has been statisfied. + Perform data network semantics on a specific interface to ensure + that any data networks that have special requirements on the + interface have been satisfied. """ - networktype = [] - if interface.networktype: - networktype = [network.strip() for network in interface.networktype.split(",")] - if constants.NETWORK_TYPE_DATA not in networktype: + + if interface.ifclass != constants.NETWORK_TYPE_DATA: return - # Fetch the list of provider networks from neutron - providernets = pecan.request.rpcapi.iinterface_get_providernets( - pecan.request.context) - # Cleanup the list of provider networks stored on the interface - values = interface.providernetworks.strip() - values = re.sub(',,+', ',', values) - providernet_names = values.split(',') - # Check for VXLAN provider networks that require IP addresses - for providernet_name in providernet_names: - providernet = providernets.get(providernet_name) - if not providernet: - msg = (_("Interface %(ifname)s is associated to provider " - "network %(name)s which does not exist") % - {'ifname': interface.ifname, 'name': providernet_name}) - raise wsme.exc.ClientSideError(msg) - if providernet['type'] != "vxlan": + + ifdatanets = \ + pecan.request.dbapi.interface_datanetwork_get_by_interface( + interface.uuid) + + # Check for VXLAN data networks that require IP addresses + for ifdn in ifdatanets: + if ifdn.datanetwork_network_type != \ + constants.DATANETWORK_TYPE_VXLAN: continue - for r in providernet['ranges']: - if r['vxlan']['group'] is None: - continue # static range; fallback to generic check - # Check for address family specific ranges - address = netaddr.IPAddress(r['vxlan']['group']) - if ((address.version == constants.IPV4_FAMILY) and - (interface.ipv4_mode == constants.IPV4_DISABLED)): - msg = (_("Interface %(ifname)s is associated to VXLAN " - "provider network %(name)s which requires an " - "IPv4 address") % - {'ifname': interface.ifname, - 'name': providernet_name}) - raise wsme.exc.ClientSideError(msg) - if ((address.version == constants.IPV6_FAMILY) and - (interface.ipv6_mode == constants.IPV6_DISABLED)): - msg = (_("Interface %(ifname)s is associated to VXLAN " - "provider network %(name)s which requires an " - "IPv6 address") % - {'ifname': interface.ifname, - 'name': providernet_name}) - raise wsme.exc.ClientSideError(msg) + + dn = pecan.request.dbapi.datanetwork_get(ifdn.datanetwork_uuid) + if not dn.multicast_group: + # static range; fallback to generic check + continue + + # Check for address family specific ranges + address = netaddr.IPAddress(dn.multicast_group) + if ((address.version == constants.IPV4_FAMILY) and + (interface.ipv4_mode == constants.IPV4_DISABLED or not + interface.ipv4_mode)): + msg = (_("Interface %(ifname)s is associated to VXLAN " + "data network %(name)s which requires an " + "IPv4 address") % + {'ifname': interface.ifname, + 'name': ifdn.datanetwork_name}) + raise wsme.exc.ClientSideError(msg) + if ((address.version == constants.IPV6_FAMILY) and + (interface.ipv6_mode == constants.IPV6_DISABLED or not + interface.ipv6_mode)): + msg = (_("Interface %(ifname)s is associated to VXLAN " + "data network %(name)s which requires an " + "IPv6 address") % + {'ifname': interface.ifname, + 'name': ifdn.datanetwork_name}) + raise wsme.exc.ClientSideError(msg) + # Check for at least 1 address if no ranges exist yet if ((interface.ipv4_mode == constants.IPV4_DISABLED) and - (interface.ipv6_mode == constants.IPV6_DISABLED)): + (interface.ipv6_mode == constants.IPV6_DISABLED) or + (not interface.ipv4_mode and not interface.ipv6_mode)): msg = (_("Interface %(ifname)s is associated to VXLAN " - "provider network %(name)s which requires an IP " + "data network %(name)s which requires an IP " "address") % - {'ifname': interface.ifname, 'name': providernet_name}) + {'ifname': interface.ifname, + 'name': ifdn.datanetwork_name}) raise wsme.exc.ClientSideError(msg) @staticmethod @@ -3263,11 +3267,11 @@ class HostController(rest.RestController): if ((vswitch_type == constants.VSWITCH_TYPE_OVS_DPDK) and (iif.ifclass == constants.INTERFACE_CLASS_DATA)): self._semantic_check_non_accelerated_interface_support(iif) - self._semantic_check_interface_providernets(ihost, iif) + self._semantic_check_interface_datanets(iif) self._semantic_check_interface_addresses(ihost, iif) - if not iif.networktype: + if not iif.ifclass: continue - if any(n in [constants.NETWORK_TYPE_DATA] for n in iif.networktype.split(",")): + if iif.ifclass == constants.NETWORK_TYPE_DATA: data_interface_configured = True if not data_interface_configured: diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py index e20d772549..23c7cc1809 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface.py @@ -16,7 +16,9 @@ # License for the specific language governing permissions and limitations # under the License. # -# Copyright (c) 2013-2016 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 # @@ -42,6 +44,7 @@ from sysinv.api.controllers.v1 import route from sysinv.api.controllers.v1 import types from sysinv.api.controllers.v1 import utils from sysinv.api.controllers.v1 import interface_network +from sysinv.api.controllers.v1 import interface_datanetwork from sysinv.common import constants from sysinv.common import exception from sysinv.common import utils as cutils @@ -101,8 +104,8 @@ DATA_NETWORK_TYPES = [constants.NETWORK_TYPE_DATA] MAX_IFNAME_LEN = 10 MAX_VLAN_ID_LEN = 5 -# Maximum number of characters in provider network list -MAX_PROVIDERNETWORK_LEN = 255 +# Maximum number of characters in data network list +MAX_DATANETWORK_LEN = 255 DEFAULT_MTU = 1500 @@ -152,12 +155,8 @@ class Interface(base.APIBase): txhashpolicy = wtypes.text "Represent the txhashpolicy of the interface" - providernetworks = wtypes.text - "Represent the providernetworks of the interface" - - providernetworksdict = {wtypes.text: utils.ValidTypes(wtypes.text, - six.integer_types)} - "Represent the providernetworksdict of the interface" + datanetworks = [wtypes.text] + "Represent the datanetworks of the interface" ifcapabilities = {wtypes.text: utils.ValidTypes(wtypes.text, six.integer_types)} @@ -219,15 +218,19 @@ class Interface(base.APIBase): # fields = ['uuid', 'address'] if not expand else None # interface = iinterface.from_rpc_object(rpc_interface, fields) - interface = Interface(**rpc_interface.as_dict()) + kwargs = rpc_interface.as_dict() + datanetworks_list = kwargs.pop('datanetworks') + + interface = Interface(**kwargs) if not expand: interface.unset_fields_except(['uuid', 'ifname', 'iftype', 'imac', 'imtu', 'ifclass', 'networktype', 'networks', + 'ihost_uuid', 'forihostid', 'aemode', 'schedpolicy', 'txhashpolicy', - 'providernetworks', 'ihost_uuid', 'forihostid', 'vlan_id', 'uses', 'usesmodify', 'used_by', 'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool', - 'sriov_numvfs']) + 'sriov_numvfs', + 'datanetworks']) # never expose the ihost_id attribute interface.ihost_id = wtypes.Unset @@ -282,6 +285,13 @@ class Interface(base.APIBase): if interface.ipv6_mode != constants.IPV6_POOL: interface.ipv6_pool = wtypes.Unset + datanetworks_names_list = [] + for dn in datanetworks_list: + dn = pecan.request.dbapi.datanetwork_get(dn) + datanetworks_names_list.append(dn.name) + + interface.datanetworks = datanetworks_names_list + return interface @@ -323,6 +333,11 @@ class InterfaceController(rest.RestController): parent="iinterfaces") "Expose interface_networks as a sub-element of interface" + interface_datanetworks = \ + interface_datanetwork.InterfaceDataNetworkController( + parent="iinterfaces") + "Expose interface_datanetworks as a sub-element of interface" + _custom_actions = { 'detail': ['GET'], } @@ -442,10 +457,13 @@ class InterfaceController(rest.RestController): networks = [] networks_to_add = [] interface_networks_to_remove = [] + datanetworks = [] + datanetworks_to_add = [] + interface_datanetworks_to_remove = [] patches_to_remove = [] for p in patch: if '/ifclass' == p['path']: - if p['value'] == 'none': + if p['value'] == constants.INTERFACE_CLASS_NONE: p['value'] = None elif '/usesmodify' == p['path']: uses = p['value'].split(',') @@ -462,6 +480,15 @@ class InterfaceController(rest.RestController): elif '/interface_networks_to_remove' == p['path']: interface_networks_to_remove = p['value'].split(',') patches_to_remove.append(p) + elif '/datanetworks' == p['path']: + datanetworks = p['value'].split(',') + patches_to_remove.append(p) + elif '/datanetworks_to_add' == p['path']: + datanetworks_to_add = p['value'].split(',') + patches_to_remove.append(p) + elif '/interface_datanetworks_to_remove' == p['path']: + interface_datanetworks_to_remove = p['value'].split(',') + patches_to_remove.append(p) if uses: patch.append(dict(path='/uses', value=uses, op='replace')) @@ -524,6 +551,7 @@ class InterfaceController(rest.RestController): # Process updates vlan_id = None delete_addressing = False + delete_ifdn = False for p in patch: if '/vlan_id' in p['path']: @@ -569,6 +597,7 @@ class InterfaceController(rest.RestController): interface['ipv4_mode'] = None interface['ipv6_mode'] = None delete_addressing = True + delete_ifdn = True else: # Otherwise make sure that appropriate defaults are set. interface = _set_defaults(interface) @@ -581,7 +610,8 @@ class InterfaceController(rest.RestController): interface = _check("modify", interface, ports=ports, ifaces=uses, - existing_interface=rpc_interface.as_dict()) + existing_interface=rpc_interface.as_dict(), + datanetworks=datanetworks) if uses: # Update MAC address if uses list changed @@ -623,6 +653,40 @@ class InterfaceController(rest.RestController): if _is_ipv6_address_mode_updated(interface, rpc_interface): _update_ipv6_address_mode(interface) + # Update interface-datanetworks + if datanetworks_to_add: + for datanetwork_id in datanetworks_to_add: + values = {'interface_id': interface['id'], + 'datanetwork_id': datanetwork_id} + try: + pecan.request.dbapi.interface_datanetwork_create(values) + except exception.InterfaceDataNetworkAlreadyExists: + pass + elif datanetworks: + _update_interface_datanetworks( + ihost['uuid'], interface, datanetworks, delete_ifdn) + + try: + # Remove old datanetworks from the interface + if interface_datanetworks_to_remove: + for ifdatanet_id in interface_datanetworks_to_remove: + pecan.request.dbapi.interface_datanetwork_destroy( + ifdatanet_id) + elif (orig_ifclass == constants.INTERFACE_CLASS_DATA and + (not ifclass or + ifclass != constants.INTERFACE_CLASS_DATA)): + # data networks apply only for DATA + ifdatanets = \ + pecan.request.dbapi.interface_datanetwork_get_by_interface( + rpc_interface['uuid']) + for ifdatanet in ifdatanets: + pecan.request.dbapi.interface_datanetwork_destroy(ifdatanet.uuid) + except Exception as e: + LOG.exception(e) + msg = _("Failed to remove interface datanetwork association for " + "interface %s" % (interface['ifname'])) + raise wsme.exc.ClientSideError(msg) + # Commit operation with neutron if (interface['ifclass'] and interface['ifclass'] in NEUTRON_INTERFACE_CLASS): @@ -755,6 +819,65 @@ class InterfaceController(rest.RestController): # UTILS ############## +def _update_interface_datanetworks(host_uuid, interface, + datanetworks=None, + delete_ifdn=False): + + pns = [] + + if datanetworks: + # remove 'none' from datanetworks + datanetworks = \ + [x for x in datanetworks if x != constants.DATANETWORK_TYPE_NONE] + for datanetwork_id in datanetworks: + dn = pecan.request.dbapi.datanetwork_get(datanetwork_id) + pns.append(dn.name) + elif 'datanetworks' in interface: + pns = interface['datanetworks'] + + LOG.info("_update_interface_datanetworks interface=%s datanetworks=%s pns=%s" % + (interface, datanetworks, pns)) + + # remove from the interface datanetworks not in list + ifdns = \ + pecan.request.dbapi.interface_datanetwork_get_by_host( + host_uuid) + for ifdn in ifdns: + # if this is not this interface, continue + if_uuid = interface.get('uuid', None) + if if_uuid: + if if_uuid != ifdn.interface_uuid: + continue + elif ifdn.ifname != interface.get('ifname'): + continue + + LOG.debug("_update_interface_datanetworks host_uuid %s " + "interface=%s ifdn=%s" % + (host_uuid, interface, ifdn.as_dict())) + if (pns and ifdn.datanetwork_name not in pns) or delete_ifdn: + LOG.info("interface_datanetwork_destroy %s %s delete_ifdn=%s" % + (ifdn.uuid, ifdn.ifname, delete_ifdn)) + pecan.request.dbapi.interface_datanetwork_destroy( + ifdn.uuid) + + for pn in pns: + dn = pecan.request.dbapi.datanetwork_get(pn) + values = {'interface_id': interface['id'], + 'datanetwork_id': dn.id} + try: + ifdn = pecan.request.dbapi.interface_datanetwork_create(values) + except exception.InterfaceDataNetworkAlreadyExists: + pass + except Exception as e: + LOG.exception(e) + msg = _("Failed to create interface datanetwork " + "assignment for interface %s" % + (interface['ifname'])) + raise wsme.exc.ClientSideError(msg) + + return ifdns + + def _dynamic_address_allocation(): mgmt_network = pecan.request.dbapi.network_get_by_type( constants.NETWORK_TYPE_MGMT) @@ -1258,11 +1381,140 @@ def _check_networks(interface): raise wsme.exc.ClientSideError(msg) -def _check_interface_data(op, interface, ihost, existing_interface): +def _check_datanetworks(ihost, + interface, + interface_list, + existing_interface, + networktypelist, + datanetworks=None): + + if 'id' in interface: + this_interface_id = interface['id'] + else: + this_interface_id = 0 + + ifclass = interface['ifclass'] + iftype = interface['iftype'] + + if not datanetworks: + datanetworks = interface.get('datanetworks') or [] + + # remove 'none' from datanetworks + datanetworks = \ + [x for x in datanetworks if x != constants.DATANETWORK_TYPE_NONE] + + LOG.debug("_check_datanetworks datanetworks interface=%s datanetworks=%s" % + (interface, datanetworks)) + + # Get all provisioned datanetworks + all_datanetworks = {} + db_datanetworks = pecan.request.dbapi.datanetworks_get_all() + for db in db_datanetworks: + all_datanetworks[db.name] = { + 'network_type': db.network_type} + + # Ensure a valid datanetwork is specified + # Ensure at least one datanetwork is selected for 'data', + # and none for 'oam', 'mgmt' and 'infra' + # Ensure uniqueness of the datanetworks + + datanetworks_list = [] + for datanetwork in datanetworks: + if datanetwork == constants.DATANETWORK_TYPE_NONE: + continue + dn = pecan.request.dbapi.datanetwork_get(datanetwork) + datanetworks_list.append(dn.name) + + if interface['ifclass'] in NEUTRON_INTERFACE_CLASS: + if not datanetworks: + msg = _("At least one data network must be selected.") + raise wsme.exc.ClientSideError(msg) + if len(datanetworks) > MAX_DATANETWORK_LEN: + msg = _("Data network list must not exceed %d characters." % + MAX_DATANETWORK_LEN) + raise wsme.exc.ClientSideError(msg) + + for pn in [n.strip() for n in datanetworks_list]: + if pn not in all_datanetworks.keys(): + msg = _("Data network '%s' does not exist." % pn) + raise wsme.exc.ClientSideError(msg) + if datanetworks_list.count(pn) > 1: + msg = (_("Specifying duplicate data network '%(name)s' " + "is not permitted") % {'name': pn}) + raise wsme.exc.ClientSideError(msg) + datanet = all_datanetworks[pn] + if iftype == constants.INTERFACE_TYPE_VLAN: + if datanet['network_type'] == \ + constants.DATANETWORK_TYPE_VLAN: + msg = _("VLAN based data network '%s' cannot be " + "assigned to a VLAN interface" % pn) + raise wsme.exc.ClientSideError(msg) + + # If pxeboot, Mgmt, Infra network types are consolidated + # with a data network type on the same interface, + # in which case, they would be the primary network + # type. Ensure that the only data type that + # can be assigned is VLAN. + if (datanet['network_type'] != constants.DATANETWORK_TYPE_VLAN and + ifclass not in NEUTRON_NETWORK_TYPES): + msg = _("Data network '%s' of type '%s' cannot be assigned " + "to an interface with interface class '%s'" + % (pn, datanet['network_type'], ifclass)) + raise wsme.exc.ClientSideError(msg) + + # This ensures that a specific data network type can + # only be assigned to 1 data interface. Such as the case of + # when only 1 vxlan data is required when SDN is enabled + if constants.NETWORK_TYPE_DATA in networktypelist and interface_list: + for pn in [n.strip() for n in datanetworks_list]: + for i in interface_list: + if i.id == this_interface_id: + continue + if not i.ifclass or not i.datanetworks: + continue + if constants.NETWORK_TYPE_DATA != i.ifclass: + continue + + other_datanetworks = [] + for datanetwork in i.datanetworks: + dn = pecan.request.dbapi.datanetwork_get(datanetwork) + other_datanetworks.append(dn.name) + if pn in other_datanetworks: + msg = _("Data interface %(ifname)s is already " + "attached to this Data Network: " + "%(datanetwork)s." % + {'ifname': i.ifname, 'datanetwork': pn}) + raise wsme.exc.ClientSideError(msg) + + elif (not _neutron_providernet_extension_supported() and + any(nt in PCI_NETWORK_TYPES for nt in networktypelist)): + # When the neutron implementation is not our own and it does not + # support our data network extension we still want to do minimal + # validation of the data network list but we cannot do more + # complex validation because we do not have any additional information + # about the data networks. + if not datanetworks: + msg = _("At least one data network must be selected.") + raise wsme.exc.ClientSideError(msg) + + elif (interface['ifclass'] and + interface['ifclass'] not in NEUTRON_INTERFACE_CLASS and + not existing_interface): + if datanetworks: + msg = _("Data network(s) not supported " + "for non-data interfaces. (%s) (%s)" % + (interface['ifclass'], str(existing_interface))) + raise wsme.exc.ClientSideError(msg) + elif (_neutron_providernet_extension_supported() or + interface['ifclass'] not in NEUTRON_INTERFACE_CLASS): + interface['datanetworks'] = None + + +def _check_interface_data(op, interface, ihost, existing_interface, + datanetworks=None): # Get data ihost_id = interface['forihostid'] ihost_uuid = interface['ihost_uuid'] - providernetworks = interface['providernetworks'] ifclass = interface['ifclass'] networktypelist = [] if ifclass == constants.INTERFACE_CLASS_PLATFORM: @@ -1274,9 +1526,6 @@ def _check_interface_data(op, interface, ihost, existing_interface): else: networktypelist.append(constants.INTERFACE_CLASS_NONE) - # Get providernet dict - all_providernetworks = _neutron_providernet_list() - # Check interface name for validity _check_interface_name(op, interface, ihost, existing_interface) @@ -1448,96 +1697,13 @@ def _check_interface_data(op, interface, ihost, existing_interface): host_port, networktypelist) - # Ensure a valid providernetwork is specified - # Ensure at least one providernetwork is selected for 'data', - # or interface (when SDN L3 services are enabled) - # and none for 'oam', 'mgmt' and 'infra' - # Ensure uniqueness wrt the providernetworks - if (_neutron_providernet_extension_supported() and - interface['ifclass'] in NEUTRON_INTERFACE_CLASS): - if not providernetworks: - msg = _("At least one provider network must be selected.") - raise wsme.exc.ClientSideError(msg) - if len(providernetworks) > MAX_PROVIDERNETWORK_LEN: - msg = _("Provider network list must not exceed %d characters." % - MAX_PROVIDERNETWORK_LEN) - raise wsme.exc.ClientSideError(msg) - providernetworks_list = providernetworks.split(',') - for pn in [n.strip() for n in providernetworks_list]: - if pn not in all_providernetworks.keys(): - msg = _("Provider network '%s' does not exist." % pn) - raise wsme.exc.ClientSideError(msg) - if providernetworks_list.count(pn) > 1: - msg = (_("Specifying duplicate provider network '%(name)s' " - "is not permitted") % {'name': pn}) - raise wsme.exc.ClientSideError(msg) - providernet = all_providernetworks[pn] - if iftype == constants.INTERFACE_TYPE_VLAN: - if providernet['type'] == 'vlan': - msg = _("VLAN based provider network '%s' cannot be " - "assigned to a VLAN interface" % pn) - raise wsme.exc.ClientSideError(msg) - - # If pxeboot, Mgmt, Infra network types are consolidated - # with a data network type on the same interface, - # in which case, they would be the primary network - # type. Ensure that the only provider type that - # can be assigned is VLAN. - if (providernet['type'] != constants.NEUTRON_PROVIDERNET_VLAN and - ifclass not in NEUTRON_NETWORK_TYPES): - msg = _("Provider network '%s' of type '%s' cannot be assigned " - "to an interface with interface class '%s'" - % (pn, providernet['type'], ifclass)) - raise wsme.exc.ClientSideError(msg) - - # This ensures that a specific provider network type can - # only be assigned to 1 data interface. Such as the case of - # when only 1 vxlan provider is required when SDN is enabled - if constants.NETWORK_TYPE_DATA in networktypelist and interface_list: - for pn in [n.strip() for n in providernetworks.split(',')]: - for i in interface_list: - if i.id == this_interface_id: - continue - if not i.ifclass or not i.providernetworks: - continue - if constants.NETWORK_TYPE_DATA != i.ifclass: - continue - other_providernetworks = i.providernetworks.split(',') - if pn in other_providernetworks: - msg = _("Data interface %(ifname)s is already " - "attached to this Provider Network: " - "%(network)s." % - {'ifname': i.ifname, 'network': pn}) - raise wsme.exc.ClientSideError(msg) - - # Send the interface and provider network details to neutron for - # additional validation. - _neutron_bind_interface(ihost, interface, test=True) - # Send the shared data interface(s) and provider networks details to - # neutron for additional validation, if required - _update_shared_interface_neutron_bindings(ihost, interface, test=True) - - elif (not _neutron_providernet_extension_supported() and - any(nt in PCI_NETWORK_TYPES for nt in networktypelist)): - # When the neutron implementation is not our own and it does not - # support our provider network extension we still want to do minimal - # validation of the provider network list but we cannot do more - # complex validation because we do not have any additional information - # about the provider networks. - if not providernetworks: - msg = _("At least one provider network must be selected.") - raise wsme.exc.ClientSideError(msg) - - elif (interface['ifclass'] and - interface['ifclass'] not in NEUTRON_INTERFACE_CLASS and - not existing_interface): - if providernetworks is not None: - msg = _("Provider network(s) not supported " - "for non-data interfaces. (%s) (%s)" % (interface['ifclass'], str(existing_interface))) - raise wsme.exc.ClientSideError(msg) - elif (_neutron_providernet_extension_supported() or - interface['ifclass'] not in NEUTRON_INTERFACE_CLASS): - interface['providernetworks'] = None + # Check datanetworks (formerly known as providernetworks) + _check_datanetworks(ihost, + interface, + interface_list, + existing_interface, + networktypelist, + datanetworks) # check MTU if interface['iftype'] == constants.INTERFACE_TYPE_VLAN: @@ -1894,31 +2060,6 @@ def _update_host_cluster_address(host, interface): address_name) -def _clean_providernetworks(providernetworks): - pn = [','.join(p['name']) for p in providernetworks] - return pn - - -""" -Params: - pn_all: all providernets stored in neutron - pn_names: providernets specified for this interface - -Return: - pn_dict: a dictionary of providernets specified - for this interface: item format {name:body} -""" - - -def _get_providernetworksdict(pn_all, pn_names): - pn_dict = {} - if pn_names: - for name, body in pn_all.items(): - if name in pn_names.split(','): - pn_dict.update({name: body}) - return pn_dict - - def _get_interface_vlans(ihost_uuid, interface): """ Retrieve the VLAN id values (if any) that are dependent on this @@ -2053,6 +2194,38 @@ def _update_shared_interface_neutron_bindings(ihost, interface, test=False): _neutron_bind_interface(ihost, shared_interface, test) +def _datanetworks_get_by_interface(interface_uuid): + ifdatanets = pecan.request.dbapi.interface_datanetwork_get_by_interface( + interface_uuid) + + LOG.debug("_datanetworks_get_by_interface %s ifdnets=%s" % + (interface_uuid, ifdatanets)) + + datanetworks = [] + for ifdatanet in ifdatanets: + datanetworks.append(ifdatanet.datanetwork_uuid) + + datanetworks_list = [] + datanetworks_names_list = [] + for datanetwork in datanetworks: + dn = pecan.request.dbapi.datanetwork_get(datanetwork) + datanetwork_dict = \ + {'name': dn.name, + 'uuid': dn.uuid, + 'network_type': dn.network_type, + 'mtu': dn.mtu} + datanetworks_names_list.append(dn.name) + if dn.network_type == constants.DATANETWORK_TYPE_VXLAN: + datanetwork_dict.update( + {'port_num': dn.port_num, + 'multicast_group': dn.multicast_group, + 'ttl': dn.ttl, + 'mode': dn.mode}) + datanetworks_list.append(datanetwork_dict) + + return datanetworks_names_list, datanetworks_list + + def _neutron_bind_interface(ihost, interface, test=False): """ Send a request to neutron to bind the interface to the specified @@ -2082,7 +2255,13 @@ def _neutron_bind_interface(ihost, interface, test=False): raise wsme.exc.ClientSideError(msg) interface_uuid = interface['uuid'] - providernetworks = interface.get('providernetworks', '') + datanetworks_names_list, _dl = \ + _datanetworks_get_by_interface(interface_uuid) + + providernetworks = ",".join([str(x) for x in datanetworks_names_list]) + LOG.info("_neutron_bind_interface uuid=%s datanetworks_names=%s" % + (interface_uuid, providernetworks)) + vlans = _get_interface_vlans(ihost_uuid, interface) try: # Send the request to neutron @@ -2210,6 +2389,8 @@ def _create(interface, from_profile=False): else: forihostid = ihostId + datanetworks = interface.get('datanetworks') + LOG.debug("iinterface post interfaces ihostid: %s" % forihostid) interface.update({'forihostid': ihost['id'], @@ -2267,6 +2448,9 @@ def _create(interface, from_profile=False): forihostid, interface) + # Create interface-datanetworks + _update_interface_datanetworks(ihost['uuid'], new_interface, datanetworks) + # Create network-interface try: if (new_interface['ifclass'] and @@ -2382,7 +2566,7 @@ def _create(interface, from_profile=False): def _check(op, interface, ports=None, ifaces=None, from_profile=False, - existing_interface=None): + existing_interface=None, datanetworks=None): # Semantic checks ihost = pecan.request.dbapi.ihost_get(interface['ihost_uuid']).as_dict() _check_host(ihost) @@ -2414,9 +2598,11 @@ def _check(op, interface, ports=None, ifaces=None, from_profile=False, if 'txhashpolicy' not in iface: iface['txhashpolicy'] = None - _check_interface_data("modify", iface, ihost, existing_iface) + _check_interface_data( + "modify", iface, ihost, existing_iface, datanetworks) - interface = _check_interface_data(op, interface, ihost, existing_interface) + interface = _check_interface_data( + op, interface, ihost, existing_interface, datanetworks) return interface diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_datanetwork.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_datanetwork.py new file mode 100644 index 0000000000..1c37683682 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/interface_datanetwork.py @@ -0,0 +1,286 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# Copyright 2013 UnitedStack Inc. +# All 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. +# +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import uuid +import wsme +import pecan +from pecan import rest +from wsme import types as wtypes +import wsmeext.pecan as wsme_pecan + +from sysinv.api.controllers.v1 import base +from sysinv.api.controllers.v1 import collection +from sysinv.api.controllers.v1 import types +from sysinv.api.controllers.v1 import utils +from sysinv.common import utils as cutils +from sysinv.common import constants +from sysinv.common import exception +from sysinv.openstack.common.gettextutils import _ + +from sysinv import objects + + +class InterfaceDataNetwork(base.APIBase): + + id = int + "Unique ID for this interface data network" + + uuid = types.uuid + "Unique UUID for this interface data network" + + forihostid = int + "The ID of the host the interface data network belongs to" + + interface_uuid = types.uuid + "Unique UUID of the parent interface" + + ifname = wtypes.text + "User defined name of the interface" + + datanetwork_id = int + "Unique ID of the parent datanetwork" + + datanetwork_uuid = types.uuid + "Unique UUID of the parent datanetwork" + + datanetwork_name = wtypes.text + "User defined name of the datanetwork" + + network_type = wtypes.text + "Represents the type for the datanetwork" + + def __init__(self, **kwargs): + self.fields = objects.interface_datanetwork.fields.keys() + for k in self.fields: + if not hasattr(self, k): + continue + setattr(self, k, kwargs.get(k, wtypes.Unset)) + + @classmethod + def convert_with_links(cls, rpc_interface_datanetwork, expand=True): + interface_datanetwork = InterfaceDataNetwork( + **rpc_interface_datanetwork.as_dict()) + if not expand: + interface_datanetwork.unset_fields_except([ + 'forihostid', 'id', 'uuid', 'interface_uuid', 'ifname', + 'datanetwork_id', 'datanetwork_uuid', + 'datanetwork_name', 'network_type' + ]) + return interface_datanetwork + + +class InterfaceDataNetworkCollection(collection.Collection): + """API representation of a collection of IP addresses.""" + + interface_datanetworks = [InterfaceDataNetwork] + "A list containing Interface Data Network objects" + + def __init__(self, **kwargs): + self._type = 'interface_datanetworks' + + @classmethod + def convert_with_links(cls, rpc_interface_datanetwork, limit, url=None, + expand=False, **kwargs): + collection = InterfaceDataNetworkCollection() + collection.interface_datanetworks = [ + InterfaceDataNetwork.convert_with_links(p, expand) + for p in rpc_interface_datanetwork] + collection.next = collection.get_next(limit, url=url, **kwargs) + return collection + + +LOCK_NAME = 'InterfaceDataNetworkController' + + +class InterfaceDataNetworkController(rest.RestController): + + def __init__(self, parent=None): + self._parent = parent + + def _create_interface_datanetwork(self, interface_datanetwork): + interface_datanetwork_dict = interface_datanetwork.as_dict() + interface_datanetwork_dict['uuid'] = str(uuid.uuid4()) + + # Remove UUIDs from dict to be replaced with IDs + interface_uuid = interface_datanetwork_dict.pop('interface_uuid') + datanetwork_uuid = interface_datanetwork_dict.pop('datanetwork_uuid') + + interface_id = self._get_interface_id(interface_uuid) + + try: + datanetwork_obj = \ + pecan.request.dbapi.datanetwork_get(datanetwork_uuid) + except exception.DataNetworkNotFound: + msg = _("DataNetwork with uuid '%s' does not exist. " % + datanetwork_uuid) + raise wsme.exc.ClientSideError(msg) + + datanetwork_id = datanetwork_obj['id'] + + interface_datanetwork_dict['interface_id'] = interface_id + interface_datanetwork_dict['datanetwork_id'] = datanetwork_id + + interface_obj = pecan.request.dbapi.iinterface_get(interface_uuid) + self._check_host(interface_obj.ihost_uuid) + + self._check_interface_class(interface_obj) + self._check_interface_mtu(interface_obj, datanetwork_obj) + self._check_duplicate_interface_datanetwork(interface_datanetwork_dict) + + result = pecan.request.dbapi.interface_datanetwork_create( + interface_datanetwork_dict) + + return InterfaceDataNetwork.convert_with_links(result) + + def _get_interface_datanetwork_collection( + self, parent_uuid=None, marker=None, limit=None, sort_key=None, + sort_dir=None, expand=False, resource_url=None): + limit = utils.validate_limit(limit) + sort_dir = utils.validate_sort_dir(sort_dir) + marker_obj = None + + if marker: + marker_obj = objects.interface_datanetwork.get_by_uuid( + pecan.request.context, marker) + + if self._parent == "ihosts": + interface_datanetworks = \ + pecan.request.dbapi.interface_datanetwork_get_by_host( + parent_uuid, + limit=limit, marker=marker_obj, + sort_key=sort_key, sort_dir=sort_dir) + elif self._parent == "iinterfaces": + interface_datanetworks = \ + pecan.request.dbapi.interface_datanetwork_get_by_interface( + parent_uuid, limit=limit, marker=marker_obj, + sort_key=sort_key, sort_dir=sort_dir) + else: + interface_datanetworks = \ + pecan.request.dbapi.interface_datanetwork_get_all( + limit=limit, marker=marker_obj, + sort_key=sort_key, sort_dir=sort_dir) + + return InterfaceDataNetworkCollection.convert_with_links( + interface_datanetworks, limit, url=resource_url, expand=expand, + sort_key=sort_key, sort_dir=sort_dir) + + @staticmethod + def _get_one(interface_datanetwork_uuid): + rpc_interface_datanetwork = objects.interface_datanetwork.get_by_uuid( + pecan.request.context, interface_datanetwork_uuid) + return InterfaceDataNetwork.convert_with_links( + rpc_interface_datanetwork) + + @staticmethod + def _check_interface_class(interface_obj): + if (not interface_obj.ifclass or + interface_obj.ifclass == constants.INTERFACE_CLASS_NONE): + values = {'ifclass': constants.INTERFACE_CLASS_DATA} + pecan.request.dbapi.iinterface_update(interface_obj.uuid, values) + return + else: + # Allow ifclass data to assign another; disallow other ifclass + if interface_obj.ifclass != constants.INTERFACE_CLASS_DATA: + msg = _("An interface with interface class '%s' " + "cannot assign datanetworks." % + interface_obj.ifclass) + raise wsme.exc.ClientSideError(msg) + + @staticmethod + def _check_host(host_uuid): + host = pecan.request.dbapi.ihost_get(host_uuid) + if host.administrative != constants.ADMIN_LOCKED: + msg = _("Operation Rejected: Host '%s' is adminstrative '%s' " % + (host.hostname, host.administrative)) + raise wsme.exc.ClientSideError(msg) + + @staticmethod + def _check_interface_mtu(interface_obj, datanetwork_obj): + if datanetwork_obj.network_type == constants.DATANETWORK_TYPE_VXLAN: + overhead = constants.VXLAN_MTU_OVERHEAD + else: + overhead = 0 + + if interface_obj.imtu < datanetwork_obj.mtu + overhead: + msg = _("The interface MTU %s must be larger than the '%s' " + "datanetwork MTU requirement." % + (interface_obj.imtu, datanetwork_obj.mtu)) + raise wsme.exc.ClientSideError(msg) + + @staticmethod + def _query_interface_datanetwork(interface_datanetwork): + try: + result = pecan.request.dbapi.interface_datanetwork_query( + interface_datanetwork) + except exception.InterfaceDataNetworkNotFoundByKeys: + return None + return result + + def _check_duplicate_interface_datanetwork(self, interface_datanetwork): + result = self._query_interface_datanetwork(interface_datanetwork) + if not result: + return + msg = _("Interface '%s' assignment with Data Network '%s' " + "already exists." + % (interface_datanetwork['interface_id'], + interface_datanetwork['datanetwork_id'])) + raise wsme.exc.ClientSideError(msg) + + @staticmethod + def _get_interface_id(interface_uuid): + interface = pecan.request.dbapi.iinterface_get(interface_uuid) + return interface['id'] + + @staticmethod + def _get_datanetwork_id_and_type(datanetwork_uuid): + datanetwork = pecan.request.dbapi.datanetwork_get(datanetwork_uuid) + return datanetwork['id'], datanetwork['network_type'] + + @wsme_pecan.wsexpose(InterfaceDataNetwork, types.uuid) + def get_one(self, interface_datanetwork_uuid): + return self._get_one(interface_datanetwork_uuid) + + @wsme_pecan.wsexpose(InterfaceDataNetworkCollection, + wtypes.text, types.uuid, int, + wtypes.text, wtypes.text) + def get_all(self, parent_uuid=None, marker=None, + limit=None, sort_key='id', sort_dir='asc'): + return self._get_interface_datanetwork_collection( + parent_uuid, marker, limit, sort_key, sort_dir) + + @cutils.synchronized(LOCK_NAME) + @wsme_pecan.wsexpose(InterfaceDataNetwork, body=InterfaceDataNetwork) + def post(self, interface_datanetwork): + return self._create_interface_datanetwork(interface_datanetwork) + + @cutils.synchronized(LOCK_NAME) + @wsme_pecan.wsexpose(None, types.uuid, status_code=204) + def delete(self, interface_datanetwork_uuid): + ifdn_obj = pecan.request.dbapi.interface_datanetwork_get( + interface_datanetwork_uuid) + interface_obj = pecan.request.dbapi.iinterface_get( + ifdn_obj.interface_uuid) + self._check_host(interface_obj.ihost_uuid) + pecan.request.dbapi.interface_datanetwork_destroy( + interface_datanetwork_uuid) diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile.py index 438c0127fb..b867313f5a 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile.py @@ -74,7 +74,7 @@ CONF.import_opt('journal_default_size', # Defines the fields that must be copied in/out of interface profiles INTERFACE_PROFILE_FIELDS = ['ifname', 'iftype', 'imtu', 'networktype', 'ifclass', 'aemode', 'networks', - 'txhashpolicy', 'forihostid', 'providernetworks', + 'txhashpolicy', 'forihostid', 'datanetworks', 'vlan_id', 'ipv4_mode', 'ipv6_mode', 'ipv4_pool', 'ipv6_pool', 'sriov_numvfs'] @@ -1350,7 +1350,7 @@ def _create_if_profile(profile_name, profile_node): 'imtu': ethIf.mtu, 'networktype': nt, 'forihostid': iprofile_id, - 'providernetworks': providernets, + 'datanetworks': providernets, 'ipv4_mode': ipv4_mode['mode'], 'ipv6_mode': ipv6_mode['mode'], 'ipv4_pool': ipv4_mode['pool'], @@ -1390,7 +1390,7 @@ def _create_if_profile(profile_name, profile_node): 'aemode': aeIf.aeMode, 'txhashpolicy': aeIf.txPolicy, 'forihostid': iprofile_id, - 'providernetworks': providernets, + 'datanetworks': providernets, 'ipv4_mode': ipv4_mode, 'ipv6_mode': ipv6_mode, 'ipv4_pool': ipv4_pool, @@ -1417,7 +1417,7 @@ def _create_if_profile(profile_name, profile_node): 'networktype': nt, 'vlan_id': vlanIf.vlanId, 'forihostid': iprofile_id, - 'providernetworks': providernets, + 'datanetworks': providernets, 'ipv4_mode': ipv4_mode, 'ipv6_mode': ipv6_mode, 'ipv4_pool': ipv4_pool, diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile_utils.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile_utils.py index d59ee5808c..e605173ed9 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile_utils.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile_utils.py @@ -54,13 +54,13 @@ class Network(object): raise InvalidProfileData("At least one provider network must be selected.") -class DataNetwork(Network): +class DataclassNetwork(Network): def __init__(self, node): - super(DataNetwork, self).__init__(node, constants.NETWORK_TYPE_DATA) - self.ipv4Mode = DataNetwork.getIpMode(node, "ipv4") - self.ipv6Mode = DataNetwork.getIpMode(node, "ipv6") - self.routes = DataNetwork.getRoutes(node) + super(DataclassNetwork, self).__init__(node, constants.NETWORK_TYPE_DATA) + self.ipv4Mode = DataclassNetwork.getIpMode(node, "ipv4") + self.ipv6Mode = DataclassNetwork.getIpMode(node, "ipv6") + self.routes = DataclassNetwork.getRoutes(node) @staticmethod def getRoutes(node): @@ -288,7 +288,7 @@ class EthInterface(Interface): def getNetworkMap(self): return { - 'dataNetwork': lambda node: DataNetwork(node), + 'dataclassNetwork': lambda node: DataclassNetwork(node), 'infraNetwork': lambda node: ExternalNetwork(node, constants.NETWORK_TYPE_INFRA), 'oamNetwork': lambda node: ExternalNetwork(node, constants.NETWORK_TYPE_OAM), 'mgmtNetwork': lambda node: ExternalNetwork(node, constants.NETWORK_TYPE_MGMT), @@ -330,7 +330,7 @@ class AeInterface(Interface): def getNetworkMap(self): return { - 'dataNetwork': lambda node: DataNetwork(node), + 'dataclassNetwork': lambda node: DataclassNetwork(node), 'infraNetwork': lambda node: ExternalNetwork(node, constants.NETWORK_TYPE_INFRA), 'oamNetwork': lambda node: ExternalNetwork(node, constants.NETWORK_TYPE_OAM), 'mgmtNetwork': lambda node: ExternalNetwork(node, constants.NETWORK_TYPE_MGMT) @@ -365,7 +365,7 @@ class VlanInterface(Interface): def getNetworkMap(self): return { - 'dataNetwork': lambda (node): DataNetwork(node), + 'dataclassNetwork': lambda (node): DataclassNetwork(node), 'infraNetwork': lambda (node): ExternalNetwork(node, constants.NETWORK_TYPE_INFRA), 'oamNetwork': lambda (node): ExternalNetwork(node, constants.NETWORK_TYPE_OAM), 'mgmtNetwork': lambda (node): ExternalNetwork(node, constants.NETWORK_TYPE_MGMT) diff --git a/sysinv/sysinv/sysinv/sysinv/common/constants.py b/sysinv/sysinv/sysinv/sysinv/common/constants.py index 87e072f82f..0927e82a3f 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/constants.py +++ b/sysinv/sysinv/sysinv/sysinv/common/constants.py @@ -239,10 +239,42 @@ HWMON_PORT = 2212 NEUTRON_HOST_ALIAS = "host" NEUTRON_WRS_PROVIDER_ALIAS = "wrs-provider" -# Neutron provider networks -NEUTRON_PROVIDERNET_FLAT = "flat" -NEUTRON_PROVIDERNET_VXLAN = "vxlan" -NEUTRON_PROVIDERNET_VLAN = "vlan" +# Data Networks +DATANETWORK_TYPE_NONE = "none" +DATANETWORK_TYPE_FLAT = "flat" +DATANETWORK_TYPE_VLAN = "vlan" +DATANETWORK_TYPE_VXLAN = "vxlan" + +DATANETWORK_MODE_DYNAMIC = "dynamic" +DATANETWORK_MODE_STATIC = "static" + +DATANETWORK_VXLAN_MODES = [ + DATANETWORK_MODE_DYNAMIC, + DATANETWORK_MODE_STATIC +] + +# Represents the number of bytes added to a tenant packet when it is carried +# by a VXLAN based provider network. We start by assuming a tenant network +# with an MTU of 1500 bytes. This means that at the host vswitch the +# ethernet frame will be 1514 bytes (+4 if VLAN tagged) not including the FCS +# trailer. To get this packet on to the provider network it must be +# encapsulated as-is with a {IPv4|IPv6}+UDP+VXLAN headers. The ETH+VLAN +# headers are not included because they themselves are not included in the +# provider network MTU (i.e., the VXLAN packet must fit within the ethernet +# payload of the provider interface). +# Therefore the maximum overhead, assuming a VLAN tagged provider network, is: +# +# IPv4 = 20 + 8 + 8 = 36 +# IPv6 = 40 + 8 + 8 = 56 +# +# This brings the maximum tenant packet size to: +# IPv4 = 36 + 1518 = 1554 +# IPv6 = 56 + 1518 = 1574 +# +# Therefore to support an tenant MTU of 1500 the underlying physical +# interface must support an MTU of 1574 bytes. +# +VXLAN_MTU_OVERHEAD = 74 # Supported worker node vswitch types VSWITCH_TYPE_OVS_DPDK = "ovs-dpdk" diff --git a/sysinv/sysinv/sysinv/sysinv/common/exception.py b/sysinv/sysinv/sysinv/sysinv/common/exception.py index 8bb17e0c27..1747637dd8 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/exception.py +++ b/sysinv/sysinv/sysinv/sysinv/common/exception.py @@ -1293,3 +1293,57 @@ class LocalManagementIpNotFound(NotFound): class InvalidHelmDockerImageSource(Invalid): message = _("Invalid docker image source: %(source)s. Must be one of %(valid_srcs)s") + + +# DataNetwork +class UnsupportedInterfaceDataNetworkType(Conflict): + message = _("Interface with datanetwork type '%(datanetworktype)s' " + "is not supported.") + + +class DataNetworkNotFound(NotFound): + message = _("DataNetwork %(datanetwork_uuid)s could not be found.") + + +class DataNetworkTypeNotFound(NotFound): + message = _("DataNetwork of type %(network_type)s could not be found.") + + +class DataNetworkIDNotFound(NotFound): + message = _("DataNetwork with id %(id)s could not be found.") + + +class DataNetworkNameNotFound(NotFound): + message = _("DataNetwork with name %(name)s could not be found.") + + +class DataNetworkAlreadyExists(Conflict): + message = _("DataNetwork of name %(name)s already exists.") + + +class DataNetworkTypeUnsupported(Conflict): + message = _("DataNetwork of type %(network_type)s is not supported.") + + +class InterfaceDataNetworkNotFound(NotFound): + message = _("Interface datanetwork %(uuid)s could not be found.") + + +class InterfaceDataNetworkAlreadyExists(Conflict): + message = _("Interface datanetwork with interface ID %(interface_id)s " + "and datanetwork ID %(datanetwork_id)s already exists.") + + +class InterfaceDataNetworkNotFoundByKeys(NotFound): + message = _("Interface datanetwork with interface ID %(interface_id)s " + "and datanetwork ID %(datanetwork_id)s not found") + + +class UnsupportedAssignedInterfaceDataNetworkType(Conflict): + message = _("Cannot assign datanetwork with type '%(network_type)s' " + "to an interface.") + + +class UnsupportedRemovedInterfaceDataNetworkType(Conflict): + message = _("Cannot remove datanetwork with type '%(network_type)s' " + "from an interface.") diff --git a/sysinv/sysinv/sysinv/sysinv/common/utils.py b/sysinv/sysinv/sysinv/sysinv/common/utils.py index bd5cb5b7c9..4a9af7e5d8 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/common/utils.py @@ -78,7 +78,6 @@ utils_opts = [ default=None, help='Explicitly specify the temporary working directory'), ] - CONF = cfg.CONF CONF.register_opts(utils_opts) @@ -425,6 +424,18 @@ def is_valid_ipv6_cidr(address): return False +def validate_ip_multicast_address(address, valid_values=None): + """ + Validates that an IP address is a multicast address. + """ + try: + return netaddr.IPAddress(address).is_multicast() + except Exception: + msg = _("'%s' is not a valid multicast IP address") % address + LOG.debug(msg) + return False + + def get_shortened_ipv6(address): addr = netaddr.IPAddress(address, version=6) return str(addr.ipv6()) diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/openstack.py b/sysinv/sysinv/sysinv/sysinv/conductor/openstack.py index 441a386262..d1c1d2d7e7 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/openstack.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/openstack.py @@ -451,6 +451,14 @@ class OpenStackOperator(object): (ihost.hostname, agg_add_to)) raise + def _get_interface_datanetworks(self, interface): + ifdatanets = self.dbapi.interface_datanetwork_get_by_interface( + interface.uuid) + names = [x.datanetwork_name for x in ifdatanets] + names_csv = ",".join(str(x) for x in names) + + return names_csv + def nova_host_available(self, ihost_uuid): """ Perform sysinv driven nova operations for an available ihost @@ -500,22 +508,22 @@ class OpenStackOperator(object): availability_zone = None aggregate_name_prefix = 'provider_' - ihost_providernets = [] + ihost_datanets = [] - ihost_aggset_provider = set() + host_aggset_datanet = set() nova_aggset_provider = set() - # determine which providernets are on this ihost + # determine which datanets are on this host try: iinterfaces = self.try_interface_get_by_host(ihost_uuid) for interface in iinterfaces: if interface['ifclass'] == constants.INTERFACE_CLASS_DATA: - providernets = interface.providernetworks - for providernet in providernets.split(',') if providernets else []: - ihost_aggset_provider.add(aggregate_name_prefix + - providernet) + datanets = self._get_interface_datanetworks(interface) + for datanet in datanets.split(',') if datanets else []: + host_aggset_datanet.add(aggregate_name_prefix + + datanet) - ihost_providernets = list(ihost_aggset_provider) + ihost_datanets = list(host_aggset_datanet) except Exception: LOG.exception("AGG iinterfaces_get failed for %s." % ihost_uuid) @@ -529,8 +537,8 @@ class OpenStackOperator(object): for aggregate in aggregates: nova_aggset_provider.add(aggregate.name) - if ihost_providernets: - agglist_missing = list(ihost_aggset_provider - nova_aggset_provider) + if ihost_datanets: + agglist_missing = list(host_aggset_datanet - nova_aggset_provider) LOG.debug("AGG agglist_missing = %s." % agglist_missing) for i in agglist_missing: @@ -538,8 +546,8 @@ class OpenStackOperator(object): # use None for the availability zone # cs.aggregates.create(args.name, args.availability_zone) try: - aggregate = self._get_novaclient().aggregates.create(i, - availability_zone) + aggregate = self._get_novaclient().aggregates.create( + i, availability_zone) aggregates.append(aggregate) LOG.debug("AGG6 aggregate= %s. aggregates= %s" % (aggregate, aggregates)) @@ -577,7 +585,7 @@ class OpenStackOperator(object): ihost = self.dbapi.ihost_get(ihost_uuid) for i in aggregates: - if i.name in ihost_providernets: + if i.name in ihost_datanets: metadata = self._get_novaclient().aggregates.get(int(i.id)) nhosts = [] @@ -596,7 +604,7 @@ class OpenStackOperator(object): % (i.id, ihost.hostname)) return False else: - LOG.warn("AGG ihost_providernets empty %s." % ihost_uuid) + LOG.warn("AGG ihost_datanets empty %s." % ihost_uuid) def nova_host_offline(self, ihost_uuid): """ @@ -618,22 +626,21 @@ class OpenStackOperator(object): # aggregate_name_prefix = 'provider_' - ihost_providernets = [] + ihost_datanets = [] - ihost_aggset_provider = set() + host_aggset_datanet = set() nova_aggset_provider = set() - # determine which providernets are on this ihost + # determine which datanets are on this ihost try: iinterfaces = self.try_interface_get_by_host(ihost_uuid) for interface in iinterfaces: if interface['ifclass'] == constants.INTERFACE_CLASS_DATA: - providernets = interface.providernetworks - for providernet in ( - providernets.split(',') if providernets else []): - ihost_aggset_provider.add(aggregate_name_prefix + - providernet) - ihost_providernets = list(ihost_aggset_provider) + datanets = self._get_interface_datanetworks(interface) + for datanet in (datanets.split(',') if datanets else []): + host_aggset_datanet.add(aggregate_name_prefix + + datanet) + ihost_datanets = list(host_aggset_datanet) except Exception: LOG.exception("AGG iinterfaces_get failed for %s." % ihost_uuid) @@ -643,11 +650,11 @@ class OpenStackOperator(object): self.nova_client = None # password may have updated aggregates = self._get_novaclient().aggregates.list() - if ihost_providernets: + if ihost_datanets: for aggregate in aggregates: nova_aggset_provider.add(aggregate.name) else: - LOG.debug("AGG ihost_providernets empty %s." % ihost_uuid) + LOG.debug("AGG ihost_datanets empty %s." % ihost_uuid) # setup the valid set of storage aggregates for host removal aggset_storage = set([ @@ -661,7 +668,7 @@ class OpenStackOperator(object): ihost = self.dbapi.ihost_get(ihost_uuid) for aggregate in aggregates: - if aggregate.name in ihost_providernets or \ + if aggregate.name in ihost_datanets or \ aggregate.name in aggset_storage: # or just do it for all aggs try: LOG.debug("AGG10 remove aggregate id = %s ihost= %s." % diff --git a/sysinv/sysinv/sysinv/sysinv/db/api.py b/sysinv/sysinv/sysinv/sysinv/db/api.py index 769ea3e37a..cb5ea3d4a8 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/api.py @@ -836,7 +836,6 @@ class Connection(object): 'aemode': 'balanced', 'schedpolicy': 'xor', 'txhashpolicy': 'L2', - 'providernetworks': 'physnet0, physnet1' 'extra': { ... }, } :returns: An iinterface. diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py index 9345ea7745..9dbb30b86e 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/api.py @@ -404,6 +404,26 @@ def add_interface_filter_by_ihost(query, value): return query.filter(models.ihost.uuid == value) +def add_datanetwork_filter(query, value): + """Adds a datanetwork-specific filter to a query. + + :param query: Initial query to add filter to. + :param value: Value for filtering results by. + :return: Modified query. + """ + + if uuidutils.is_uuid_like(value): + return query.filter(or_(models.DataNetworksFlat.uuid == value, + models.DataNetworksVlan.uuid == value, + models.DataNetworksVXlan.uuid == value)) + elif utils.is_int_like(value): + return query.filter(or_(models.DataNetworksFlat.id == value, + models.DataNetworksVlan.id == value, + models.DataNetworksVXlan.id == value)) + else: + return add_identity_filter(query, value, use_name=True) + + def add_port_filter_by_numa_node(query, nodeid): """Adds a port-specific numa node filter to a query. @@ -534,9 +554,9 @@ def add_port_filter_by_host_interface(query, hostid, interfaceid): elif utils.is_uuid_like(hostid) and utils.is_uuid_like(interfaceid): query = query.join(models.ihost, - models.iinterface) + models.Interface) return query.filter(models.ihost.uuid == hostid, - models.iinterface.uuid == interfaceid) + models.Interface.uuid == interfaceid) LOG.debug("port_filter_by_host_iinterface: " "No match for supplied filter ids (%s, %s)" @@ -1325,9 +1345,6 @@ class Connection(api.Connection): model_query(models.imemory, read_deleted="no").\ filter_by(forihostid=server_id).\ delete() - model_query(models.iinterface, read_deleted="no").\ - filter_by(forihostid=server_id).\ - delete() model_query(models.idisk, read_deleted="no").\ filter_by(forihostid=server_id).\ delete() @@ -2063,7 +2080,6 @@ class Connection(api.Connection): return query.all() def _iinterface_get(self, iinterface_id, ihost=None, network=None): - # query = model_query(models.iinterface) entity = with_polymorphic(models.Interfaces, '*') query = model_query(entity) query = add_interface_filter(query, iinterface_id) @@ -2215,16 +2231,13 @@ class Connection(api.Connection): if obj.id is None: obj.id = temp_id - # Ensure networktype and providernetworks results are None when they + # Ensure networktype results are None when they # are specified as 'none'. Otherwise the 'none' value is written to # the database which causes issues with checks that expects it to be # the None type if getattr(obj, 'networktype', None) == constants.NETWORK_TYPE_NONE: setattr(obj, 'networktype', None) - if getattr(obj, 'providernetworks', None) == 'none': - setattr(obj, 'providernetworks', None) - try: session.add(obj) session.flush() @@ -2301,7 +2314,7 @@ class Connection(api.Connection): for k, v in values.items(): if k == 'networktype' and v == constants.NETWORK_TYPE_NONE: v = None - if k == 'providernetworks' and v == 'none': + if k == 'datanetworks' and v == 'none': v = None if k == 'uses': del obj.uses[:] @@ -7579,3 +7592,263 @@ class Connection(api.Connection): except NoResultFound: raise exception.KubeAppNotFound(name) query.delete() + + def _datanetwork_get(self, model_class, datanetwork_id, obj=None): + session = None + if obj: + session = inspect(obj).session + query = model_query(model_class, session=session) + + query = add_datanetwork_filter(query, datanetwork_id) + + try: + result = query.one() + except NoResultFound: + raise exception.DataNetworkNotFound( + datanetwork_uuid=datanetwork_id) + except MultipleResultsFound: + raise exception.InvalidParameterValue( + err="Multiple entries found for datanetwork %s" % datanetwork_id) + return result + + def _datanetwork_get_one(self, datanetwork_id, datanetwork=None): + entity = with_polymorphic(models.DataNetworks, '*') + query = model_query(entity) + query = add_datanetwork_filter(query, datanetwork_id) + if datanetwork is not None: + query = query.filter_by(network_type=datanetwork) + try: + result = query.one() + except NoResultFound: + raise exception.DataNetworkNotFound( + datanetwork_uuid=datanetwork_id) + except MultipleResultsFound: + raise exception.InvalidParameterValue( + err="Multiple entries found for datanetwork %s" % datanetwork_id) + + return result + + def _datanetwork_create(self, obj, values): + if not values.get('uuid'): + values['uuid'] = uuidutils.generate_uuid() + + with _session_for_write() as session: + # The id is null for ae interfaces with more than one member interface + temp_id = obj.id + obj.update(values) + if obj.id is None: + obj.id = temp_id + + try: + session.add(obj) + session.flush() + except db_exc.DBDuplicateEntry: + LOG.error("Failed to add datanetwork (uuid: %s), " + "name %s already exists." % + (values['uuid'], values.get('name'))) + + raise exception.DataNetworkAlreadyExists( + name=values.get('name')) + + return self._datanetwork_get(type(obj), values['uuid']) + + @objects.objectify(objects.datanetwork) + def datanetwork_create(self, values): + if not values.get('uuid'): + values['uuid'] = uuidutils.generate_uuid() + + network_type = values.get('network_type') + if network_type == constants.DATANETWORK_TYPE_FLAT: + datanetwork = models.DataNetworksFlat() + elif network_type == constants.DATANETWORK_TYPE_VLAN: + datanetwork = models.DataNetworksVlan() + elif network_type == constants.DATANETWORK_TYPE_VXLAN: + datanetwork = models.DataNetworksVXlan() + else: + raise exception.DataNetworkTypeUnsupported( + network_type=network_type) + return self._datanetwork_create(datanetwork, values) + + @objects.objectify(objects.datanetwork) + def datanetwork_get(self, datanetwork_id): + return self._datanetwork_get_one(datanetwork_id) + + def _add_datanetworks_filters(self, query, filters): + if filters is None: + filters = dict() + supported_filters = {'network_type', + 'name', + } + unsupported_filters = set(filters).difference(supported_filters) + if unsupported_filters: + msg = _("SqlAlchemy API does not support " + "filtering by %s") % ', '.join(unsupported_filters) + raise ValueError(msg) + + for field in supported_filters: + if field in filters: + query = query.filter_by(**{field: filters[field]}) + + return query + + @objects.objectify(objects.datanetwork) + def datanetworks_get_all(self, filters=None, limit=None, marker=None, + sort_key=None, sort_dir=None): + + with _session_for_read() as session: + datanetworks = with_polymorphic(models.DataNetworks, '*') + query = model_query(datanetworks, session=session) + query = self._add_datanetworks_filters(query, filters) + + return _paginate_query(models.DataNetworks, limit, marker, + sort_key, sort_dir, query) + + @objects.objectify(objects.datanetwork) + def datanetwork_update(self, datanetwork_uuid, values): + with _session_for_write() as session: + query = model_query(models.DataNetworks, session=session) + query = add_identity_filter(query, datanetwork_uuid) + + count = query.update(values, synchronize_session='fetch') + if count != 1: + raise exception.DataNetworkNotFound( + datanetwork_uuid=datanetwork_uuid) + return query.one() + + def datanetwork_destroy(self, datanetwork_uuid): + query = model_query(models.DataNetworks) + query = add_identity_filter(query, datanetwork_uuid) + try: + query.one() + except NoResultFound: + raise exception.DataNetworkNotFound( + datanetwork_uuid=datanetwork_uuid) + query.delete() + + def _interface_datanetwork_get(self, uuid, session=None): + query = model_query(models.InterfaceDataNetworks, session=session) + query = add_identity_filter(query, uuid) + try: + result = query.one() + except NoResultFound: + raise exception.InterfaceDataNetworkNotFound(uuid=uuid) + return result + + def _interface_datanetwork_get_all( + self, limit=None, marker=None, + sort_key=None, sort_dir=None): + query = model_query(models.InterfaceDataNetworks) + return _paginate_query( + models.InterfaceDataNetworks, limit, marker, + sort_key, sort_dir, query) + + def _interface_datanetwork_get_by_host( + self, host_uuid, limit=None, marker=None, + sort_key=None, sort_dir=None): + + query = model_query(models.InterfaceDataNetworks) + query = (query. + join(models.Interfaces). + join(models.ihost, + models.ihost.id == models.Interfaces.forihostid)) + query, field = add_filter_by_many_identities( + query, models.ihost, [host_uuid]) + return _paginate_query( + models.InterfaceDataNetworks, limit, marker, + sort_key, sort_dir, query) + + def _interface_datanetwork_get_by_interface( + self, interface_uuid, limit=None, marker=None, + sort_key=None, sort_dir=None): + query = model_query(models.InterfaceDataNetworks) + query = (query.join(models.Interfaces)) + query, field = add_filter_by_many_identities( + query, models.Interfaces, [interface_uuid]) + return _paginate_query(models.InterfaceDataNetworks, + limit, marker, sort_key, sort_dir, query) + + def _interface_datanetwork_get_by_datanetwork( + self, datanetwork_uuid, limit=None, marker=None, + sort_key=None, sort_dir=None): + query = model_query(models.InterfaceDataNetworks) + query = (query.join(models.DataNetworks)) + query, field = add_filter_by_many_identities( + query, models.DataNetworks, [datanetwork_uuid]) + return _paginate_query(models.InterfaceDataNetworks, + limit, marker, sort_key, sort_dir, query) + + def _interface_datanetwork_query(self, values): + query = model_query(models.InterfaceDataNetworks) + query = (query. + filter(models.InterfaceDataNetworks.interface_id == + values['interface_id']). + filter(models.InterfaceDataNetworks.datanetwork_id == + values['datanetwork_id'])) + try: + result = query.one() + except NoResultFound: + raise exception.InterfaceDataNetworkNotFoundByKeys( + interface_id=values['interface_id'], + datanetwork_id=values['datanetwork_id']) + return result + + @objects.objectify(objects.interface_datanetwork) + def interface_datanetwork_create(self, values): + if not values.get('uuid'): + values['uuid'] = uuidutils.generate_uuid() + + interface_datanetwork = models.InterfaceDataNetworks(**values) + with _session_for_write() as session: + try: + session.add(interface_datanetwork) + session.flush() + except db_exc.DBDuplicateEntry: + raise exception.InterfaceDataNetworkAlreadyExists( + interface_id=values['interface_id'], + datanetwork_id=values['datanetwork_id']) + return self._interface_datanetwork_get(values['uuid'], session) + + @objects.objectify(objects.interface_datanetwork) + def interface_datanetwork_get(self, uuid): + return self._interface_datanetwork_get(uuid) + + @objects.objectify(objects.interface_datanetwork) + def interface_datanetwork_get_all( + self, limit=None, marker=None, + sort_key=None, sort_dir=None): + return self._interface_datanetwork_get_all( + limit, marker, sort_key, sort_dir) + + @objects.objectify(objects.interface_datanetwork) + def interface_datanetwork_get_by_host( + self, host_id, limit=None, marker=None, + sort_key=None, sort_dir=None): + return self._interface_datanetwork_get_by_host( + host_id, limit, marker, sort_key, sort_dir) + + @objects.objectify(objects.interface_datanetwork) + def interface_datanetwork_get_by_interface( + self, interface_id, limit=None, marker=None, + sort_key=None, sort_dir=None): + return self._interface_datanetwork_get_by_interface( + interface_id, limit, marker, sort_key, sort_dir) + + @objects.objectify(objects.interface_datanetwork) + def interface_datanetwork_get_by_datanetwork( + self, datanetwork_id, limit=None, marker=None, + sort_key=None, sort_dir=None): + return self._interface_datanetwork_get_by_datanetwork( + datanetwork_id, limit, marker, sort_key, sort_dir) + + def interface_datanetwork_destroy(self, uuid): + query = model_query(models.InterfaceDataNetworks) + query = add_identity_filter(query, uuid) + try: + query.one() + except NoResultFound: + raise exception.InterfaceDataNetworkNotFound(uuid=uuid) + query.delete() + + @objects.objectify(objects.interface_datanetwork) + def interface_datanetwork_query(self, values): + return self._interface_datanetwork_query(values) diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/084_data_networks.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/084_data_networks.py new file mode 100644 index 0000000000..93a0a74e70 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/migrate_repo/versions/084_data_networks.py @@ -0,0 +1,144 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from sqlalchemy import Column, MetaData, Table +from sqlalchemy import DateTime, Integer, String +from sqlalchemy import ForeignKey, UniqueConstraint +from sysinv.common import constants + +ENGINE = 'InnoDB' +CHARSET = 'utf8' + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + Table('interfaces', meta, autoload=True) + + datanetworks = Table( + 'datanetworks', + meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('id', Integer, primary_key=True, nullable=False), + Column('uuid', String(36), unique=True), + Column('name', String(255), unique=True), + Column('network_type', String(255)), + Column('description', String(255)), + Column('mtu', Integer, nullable=False), + mysql_engine=ENGINE, + mysql_charset=CHARSET, + ) + + datanetworks_flat = Table( + 'datanetworks_flat', + meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('id', Integer, + ForeignKey('datanetworks.id', ondelete="CASCADE"), + primary_key=True, nullable=False), + + mysql_engine=ENGINE, + mysql_charset=CHARSET, + ) + + datanetworks_vlan = Table( + 'datanetworks_vlan', + meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('id', Integer, + ForeignKey('datanetworks.id', ondelete="CASCADE"), + primary_key=True, nullable=False), + + mysql_engine=ENGINE, + mysql_charset=CHARSET, + ) + + datanetworks_vxlan = Table( + 'datanetworks_vxlan', + meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('id', Integer, + ForeignKey('datanetworks.id', ondelete="CASCADE"), + primary_key=True, nullable=False), + + Column('multicast_group', String(64), nullable=True), + Column('port_num', Integer, nullable=False), + Column('ttl', Integer, nullable=False), + Column('mode', String(32), nullable=False, + default=constants.DATANETWORK_MODE_DYNAMIC), + + mysql_engine=ENGINE, + mysql_charset=CHARSET, + ) + + interface_datanetworks = Table( + 'interface_datanetworks', + meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('id', Integer, primary_key=True, nullable=False), + Column('uuid', String(36), unique=True), + + Column('interface_id', Integer, + ForeignKey('interfaces.id', ondelete='CASCADE')), + Column('datanetwork_id', Integer, + ForeignKey('datanetworks.id', ondelete='CASCADE')), + + UniqueConstraint('interface_id', 'datanetwork_id', + name='u_interface_id@datanetwork_id'), + mysql_engine=ENGINE, + mysql_charset=CHARSET, + ) + + tables = ( + datanetworks, + datanetworks_flat, + datanetworks_vlan, + datanetworks_vxlan, + interface_datanetworks, + ) + + for index, table in enumerate(tables): + try: + table.create() + except Exception: + # If an error occurs, drop all tables created so far to return + # to the previously existing state. + meta.drop_all(tables=tables[:index]) + raise + + ethernet_interfaces = Table('ethernet_interfaces', meta, autoload=True) + ethernet_interfaces.drop_column('providernetworks') + ethernet_interfaces.drop_column('providernetworksdict') + + ae_interfaces = Table('ae_interfaces', meta, autoload=True) + ae_interfaces.drop_column('providernetworks') + ae_interfaces.drop_column('providernetworksdict') + + vlan_interfaces = Table('vlan_interfaces', meta, autoload=True) + vlan_interfaces.drop_column('providernetworks') + vlan_interfaces.drop_column('providernetworksdict') + + virtual_interfaces = Table('virtual_interfaces', meta, autoload=True) + virtual_interfaces.drop_column('providernetworks') + virtual_interfaces.drop_column('providernetworksdict') + + +def downgrade(migrate_engine): + # As per other openstack components, downgrade is + # unsupported in this release. + raise NotImplementedError('SysInv database downgrade is unsupported.') diff --git a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py index 80f02b8053..5aa47d91b1 100644 --- a/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py +++ b/sysinv/sysinv/sysinv/sysinv/db/sqlalchemy/models.py @@ -17,7 +17,8 @@ # # Copyright (c) 2013-2018 Wind River Systems, Inc. # - +# SPDX-License-Identifier: Apache-2.0 +# """ SQLAlchemy models for sysinv data. """ @@ -26,6 +27,7 @@ import json from six.moves.urllib.parse import urlparse from oslo_config import cfg +from oslo_db.sqlalchemy import models from sqlalchemy import Column, ForeignKey, Integer, BigInteger, Boolean from sqlalchemy import Enum, UniqueConstraint, String, Table, Text, Float @@ -35,7 +37,7 @@ from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.types import TypeDecorator, VARCHAR from sqlalchemy.orm import relationship, backref -from oslo_db.sqlalchemy import models +from sysinv.common import constants sql_opts = [ cfg.StrOpt('mysql_engine', @@ -321,32 +323,6 @@ class imemory(Base): UniqueConstraint('forihostid', 'forinodeid', name='u_hostnode') -class iinterface(Base): - __tablename__ = 'i_interface' - - id = Column(Integer, primary_key=True, nullable=False) - uuid = Column(String(36)) - - ifname = Column(String(255)) - iftype = Column(String(255)) - imac = Column(String(255), unique=True) - imtu = Column(Integer) - networktype = Column(String(255)) - aemode = Column(String(255)) # e.g. balanced, active_standby - aedict = Column(JSONEncodedDict) # e.g. 802.3ad parameters - txhashpolicy = Column(String(255)) # e.g. L2, L2L3, L3L4 - providernetworks = Column(String(255)) # ['physnet0','physnet1'] - providernetworksdict = Column(JSONEncodedDict) - schedpolicy = Column(String(255)) - ifcapabilities = Column(JSONEncodedDict) - sriov_numvfs = Column(Integer) - # JSON{'mode':"xor", 'bond':'false'} - - farend = Column(JSONEncodedDict) - forihostid = Column(Integer, ForeignKey('i_host.id', ondelete='CASCADE')) - UniqueConstraint('ifname', 'forihostid', name='u_ifnameihost') - - interfaces_to_interfaces = Table("interfaces_to_interfaces", Base.metadata, Column("used_by_id", Integer, ForeignKey("interfaces.id", ondelete='CASCADE'), primary_key=True), Column("uses_id", Integer, ForeignKey("interfaces.id", ondelete='CASCADE'), primary_key=True) @@ -408,8 +384,6 @@ class EthernetCommon(object): imac = Column(String(255)) imtu = Column(Integer) - providernetworks = Column(String(255)) # ['physnet0','physnet1'] - providernetworksdict = Column(JSONEncodedDict) class EthernetInterfaces(EthernetCommon, Interfaces): @@ -1180,6 +1154,85 @@ class InterfaceNetworks(Base): UniqueConstraint('interface_id', 'network_id', name='u_interface_id@network_id') +class DataNetworks(Base): + __tablename__ = 'datanetworks' + id = Column(Integer, primary_key=True, nullable=False) + uuid = Column(String(36), unique=True) + name = Column(String(255), unique=True) + network_type = Column(String(255)) + description = Column(String(255)) + mtu = Column(Integer) + + __mapper_args__ = { + 'polymorphic_identity': 'datanetwork', + 'polymorphic_on': network_type + } + + +class DataNetworksCommon(object): + @declared_attr + def id(cls): + return Column(Integer, + ForeignKey('datanetworks.id', ondelete="CASCADE"), + primary_key=True, nullable=False) + + +class DataNetworksFlat(DataNetworksCommon, DataNetworks): + __tablename__ = 'datanetworks_flat' + + __mapper_args__ = { + 'polymorphic_identity': 'flat', + } + + +class DataNetworksVlan(DataNetworksCommon, DataNetworks): + __tablename__ = 'datanetworks_vlan' + + __mapper_args__ = { + 'polymorphic_identity': 'vlan', + } + + +class DataNetworksVXlan(DataNetworksCommon, DataNetworks): + __tablename__ = 'datanetworks_vxlan' + + # IP address of the multicast group + multicast_group = Column(String(64), nullable=True) + + # Destination DP port for all instances + port_num = Column(Integer, nullable=False) + + # Time-to-live value for all instances + ttl = Column(Integer, nullable=False) + + # defines dynamic learning with multicast enable/disabled + mode = Column(String(32), nullable=False, + default=constants.DATANETWORK_MODE_DYNAMIC) + + __mapper_args__ = { + 'polymorphic_identity': 'vxlan', + } + + +class InterfaceDataNetworks(Base): + __tablename__ = 'interface_datanetworks' + + id = Column(Integer, primary_key=True, nullable=False) + uuid = Column(String(36), unique=True) + + interface_id = Column( + Integer, ForeignKey('interfaces.id', ondelete='CASCADE')) + datanetwork_id = Column( + Integer, ForeignKey('datanetworks.id', ondelete='CASCADE')) + + interface = relationship( + "Interfaces", lazy="joined", backref="interface_datanetworks") + datanetwork = relationship( + "DataNetworks", lazy="joined", backref="interface_datanetworks") + UniqueConstraint( + 'interface_id', 'datanetwork_id', name='u_interface_id@datanetwork_id') + + class SensorGroups(Base): __tablename__ = 'i_sensorgroups' diff --git a/sysinv/sysinv/sysinv/sysinv/helm/neutron.py b/sysinv/sysinv/sysinv/sysinv/helm/neutron.py index b7c353d5e3..2d59b13e98 100644 --- a/sysinv/sysinv/sysinv/sysinv/helm/neutron.py +++ b/sysinv/sysinv/sysinv/sysinv/helm/neutron.py @@ -192,15 +192,17 @@ class NeutronHelm(openstack.OpenstackBaseHelm): # obtain the assigned bridge for interface brname = 'br-phy%d' % index if brname: - providernets = self._get_interface_providernets(iface) - for providernet in providernets: + datanets = self._get_interface_datanets(iface) + for datanet in datanets: + LOG.info("_get_dynamic_ovs_agent_config datanet %s" % + datanet) address = self._get_interface_primary_address( self.context, host, iface) if address: local_ip = address - tunnel_types = constants.NEUTRON_PROVIDERNET_VXLAN + tunnel_types = constants.DATANETWORK_TYPE_VXLAN else: - bridge_mappings += ('%s:%s,' % (providernet, brname)) + bridge_mappings += ('%s:%s,' % (datanet, brname)) index += 1 agent = {} @@ -217,6 +219,12 @@ class NeutronHelm(openstack.OpenstackBaseHelm): if bridge_mappings: ovs['bridge_mappings'] = str(bridge_mappings) + # https://access.redhat.com/documentation/en-us/ + # red_hat_enterprise_linux_openstack_platform/7/html/ + # networking_guide/bridge-mappings + # required for vlan, not flat, vxlan: + # ovs['network_vlan_ranges'] = physnet1:10:20,physnet2:21:25 + return { 'agent': agent, 'ovs': ovs, @@ -230,11 +238,11 @@ class NeutronHelm(openstack.OpenstackBaseHelm): for iface in sorted(self.dbapi.iinterface_get_by_ihost(host.id), key=self._interface_sort_key): if self._is_sriov_network_type(iface): - # obtain the assigned providernets for interface - providernets = self._get_interface_providernets(iface) + # obtain the assigned datanets for interface + datanets = self._get_interface_datanets(iface) port_name = self._get_interface_port_name(iface) - for providernet in providernets: - physical_device_mappings += ('%s:%s,' % (providernet, port_name)) + for datanet in datanets: + physical_device_mappings += ('%s:%s,' % (datanet, port_name)) sriov_nic = { 'physical_device_mappings': str(physical_device_mappings), } @@ -260,10 +268,6 @@ class NeutronHelm(openstack.OpenstackBaseHelm): 'router_status_managed': True, 'vlan_transparent': True, 'wsgi_default_pool_size': 100, - 'router_scheduler_driver': - 'neutron.scheduler.l3_host_agent_scheduler.HostBasedScheduler', - 'network_scheduler_driver': - 'neutron.scheduler.dhcp_host_agent_scheduler.HostBasedScheduler', 'notify_nova_on_port_data_changes': True, 'notify_nova_on_port_status_changes': True, 'host_driver': @@ -292,6 +296,15 @@ class NeutronHelm(openstack.OpenstackBaseHelm): return neutron_config + def _get_ml2_physical_network_mtus(self): + ml2_physical_network_mtus = [] + datanetworks = self.dbapi.datanetworks_get_all() + for datanetwork in datanetworks: + dn_str = str(datanetwork.name) + ":" + str(datanetwork.mtu) + ml2_physical_network_mtus.append(dn_str) + + return ",".join(ml2_physical_network_mtus) + def _get_neutron_ml2_config(self): ml2_config = { 'ml2': { @@ -299,12 +312,14 @@ class NeutronHelm(openstack.OpenstackBaseHelm): 'tenant_network_types': 'vlan,vxlan', 'mechanism_drivers': 'openvswitch,sriovnicswitch,l2population', 'path_mtu': 0, + 'physical_network_mtus': self._get_ml2_physical_network_mtus() }, 'securitygroup': { 'firewall_driver': 'noop', }, } + LOG.info("_get_neutron_ml2_config=%s" % ml2_config) return ml2_config def _is_data_network_type(self, iface): @@ -315,14 +330,14 @@ class NeutronHelm(openstack.OpenstackBaseHelm): networktypelist = utils.get_network_type_list(iface) return bool(any(n in SRIOV_NETWORK_TYPES for n in networktypelist)) - def _get_interface_providernets(self, iface): + def _get_interface_datanets(self, iface): """ - Return the provider networks of the supplied interface as a list. + Return the data networks of the supplied interface as a list. """ - providernetworks = iface['providernetworks'] - if not providernetworks: - return [] - return [x.strip() for x in providernetworks.split(',')] + + ifdatanets = self.dbapi.interface_datanetwork_get_by_interface( + iface.uuid) + return [ifdn['datanetwork_name'].strip() for ifdn in ifdatanets] def _get_interface_port_name(self, iface): """ diff --git a/sysinv/sysinv/sysinv/sysinv/objects/__init__.py b/sysinv/sysinv/sysinv/sysinv/objects/__init__.py index 5003794329..a7b754799b 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/__init__.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/__init__.py @@ -27,6 +27,7 @@ from sysinv.objects import cluster from sysinv.objects import community from sysinv.objects import controller_fs from sysinv.objects import cpu +from sysinv.objects import datanetwork from sysinv.objects import disk from sysinv.objects import firewallrules from sysinv.objects import partition @@ -41,6 +42,7 @@ from sysinv.objects import network_infra from sysinv.objects import interface from sysinv.objects import interface_ae from sysinv.objects import interface_ethernet +from sysinv.objects import interface_datanetwork from sysinv.objects import interface_network from sysinv.objects import interface_virtual from sysinv.objects import interface_vlan @@ -128,6 +130,7 @@ ae_interface = interface_ae.AEInterface virtual_interface = interface_virtual.VirtualInterface vlan_interface = interface_vlan.VLANInterface interface_network = interface_network.InterfaceNetwork +interface_datanetwork = interface_datanetwork.InterfaceDataNetwork port = port.Port ethernet_port = port_ethernet.EthernetPort disk = disk.Disk @@ -183,6 +186,7 @@ storage_ceph_external = storage_ceph_external.StorageCephExternal helm_overrides = helm_overrides.HelmOverrides label = label.Label kube_app = kube_app.KubeApp +datanetwork = datanetwork.DataNetwork __all__ = (system, cluster, @@ -251,6 +255,8 @@ __all__ = (system, storage_ceph_external, helm_overrides, kube_app, + datanetwork, + interface_network, # alias objects for RPC compatibility ihost, ilvg, diff --git a/sysinv/sysinv/sysinv/sysinv/objects/datanetwork.py b/sysinv/sysinv/sysinv/sysinv/objects/datanetwork.py new file mode 100644 index 0000000000..0d61839ed5 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/objects/datanetwork.py @@ -0,0 +1,42 @@ +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# coding=utf-8 +# + +from sysinv.db import api as db_api +from sysinv.objects import base +from sysinv.objects import utils + + +class DataNetwork(base.SysinvObject): + VERSION = '1.0' + + dbapi = db_api.get_instance() + + fields = {'id': int, + 'uuid': utils.uuid_or_none, + 'network_type': utils.str_or_none, + 'name': utils.str_or_none, + 'description': utils.str_or_none, + 'mtu': utils.int_or_none, + 'multicast_group': utils.str_or_none, + 'port_num': utils.int_or_none, + 'ttl': utils.int_or_none, + 'mode': utils.str_or_none, + } + + _optional_fields = {'port_num', + 'multicast_group', + 'ttl', + 'mode'} + + @base.remotable_classmethod + def get_by_uuid(cls, context, uuid): + return cls.dbapi.datanetwork_get(uuid) + + def save_changes(self, context, updates): + self.dbapi.datanetwork_update(self.uuid, updates) diff --git a/sysinv/sysinv/sysinv/sysinv/objects/interface.py b/sysinv/sysinv/sysinv/sysinv/objects/interface.py index 26855361d1..1db20e1727 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/interface.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/interface.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2013-2016 Wind River Systems, Inc. +# Copyright (c) 2013-2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -88,6 +88,15 @@ def get_networks(field, db_object): return result +def get_datanetworks(field, db_object): + result = [] + if hasattr(db_object, 'interface_datanetworks'): + for entry in getattr(db_object, 'interface_datanetworks', []): + id_str = str(entry.datanetwork_id) + result.append(id_str) + return result + + class Interface(base.SysinvObject): # VERSION 1.0: Initial version # VERSION 1.1: Added VLAN and uses/used_by interface support @@ -110,9 +119,8 @@ class Interface(base.SysinvObject): 'aemode': utils.str_or_none, 'schedpolicy': utils.str_or_none, 'txhashpolicy': utils.str_or_none, - 'providernetworks': utils.str_or_none, - 'providernetworksdict': utils.dict_or_none, 'networks': utils.list_of_strings_or_none, + 'datanetworks': utils.list_of_strings_or_none, 'ifcapabilities': utils.dict_or_none, @@ -136,7 +144,8 @@ class Interface(base.SysinvObject): 'ipv4_pool': get_ipv4_address_pool, 'ipv6_pool': get_ipv6_address_pool, 'ihost_uuid': get_host_uuid, - 'networks': get_networks} + 'networks': get_networks, + 'datanetworks': get_datanetworks} _optional_fields = ['aemode', 'txhashpolicy', 'schedpolicy', 'vlan_id', 'vlan_type'] diff --git a/sysinv/sysinv/sysinv/sysinv/objects/interface_datanetwork.py b/sysinv/sysinv/sysinv/sysinv/objects/interface_datanetwork.py new file mode 100644 index 0000000000..5fc8a85936 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/objects/interface_datanetwork.py @@ -0,0 +1,110 @@ +# +# Copyright (c) 2019 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# coding=utf-8 +# + +from sysinv.db import api as db_api +from sysinv.objects import base +from sysinv.objects import utils + +from oslo_log import log + +LOG = log.getLogger(__name__) + + +def _get_mtu(field, db_object): + mtu = None + datanetwork = getattr(db_object, 'datanetwork', None) + if hasattr(datanetwork, 'mtu'): + mtu = datanetwork.mtu + return mtu + + +def _get_multicast_group(field, db_object): + multicast_group = None + datanetwork = getattr(db_object, 'datanetwork', None) + if hasattr(datanetwork, 'multicast_group'): + multicast_group = datanetwork.multicast_group + return multicast_group + + +def _get_port_num(field, db_object): + port_num = None + datanetwork = getattr(db_object, 'datanetwork', None) + if hasattr(datanetwork, 'port_num'): + port_num = datanetwork.port_num + return port_num + + +def _get_ttl(field, db_object): + ttl = None + datanetwork = getattr(db_object, 'datanetwork', None) + if hasattr(datanetwork, 'ttl'): + ttl = datanetwork.ttl + return ttl + + +def _get_mode(field, db_object): + mode = None + datanetwork = getattr(db_object, 'datanetwork', None) + if hasattr(datanetwork, 'mode'): + mode = datanetwork.mode + return mode + + +class InterfaceDataNetwork(base.SysinvObject): + VERSION = '1.0' + + dbapi = db_api.get_instance() + + fields = { + 'id': int, + 'uuid': utils.uuid_or_none, + 'forihostid': utils.int_or_none, + 'interface_id': utils.int_or_none, + 'interface_uuid': utils.uuid_or_none, + 'ifname': utils.str_or_none, + 'datanetwork_id': utils.int_or_none, + 'datanetwork_uuid': utils.uuid_or_none, + 'datanetwork_name': utils.str_or_none, + 'datanetwork_network_type': utils.str_or_none, + 'datanetwork_description': utils.str_or_none, + 'datanetwork_mtu': utils.int_or_none, + 'datanetwork_port_num': utils.int_or_none, + 'datanetwork_multicast_group': utils.str_or_none, + 'datanetwork_ttl': utils.int_or_none, + 'datanetwork_mode': utils.str_or_none, + } + + _foreign_fields = { + 'forihostid': 'interface:forihostid', + 'interface_id': 'interface:id', + 'interface_uuid': 'interface:uuid', + 'ifname': 'interface:ifname', + 'datanetwork_uuid': 'datanetwork:uuid', + 'datanetwork_id': 'datanetwork:id', + 'datanetwork_name': 'datanetwork:name', + 'datanetwork_network_type': 'datanetwork:network_type', + 'datanetwork_description': 'datanetwork:description', + 'datanetwork_mtu': _get_mtu, + 'datanetwork_port_num': _get_port_num, + 'datanetwork_multicast_group': _get_multicast_group, + 'datanetwork_ttl': _get_ttl, + 'datanetwork_mode': _get_mode, + } + + _optional_fields = { + 'datanetwork_port_num', + 'datanetwork_multicast_group', + 'datanetwork_ttl', + 'datanetwork_mode', + } + + @base.remotable_classmethod + def get_by_uuid(cls, context, uuid): + return cls.dbapi.interface_datanetwork_get(uuid) diff --git a/sysinv/sysinv/sysinv/sysinv/objects/interface_ethernet.py b/sysinv/sysinv/sysinv/sysinv/objects/interface_ethernet.py index 257ae4ed5f..470fedd72d 100644 --- a/sysinv/sysinv/sysinv/sysinv/objects/interface_ethernet.py +++ b/sysinv/sysinv/sysinv/sysinv/objects/interface_ethernet.py @@ -18,8 +18,6 @@ class EthernetInterface(interface_base.InterfaceBase): fields = dict({ 'imtu': utils.int_or_none, 'imac': utils.str_or_none, - 'providernetworks': utils.str_or_none, - 'providernetworksdict': utils.dict_or_none, }, **interface_base.InterfaceBase.fields) @base.remotable_classmethod diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/interface.py b/sysinv/sysinv/sysinv/sysinv/puppet/interface.py index 303e3122f6..6c32dbccad 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/interface.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/interface.py @@ -122,13 +122,14 @@ class InterfacePuppet(base.BasePuppet): 'system_mode': self._get_system().system_mode, 'ports': self._get_port_interface_id_index(host), 'interfaces': self._get_interface_name_index(host), + 'interfaces_datanets': self._get_interface_name_datanets(host), 'devices': self._get_port_pciaddr_index(host), 'addresses': self._get_address_interface_name_index(host), 'routes': self._get_routes_interface_name_index(host), 'networks': self._get_network_type_index(), 'gateways': self._get_gateway_index(), 'floatingips': self._get_floating_ip_index(), - 'providernets': self._get_provider_networks(host), + 'datanets': self._get_datanetworks(host), } return context @@ -160,6 +161,41 @@ class InterfacePuppet(base.BasePuppet): interfaces = {} for iface in self.dbapi.iinterface_get_by_ihost(host.id): interfaces[iface.ifname] = iface + + return interfaces + + def _get_interface_name_datanets(self, host): + """ + Builds a dictionary of datanets indexed by interface name. + """ + interfaces = {} + for iface in self.dbapi.iinterface_get_by_ihost(host.id): + ifdatanets = self.dbapi.interface_datanetwork_get_by_interface( + iface.uuid) + + datanetworks = [] + for ifdatanet in ifdatanets: + datanetworks.append(ifdatanet.datanetwork_uuid) + + datanetworks_list = [] + for datanetwork in datanetworks: + dn = self.dbapi.datanetwork_get(datanetwork) + datanetwork_dict = \ + {'name': dn.name, + 'uuid': dn.uuid, + 'network_type': dn.network_type, + 'mtu': dn.mtu} + if dn.network_type == constants.DATANETWORK_TYPE_VXLAN: + datanetwork_dict.update( + {'multicast_group': dn.multicast_group, + 'port_num': dn.port_num, + 'ttl': dn.ttl, + 'mode': dn.mode}) + datanetworks_list.append(datanetwork_dict) + interfaces[iface.ifname] = datanetworks_list + + LOG.debug("_get_interface_name_datanets ifdatanet=%s" % interfaces) + return interfaces def _get_port_pciaddr_index(self, host): @@ -277,17 +313,11 @@ class InterfacePuppet(base.BasePuppet): return floating_ips - def _get_provider_networks(self, host): - # TODO(alegacy): this will not work as intended for upgrades of AIO-SX - # and -DX. The call to get_providernetworksdict will return an empty - # dictionary because the neutron endpoint is not available yet. Since - # we do not currently support SDN/OVS over upgrades we will need to - # deal with this in a later commit. - pnets = {} - if (self.openstack and - constants.WORKER in utils.get_personalities(host)): - pnets = self.openstack.get_providernetworksdict(quiet=True) - return pnets + def _get_datanetworks(self, host): + dnets = {} + if constants.WORKER in utils.get_personalities(host): + dnets = self.dbapi.datanetworks_get_all() + return dnets def is_platform_network_type(iface): @@ -461,14 +491,11 @@ def get_interface_mtu(context, iface): return iface['imtu'] -def get_interface_providernets(iface): +def get_interface_datanets(context, iface): """ - Return the provider networks of the supplied interface as a list. + Return the list of data networks of the supplied interface """ - providernetworks = iface['providernetworks'] - if not providernetworks: - return [] - return [x.strip() for x in providernetworks.split(',')] + return context['interfaces_datanets'][iface.ifname] def get_interface_port(context, iface): diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/neutron.py b/sysinv/sysinv/sysinv/sysinv/puppet/neutron.py index 15ee1e3999..ddd3cb5338 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/neutron.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/neutron.py @@ -10,6 +10,9 @@ from sysinv.common import utils from sysinv.puppet import interface from sysinv.puppet import openstack +from oslo_log import log +LOG = log.getLogger(__name__) + class NeutronPuppet(openstack.OpenstackBasePuppet): """Class to encapsulate puppet operations for neutron configuration""" @@ -164,9 +167,14 @@ class NeutronPuppet(openstack.OpenstackBasePuppet): for iface in self.context['interfaces'].values(): if (iface['ifclass'] in [constants.INTERFACE_CLASS_PCI_SRIOV]): port = interface.get_interface_port(self.context, iface) - providernets = interface.get_interface_providernets(iface) - for net in providernets: - device_mappings.append("%s:%s" % (net, port['name'])) + + datanets = interface.get_interface_datanets( + self.context, iface) + for dnet in datanets: + device_mappings.append( + "%s:%s" % (dnet['name'], port['name'])) + LOG.debug("get_host_config device_mappings=%s" % + device_mappings) config = { 'neutron::agents::ml2::sriov::physical_device_mappings': diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/nova.py b/sysinv/sysinv/sysinv/sysinv/puppet/nova.py index 90ca787825..80412b25c4 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/nova.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/nova.py @@ -17,6 +17,9 @@ from sysinv.common import utils from sysinv.puppet import openstack from sysinv.puppet import interface +from oslo_log import log +LOG = log.getLogger(__name__) + SCHEDULER_FILTERS_COMMON = [ 'RetryFilter', @@ -580,6 +583,13 @@ class NovaPuppet(openstack.OpenstackBasePuppet): return "\"%s\"" % ','.join( "%r:%r" % (node, cpu) for node, cpu in cpu_map.items()) + def _get_datanetwork_names(self, iface): + dnets = interface.get_interface_datanets( + self.context, iface) + dnames_list = [dnet['name'] for dnet in dnets] + dnames = ",".join(dnames_list) + return dnames + def _get_pci_pt_whitelist(self, host): # Process all configured PCI passthrough interfaces and add them to # the list of devices to whitelist @@ -587,10 +597,13 @@ class NovaPuppet(openstack.OpenstackBasePuppet): for iface in self.context['interfaces'].values(): if iface['ifclass'] in [constants.INTERFACE_CLASS_PCI_PASSTHROUGH]: port = interface.get_interface_port(self.context, iface) + + dnames = self._get_datanetwork_names(iface) device = { 'address': port['pciaddr'], - 'physical_network': iface['providernetworks'] + 'physical_network': dnames } + LOG.debug("_get_pci_pt_whitelist device=%s" % device) devices.append(device) # Process all enabled PCI devices configured for PT and SRIOV and @@ -616,11 +629,13 @@ class NovaPuppet(openstack.OpenstackBasePuppet): for iface in self.context['interfaces'].values(): if iface['ifclass'] in [constants.INTERFACE_CLASS_PCI_SRIOV]: port = interface.get_interface_port(self.context, iface) + dnames = self._get_datanetwork_names(iface) device = { 'address': port['pciaddr'], - 'physical_network': iface['providernetworks'], + 'physical_network': dnames, 'sriov_numvfs': iface['sriov_numvfs'] } + LOG.info("_get_pci_sriov_whitelist device=%s" % device) devices.append(device) return json.dumps(devices) if devices else None diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py b/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py index ced5410d35..4f932ec4e7 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/ovs.py @@ -4,12 +4,15 @@ # SPDX-License-Identifier: Apache-2.0 # +from oslo_log import log from sysinv.common import constants from sysinv.common import utils from sysinv.puppet import base from sysinv.puppet import interface +LOG = log.getLogger(__name__) + class OVSPuppet(base.BasePuppet): """Class to encapsulate puppet operations for vswitch configuration""" @@ -90,12 +93,10 @@ class OVSPuppet(base.BasePuppet): index += 1 - # currently only one provider network is supported per - # interface, therefore obtain first entry - providernet = interface.get_interface_providernets(iface)[0] + datanets = interface.get_interface_datanets(self.context, iface) # setup tunnel address if assigned provider network is vxlan - if self._is_vxlan_providernet(providernet): + if datanets and self._is_vxlan_datanet(datanets[0]): address = interface.get_interface_primary_address( self.context, iface) if address: @@ -105,7 +106,7 @@ class OVSPuppet(base.BasePuppet): 'prefixlen': address['prefix'], } - return { + ovs_dict = { 'platform::vswitch::ovs::devices': ovs_devices, 'platform::vswitch::ovs::bridges': ovs_bridges, 'platform::vswitch::ovs::ports': ovs_ports, @@ -113,6 +114,10 @@ class OVSPuppet(base.BasePuppet): 'platform::vswitch::ovs::flows': ovs_flows, } + LOG.debug("_get_port_config=%s" % ovs_dict) + + return ovs_dict + def _get_ethernet_device(self, iface): if interface.is_a_mellanox_device(self.context, iface): # Mellanox devices are not bound to the DPDK driver @@ -370,6 +375,9 @@ class OVSPuppet(base.BasePuppet): }) return config + def _is_vxlan_datanet(self, datanet): + return datanet.get('network_type') == constants.DATANETWORK_TYPE_VXLAN + def _get_neutron_config(self, host): local_ip = None tunnel_types = set() @@ -379,24 +387,29 @@ class OVSPuppet(base.BasePuppet): # obtain the assigned bridge for interface brname = iface.get('_ovs_bridge') if brname: - providernets = interface.get_interface_providernets(iface) - for providernet in providernets: - if self._is_vxlan_providernet(providernet): - address = interface.get_interface_primary_address( - self.context, iface) + datanets = interface.get_interface_datanets( + self.context, iface) + for datanet in datanets: + if self._is_vxlan_datanet(datanet): + address = \ + interface.get_interface_primary_address( + self.context, iface) if address: local_ip = address['address'] tunnel_types.add( - constants.NEUTRON_PROVIDERNET_VXLAN) + constants.DATANETWORK_TYPE_VXLAN) else: bridge_mappings.append('%s:%s' % - (providernet, brname)) + (datanet['name'], brname)) - return { + neutron_dict = { 'neutron::agents::ml2::ovs::local_ip': local_ip, 'neutron::agents::ml2::ovs::tunnel_types': list(tunnel_types), 'neutron::agents::ml2::ovs::bridge_mappings': bridge_mappings } + LOG.debug("OVS get_neutron_config neutron_dict=%s" % neutron_dict) + + return neutron_dict def _get_providernet_type(self, name): if name in self.context['providernets']: diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface.py b/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface.py index 2313cbe2d0..0256a69d0b 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/test_interface.py @@ -12,6 +12,7 @@ Tests for the API /interfaces/ methods. """ import mock +import testtools from six.moves import http_client from sysinv.api.controllers.v1 import interface as api_if_v1 @@ -107,7 +108,7 @@ providernet_list = { "port": 8472, "ttl": 10}}], "vlan_transparent": False, "type": "vxlan", - "id": "da9f7bb1-2114-4ffd-8a4c-9ca215d98fa2", + "id": "da9f7bb1-2114-4ffd-8a4c-9ca215d98fa4", "name": "group0-ext2"}, 'group0-ext3': { "status": "ACTIVE", "description": None, @@ -121,7 +122,7 @@ providernet_list = { "port": 8472, "ttl": 10}}], "vlan_transparent": False, "type": "vxlan", - "id": "da9f7bb1-2114-4ffd-8a4c-9ca215d98fa2", + "id": "da9f7bb1-2114-4ffd-8a4c-9ca215d98fa5", "name": "group0-ext3"}, 'group0-flat': { "status": "ACTIVE", "description": None, @@ -130,13 +131,12 @@ providernet_list = { "id": "72f21b11-6d17-486e-a4e6-4eaf5f00f23e", "name": "group0-flat-r0-0", "tenant_id": None, "maximum": 4, - "tenant_id": None, "maximum": 4, "shared": True, "vxlan": {"group": "239.0.2.1", "port": 8472, "ttl": 10}}], "vlan_transparent": False, "type": "flat", - "id": "da9f7bb1-2114-4ffd-8a4c-9ca215d98fa3", + "id": "da9f7bb1-2114-4ffd-8a4c-9ca215d98fa6", "name": "group0-flat"} } @@ -261,8 +261,26 @@ class InterfaceTestCase(base.FunctionalTest): self.worker = host return + def _create_datanetworks(self): + for name, v in providernet_list.items(): + dn_values = { + 'name': name, + 'uuid': v.get('id', None), + 'network_type': v['type'], + 'mtu': v['mtu']} + if v['type'] == constants.DATANETWORK_TYPE_VXLAN: + for r in v['ranges']: + dn_values.update( + {'multicast_group': r['vxlan'].get('group'), + 'port_num': r['vxlan'].get('port'), + 'ttl': r['vxlan'].get('ttl'), + 'mode': r['vxlan'].get('mode', 'dynamic'), + }) + + dbutils.create_test_datanetwork(**dn_values) + def _create_ethernet(self, ifname=None, networktype=None, ifclass=None, - providernetworks=None, host=None, expect_errors=False): + datanetworks=None, host=None, expect_errors=False): if not isinstance(networktype, list): networktypelist = [networktype] else: @@ -308,7 +326,7 @@ class InterfaceTestCase(base.FunctionalTest): ifclass=ifclass, networktype=networktype, networks=networks, - providernetworks=providernetworks, + datanetworks=datanetworks, forihostid=host.id, ihost_uuid=host.uuid) response = self._post_and_check(interface, expect_errors) @@ -322,7 +340,7 @@ class InterfaceTestCase(base.FunctionalTest): return port, interface def _create_bond(self, ifname, networktype=None, ifclass=None, - providernetworks=None, host=None, expect_errors=False): + datanetworks=None, host=None, expect_errors=False): if not isinstance(networktype, list): networktypelist = [networktype] else: @@ -357,7 +375,7 @@ class InterfaceTestCase(base.FunctionalTest): networks=networks, uses=[iface1['ifname'], iface2['ifname']], txhashpolicy='layer2', - providernetworks=providernetworks, + datanetworks=datanetworks, forihostid=host.id, ihost_uuid=host.uuid) lacp_types = [constants.NETWORK_TYPE_MGMT, @@ -378,12 +396,12 @@ class InterfaceTestCase(base.FunctionalTest): return interface def _create_worker_bond(self, ifname, networktype=None, ifclass=None, - providernetworks=None, expect_errors=False): - return self._create_bond(ifname, networktype, ifclass, providernetworks, + datanetworks=None, expect_errors=False): + return self._create_bond(ifname, networktype, ifclass, datanetworks, self.worker, expect_errors) def _create_vlan(self, ifname, networktype, ifclass, vlan_id, - lower_iface=None, providernetworks=None, host=None, + lower_iface=None, datanetworks=None, host=None, expect_errors=False): if not isinstance(networktype, list): networktypelist = [networktype] @@ -417,7 +435,7 @@ class InterfaceTestCase(base.FunctionalTest): networks=networks, vlan_id=vlan_id, uses=[lower_iface['ifname']], - providernetworks=providernetworks, + datanetworks=datanetworks, forihostid=host.id, ihost_uuid=host.uuid) self._post_and_check(interface, expect_errors) @@ -425,11 +443,11 @@ class InterfaceTestCase(base.FunctionalTest): return interface def _create_worker_vlan(self, ifname, networktype, ifclass, vlan_id, - lower_iface=None, providernetworks=None, + lower_iface=None, datanetworks=None, host=None, expect_errors=False): return self._create_vlan(ifname, networktype, ifclass, vlan_id, lower_iface, - providernetworks, self.worker, expect_errors) + datanetworks, self.worker, expect_errors) def _post_and_check_success(self, ndict): response = self.post_json('%s' % self._get_path(), ndict) @@ -585,6 +603,7 @@ class InterfaceComputeEthernet(InterfaceTestCase): # Setup a sample configuration where the personality is set to a # worker and all interfaces are ethernet interfaces. self._create_host(constants.CONTROLLER, admin=constants.ADMIN_UNLOCKED) + self._create_datanetworks() self._create_ethernet('oam', constants.NETWORK_TYPE_OAM) self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT) self._create_ethernet('infra', constants.NETWORK_TYPE_INFRA) @@ -642,6 +661,7 @@ class InterfaceComputeVlanOverEthernet(InterfaceTestCase): # controller and all interfaces are vlan interfaces over ethernet # interfaces. self._create_host(constants.CONTROLLER) + self._create_datanetworks() port, iface = self._create_ethernet( 'pxeboot', constants.NETWORK_TYPE_PXEBOOT) self._create_vlan('oam', constants.NETWORK_TYPE_OAM, @@ -663,7 +683,7 @@ class InterfaceComputeVlanOverEthernet(InterfaceTestCase): constants.INTERFACE_CLASS_PLATFORM, 3) self._create_worker_vlan('data', constants.INTERFACE_CLASS_DATA, constants.NETWORK_TYPE_DATA, 5, - providernetworks='group0-ext0') + datanetworks='group0-ext0') self._create_ethernet('sriov', constants.NETWORK_TYPE_PCI_SRIOV, constants.INTERFACE_CLASS_PCI_SRIOV, @@ -686,6 +706,7 @@ class InterfaceComputeBond(InterfaceTestCase): # Setup a sample configuration where all platform interfaces are # aggregated ethernet interfaces. self._create_host(constants.CONTROLLER, admin=constants.ADMIN_UNLOCKED) + self._create_datanetworks() self._create_bond('oam', constants.NETWORK_TYPE_OAM) self._create_bond('mgmt', constants.NETWORK_TYPE_MGMT) self._create_bond('infra', constants.NETWORK_TYPE_INFRA) @@ -698,7 +719,7 @@ class InterfaceComputeBond(InterfaceTestCase): self._create_worker_bond('data', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0') + datanetworks='group0-data0') self._create_ethernet('sriov', constants.NETWORK_TYPE_PCI_SRIOV, constants.INTERFACE_CLASS_PCI_SRIOV, @@ -719,6 +740,7 @@ class InterfaceComputeVlanOverBond(InterfaceTestCase): def _setup_configuration(self): self._create_host(constants.CONTROLLER) + self._create_datanetworks() bond = self._create_bond('pxeboot', constants.NETWORK_TYPE_PXEBOOT, constants.INTERFACE_CLASS_PLATFORM) self._create_vlan('oam', constants.NETWORK_TYPE_OAM, @@ -745,7 +767,7 @@ class InterfaceComputeVlanOverBond(InterfaceTestCase): constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, 5, bond2, - providernetworks='group0-ext0') + datanetworks='group0-ext0') self._create_worker_bond('bond3', constants.NETWORK_TYPE_NONE) @@ -769,6 +791,7 @@ class InterfaceComputeVlanOverDataEthernet(InterfaceTestCase): def _setup_configuration(self): self._create_host(constants.CONTROLLER) + self._create_datanetworks() bond = self._create_bond('pxeboot', constants.NETWORK_TYPE_PXEBOOT) self._create_vlan('oam', constants.NETWORK_TYPE_OAM, constants.INTERFACE_CLASS_PLATFORM, 1, bond) @@ -790,7 +813,7 @@ class InterfaceComputeVlanOverDataEthernet(InterfaceTestCase): host=self.worker) self._create_worker_vlan('data2', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, 5, - iface, providernetworks='group0-ext0') + iface, datanetworks='group0-ext0') self._create_ethernet('sriov', constants.NETWORK_TYPE_PCI_SRIOV, constants.INTERFACE_CLASS_PCI_SRIOV, @@ -815,6 +838,7 @@ class InterfaceCpeEthernet(InterfaceTestCase): # ethernet interfaces. self._create_host(constants.CONTROLLER, constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() self._create_ethernet('oam', constants.NETWORK_TYPE_OAM) self._create_ethernet('mgmt', constants.NETWORK_TYPE_MGMT) self._create_ethernet('infra', constants.NETWORK_TYPE_INFRA) @@ -857,6 +881,7 @@ class InterfaceCpeVlanOverEthernet(InterfaceTestCase): # vlan interfaces over ethernet interfaces. self._create_host(constants.CONTROLLER, constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() port, iface = self._create_ethernet( 'pxeboot', constants.NETWORK_TYPE_PXEBOOT) self._create_vlan('oam', constants.NETWORK_TYPE_OAM, @@ -867,7 +892,7 @@ class InterfaceCpeVlanOverEthernet(InterfaceTestCase): constants.INTERFACE_CLASS_PLATFORM, 3) self._create_ethernet('data', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, - providernetworks='group0-ext0') + datanetworks='group0-ext0') self._create_ethernet('sriov', constants.NETWORK_TYPE_PCI_SRIOV, constants.INTERFACE_CLASS_PCI_SRIOV, 'group0-ext1') @@ -891,18 +916,19 @@ class InterfaceCpeBond(InterfaceTestCase): self._create_host(constants.CONTROLLER, subfunction=constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() self._create_bond('oam', constants.NETWORK_TYPE_OAM) self._create_bond('mgmt', constants.NETWORK_TYPE_MGMT) self._create_bond('infra', constants.NETWORK_TYPE_INFRA) self._create_bond('data', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0') + datanetworks='group0-data0') self._create_ethernet('sriov', constants.NETWORK_TYPE_PCI_SRIOV, constants.INTERFACE_CLASS_PCI_SRIOV, - providernetworks='group0-ext0') + datanetworks='group0-ext0') self._create_ethernet('pthru', constants.NETWORK_TYPE_PCI_PASSTHROUGH, constants.INTERFACE_CLASS_PCI_PASSTHROUGH, - providernetworks='group0-ext1') + datanetworks='group0-ext1') def setUp(self): super(InterfaceCpeBond, self).setUp() @@ -919,6 +945,7 @@ class InterfaceCpeVlanOverBond(InterfaceTestCase): # vlan interfaces over aggregated ethernet interfaces. self._create_host(constants.CONTROLLER, constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() bond = self._create_bond('pxeboot', constants.NETWORK_TYPE_PXEBOOT) self._create_vlan('oam', constants.NETWORK_TYPE_OAM, constants.INTERFACE_CLASS_PLATFORM, 1, bond) @@ -930,7 +957,7 @@ class InterfaceCpeVlanOverBond(InterfaceTestCase): self._create_vlan('data', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, 5, bond2, - providernetworks='group0-ext0') + datanetworks='group0-ext0') self._create_ethernet('sriov', constants.NETWORK_TYPE_PCI_SRIOV, constants.INTERFACE_CLASS_PCI_SRIOV, 'group0-ext1') @@ -954,6 +981,7 @@ class InterfaceCpeVlanOverDataEthernet(InterfaceTestCase): # vlan interfaces over data ethernet interfaces. self._create_host(constants.CONTROLLER, constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() port, iface = ( self._create_ethernet('data', constants.NETWORK_TYPE_DATA, @@ -971,15 +999,15 @@ class InterfaceCpeVlanOverDataEthernet(InterfaceTestCase): self._create_vlan('data2', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, 5, iface, - providernetworks='group0-ext0', + datanetworks='group0-ext0', expect_errors=False) self._create_ethernet('sriov', constants.NETWORK_TYPE_PCI_SRIOV, ifclass=constants.INTERFACE_CLASS_PCI_SRIOV, - providernetworks='group0-ext1', + datanetworks='group0-ext1', expect_errors=False) self._create_ethernet('pthru', constants.NETWORK_TYPE_PCI_PASSTHROUGH, ifclass=constants.INTERFACE_CLASS_PCI_PASSTHROUGH, - providernetworks='group0-ext2', + datanetworks='group0-ext2', expect_errors=False) def setUp(self): @@ -1008,6 +1036,7 @@ class TestPatch(InterfaceTestCase): super(TestPatch, self).setUp() self._create_host(constants.CONTROLLER) self._create_host(constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() def test_modify_ifname(self): interface = dbutils.create_test_interface(forihostid='1') @@ -1030,7 +1059,7 @@ class TestPatch(InterfaceTestCase): def test_interface_usesmodify_success(self): data_bond = self._create_bond('data', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0', + datanetworks='group0-data0', host=self.worker) port, new_ethernet = self._create_ethernet( @@ -1055,7 +1084,7 @@ class TestPatch(InterfaceTestCase): networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, iftype=constants.INTERFACE_TYPE_ETHERNET, - providernetworks='group0-data0', + datanetworks='group0-data0', aemode='balanced', txhashpolicy='layer2', uses=['pxeboot'], @@ -1080,7 +1109,7 @@ class TestPatch(InterfaceTestCase): ifclass=constants.INTERFACE_CLASS_DATA, iftype=constants.INTERFACE_TYPE_VLAN, vlan_id=100, - providernetworks='group0-ext0', + datanetworks='group0-ext0', aemode='balanced', txhashpolicy='layer2', uses=['pxeboot'], @@ -1111,6 +1140,7 @@ class TestPost(InterfaceTestCase): super(TestPost, self).setUp() self._create_host(constants.CONTROLLER) self._create_host(constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() # Expected error: The oam network type is only supported on controller nodes def test_invalid_oam_on_worker(self): @@ -1130,28 +1160,28 @@ class TestPost(InterfaceTestCase): def test_invalid_network_type_on_nonworker(self): self._create_ethernet('data0', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-ext0', + datanetworks='group0-ext0', expect_errors=True) # Expected error: Interface name cannot be whitespace. def test_invalid_whitespace_interface_name(self): self._create_ethernet(' ', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-ext0', + datanetworks='group0-ext0', expect_errors=True) # Expected error: Interface name must be in lower case. def test_invalid_uppercase_interface_name(self): self._create_ethernet('miXedCaSe', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-ext0', + datanetworks='group0-ext0', expect_errors=True) # Expected error: Cannot use special characters in interface name. def test_invalid_character_interface_name(self): self._create_ethernet('bad-name', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-ext0', + datanetworks='group0-ext0', expect_errors=True) # Expected error: Interface ___ has name length greater than 10. @@ -1163,11 +1193,11 @@ class TestPost(InterfaceTestCase): def test_create_duplicate_interface_name(self): self._create_ethernet('data0', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0', + datanetworks='group0-data0', host=self.worker) self._create_ethernet('data0', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-ext0', + datanetworks='group0-ext0', host=self.worker, expect_errors=True) @@ -1303,7 +1333,7 @@ class TestPost(InterfaceTestCase): def test_aemode_invalid_iftype(self): ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-data0', + datanetworks='group0-data0', ifname='name', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1317,7 +1347,7 @@ class TestPost(InterfaceTestCase): def test_aemode_no_txhash(self): ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-data0', + datanetworks='group0-data0', ifname='name', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1344,7 +1374,7 @@ class TestPost(InterfaceTestCase): def test_aemode_invalid_txhash_none(self): ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-data0', + datanetworks='group0-data0', ifname='name', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1355,7 +1385,7 @@ class TestPost(InterfaceTestCase): ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-data0', + datanetworks='group0-data0', ifname='name', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1369,7 +1399,7 @@ class TestPost(InterfaceTestCase): def test_aemode_invalid_mgmt(self): ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-data0', + datanetworks='group0-data0', ifname='name', networktype=constants.NETWORK_TYPE_MGMT, networks=['1'], @@ -1385,7 +1415,7 @@ class TestPost(InterfaceTestCase): def test_aemode_invalid_data(self): ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-data0', + datanetworks='group0-data0', ifname='name', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1460,12 +1490,12 @@ class TestPost(InterfaceTestCase): bond_iface = self._create_worker_bond('bond0', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0') + datanetworks='group0-data0') port, iface1 = self._create_ethernet() ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-ext1', + datanetworks='group0-ext1', ifname='bond1', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1480,7 +1510,7 @@ class TestPost(InterfaceTestCase): self._create_worker_vlan('vlan0', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, vlan_id=4095, - providernetworks='group0-ext0', + datanetworks='group0-ext0', expect_errors=True) # Expected message: Interface eth0 is already used by another VLAN @@ -1490,12 +1520,12 @@ class TestPost(InterfaceTestCase): 'vlan0', constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - vlan_id=10, providernetworks='group0-ext0') + vlan_id=10, datanetworks='group0-ext0') port, iface1 = self._create_ethernet() ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-ext1', + datanetworks='group0-ext1', ifname='bond0', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1510,12 +1540,12 @@ class TestPost(InterfaceTestCase): bond_iface = self._create_worker_bond('bond0', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0') + datanetworks='group0-data0') port, iface1 = self._create_ethernet() ndict = dbutils.post_get_test_interface( ihost_uuid=self.worker.uuid, - providernetworks='group0-ext1', + datanetworks='group0-ext1', ifname='bond1', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1531,13 +1561,13 @@ class TestPost(InterfaceTestCase): vlan_iface = self._create_worker_vlan( 'vlan1', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, 1, - providernetworks='group0-ext0') + datanetworks='group0-ext0') self._create_worker_vlan('vlan2', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, vlan_id=2, lower_iface=vlan_iface, - providernetworks='group0-ext1', + datanetworks='group0-ext1', expect_errors=True) # Expected message: data VLAN cannot be created over a LAG interface with @@ -1549,7 +1579,7 @@ class TestPost(InterfaceTestCase): self._create_worker_vlan( 'vlan2', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, 2, - lower_iface=bond_iface, providernetworks='group0-ext1', + lower_iface=bond_iface, datanetworks='group0-ext1', expect_errors=True) # Expected message: data VLAN cannot be created over a LAG interface with @@ -1561,7 +1591,7 @@ class TestPost(InterfaceTestCase): self._create_worker_vlan( 'vlan2', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, 2, - lower_iface=bond_iface, providernetworks='group0-ext1', + lower_iface=bond_iface, datanetworks='group0-ext1', expect_errors=True) # Expected message: mgmt VLAN cannot be created over a LAG interface with @@ -1569,20 +1599,20 @@ class TestPost(InterfaceTestCase): def test_create_mgmt_vlan_over_data_lag(self): bond_iface = self._create_worker_bond( 'data', constants.NETWORK_TYPE_DATA, - constants.INTERFACE_CLASS_DATA, providernetworks='group0-ext1') + constants.INTERFACE_CLASS_DATA, datanetworks='group0-ext1') self._create_worker_vlan( 'mgmt', constants.NETWORK_TYPE_MGMT, constants.INTERFACE_CLASS_PLATFORM, 2, - lower_iface=bond_iface, providernetworks='group0-ext1', + lower_iface=bond_iface, datanetworks='group0-ext1', expect_errors=True) # Expected message: - # Provider network(s) not supported for non-data interfaces. - def test_create_nondata_provider_network(self): + # Data network(s) not supported for non-data interfaces. + def test_create_nondata_data_network(self): self._create_worker_bond( 'pxeboot', constants.NETWORK_TYPE_PXEBOOT, constants.INTERFACE_CLASS_PLATFORM, - providernetworks='group0-data0', expect_errors=True) + datanetworks='group0-data0', expect_errors=True) # Expected message: Name must be unique def test_create_invalid_ae_name(self): @@ -1607,7 +1637,7 @@ class TestPost(InterfaceTestCase): self._create_ethernet('shared', networktype=[constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_DATA], - providernetworks='group0-data0', + datanetworks='group0-data0', host=self.worker, expect_errors=True) @@ -1618,7 +1648,7 @@ class TestPost(InterfaceTestCase): self._create_ethernet('shared', networktype=[constants.NETWORK_TYPE_DATA, constants.NETWORK_TYPE_PXEBOOT], - providernetworks='group0-data0', + datanetworks='group0-data0', host=self.worker, expect_errors=True) @@ -1628,6 +1658,7 @@ class TestCpePost(InterfaceTestCase): super(TestCpePost, self).setUp() self._create_host(constants.CONTROLLER, constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() # Expected message: # Network type list may only contain at most one type @@ -1635,7 +1666,7 @@ class TestCpePost(InterfaceTestCase): self._create_bond('bond0', networktype=[constants.NETWORK_TYPE_DATA, constants.NETWORK_TYPE_PXEBOOT], - providernetworks='group0-data0', expect_errors=True) + datanetworks='group0-data0', expect_errors=True) # Expected message: # Network type list may only contain at most one type @@ -1643,7 +1674,7 @@ class TestCpePost(InterfaceTestCase): self._create_bond('shared', networktype=[constants.NETWORK_TYPE_INFRA, constants.NETWORK_TYPE_DATA], - providernetworks='group0-data0', + datanetworks='group0-data0', expect_errors=True) # Expected message: oam VLAN cannot be created over an interface with @@ -1651,11 +1682,11 @@ class TestCpePost(InterfaceTestCase): def test_create_oam_vlan_over_data_lag(self): bond_iface = self._create_bond( 'data', constants.NETWORK_TYPE_DATA, - constants.INTERFACE_CLASS_DATA, providernetworks='group0-ext1') + constants.INTERFACE_CLASS_DATA, datanetworks='group0-ext1') self._create_vlan( 'oam', constants.NETWORK_TYPE_OAM, constants.INTERFACE_CLASS_PLATFORM, 2, - lower_iface=bond_iface, providernetworks='group0-ext1', + lower_iface=bond_iface, datanetworks='group0-ext1', expect_errors=True) # Expected message: infra VLAN cannot be created over an interface with @@ -1663,11 +1694,11 @@ class TestCpePost(InterfaceTestCase): def test_create_infra_vlan_over_data_lag(self): bond_iface = self._create_bond( 'data', constants.NETWORK_TYPE_DATA, - constants.INTERFACE_CLASS_DATA, providernetworks='group0-ext1') + constants.INTERFACE_CLASS_DATA, datanetworks='group0-ext1') self._create_vlan( 'infra', constants.NETWORK_TYPE_INFRA, constants.INTERFACE_CLASS_PLATFORM, 2, - lower_iface=bond_iface, providernetworks='group0-ext1', + lower_iface=bond_iface, datanetworks='group0-ext1', expect_errors=True) # Expected message: mgmt VLAN cannot be created over an interface with @@ -1675,11 +1706,11 @@ class TestCpePost(InterfaceTestCase): def test_create_mgmt_vlan_over_data_ethernet(self): port, iface = self._create_ethernet( 'data', constants.NETWORK_TYPE_DATA, - constants.INTERFACE_CLASS_DATA, providernetworks='group0-ext1') + constants.INTERFACE_CLASS_DATA, datanetworks='group0-ext1') self._create_vlan( 'mgmt', constants.NETWORK_TYPE_MGMT, constants.INTERFACE_CLASS_PLATFORM, 2, - lower_iface=iface, providernetworks='group0-ext1', + lower_iface=iface, datanetworks='group0-ext1', expect_errors=True) # Expected message: An interface with \'oam\' network type is already @@ -1694,10 +1725,10 @@ class TestCpePost(InterfaceTestCase): port, iface = self._create_ethernet('eth1', constants.NETWORK_TYPE_NONE) self._create_vlan('vlan1', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, vlan_id=1, - lower_iface=iface, providernetworks='group0-ext0') + lower_iface=iface, datanetworks='group0-ext0') self._create_vlan('vlan2', constants.NETWORK_TYPE_DATA, constants.INTERFACE_CLASS_DATA, vlan_id=1, - lower_iface=iface, providernetworks='group0-ext1', + lower_iface=iface, datanetworks='group0-ext1', expect_errors=True) # Expected message: Network type list may only contain at most one type @@ -1718,13 +1749,14 @@ class TestCpePost(InterfaceTestCase): # Expected error: VLAN based provider network group0-data0 cannot be # assigned to a VLAN interface - def test_create_invalid_vlan_with_vlan_provider_network(self): + def test_create_invalid_vlan_with_vlan_data_network(self): port, lower = self._create_ethernet('eth1', constants.NETWORK_TYPE_NONE) self._create_vlan('vlan2', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0', + datanetworks='group0-data0', vlan_id=2, lower_iface=lower, expect_errors=True) + @testtools.skip("deprecate neutron bind interface") @mock.patch.object(dbsql_api.Connection, 'iinterface_destroy') @mock.patch.object(rpcapi.ConductorAPI, 'neutron_bind_interface') def test_create_neutron_bind_failed(self, mock_neutron_bind_interface, @@ -1738,7 +1770,7 @@ class TestCpePost(InterfaceTestCase): ndict = dbutils.post_get_test_interface( forihostid=self.controller.id, ihost_uuid=self.controller.uuid, - providernetworks='group0-ext1', + datanetworks='group0-ext1', ifname='data1', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, @@ -1751,39 +1783,39 @@ class TestCpePost(InterfaceTestCase): mock_iinterface_destroy.assert_called_once_with(mock.ANY) # Expected error: At least one provider network must be selected. - def test_create_invalid_no_provider_network(self): + def test_create_invalid_no_data_network(self): self._create_ethernet('data', networktype=constants.NETWORK_TYPE_DATA, expect_errors=True) # Expected error: Data interface data0 is already attached to this - # Provider Network: group0-data0. - def test_create_invalid_provider_network_used(self): + # Data Network: group0-data0. + def test_create_invalid_data_network_used(self): self._create_ethernet('data0', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0') + datanetworks='group0-data0') self._create_ethernet('data1', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0', + datanetworks='group0-data0', expect_errors=True) - # Expected error: Provider network \'group0-dataXX\' does not exist. - def test_create_invalid_provider_network_not_exist(self): + # Expected error: Data network \'group0-dataXX\' does not exist. + def test_create_invalid_data_network_not_exist(self): self._create_ethernet('data0', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-dataXX', + datanetworks='group0-dataXX', expect_errors=True) # Expected error: Specifying duplicate provider network 'group0-data1' # is not permitted - def test_create_invalid_duplicate_provider_network(self): + def test_create_invalid_duplicate_data_network(self): self._create_ethernet('data0', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data1,group0-data1', + datanetworks='group0-data1,group0-data1', expect_errors=True) @@ -1792,20 +1824,22 @@ class TestCpePatch(InterfaceTestCase): super(TestCpePatch, self).setUp() self._create_host(constants.CONTROLLER, constants.WORKER, admin=constants.ADMIN_LOCKED) + self._create_datanetworks() def test_create_invalid_infra_data_ethernet(self): self._create_ethernet('shared', networktype=[constants.NETWORK_TYPE_INFRA, constants.NETWORK_TYPE_DATA], - providernetworks='group0-data0', + datanetworks='group0-data0', expect_errors=True) + @testtools.skip("deprecate neutron bind interface") @mock.patch.object(rpcapi.ConductorAPI, 'neutron_bind_interface') def test_patch_neutron_bind_failed(self, mock_neutron_bind_interface): port, interface = self._create_ethernet( 'data0', networktype=constants.NETWORK_TYPE_DATA, ifclass=constants.INTERFACE_CLASS_DATA, - providernetworks='group0-data0') + datanetworks='group0-data0') mock_neutron_bind_interface.side_effect = [ None, diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py b/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py index 5a15633607..afcabbe948 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/sqlalchemy/test_migrations.py @@ -770,7 +770,6 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin): 'created_at': 'DateTime', 'updated_at': 'DateTime', 'ifname': 'String', 'iftype': 'String', 'imac': 'String', 'imtu': 'Integer', 'networktype': 'String', 'aemode': 'String', 'txhashpolicy': 'String', - 'providernetworks': 'String', 'providernetworksdict': 'Text', 'schedpolicy': 'String', 'ifcapabilities': 'Text', 'farend': 'Text', 'forihostid': 'Integer', } @@ -1016,7 +1015,6 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin): ethernet_interfaces_col = { 'id': 'Integer', 'deleted_at': 'DateTime', 'created_at': 'DateTime', 'updated_at': 'DateTime', 'imac': 'String', 'imtu': 'Integer', - 'providernetworks': 'String', 'providernetworksdict': 'Text', } for col, coltype in ethernet_interfaces_col.items(): self.assertTrue(isinstance(ethernet_interfaces.c[col].type, @@ -1027,7 +1025,7 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin): 'id': 'Integer', 'deleted_at': 'DateTime', 'created_at': 'DateTime', 'updated_at': 'DateTime', 'aemode': 'String', 'aedict': 'Text', 'txhashpolicy': 'String', 'schedpolicy': 'String', 'imac': 'String', - 'imtu': 'Integer', 'providernetworks': 'String', 'providernetworksdict': 'Text', + 'imtu': 'Integer', } for col, coltype in ae_interfaces_col.items(): self.assertTrue(isinstance(ae_interfaces.c[col].type, @@ -1037,8 +1035,7 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin): vlan_interfaces_col = { 'id': 'Integer', 'deleted_at': 'DateTime', 'created_at': 'DateTime', 'updated_at': 'DateTime', 'vlan_id': 'String', 'vlan_type': 'String', - 'imac': 'String', 'imtu': 'Integer', 'providernetworks': 'String', - 'providernetworksdict': 'Text', + 'imac': 'String', 'imtu': 'Integer', } for col, coltype in vlan_interfaces_col.items(): self.assertTrue(isinstance(vlan_interfaces.c[col].type, @@ -1708,8 +1705,6 @@ class TestMigrations(BaseMigrationTestCase, WalkVersionsMixin): 'id': 'Integer', 'imac': 'String', 'imtu': 'Integer', - 'providernetworks': 'String', - 'providernetworksdict': 'Text', } for col, coltype in virtual_interfaces_col.items(): self.assertTrue(isinstance(virtual_interfaces.c[col].type, diff --git a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py index 5a33ef01ff..f3831e58ca 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/db/utils.py @@ -609,6 +609,39 @@ def get_test_ethernet_port(**kw): return ethernet_port +def get_test_datanetwork(**kw): + datanetwork = { + 'uuid': kw.get('uuid', '60d41820-a4a0-4c25-a6a0-2a3b98746640'), + 'name': kw.get('name'), + 'network_type': kw.get('network_type', 'vxlan'), + 'mtu': kw.get('mtu', '1500'), + 'multicast_group': kw.get('multicast_group', '239.0.2.1'), + 'port_num': kw.get('port_num', 8472), + 'ttl': kw.get('ttl', 10), + 'mode': kw.get('mode', 'dynamic'), + } + return datanetwork + + +def create_test_datanetwork(**kw): + """Create test datanetwork entry in DB and return datanetwork DB object. + Function to be used to create test datanetwork objects in the database. + :param kw: kwargs with overriding values for datanework attributes. + :returns: Test datanetwork DB object. + """ + datanetwork = get_test_datanetwork(**kw) + + if kw['network_type'] != constants.DATANETWORK_TYPE_VXLAN: + # Remove DB fields which are specific to VXLAN + del datanetwork['multicast_group'] + del datanetwork['port_num'] + del datanetwork['ttl'] + del datanetwork['mode'] + + dbapi = db_api.get_instance() + return dbapi.datanetwork_create(datanetwork) + + def create_test_ethernet_port(**kw): """Create test ethernet port entry in DB and return ethernet port DB object. Function to be used to create test ethernet port objects in the database. @@ -624,6 +657,13 @@ def create_test_ethernet_port(**kw): def post_get_test_interface(**kw): + datanetworks = kw.get('datanetworks') or "" + + if datanetworks: + datanetworks_list = datanetworks.split(',') + else: + datanetworks_list = [] + interface = { 'forihostid': kw.get('forihostid'), 'ihost_uuid': kw.get('ihost_uuid'), @@ -636,7 +676,7 @@ def post_get_test_interface(**kw): 'networks': kw.get('networks', []), 'aemode': kw.get('aemode', 'balanced'), 'txhashpolicy': kw.get('txhashpolicy', 'layer2'), - 'providernetworks': kw.get('providernetworks'), + 'datanetworks': datanetworks_list, 'vlan_id': kw.get('vlan_id'), 'uses': kw.get('uses', None), 'used_by': kw.get('used_by', []), @@ -650,6 +690,13 @@ def post_get_test_interface(**kw): def get_test_interface(**kw): + + datanetworks = kw.get('datanetworks') or "" + if datanetworks: + datanetworks_list = datanetworks.split(',') + else: + datanetworks_list = [] + interface = { 'id': kw.get('id'), 'uuid': kw.get('uuid'), @@ -664,7 +711,7 @@ def get_test_interface(**kw): 'networks': kw.get('networks', []), 'aemode': kw.get('aemode'), 'txhashpolicy': kw.get('txhashpolicy', None), - 'providernetworks': kw.get('providernetworks'), + 'datanetworks': datanetworks_list, 'vlan_id': kw.get('vlan_id', None), 'uses': kw.get('uses', []), 'used_by': kw.get('used_by', []), @@ -683,13 +730,31 @@ def create_test_interface(**kw): :param kw: kwargs with overriding values for interface's attributes. :returns: Test Interface DB object. """ + interface = get_test_interface(**kw) + datanetworks_list = interface.get('datanetworks') or [] + # Let DB generate ID if it isn't specified explicitly if 'id' not in kw: del interface['id'] + + if 'datanetworks' in interface: + del interface['datanetworks'] + dbapi = db_api.get_instance() forihostid = kw.get('forihostid') - return dbapi.iinterface_create(forihostid, interface) + interface_obj = dbapi.iinterface_create(forihostid, interface) + + # assign the interface to the datanetwork + for datanetwork in datanetworks_list: + if not datanetwork: + continue + dn = dbapi.datanetwork_get(datanetwork) + values = {'interface_id': interface_obj.id, + 'datanetwork_id': dn.id} + dbapi.interface_datanetwork_create(values) + + return interface_obj def create_test_interface_network(**kw):