Merge remote-tracking branch 'starlingx/master' into HEAD

Change-Id: Iddb556a26c1ac22eed5bd0536592c37856b9d86e
Signed-off-by: Scott Little <scott.little@windriver.com>
This commit is contained in:
Scott Little 2019-01-29 12:56:01 -05:00
commit 23f303132c
56 changed files with 2939 additions and 489 deletions

View File

@ -29,4 +29,5 @@ from configutilities.common.utils import validate_address # noqa: F401
from configutilities.common.utils import ip_version_to_string # noqa: F401
from configutilities.common.utils import lag_mode_to_str # noqa: F401
from configutilities.common.utils import validate_openstack_password # noqa: F401
from configutilities.common.utils import validate_nameserver_address_str # noqa: F401
from configutilities.common.utils import extract_openstack_password_rules_from_file # noqa: F401

View File

@ -23,6 +23,7 @@ from configutilities.common.utils import is_mtu_valid
from configutilities.common.utils import get_service
from configutilities.common.utils import get_optional
from configutilities.common.utils import validate_address_str
from configutilities.common.utils import validate_nameserver_address_str
from configutilities.common.exceptions import ConfigFail
from configutilities.common.exceptions import ValidateFail
@ -944,8 +945,25 @@ class ConfigValidator(object):
raise ConfigFail("SDN Configuration is no longer supported")
def validate_dns(self):
if self.conf.has_section('DNS'):
raise ConfigFail("DNS Configuration is no longer supported")
# DNS config is optional
if not self.conf.has_section('DNS'):
return
if self.cgcs_conf is not None:
self.cgcs_conf.add_section('cDNS')
for x in range(0, 3):
if self.conf.has_option('DNS', 'NAMESERVER_' + str(x + 1)):
dns_address_str = self.conf.get('DNS', 'NAMESERVER_' + str(
x + 1))
try:
dns_address = validate_nameserver_address_str(
dns_address_str)
if self.cgcs_conf is not None:
self.cgcs_conf.set('cDNS', 'NAMESERVER_' + str(x + 1),
str(dns_address))
except ValidateFail as e:
raise ConfigFail(
"Invalid DNS NAMESERVER value of %s.\nReason: %s" %
(dns_address_str, e))
def validate_ntp(self):
if self.conf.has_section('NTP'):

View File

