
attributes. Mainly affected attempts to statically assigns addresses to the PXE interface.
278 lines
11 KiB
Python
278 lines
11 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
import logging
|
|
|
|
import drydock_provisioner.drivers.node.maasdriver.models.base as model_base
|
|
import drydock_provisioner.drivers.node.maasdriver.models.fabric as maas_fabric
|
|
import drydock_provisioner.drivers.node.maasdriver.models.subnet as maas_subnet
|
|
import drydock_provisioner.drivers.node.maasdriver.models.vlan as maas_vlan
|
|
|
|
import drydock_provisioner.error as errors
|
|
|
|
class Interface(model_base.ResourceBase):
|
|
|
|
resource_url = 'nodes/{system_id}/interfaces/{resource_id}/'
|
|
fields = ['resource_id', 'system_id', 'name', 'type', 'mac_address', 'vlan',
|
|
'links', 'effective_mtu', 'fabric_id']
|
|
json_fields = ['name', 'type', 'mac_address', 'vlan', 'links', 'effective_mtu']
|
|
|
|
def __init__(self, api_client, **kwargs):
|
|
super(Interface, self).__init__(api_client, **kwargs)
|
|
self.logger = logging.getLogger('drydock.nodedriver.maasdriver')
|
|
|
|
def attach_fabric(self, fabric_id=None, fabric_name=None):
|
|
"""
|
|
Attach this interface to a MaaS fabric. Only one of fabric_id
|
|
or fabric_name should be specified. If both are, fabric_id rules
|
|
|
|
:param fabric_id: The MaaS resource ID of a network Fabric to connect to
|
|
:param fabric_name: The name of a MaaS fabric to connect to
|
|
"""
|
|
|
|
fabric = None
|
|
|
|
fabrics = maas_fabric.Fabrics(self.api_client)
|
|
fabrics.refresh()
|
|
|
|
if fabric_id is not None:
|
|
fabric = fabrics.select(fabric_id)
|
|
elif fabric_name is not None:
|
|
fabric = fabrics.singleton({'name': fabric_name})
|
|
else:
|
|
self.logger.warning("Must specify fabric_id or fabric_name")
|
|
raise ValueError("Must specify fabric_id or fabric_name")
|
|
|
|
if fabric is None:
|
|
self.logger.warning("Fabric not found in MaaS for fabric_id %s, fabric_name %s" %
|
|
(fabric_id, fabric_name))
|
|
raise errors.DriverError("Fabric not found in MaaS for fabric_id %s, fabric_name %s" %
|
|
(fabric_id, fabric_name))
|
|
|
|
# Locate the untagged VLAN for this fabric.
|
|
fabric_vlan = fabric.vlans.singleton({'vid': 0})
|
|
|
|
if fabric_vlan is None:
|
|
self.logger.warning("Cannot locate untagged VLAN on fabric %s" % (fabric_id))
|
|
raise errors.DriverError("Cannot locate untagged VLAN on fabric %s" % (fabric_id))
|
|
|
|
self.vlan = fabric_vlan.resource_id
|
|
self.logger.info("Attaching interface %s on system %s to VLAN %s on fabric %s" %
|
|
(self.resource_id, self.system_id, fabric_vlan.resource_id, fabric.resource_id))
|
|
self.update()
|
|
|
|
def is_linked(self, subnet_id):
|
|
for l in self.links:
|
|
if l.get('subnet_id', None) == subnet_id:
|
|
return True
|
|
|
|
return False
|
|
|
|
def unlink_subnet(self, subnet_id):
|
|
for l in self.links:
|
|
if l.get('subnet_id', None) == subnet_id:
|
|
url = self.interpolate_url()
|
|
|
|
resp = self.api_client.post(url, op='unlink_subnet', files={'id': l.get('resource_id')})
|
|
|
|
if not resp.ok:
|
|
raise errors.DriverError("Error unlinking subnet")
|
|
else:
|
|
return
|
|
|
|
raise errors.DriverError("Error unlinking interface, Link to subnet_id %s not found." % subnet_id)
|
|
|
|
def link_subnet(self, subnet_id=None, subnet_cidr=None, ip_address=None, primary=False):
|
|
"""
|
|
Link this interface to a MaaS subnet. One of subnet_id or subnet_cidr
|
|
should be specified. If both are, subnet_id rules.
|
|
|
|
:param subnet_id: The MaaS resource ID of a network subnet to connect to
|
|
:param subnet_cidr: The CIDR of a MaaS subnet to connect to
|
|
:param ip_address: The IP address to assign this interface. Should be a string with
|
|
a static IP or None. If None, DHCP will be used.
|
|
:param primary: Boolean of whether this interface is the primary interface of the node. This
|
|
sets the node default gateway to the gateway of the subnet
|
|
"""
|
|
|
|
subnet = None
|
|
|
|
subnets = maas_subnet.Subnets(self.api_client)
|
|
subnets.refresh()
|
|
|
|
if subnet_id is not None:
|
|
subnet = subnets.select(subnet_id)
|
|
elif subnet_cidr is not None:
|
|
subnet = subnets.singleton({'cidr': subnet_cidr})
|
|
else:
|
|
self.logger.warning("Must specify subnet_id or subnet_cidr")
|
|
raise ValueError("Must specify subnet_id or subnet_cidr")
|
|
|
|
if subnet is None:
|
|
self.logger.warning("Subnet not found in MaaS for subnet_id %s, subnet_cidr %s" %
|
|
(subnet_id, subnet_cidr))
|
|
raise errors.DriverError("Subnet not found in MaaS for subnet_id %s, subnet_cidr %s" %
|
|
(subnet_id, subnet_cidr))
|
|
|
|
url = self.interpolate_url()
|
|
|
|
if self.is_linked(subnet.resource_id):
|
|
self.logger.info("Interface %s already linked to subnet %s, unlinking." %
|
|
(self.resource_id, subnet.resource_id))
|
|
self.unlink_subnet(subnet.resource_id)
|
|
|
|
|
|
# TODO Probably need to enumerate link mode
|
|
options = { 'subnet': subnet.resource_id,
|
|
'default_gateway': primary,
|
|
}
|
|
|
|
if ip_address == 'dhcp':
|
|
options['mode'] = 'dhcp'
|
|
elif ip_address is not None:
|
|
options['ip_address'] = ip_address
|
|
options['mode'] = 'static'
|
|
else:
|
|
options['mode'] = 'link_up'
|
|
|
|
self.logger.debug("Linking interface %s to subnet: subnet=%s, mode=%s, address=%s, primary=%s" %
|
|
(self.resource_id, subnet.resource_id, options['mode'], ip_address, primary))
|
|
|
|
resp = self.api_client.post(url, op='link_subnet', files=options)
|
|
|
|
if not resp.ok:
|
|
self.logger.error("Error linking interface %s to subnet %s - MaaS response %s: %s" %
|
|
(self.resouce_id, subnet.resource_id, resp.status_code, resp.text))
|
|
raise errors.DriverError("Error linking interface %s to subnet %s - MaaS response %s" %
|
|
(self.resouce_id, subnet.resource_id, resp.status_code))
|
|
|
|
self.refresh()
|
|
|
|
return
|
|
|
|
@classmethod
|
|
def from_dict(cls, api_client, obj_dict):
|
|
"""
|
|
Because MaaS decides to replace the resource ids with the
|
|
representation of the resource, we must reverse it for a true
|
|
representation of the Interface
|
|
"""
|
|
refined_dict = {k: obj_dict.get(k, None) for k in cls.fields}
|
|
if 'id' in obj_dict.keys():
|
|
refined_dict['resource_id'] = obj_dict.get('id')
|
|
|
|
if isinstance(refined_dict.get('vlan', None), dict):
|
|
refined_dict['fabric_id'] = refined_dict['vlan']['fabric_id']
|
|
refined_dict['vlan'] = refined_dict['vlan']['id']
|
|
|
|
link_list = []
|
|
if isinstance(refined_dict.get('links', None), list):
|
|
for l in refined_dict['links']:
|
|
if isinstance(l, dict):
|
|
link = { 'resource_id': l['id'],
|
|
'mode': l['mode']
|
|
}
|
|
|
|
if l.get('subnet', None) is not None:
|
|
link['subnet_id'] = l['subnet']['id']
|
|
link['ip_address'] = l.get('ip_address', None)
|
|
|
|
link_list.append(link)
|
|
|
|
refined_dict['links'] = link_list
|
|
|
|
i = cls(api_client, **refined_dict)
|
|
return i
|
|
|
|
class Interfaces(model_base.ResourceCollectionBase):
|
|
|
|
collection_url = 'nodes/{system_id}/interfaces/'
|
|
collection_resource = Interface
|
|
|
|
def __init__(self, api_client, **kwargs):
|
|
super(Interfaces, self).__init__(api_client)
|
|
self.system_id = kwargs.get('system_id', None)
|
|
|
|
def create_vlan(self, vlan_tag, parent_name, mtu=None, tags=[]):
|
|
"""
|
|
Create a new VLAN interface on this node
|
|
|
|
:param vlan_tag: The VLAN ID (not MaaS resource id of a VLAN) to create interface for
|
|
:param parent_name: The name of a MaaS interface to build the VLAN interface on top of
|
|
:param mtu: Optional configuration of the interface MTU
|
|
:param tags: Optional list of string tags to apply to the VLAN interface
|
|
"""
|
|
|
|
self.refresh()
|
|
|
|
parent_iface = self.singleton({'name': parent_name})
|
|
|
|
if parent_iface is None:
|
|
self.logger.error("Cannot locate parent interface %s" % (parent_name))
|
|
raise errors.DriverError("Cannot locate parent interface %s" % (parent_name))
|
|
|
|
if parent_iface.type != 'physical':
|
|
self.logger.error("Cannot create VLAN interface on parent of type %s" % (parent_iface.type))
|
|
raise errors.DriverError("Cannot create VLAN interface on parent of type %s" % (parent_iface.type))
|
|
|
|
if parent_iface.vlan is None:
|
|
self.logger.error("Cannot create VLAN interface on disconnected parent %s" % (parent_iface.resource_id))
|
|
raise errors.DriverError("Cannot create VLAN interface on disconnected parent %s" % (parent_iface.resource_id))
|
|
|
|
vlans = maas_vlan.Vlans(self.api_client, fabric_id=parent_iface.fabric_id)
|
|
vlans.refresh()
|
|
|
|
vlan = vlans.singleton({'vid': vlan_tag})
|
|
|
|
if vlan is None:
|
|
self.logger.error("Cannot locate VLAN %s on fabric %s to attach interface" %
|
|
(vlan_tag, parent_iface.fabric_id))
|
|
|
|
exists = self.singleton({'vlan': vlan.resource_id})
|
|
|
|
if exists is not None:
|
|
self.logger.info("Interface for VLAN %s already exists on node %s, skipping" %
|
|
(vlan_tag, self.system_id))
|
|
return exists
|
|
|
|
url = self.interpolate_url()
|
|
|
|
|
|
options = { 'tags': ','.join(tags),
|
|
'vlan': vlan.resource_id,
|
|
'parent': parent_iface.resource_id,
|
|
}
|
|
|
|
if mtu is not None:
|
|
options['mtu'] = mtu
|
|
|
|
resp = self.api_client.post(url, op='create_vlan', files=options)
|
|
|
|
|
|
if resp.status_code == 200:
|
|
resp_json = resp.json()
|
|
vlan_iface = Interface.from_dict(self.api_client, resp_json)
|
|
self.logger.debug("Created VLAN interface %s for parent %s attached to VLAN %s" %
|
|
(vlan_iface.resource_id, parent_iface.resource_id, vlan.resource_id))
|
|
return vlan_iface
|
|
else:
|
|
self.logger.error("Error creating VLAN interface to VLAN %s on system %s - MaaS response %s: %s" %
|
|
(vlan.resource_id, self.system_id, resp.status_code, resp.text))
|
|
raise errors.DriverError("Error creating VLAN interface to VLAN %s on system %s - MaaS response %s" %
|
|
(vlan.resource_id, self.system_id, resp.status_code))
|
|
|
|
self.refresh()
|
|
|
|
return
|
|
|