Drydock support for multirack and MTU
- Add steps in CreateNetworkTemplate task for dhcp relay - Add steps in CreateNetworkTemplate task for MTU settings - Add steps in ApplyNodeNetworking to set interface MTU Change-Id: Ia575c2b2686d8ad98f5878c4b755e58607470b38
This commit is contained in:
parent
32be590a53
commit
cc528a070a
@ -1055,7 +1055,16 @@ class MaasTaskRunner(drivers.DriverTaskRunner):
|
||||
if link_fabric is None:
|
||||
link_fabric = maas_fabric.Fabric(
|
||||
self.maas_client, name=l.name)
|
||||
fabrics.add(link_fabric)
|
||||
link_fabric = fabrics.add(link_fabric)
|
||||
|
||||
# Ensure that the MTU of the untagged VLAN on the fabric
|
||||
# matches that on the NetworkLink config
|
||||
|
||||
vlan_list = maas_vlan.Vlans(self.maas_client, fabric_id=link_fabric.resource_id)
|
||||
vlan_list.refresh()
|
||||
vlan = vlan_list.singleton({'vid': 0})
|
||||
vlan.mtu = l.mtu
|
||||
vlan.update()
|
||||
|
||||
# Now that we have the fabrics sorted out, check
|
||||
# that VLAN tags and subnet attributes are correct
|
||||
@ -1187,36 +1196,59 @@ class MaasTaskRunner(drivers.DriverTaskRunner):
|
||||
vlan = vlan_list.select(subnet.vlan)
|
||||
|
||||
if dhcp_on and not vlan.dhcp_on:
|
||||
# check if design requires a dhcp relay and if the MaaS vlan already uses a dhcp_relay
|
||||
self.logger.info(
|
||||
"DHCP enabled for subnet %s, activating in MaaS"
|
||||
% (subnet.name))
|
||||
|
||||
# TODO(sh8121att): Ugly hack assuming a single rack controller
|
||||
# for now until we implement multirack
|
||||
resp = self.maas_client.get("rackcontrollers/")
|
||||
rack_ctlrs = maas_rack.RackControllers(self.maas_client)
|
||||
rack_ctlrs.refresh()
|
||||
|
||||
if resp.ok:
|
||||
resp_json = resp.json()
|
||||
dhcp_config_set=False
|
||||
|
||||
if not isinstance(resp_json, list):
|
||||
self.logger.warning(
|
||||
"Unexpected response when querying list of rack controllers"
|
||||
)
|
||||
self.logger.debug("%s" % resp.text)
|
||||
else:
|
||||
if len(resp_json) > 1:
|
||||
self.logger.warning(
|
||||
"Received more than one rack controller, defaulting to first"
|
||||
for r in rack_ctlrs:
|
||||
if n.dhcp_relay_upstream_target is not None:
|
||||
if r.interface_for_ip(n.dhcp_relay_upstream_target):
|
||||
iface = r.interface_for_ip(n.dhcp_relay_upstream_target)
|
||||
vlan.relay_vlan = iface.vlan
|
||||
self.logger.debug(
|
||||
"Relaying DHCP on vlan %s to vlan %s" % (vlan.resource_id, vlan.relay_vlan)
|
||||
)
|
||||
result_detail['detail'].append(
|
||||
"Relaying DHCP on vlan %s to vlan %s" % (vlan.resource_id, vlan.relay_vlan))
|
||||
vlan.update()
|
||||
dhcp_config_set=True
|
||||
break
|
||||
else:
|
||||
for i in r.interfaces:
|
||||
if i.vlan == vlan.resource_id:
|
||||
self.logger.debug(
|
||||
"Rack controller %s has interface on vlan %s" %
|
||||
(r.resource_id, vlan.resource_id))
|
||||
rackctl_id = r.resource_id
|
||||
|
||||
rackctl_id = resp_json[0]['system_id']
|
||||
vlan.dhcp_on = True
|
||||
vlan.primary_rack = rackctl_id
|
||||
self.logger.debug(
|
||||
"Enabling DHCP on VLAN %s managed by rack ctlr %s"
|
||||
% (vlan.resource_id, rackctl_id))
|
||||
result_detail['detail'].append(
|
||||
"Enabling DHCP on VLAN %s managed by rack ctlr %s"
|
||||
% (vlan.resource_id, rackctl_id))
|
||||
vlan.update()
|
||||
dhcp_config_set=True
|
||||
break
|
||||
if dhcp_config_set:
|
||||
break
|
||||
|
||||
if not dhcp_config_set:
|
||||
self.logger.error(
|
||||
"Network %s requires DHCP, but could not locate a rack controller to serve it." %
|
||||
(n.name))
|
||||
result_detail['detail'].append(
|
||||
"Network %s requires DHCP, but could not locate a rack controller to serve it." %
|
||||
(n.name))
|
||||
|
||||
vlan.dhcp_on = True
|
||||
vlan.primary_rack = rackctl_id
|
||||
vlan.update()
|
||||
self.logger.debug(
|
||||
"Enabling DHCP on VLAN %s managed by rack ctlr %s"
|
||||
% (vlan.resource_id, rackctl_id))
|
||||
elif dhcp_on and vlan.dhcp_on:
|
||||
self.logger.info(
|
||||
"DHCP already enabled for subnet %s" %
|
||||
@ -1576,6 +1608,12 @@ class MaasTaskRunner(drivers.DriverTaskRunner):
|
||||
iface.attach_fabric(
|
||||
fabric_id=fabric.resource_id)
|
||||
|
||||
if iface.effective_mtu != nl.mtu:
|
||||
self.logger.debug(
|
||||
"Updating interface %s MTU to %s"
|
||||
% (i.device_name, nl.mtu))
|
||||
iface.set_mtu(nl.mtu)
|
||||
|
||||
for iface_net in getattr(i, 'networks', []):
|
||||
dd_net = site_design.get_network(iface_net)
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
# 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.
|
||||
"""API model for MaaS network interface resource."""
|
||||
|
||||
import logging
|
||||
|
||||
import drydock_provisioner.drivers.node.maasdriver.models.base as model_base
|
||||
@ -26,10 +28,10 @@ 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'
|
||||
'links', 'effective_mtu', 'fabric_id', 'mtu',
|
||||
]
|
||||
json_fields = [
|
||||
'name', 'type', 'mac_address', 'vlan', 'links', 'effective_mtu'
|
||||
'name', 'type', 'mac_address', 'vlan', 'links', 'mtu',
|
||||
]
|
||||
|
||||
def __init__(self, api_client, **kwargs):
|
||||
@ -37,14 +39,14 @@ class Interface(model_base.ResourceBase):
|
||||
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
|
||||
"""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)
|
||||
@ -113,9 +115,9 @@ class Interface(model_base.ResourceBase):
|
||||
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.
|
||||
"""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
|
||||
@ -124,7 +126,6 @@ class Interface(model_base.ResourceBase):
|
||||
: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)
|
||||
@ -154,7 +155,7 @@ class Interface(model_base.ResourceBase):
|
||||
(self.resource_id, subnet.resource_id))
|
||||
self.unlink_subnet(subnet.resource_id)
|
||||
|
||||
# TODO Probably need to enumerate link mode
|
||||
# TODO(sh8121att) Probably need to enumerate link mode
|
||||
options = {
|
||||
'subnet': subnet.resource_id,
|
||||
'default_gateway': primary,
|
||||
@ -188,9 +189,31 @@ class Interface(model_base.ResourceBase):
|
||||
|
||||
return
|
||||
|
||||
def responds_to_ip(self, ip_address):
|
||||
"""Check if this interface will respond to connections for an IP.
|
||||
|
||||
:param ip_address: string of the IP address we are checking
|
||||
|
||||
:return: true if this interface should respond to the IP, false otherwise
|
||||
"""
|
||||
for l in getattr(self, 'links', []):
|
||||
if l.get('ip_address', None) == ip_address:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def set_mtu(self, new_mtu):
|
||||
"""Set interface MTU
|
||||
|
||||
:param new_mtu: integer of the new MTU size for this inteface
|
||||
"""
|
||||
self.mtu = new_mtu
|
||||
self.update()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, api_client, obj_dict):
|
||||
"""
|
||||
"""Instantiate this model from a dictionary.
|
||||
|
||||
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
|
||||
@ -231,15 +254,13 @@ class Interfaces(model_base.ResourceCollectionBase):
|
||||
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
|
||||
"""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})
|
||||
|
@ -41,6 +41,18 @@ class Machine(model_base.ResourceBase):
|
||||
else:
|
||||
self.interfaces = None
|
||||
|
||||
def interface_for_ip(self, ip_address):
|
||||
"""Find the machine interface that will respond to ip_address.
|
||||
|
||||
:param ip_address: The IP address to check interfaces
|
||||
|
||||
:return: The interface that responds to this IP or None
|
||||
"""
|
||||
for i in self.interfaces:
|
||||
if i.responds_to_ip(ip_address):
|
||||
return i
|
||||
return None
|
||||
|
||||
def get_power_params(self):
|
||||
url = self.interpolate_url()
|
||||
|
||||
@ -283,9 +295,7 @@ class Machines(model_base.ResourceCollectionBase):
|
||||
return maas_node
|
||||
|
||||
def query(self, query):
|
||||
"""
|
||||
Custom query method to deal with complex fields
|
||||
"""
|
||||
"""Custom query method to deal with complex fields."""
|
||||
result = list(self.resources.values())
|
||||
for (k, v) in query.items():
|
||||
if k.startswith('power_params.'):
|
||||
|
@ -17,9 +17,10 @@ import bson
|
||||
|
||||
import drydock_provisioner.drivers.node.maasdriver.models.base as model_base
|
||||
import drydock_provisioner.drivers.node.maasdriver.models.interface as maas_interface
|
||||
import drydock_provisioner.drivers.node.maasdriver.models.machine as maas_machine
|
||||
|
||||
|
||||
class RackController(model_base.ResourceBase):
|
||||
class RackController(maas_machine.Machine):
|
||||
"""Model for a rack controller singleton."""
|
||||
|
||||
# These are the services that must be 'running'
|
||||
@ -47,29 +48,6 @@ class RackController(model_base.ResourceBase):
|
||||
def __init__(self, api_client, **kwargs):
|
||||
super().__init__(api_client, **kwargs)
|
||||
|
||||
# Replace generic dicts with interface collection model
|
||||
if getattr(self, 'resource_id', None) is not None:
|
||||
self.interfaces = maas_interface.Interfaces(
|
||||
api_client, system_id=self.resource_id)
|
||||
self.interfaces.refresh()
|
||||
else:
|
||||
self.interfaces = None
|
||||
|
||||
def get_power_params(self):
|
||||
"""Get parameters for managing server power."""
|
||||
url = self.interpolate_url()
|
||||
|
||||
resp = self.api_client.get(url, op='power_parameters')
|
||||
|
||||
if resp.status_code == 200:
|
||||
self.power_parameters = resp.json()
|
||||
|
||||
def get_network_interface(self, iface_name):
|
||||
"""Retrieve network interface on this machine."""
|
||||
if self.interfaces is not None:
|
||||
iface = self.interfaces.singleton({'name': iface_name})
|
||||
return iface
|
||||
|
||||
def get_services(self):
|
||||
"""Get status of required services on this rack controller."""
|
||||
self.refresh()
|
||||
@ -89,55 +67,6 @@ class RackController(model_base.ResourceBase):
|
||||
|
||||
return svc_status
|
||||
|
||||
def get_details(self):
|
||||
url = self.interpolate_url()
|
||||
|
||||
resp = self.api_client.get(url, op='details')
|
||||
|
||||
if resp.status_code == 200:
|
||||
detail_config = bson.loads(resp.text)
|
||||
return detail_config
|
||||
|
||||
def to_dict(self):
|
||||
"""Serialize this resource instance.
|
||||
|
||||
Serialize into a dict matching the MAAS representation of the resource
|
||||
"""
|
||||
data_dict = {}
|
||||
|
||||
for f in self.json_fields:
|
||||
if getattr(self, f, None) is not None:
|
||||
if f == 'resource_id':
|
||||
data_dict['system_id'] = getattr(self, f)
|
||||
else:
|
||||
data_dict[f] = getattr(self, f)
|
||||
|
||||
return data_dict
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, api_client, obj_dict):
|
||||
"""Create a instance of this resource class based on a dict of MaaS type attributes.
|
||||
|
||||
Customized for Machine due to use of system_id instead of id
|
||||
as resource key
|
||||
|
||||
:param api_client: Instance of api_client.MaasRequestFactory for accessing MaaS API
|
||||
:param obj_dict: Python dict as parsed from MaaS API JSON representing this resource type
|
||||
"""
|
||||
refined_dict = {k: obj_dict.get(k, None) for k in cls.fields}
|
||||
|
||||
if 'system_id' in obj_dict.keys():
|
||||
refined_dict['resource_id'] = obj_dict.get('system_id')
|
||||
|
||||
# Capture the boot interface MAC to allow for node id of VMs
|
||||
if 'boot_interface' in obj_dict.keys():
|
||||
if isinstance(obj_dict['boot_interface'], dict):
|
||||
refined_dict['boot_mac'] = obj_dict['boot_interface'][
|
||||
'mac_address']
|
||||
|
||||
i = cls(api_client, **refined_dict)
|
||||
return i
|
||||
|
||||
|
||||
class RackControllers(model_base.ResourceCollectionBase):
|
||||
"""Model for a collection of rack controllers."""
|
||||
@ -147,27 +76,3 @@ class RackControllers(model_base.ResourceCollectionBase):
|
||||
|
||||
def __init__(self, api_client, **kwargs):
|
||||
super().__init__(api_client)
|
||||
|
||||
# Add the OOB power parameters to each machine instance
|
||||
def collect_power_params(self):
|
||||
for k, v in self.resources.items():
|
||||
v.get_power_params()
|
||||
|
||||
def query(self, query):
|
||||
"""Custom query method to deal with complex fields."""
|
||||
result = list(self.resources.values())
|
||||
for (k, v) in query.items():
|
||||
if k.startswith('power_params.'):
|
||||
field = k[13:]
|
||||
result = [
|
||||
i for i in result
|
||||
if str(
|
||||
getattr(i, 'power_parameters', {}).get(field, None)) ==
|
||||
str(v)
|
||||
]
|
||||
else:
|
||||
result = [
|
||||
i for i in result if str(getattr(i, k, None)) == str(v)
|
||||
]
|
||||
|
||||
return result
|
||||
|
@ -22,11 +22,11 @@ class Vlan(model_base.ResourceBase):
|
||||
resource_url = 'fabrics/{fabric_id}/vlans/{api_id}/'
|
||||
fields = [
|
||||
'resource_id', 'name', 'description', 'vid', 'fabric_id', 'dhcp_on',
|
||||
'mtu', 'primary_rack', 'secondary_rack'
|
||||
'mtu', 'primary_rack', 'secondary_rack', 'relay_vlan',
|
||||
]
|
||||
json_fields = [
|
||||
'name', 'description', 'vid', 'dhcp_on', 'mtu', 'primary_rack',
|
||||
'secondary_rack'
|
||||
'secondary_rack', 'relay_vlan',
|
||||
]
|
||||
|
||||
def __init__(self, api_client, **kwargs):
|
||||
@ -52,6 +52,27 @@ class Vlan(model_base.ResourceBase):
|
||||
else:
|
||||
self.vid = int(new_vid)
|
||||
|
||||
def set_dhcp_relay(self, relay_vlan_id):
|
||||
self.relay_vlan = relay_vlan_id
|
||||
self.update()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, api_client, obj_dict):
|
||||
"""
|
||||
Because MaaS decides to replace the relay_vlan id with the
|
||||
representation of the VLAN, we must reverse it for a true
|
||||
representation of the resource
|
||||
"""
|
||||
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('relay_vlan', None), dict):
|
||||
refined_dict['relay_vlan'] = refined_dict['relay_vlan']['id']
|
||||
|
||||
i = cls(api_client, **refined_dict)
|
||||
return i
|
||||
|
||||
|
||||
class Vlans(model_base.ResourceCollectionBase):
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user