@ -28,6 +28,7 @@ from configutilities import validate_network_str
from configutilities import validate_address_str
from configutilities import validate_address
from configutilities import ip_version_to_string
from configutilities import validate_nameserver_address_str
from configutilities import validate_openstack_password
from configutilities import DEFAULT_DOMAIN_NAME
from netaddr import IPNetwork
@ -463,6 +464,10 @@ class ConfigAssistant():
# SDN config
self.enable_sdn = False
# DNS config
self.nameserver_addresses = ["8.8.8.8", "8.8.4.4", ""]
# HTTPS
self.enable_https = False
# Network config
@ -2666,6 +2671,64 @@ class ConfigAssistant():
""" Cluster host interface configuration complete"""
self.cluster_host_interface_configured = True
def get_dns_servers(self):
"""Produce a comma separated list of DNS servers."""
servers = [str(s) for s in self.nameserver_addresses if s]
return ",".join(servers)
def input_dns_config(self):
"""Allow user to input DNS config and perform validation."""
print("\nDomain Name System (DNS):")
print("-------------------------\n")
print(textwrap.fill(
"Configuring DNS servers accessible through the external "
"OAM network allows domain names to be mapped to IP "
"addresses.", 80))
print(textwrap.fill(
"The configuration of at least one DNS server is mandatory. To "
"skip the configuration of one or more nameservers (1 to 3 are "
"allowed), enter C to continue to the next configuration item.",
80))
print('')
if self.external_oam_subnet.version == 6:
self.nameserver_addresses = ["2001:4860:4860::8888",
"2001:4860:4860::8844", ""]
for server in range(0, len(self.nameserver_addresses)):
while True:
user_input = raw_input(
"Nameserver " + str(server + 1) + " [" +
str(self.nameserver_addresses[server]) + "]: ")
if user_input.lower() == 'q':
raise UserQuit
elif user_input.lower() == 'c':
if server == 0:
print("At least one DNS server is required.")
continue
for x in range(server, len(self.nameserver_addresses)):
self.nameserver_addresses[x] = ""
return
elif user_input == "":
user_input = self.nameserver_addresses[server]
# Pressing enter with a blank default will continue
if user_input == "":
return
try:
try:
ip_input = validate_nameserver_address_str(
user_input, self.external_oam_subnet.version)
except ValidateFail as e:
print('{}'.format(e))
continue
self.nameserver_addresses[server] = ip_input
break
except (AddrFormatError, ValueError):
print("Invalid address - please enter a valid IPv4 "
"address")
def input_authentication_config(self):
"""Allow user to input authentication config and perform validation.
"""
@ -2711,6 +2774,8 @@ class ConfigAssistant():
self.management_interface_configured = True
self.external_oam_interface_configured = True
self.default_pxeboot_config()
if not self.kubernetes:
self.nameserver_addresses = ["", "", ""]
if utils.is_cpe():
self.system_mode = sysinv_constants.SYSTEM_MODE_DUPLEX
@ -2747,6 +2812,8 @@ class ConfigAssistant():
if self.kubernetes:
self.input_cluster_host_config()
self.input_external_oam_config()
if self.kubernetes:
self.input_dns_config()
self.input_authentication_config()
def is_valid_management_multicast_subnet(self, ip_subnet):
@ -3079,6 +3146,19 @@ class ConfigAssistant():
self.external_oam_interface_configured = True
# DNS configuration
if self.kubernetes:
if config.has_section('cDNS'):
self.nameserver_addresses = ["", "", ""]
for x in range(0, len(self.nameserver_addresses)):
if config.has_option('cDNS',
'NAMESERVER_' + str(x + 1)):
cvalue = config.get('cDNS',
'NAMESERVER_' + str(x + 1))
if cvalue != "NC" and cvalue != "":
self.nameserver_addresses[x] = \
IPAddress(cvalue)
# SDN Network configuration
if config.has_option('cSDN', 'ENABLE_SDN'):
raise ConfigFail("The option ENABLE_SDN is no longer "
@ -3511,6 +3591,18 @@ class ConfigAssistant():
else:
print("External OAM address: " + str(self.external_oam_address_0))
if self.kubernetes:
print("\nDNS Configuration")
print("-----------------")
dns_config = False
for x in range(0, len(self.nameserver_addresses)):
if self.nameserver_addresses[x]:
print("Nameserver " + str(x + 1) + ": " +
str(self.nameserver_addresses[x]))
dns_config = True
if not dns_config:
print("External DNS servers not configured")
if self.region_config:
print("\nRegion Configuration")
print("--------------------")
@ -3796,6 +3888,17 @@ class ConfigAssistant():
f.write("EXTERNAL_OAM_1_ADDRESS=" +
str(self.external_oam_address_1) + "\n")
if self.kubernetes:
# DNS configuration
f.write("\n[cDNS]")
f.write("\n# DNS Configuration\n")
for x in range(0, len(self.nameserver_addresses)):
if self.nameserver_addresses[x]:
f.write("NAMESERVER_" + str(x + 1) + "=" +
str(self.nameserver_addresses[x]) + "\n")
else:
f.write("NAMESERVER_" + str(x + 1) + "=NC" + "\n")
# Network configuration
f.write("\n[cNETWORK]")
f.write("\n# Data Network Configuration\n")
@ -5188,6 +5291,17 @@ class ConfigAssistant():
"required_patches": "N/A"}
client.sysinv.load.create(**patch)
def _populate_dns_config(self, client):
# Retrieve the list of dns servers to get the uuid
dns_list = client.sysinv.idns.list()
dns_record = dns_list[0]
values = {
'nameservers': self.get_dns_servers(),
'action': 'apply'
}
patch = sysinv.dict_to_patch(values)
client.sysinv.idns.update(dns_record.uuid, patch)
def populate_initial_config(self):
"""Populate initial system inventory configuration"""
try:
@ -5195,6 +5309,8 @@ class ConfigAssistant():
self._populate_system_config(client)
self._populate_load_config(client)
self._populate_network_config(client)
if self.kubernetes:
self._populate_dns_config(client)
controller = self._populate_controller_config(client)
# ceph_mon config requires controller host to be created
self._inventory_config_complete_wait(client, controller)

