config/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/profile_utils.py

405 lines
15 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 Hewlett-Packard Development Company, L.P.
# 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) 2013-2015 Wind River Systems, Inc.
#
import netaddr
from sysinv.common import constants
from sysinv.openstack.common import log
from sysinv.openstack.common.gettextutils import _
LOG = log.getLogger(__name__)
class InvalidProfileData(Exception):
pass
class Network(object):
def __init__(self, node, networkType):
self.networkType = networkType
self.providerNetworks = []
providerNetworksNode = node.find('providerNetworks')
if providerNetworksNode:
for pnetNode in providerNetworksNode.findall('providerNetwork'):
pnetName = pnetNode.get('name')
self.addProviderNetwork(pnetName)
def addProviderNetwork(self, pnet):
if pnet not in self.providerNetworks:
self.providerNetworks.append(pnet)
# ignore if provider network is duplicated within one interface
def validate(self):
if len(self.providerNetworks) == 0:
# caller will do the translation
raise InvalidProfileData("At least one provider network must be selected.")
class DataclassNetwork(Network):
def __init__(self, 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):
routesNode = node.find('routes')
if routesNode is None:
return []
routes = []
for routeNode in routesNode.findall('route'):
route = {}
route['metric'] = int(routeNode.get('metric'))
network = routeNode.get('network')
gateway = routeNode.get('gateway')
try:
addr = netaddr.IPAddress(gateway)
except netaddr.core.AddrFormatError:
raise InvalidProfileData(_('%s is not a valid IP address') % gateway)
try:
net = netaddr.IPNetwork(network)
except netaddr.core.AddrFormatError:
raise InvalidProfileData(_('%s is not a valid network') % network)
if addr.format() != gateway:
raise InvalidProfileData(_('%s is not a valid IP address') % gateway)
if net.version != addr.version:
raise InvalidProfileData(_('network "%s" and gateway "%s" must be the same version.') %
(network, gateway))
route['network'] = net.network.format()
route['prefix'] = net.prefixlen
route['gateway'] = gateway
route['family'] = net.version
routes.append(route)
return routes
@staticmethod
def getIpMode(node, name):
modeNode = node.find(name)
if modeNode is None:
raise InvalidProfileData(_('%s is required for a datanetwork') % name)
mode = modeNode.get('mode')
pool = None
if mode == 'pool':
poolNode = modeNode.find('pool')
if poolNode is None:
raise InvalidProfileData(_('A pool is required for a %s defined as "pool"') % name)
pool = poolNode.get('name')
return {'mode': mode, 'pool': pool}
class ExternalNetwork(object):
def __init__(self, node, networktype):
self.networkType = networktype
def validate(self):
pass
class PciPassthrough(Network):
def __init__(self, node):
super(PciPassthrough, self).__init__(node, constants.NETWORK_TYPE_PCI_PASSTHROUGH)
class PciSriov(Network):
def __init__(self, node):
super(PciSriov, self).__init__(node, constants.NETWORK_TYPE_PCI_SRIOV)
self.virtualFunctions = int(node.get('virtualFunctions'))
class Interface(object):
def __init__(self, ifNode):
self.providerNetworks = []
self.networks = []
self.name = ifNode.get('ifName')
self.mtu = ifNode.get('mtu')
self.ipv4Mode = {'mode': None, 'pool': None}
self.ipv6Mode = {'mode': None, 'pool': None}
self.routes = []
self.virtualFunctions = 0
networksNode = ifNode.find('networks')
if networksNode is not None:
for netNode in networksNode:
self.addNetwork(netNode)
def getNetworkMap(self):
return {}
def addNetwork(self, node):
tag = node.tag
networkMap = self.getNetworkMap()
if tag in networkMap:
network = networkMap[tag](node)
self.networks.append(network)
if network.networkType == constants.NETWORK_TYPE_DATA:
self.ipv4Mode = network.ipv4Mode
self.ipv6Mode = network.ipv6Mode
self.routes = network.routes
elif network.networkType == constants.NETWORK_TYPE_INFRA:
self.ipv4Mode = {'mode': constants.IPV4_STATIC, 'pool': None}
self.ipv6Mode = {'mode': constants.IPV6_DISABLED, 'pool': None}
elif network.networkType == constants.NETWORK_TYPE_PCI_SRIOV:
self.virtualFunctions = network.virtualFunctions
if isinstance(network, Network):
self.providerNetworks = network.providerNetworks
else:
raise InvalidProfileData(_('network type (%s) not recognizable') % tag)
def validate(self):
# raise InvalidProfileData exception with detail msg
numberOfNetworks = len(self.networks)
if numberOfNetworks > 2:
raise InvalidProfileData(_('Too many network types selected for the interface.'))
# when change, make sure modify the displayText as well
combineTypes = [constants.NETWORK_TYPE_MGMT, constants.NETWORK_TYPE_INFRA, constants.NETWORK_TYPE_DATA]
displayText = _('Only mgmt, infra, data network types can be combined on a single interface')
if numberOfNetworks == 2:
if self.networks[0].networkType not in combineTypes or \
self.networks[1].networkType not in combineTypes:
raise InvalidProfileData(displayText)
if self.networks[0].networkType == self.networks[1].networkType:
raise InvalidProfileData(_('Interface can not combine with 2 networks with the same type.'))
# if self.networks[0].networkType == constants.NETWORK_TYPE_INFRA or self.networks[1].networkType == constants.NETWORK_TYPE_INFRA and \
# self.ipv6Mode != None and self.ipv4Mode != 'dhcp':
try:
for network in self.networks:
network.validate()
except InvalidProfileData as e:
raise InvalidProfileData(_(e.message + ' Interface: %s') % self.name)
def getNetworks(self):
pnets = ''
networkTypes = ''
hasNT = False
for network in self.networks:
if network.networkType is None:
continue
hasNT = True
if networkTypes:
networkTypes += ','
networkTypes = networkTypes + network.networkType
if hasattr(network, 'providerNetworks'):
# there should be only one network has providerNetwork
for pnet in network.providerNetworks:
if pnets:
pnets += ','
pnets = pnets + pnet
if not hasNT:
networkTypes = None
pnets = None
return networkTypes, pnets
class EthInterface(Interface):
def __init__(self, ifNode):
super(EthInterface, self).__init__(ifNode)
self.port, self.pciAddress, self.pclass, self.pdevice = self.getPort(ifNode)
def getPort(self, ifNode):
portNode = ifNode.find('port')
if portNode is None:
raise InvalidProfileData(_('Ethernet interface %s requires an Ethernet port ') %
ifNode.get('ifName'))
pciAddress = ''
tmp = portNode.get('pciAddress')
try:
pciAddress = EthInterface.formatPciAddress(tmp)
except InvalidProfileData as exc:
raise InvalidProfileData(exc.message + _('Interface %s, pciAddress %s') % (ifNode.get('ifName'), tmp))
pclass = portNode.get('class')
if pclass:
pclass = pclass.strip()
pdevice = portNode.get('device')
if pdevice:
pdevice = pdevice.strip()
return portNode.get('name'), pciAddress, pclass, pdevice
@staticmethod
def formatPciAddress(value):
# To parse a [X]:[X]:[X].[X] formatted pci address into [04x]:[02x]:[02x].[01x] pci address format
if value:
section_list1 = value.split(':')
else:
return ''
if len(section_list1) != 3:
raise InvalidProfileData(_('pciAddress is not well formatted.'))
section_list2 = section_list1[2].split('.')
if len(section_list2) != 2:
raise InvalidProfileData(_('pciAddress is not well formatted.'))
try:
sec1 = int(section_list1[0], 16)
sec2 = int(section_list1[1], 16)
sec3 = int(section_list2[0], 16)
sec4 = int(section_list2[1], 16)
except (TypeError, ValueError):
raise InvalidProfileData(_('pciAddress is not well formatted.'))
result = '{0:04x}:{1:02x}:{2:02x}.{3:01x}'.format(sec1, sec2, sec3, sec4)
return result
def getNetworkMap(self):
return {
'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),
'pciPassthrough': lambda node: PciPassthrough(node),
'pciSriov': lambda node: PciSriov(node)
}
class AeInterface(Interface):
def __init__(self, ifNode):
super(AeInterface, self).__init__(ifNode)
self.usesIf = []
aeModeNode = ifNode.find('aeMode') # aeMode is mandatory required by schema
node = aeModeNode[0] # it is mandatory required by schema
if node.tag == 'activeStandby':
self.aeMode = 'activeStandby'
self.txPolicy = None
elif node.tag == 'balanced':
self.aeMode = 'balanced'
self.txPolicy = node.get('txPolicy')
elif node.tag == 'ieee802.3ad':
self.aeMode = '802.3ad'
self.txPolicy = node.get('txPolicy')
node = ifNode.find('interfaces')
if node:
for usesIfNode in node.findall('interface'):
self.addUsesIf(usesIfNode.get('name'))
def addUsesIf(self, ifName):
if not ifName:
raise InvalidProfileData(_('Interface name value cannot be empty.'))
if ifName == self.name:
raise InvalidProfileData(_('Aggregrated ethernet interface (%s) cannot use itself.') % self.name)
if ifName not in self.usesIf:
self.usesIf.append(ifName)
def getNetworkMap(self):
return {
'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)
}
def validateWithIfNames(self, allInterfaceNames):
# raise InvalidProfileData exception if invalid
if len(self.usesIf) == 0:
msg = _('Aggregrated ethernet interface (%s) should have at least one interface.') % self.name
raise InvalidProfileData(msg)
for usesIfName in self.usesIf:
if usesIfName not in allInterfaceNames:
msg = _('Aggregrated ethernet interface (%s) uses a undeclared interface (%s)') % \
(self.name, usesIfName)
raise InvalidProfileData(msg)
super(AeInterface, self).validate()
class VlanInterface(Interface):
def __init__(self, ifNode):
super(VlanInterface, self).__init__(ifNode)
self.vlanId = int(ifNode.get('vlanId'))
usesIf = ifNode.get('interface')
if not usesIf:
raise InvalidProfileData(_('<usesIf> value cannot be empty.'))
if usesIf == self.name:
raise InvalidProfileData(_('vlan interface (%s) cannot use itself.') % self.name)
self.usesIfName = usesIf
self.usesIf = [usesIf]
def getNetworkMap(self):
return {
'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)
}
@staticmethod
def isEthInterface(ifName, ethIfMap):
return ifName in ethIfMap
def validateWithIfNames(self, allInterfaceNames, aeIfMap, vlanIfMap, ethIfMap):
# raise InvalidProfileData exception if invalid
if self.usesIfName not in allInterfaceNames:
msg = _('vlan interface (%s) uses a undeclared interface (%s)') % \
(self.name, self.usesIfName)
raise InvalidProfileData(msg)
isEthIf = self.isEthInterface(self.usesIfName, ethIfMap)
good = True
if not isEthIf:
ifNameToCheck = [self.usesIfName]
while len(ifNameToCheck) > 0:
ifName = ifNameToCheck.pop(0)
if ifName in aeIfMap:
aeIf = aeIfMap[ifName]
for n in aeIf.usesIf:
ifNameToCheck.append(n)
elif ifName in vlanIfMap:
good = False
break # not good,a vlan in uses tree
if not good:
raise InvalidProfileData(_('A vlan interface cannot use a vlan interface.'))
super(VlanInterface, self).validate()