View File

@ -0,0 +1,86 @@
[cSYSTEM]
# System Configuration
SYSTEM_MODE=duplex
TIMEZONE=UTC
[cPXEBOOT]
# PXEBoot Network Support Configuration
PXECONTROLLER_FLOATING_HOSTNAME=pxecontroller
[cMGMT]
# Management Network Configuration
MANAGEMENT_INTERFACE_NAME=eth1
MANAGEMENT_INTERFACE=eth1
MANAGEMENT_MTU=1500
MANAGEMENT_SUBNET=192.168.204.0/24
LAG_MANAGEMENT_INTERFACE=no
CONTROLLER_FLOATING_ADDRESS=192.168.204.2
CONTROLLER_0_ADDRESS=192.168.204.3
CONTROLLER_1_ADDRESS=192.168.204.4
NFS_MANAGEMENT_ADDRESS_1=192.168.204.5
NFS_MANAGEMENT_ADDRESS_2=192.168.204.6
CONTROLLER_FLOATING_HOSTNAME=controller
CONTROLLER_HOSTNAME_PREFIX=controller-
OAMCONTROLLER_FLOATING_HOSTNAME=oamcontroller
DYNAMIC_ADDRESS_ALLOCATION=yes
MANAGEMENT_MULTICAST_SUBNET=239.1.1.0/28
[cINFRA]
# Infrastructure Network Configuration
INFRASTRUCTURE_INTERFACE_NAME=NC
INFRASTRUCTURE_INTERFACE=NC
INFRASTRUCTURE_VLAN=NC
INFRASTRUCTURE_MTU=NC
INFRASTRUCTURE_SUBNET=NC
LAG_INFRASTRUCTURE_INTERFACE=no
INFRASTRUCTURE_BOND_MEMBER_0=NC
INFRASTRUCTURE_BOND_MEMBER_1=NC
INFRASTRUCTURE_BOND_POLICY=NC
CONTROLLER_0_INFRASTRUCTURE_ADDRESS=NC
CONTROLLER_1_INFRASTRUCTURE_ADDRESS=NC
NFS_INFRASTRUCTURE_ADDRESS_1=NC
STORAGE_0_INFRASTRUCTURE_ADDRESS=NC
STORAGE_1_INFRASTRUCTURE_ADDRESS=NC
CONTROLLER_INFRASTRUCTURE_HOSTNAME_SUFFIX=NC
INFRASTRUCTURE_START_ADDRESS=NC
INFRASTRUCTURE_END_ADDRESS=NC
[cCLUSTER]
# Cluster Host Network Configuration
CLUSTER_INTERFACE_NAME=eth1
CLUSTER_INTERFACE=eth1
CLUSTER_VLAN=NC
CLUSTER_MTU=1500
CLUSTER_SUBNET=192.168.206.0/24
LAG_CLUSTER_INTERFACE=no
[cEXT_OAM]
# External OAM Network Configuration
EXTERNAL_OAM_INTERFACE_NAME=eth0
EXTERNAL_OAM_INTERFACE=eth0
EXTERNAL_OAM_VLAN=NC
EXTERNAL_OAM_MTU=1500
LAG_EXTERNAL_OAM_INTERFACE=no
EXTERNAL_OAM_SUBNET=10.10.10.0/24
EXTERNAL_OAM_GATEWAY_ADDRESS=10.10.10.1
EXTERNAL_OAM_FLOATING_ADDRESS=10.10.10.2
EXTERNAL_OAM_0_ADDRESS=10.10.10.3
EXTERNAL_OAM_1_ADDRESS=10.10.10.4
[cDNS]
# DNS Configuration
NAMESERVER_1=1.2.3.4
NAMESERVER_2=5.6.7.8
NAMESERVER_3=NC
[cNETWORK]
# Data Network Configuration
VSWITCH_TYPE=ovs-dpdk
[cSECURITY]
[cREGION]
# Region Configuration
REGION_CONFIG=False
[cAUTHENTICATION]
ADMIN_PASSWORD=Li69nux*

View File

@ -44,6 +44,11 @@ CIDR=10.10.10.0/24
GATEWAY=10.10.10.1
LOGICAL_INTERFACE=LOGICAL_INTERFACE_2
[DNS]
# DNS Configuration
NAMESERVER_1=1.2.3.4
NAMESERVER_2=5.6.7.8
;[PXEBOOT_NETWORK]
;PXEBOOT_CIDR=192.168.203.0/24

View File

@ -19,13 +19,14 @@ import controllerconfig.common.constants as constants
def _test_answerfile(tmpdir, filename,
mock_get_net_device_list,
mock_get_rootfs_node,
compare_results=True):
compare_results=True,
ca_options={}):
""" Test import and generation of answerfile """
mock_get_net_device_list.return_value = \
['eth0', 'eth1', 'eth2']
mock_get_rootfs_node.return_value = '/dev/sda'
assistant = ca.ConfigAssistant()
assistant = ca.ConfigAssistant(**ca_options)
# Create the path to the answerfile
answerfile = os.path.join(
@ -93,3 +94,10 @@ def test_answerfile_region_nuage_vrs(tmpdir):
""" Test import of answerfile with region values for nuage_vrs"""
_test_answerfile(tmpdir, "cgcs_config.region_nuage_vrs")
def test_answerfile_kubernetes(tmpdir):
""" Test import of answerfile with kubernetes values """
_test_answerfile(tmpdir, "cgcs_config.kubernetes",
ca_options={"kubernetes": True})

View File

@ -42,7 +42,7 @@ def _test_system_config(filename):
cr.create_cgcs_config_file(None, system_config, None, None, None, 0,
validate_only=True)
# Validate the region config file.
# Validate the system config file.
# Using onboard validation since the validator's reference version number
# is only set at build-time when validating offboard
validate(system_config, DEFAULT_CONFIG, None, False)
@ -508,14 +508,6 @@ def test_system_config_validation():
with pytest.raises(exceptions.ConfigFail):
validate(system_config, DEFAULT_CONFIG, None, False)
# Test detection of unsupported DNS NAMESERVER
system_config = cr.parse_system_config(simple_systemfile)
system_config.add_section('DNS')
system_config.set('DNS', 'NAMESERVER_1', '8.8.8.8')
with pytest.raises(exceptions.ConfigFail):
cr.create_cgcs_config_file(None, system_config, None, None, None, 0,
validate_only=True)
# Test detection of unsupported NTP NTP_SERVER
system_config = cr.parse_system_config(simple_systemfile)
system_config.add_section('NTP')
@ -606,12 +598,13 @@ def test_pxeboot_range():
validate(system_config, DEFAULT_CONFIG, None, False)
def test_cluster_network():
""" Test import of system_config file for cluster network address """
def test_kubernetes():
""" Test import of system_config file for kubernetes """
# Create the path to the system_config file
systemfile = os.path.join(
os.getcwd(), "controllerconfig/tests/files/", "system_config.cluster")
os.getcwd(), "controllerconfig/tests/files/",
"system_config.kubernetes")
# Test import and generation of answer file
_test_system_config(systemfile)
@ -647,3 +640,10 @@ def test_cluster_network():
validate_only=True)
with pytest.raises(exceptions.ConfigFail):
validate(system_config, DEFAULT_CONFIG, None, False)
# Test absence of optional DNS configuration
system_config = cr.parse_system_config(systemfile)
system_config.remove_section('DNS')
cr.create_cgcs_config_file(None, system_config, None, None, None, 0,
validate_only=True)
validate(system_config, DEFAULT_CONFIG, None, False)

View File

@ -176,7 +176,6 @@ function install_sysinv {
sudo install -d -m 755 $SYSINV_CONF_DIR
sudo install -p -D -m 755 $SYSINV_DIR/etc/sysinv/policy.json $SYSINV_CONF_DIR/policy.json
sudo install -p -D -m 640 $SYSINV_DIR/etc/sysinv/profileSchema.xsd $SYSINV_CONF_DIR/profileSchema.xsd
sudo install -p -D -m 655 $SYSINV_DIR/etc/sysinv/crushmap.bin $SYSINV_CONF_DIR/crushmap.bin
sudo install -d -m 755 $SYSINV_ETC_MOTDD
sudo install -p -D -m 755 $SYSINV_DIR/etc/sysinv/motd-system $SYSINV_ETC_MOTDD/10-system
sudo install -d -m 755 $SYSINV_CONF_DIR/upgrades

View File

@ -93,8 +93,14 @@ class platform::dns::resolv (
class platform::dns {
include ::platform::dns::resolv
include ::platform::dns::dnsmasq
Anchor['platform::networking'] -> Class[$name]
# The "contain" ensures that the resolv and dnsmasq classes are not applied
# until the dns class is begun, which will wait for networking to be
# complete, as per the anchor dependency above. This is necessary because
# the networking configuration can wipe the /etc/resolv.conf file.
contain ::platform::dns::resolv
contain ::platform::dns::dnsmasq
}

View File

@ -58,15 +58,8 @@ class platform::kubernetes::master::init
# For initial controller install, configure kubernetes from scratch.
$resolv_conf = '/etc/resolv.conf'
# Add a DNS server to allow access to kubernetes repo. This will no longer
# be required once we are using our own internal repo.
file_line { "${resolv_conf} nameserver 8.8.8.8":
path => $resolv_conf,
line => 'nameserver 8.8.8.8',
}
# Configure the master node.
-> file { '/etc/kubernetes/kubeadm.yaml':
file { '/etc/kubernetes/kubeadm.yaml':
ensure => file,
content => template('platform/kubeadm.yaml.erb'),
}

View File

@ -1,2 +1,2 @@
SRC_DIR="cgts-client"
TIS_PATCH_VER=61
TIS_PATCH_VER=62

View File

@ -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)

View File

@ -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 "<datanetwork %s>" % 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)

View File

@ -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='<datanetwork_id>',
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='<datanetwork_name>',
help="Name of the datanetwork [REQUIRED]")
@utils.arg('network_type',
metavar='<network_type>',
choices=['flat', 'vlan', 'vxlan'],
help="Type of the datanetwork [REQUIRED]")
@utils.arg('-d', '--description',
metavar='<description>',
help='User description of the datanetwork')
@utils.arg('-m', '--mtu',
metavar='<mtu>',
default=1500,
help='MTU of the datanetwork')
@utils.arg('-p', '--port_num',
metavar='<port_num>',
help='port_num of the datanetwork')
@utils.arg('-g', '--multicast_group',
metavar='<multicast_group>',
help='multicast_group of the datanetwork')
@utils.arg('-t', '--ttl',
metavar='<ttl>',
help='time-to-live of the datanetwork')
@utils.arg('-M', '--mode',
metavar='<mode>',
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='<datanetwork_id>',
help="Name of the datanetwork [REQUIRED]")
@utils.arg('-m', '--mtu',
metavar='<mtu>',
help='MTU of the datanetwork')
@utils.arg('-d', '--description',
metavar='<description>',
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='<datanetwork_uuid>',
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)

View File

@ -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']

View File

@ -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='<providernetworks>',
@utils.arg('datanetworks',
metavar='<datanetworks>',
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='<providernetworks>',
help='The provider network attached to the interface [REQUIRED]')
help='[DEPRECATED] The provider network attached to the interface')
@utils.arg('-d', '--datanetworks',
metavar='<datanetworks>',
help='The data network attached to the interface')
@utils.arg('-a', '--aemode',
metavar='<ae mode>',
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():

View File

@ -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 "<interface_datanetwork %s>" % 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)

View File

@ -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='<hostnameorid>',
help="Name or ID of host")
@utils.arg('ifnameoruuid',
metavar='<ifnameoruuid>',
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='<interface datanetwork uuid>',
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='<hostnameorid>',
help="Name or ID of host [REQUIRED]")
@utils.arg('ifnameoruuid',
metavar='<ifnameoruuid>',
help="Name or UUID of interface [REQUIRED]")
@utils.arg('datanetnameoruuid',
metavar='<datanetnameoruuid>',
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='<interface_datanetwork_uuid>',
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)

View File

@ -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)

View File

@ -60,7 +60,7 @@ def do_show(cc, args):
help='The name of the system')
@utils.arg('-s', '--sdn_enabled',
metavar='<sdn_enabled>',
choices=['true', 'false'],
choices=['true', 'false', 'True', 'False'],
help='The SDN enabled or disabled flag')
@utils.arg('-t', '--timezone',
metavar='<timezone>',
@ -79,7 +79,7 @@ def do_show(cc, args):
help='The location of the system')
@utils.arg('-p', '--https_enabled',
metavar='<https_enabled>',
choices=['true', 'false'],
choices=['true', 'false', 'True', 'False'],
help='The HTTPS enabled or disabled flag')
@utils.arg('-v', '--vswitch_type',
metavar='<vswitch_type>',

View File

@ -47,7 +47,7 @@ def do_network_list(cc, args):
help="Type of network [REQUIRED]")
@utils.arg('dynamic',
metavar='<dynamic>',
choices=['true', 'false'],
choices=['true', 'false', 'True', 'False'],
help="dynamic [REQUIRED]")
@utils.arg('pool_uuid',
metavar='<pool_uuid>',
@ -57,6 +57,10 @@ def do_network_add(cc, args):
field_list = ['name', 'type', 'dynamic', 'pool_uuid']
# make sure dynamic is lower
if args.dynamic is not None:
args.dynamic = args.dynamic.lower()
# 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))

View File

@ -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,

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv"
TIS_PATCH_VER=295
TIS_PATCH_VER=296

View File

@ -133,7 +133,7 @@
<xs:attribute name="mode" type="Ipv6Mode" use="required" />
</xs:complexType>
<xs:complexType name="dataNetwork">
<xs:complexType name="dataclassNetwork">
<xs:sequence>
<xs:element name="providerNetworks" type="providerNetworks" minOccurs="1" maxOccurs="unbounded">
</xs:element>
@ -200,7 +200,7 @@
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs="1" maxOccurs="2">
<xs:element name="dataNetwork" type="dataNetwork" maxOccurs="1">
<xs:element name="dataclassNetwork" type="dataclassNetwork" maxOccurs="1">
</xs:element>
<xs:element name="infraNetwork" type="externalNetwork" maxOccurs="1">
</xs:element>
@ -237,7 +237,7 @@
</xs:element>
<xs:element name="oamNetwork" type="externalNetwork" maxOccurs="1">
</xs:element>
<xs:element name="dataNetwork" type="dataNetwork" maxOccurs="1">
<xs:element name="dataclassNetwork" type="dataclassNetwork" maxOccurs="1">
</xs:element>
<xs:element name="infraNetwork" type="externalNetwork" maxOccurs="1">
</xs:element>
@ -262,7 +262,7 @@
</xs:element>
<xs:element name="oamNetwork" type="externalNetwork" maxOccurs="1">
</xs:element>
<xs:element name="dataNetwork" type="dataNetwork" maxOccurs="1">
<xs:element name="dataclassNetwork" type="dataclassNetwork" maxOccurs="1">
</xs:element>
<xs:element name="infraNetwork" type="externalNetwork" maxOccurs="1">
</xs:element>

View File

@ -213,8 +213,8 @@
be combined.
-->
<networks>
<dataNetwork>
<!--one or more provider network is required for a dataNetwork-->
<dataclassNetwork>
<!--one or more provider network is required for a dataclassNetwork-->
<providerNetworks>
<providerNetwork name="group0-data0" />
<providerNetwork name="group0-data0b" />
@ -232,7 +232,7 @@
linkLocal, and
static-->
<ipv6 mode="link-local"></ipv6>
</dataNetwork>
</dataclassNetwork>
</networks>
</ethernetInterface>
@ -267,13 +267,13 @@
<ethernetInterface ifName="data1" mtu="1500" >
<port name="eth6" pciAddress="0000:07:00.0" class="Ethernet controller" device="82599ES 10-Gigabit SFI/SFP+ Network Connection" />
<networks>
<dataNetwork>
<dataclassNetwork>
<providerNetworks>
<providerNetwork name="group0-data1" />
</providerNetworks>
<ipv4 mode="disabled"></ipv4>
<ipv6 mode="disabled"></ipv6>
</dataNetwork>
</dataclassNetwork>
</networks>
</ethernetInterface>
@ -322,8 +322,8 @@
<vlanInterface ifName="vlan11" interface="ae0" vlanId="11" mtu="1600">
<networks>
<dataNetwork>
<!--This dataNetwork uses ip address pools. See ipv4 and ipv6 tag below
<dataclassNetwork>
<!--This dataclassNetwork uses ip address pools. See ipv4 and ipv6 tag below
-->
<providerNetworks>
<providerNetwork name="group0-ext0" />
@ -336,7 +336,7 @@
<ipv6 mode="pool">
<pool name="pool-2" />
</ipv6>
</dataNetwork>
</dataclassNetwork>
</networks>
</vlanInterface>
</interfaceProfile>

View File

@ -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):

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -422,11 +422,11 @@ class SystemController(rest.RestController):
timezone))
if p['path'] == '/sdn_enabled':
sdn_enabled = p['value']
sdn_enabled = p['value'].lower()
patch.remove(p)
if p['path'] == '/https_enabled':
https_enabled = p['value']
https_enabled = p['value'].lower()
patch.remove(p)
if p['path'] == '/distributed_cloud_role':

View File

@ -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"

View File

@ -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.")

View File

@ -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())

View File

@ -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." %

View File

@ -836,7 +836,6 @@ class Connection(object):
'aemode': 'balanced',
'schedpolicy': 'xor',
'txhashpolicy': 'L2',
'providernetworks': 'physnet0, physnet1'
'extra': { ... },
}
:returns: An iinterface.

View File

@ -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)

View File

@ -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.')

View File

@ -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'

View File

@ -198,15 +198,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 = {}
@ -223,6 +225,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,
@ -236,11 +244,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),
}
@ -281,6 +289,9 @@ class NeutronHelm(openstack.OpenstackBaseHelm):
'policy_file': '/etc/neutron/policy.json',
'service_plugins': 'router',
'dns_domain': 'openstacklocal',
'enable_new_agents': False,
'allow_automatic_dhcp_failover': True,
'allow_automatic_l3agent_failover': True,
},
'vhost': {
'vhost_user_enabled': True,
@ -292,6 +303,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 +319,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 +337,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):
"""

View File

@ -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,

View File

@ -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)

View File

@ -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']

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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':

View File

@ -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

View File

@ -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']:

View File

@ -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,

View File

@ -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,

View File

@ -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):

View File

@ -20,6 +20,7 @@ import hashlib
import os
import os.path
import tempfile
import wsme
import mox
import netaddr
@ -28,6 +29,7 @@ from oslo_config import cfg
from six import StringIO
from six.moves import builtins
from sysinv.common import exception
from sysinv.common import service_parameter
from sysinv.common import utils
from sysinv.tests import base
@ -367,3 +369,44 @@ class IntLikeTestCase(base.TestCase):
self.assertFalse(
utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64"))
self.assertFalse(utils.is_int_like("a1"))
class LDAPTestCase(base.TestCase):
def test_ldapurl(self):
# Bad Network address is not acceptable as ldap url
ldap_url = 'ldap://127'
self.assertRaises(wsme.exc.ClientSideError,
service_parameter._validate_ldap_url,
'foo',
ldap_url)
# loopback is not acceptable as ldap url
ldap_url = 'ldap://127.0.0.1'
self.assertRaises(wsme.exc.ClientSideError,
service_parameter._validate_ldap_url,
'foo',
ldap_url)
# localhost is not acceptable as ldap url
ldap_url = 'ldap://localhost:1234'
self.assertRaises(wsme.exc.ClientSideError,
service_parameter._validate_ldap_url,
'foo',
ldap_url)
# A valid ldap URL should not raise an exception
ldap_url = 'ldap://dns.example.com:389'
service_parameter._validate_ldap_url('foo', ldap_url)
def test_ldap_dn(self):
# A poorly formatted ldap DN will raise a ClientSideError
ldap_dn = 'this is not a valid ldap dn'
self.assertRaises(wsme.exc.ClientSideError,
service_parameter._validate_ldap_dn,
'foo',
ldap_dn)
# A valid DN will not raise a ClientSideError
ldap_dn = 'uid=john.doe,ou=People,dc=example,dc=com'
service_parameter._validate_ldap_dn('foo', ldap_dn)

View File

@ -28,7 +28,7 @@ libvirt-python>=1.2.5
migrate
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
python-cephclient
python-ldap>=2.4.22,<3.0.0
python-ldap>=3.1.0
markupsafe
docker
# Babel>=0.9.6