Add get_node_network_data
to Neutron NetworkInterface
Implements `get_node_network_data` network interface method for Neutron networks providing Nova network metadata (AKA network_data.json) collected from Neutron VIF of ironic port objects associated with the node. Co-Authored: Iury Gregory Melo Ferreira <iurygregory@gmail.com> Change-Id: I0fa742110649ed2786f360e1ef43012e77671620 Story: 2006691 Task: 37071
This commit is contained in:
parent
e461e36ee9
commit
c84c6af087
@ -11,6 +11,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
from keystoneauth1 import loading as ks_loading
|
from keystoneauth1 import loading as ks_loading
|
||||||
from neutronclient.common import exceptions as neutron_exceptions
|
from neutronclient.common import exceptions as neutron_exceptions
|
||||||
@ -474,6 +475,160 @@ def remove_neutron_ports(task, params):
|
|||||||
{'node_uuid': node_uuid})
|
{'node_uuid': node_uuid})
|
||||||
|
|
||||||
|
|
||||||
|
def _uncidr(cidr, ipv6=False):
|
||||||
|
"""Convert CIDR network representation into network/netmask form
|
||||||
|
|
||||||
|
:param cidr: network in CIDR form
|
||||||
|
:param ipv6: if `True`, consider `cidr` being IPv6
|
||||||
|
:returns: a tuple of network/host number in dotted
|
||||||
|
decimal notation, netmask in dotted decimal notation
|
||||||
|
|
||||||
|
"""
|
||||||
|
net = ipaddress.ip_interface(cidr).network
|
||||||
|
return str(net.network_address), str(net.netmask)
|
||||||
|
|
||||||
|
|
||||||
|
def get_neutron_port_data(port_id, vif_id, client=None, context=None):
|
||||||
|
"""Gather Neutron port and network configuration
|
||||||
|
|
||||||
|
Query Neutron for port and network configuration, return whatever
|
||||||
|
is available.
|
||||||
|
|
||||||
|
:param port_id: ironic port/portgroup ID.
|
||||||
|
:param vif_id: Neutron port ID.
|
||||||
|
:param client: Optional a Neutron client object.
|
||||||
|
:param context: request context
|
||||||
|
:type context: ironic.common.context.RequestContext
|
||||||
|
:raises: NetworkError
|
||||||
|
:returns: a dict holding network configuration information
|
||||||
|
associated with this ironic or Neutron port.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not client:
|
||||||
|
client = get_client(context=context)
|
||||||
|
|
||||||
|
try:
|
||||||
|
port_config = client.show_port(
|
||||||
|
vif_id, fields=['id', 'name', 'dns_assignment', 'fixed_ips',
|
||||||
|
'mac_address', 'network_id'])
|
||||||
|
|
||||||
|
except neutron_exceptions.NeutronClientException as e:
|
||||||
|
msg = (_('Unable to get port info for %(port_id)s. Error: '
|
||||||
|
'%(err)s') % {'port_id': vif_id, 'err': e})
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.NetworkError(msg)
|
||||||
|
|
||||||
|
LOG.debug('Received port %(port)s data: %(info)s',
|
||||||
|
{'port': vif_id, 'info': port_config})
|
||||||
|
|
||||||
|
port_config = port_config['port']
|
||||||
|
|
||||||
|
port_id = port_config['name'] or port_id
|
||||||
|
|
||||||
|
network_id = port_config.get('network_id')
|
||||||
|
|
||||||
|
try:
|
||||||
|
network_config = client.show_network(
|
||||||
|
network_id, fields=['id', 'mtu', 'subnets'])
|
||||||
|
|
||||||
|
except neutron_exceptions.NeutronClientException as e:
|
||||||
|
msg = (_('Unable to get network info for %(network_id)s. Error: '
|
||||||
|
'%(err)s') % {'network_id': network_id, 'err': e})
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.NetworkError(msg)
|
||||||
|
|
||||||
|
LOG.debug('Received network %(network)s data: %(info)s',
|
||||||
|
{'network': network_id, 'info': network_config})
|
||||||
|
|
||||||
|
network_config = network_config['network']
|
||||||
|
|
||||||
|
subnets_config = {}
|
||||||
|
|
||||||
|
network_data = {
|
||||||
|
'links': [
|
||||||
|
{
|
||||||
|
'id': port_id,
|
||||||
|
'type': 'vif',
|
||||||
|
'ethernet_mac_address': port_config['mac_address'],
|
||||||
|
'vif_id': port_config['id'],
|
||||||
|
'mtu': network_config['mtu']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'networks': [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
for fixed_ip in port_config.get('fixed_ips', []):
|
||||||
|
subnet_id = fixed_ip['subnet_id']
|
||||||
|
|
||||||
|
try:
|
||||||
|
subnet_config = client.show_subnet(
|
||||||
|
subnet_id, fields=['id', 'name', 'enable_dhcp',
|
||||||
|
'dns_nameservers', 'host_routes',
|
||||||
|
'ip_version', 'gateway_ip', 'cidr'])
|
||||||
|
|
||||||
|
LOG.debug('Received subnet %(subnet)s data: %(info)s',
|
||||||
|
{'subnet': subnet_id, 'info': subnet_config})
|
||||||
|
|
||||||
|
subnets_config[subnet_id] = subnet_config['subnet']
|
||||||
|
|
||||||
|
except neutron_exceptions.NeutronClientException as e:
|
||||||
|
msg = (_('Unable to get subnet info for %(subnet_id)s. Error: '
|
||||||
|
'%(err)s') % {'subnet_id': subnet_id, 'err': e})
|
||||||
|
LOG.exception(msg)
|
||||||
|
raise exception.NetworkError(msg)
|
||||||
|
|
||||||
|
subnet_config = subnets_config[subnet_id]
|
||||||
|
|
||||||
|
subnet_network, netmask = _uncidr(
|
||||||
|
subnet_config['cidr'], subnet_config['ip_version'] == 6)
|
||||||
|
|
||||||
|
network = {
|
||||||
|
'id': fixed_ip['subnet_id'],
|
||||||
|
'network_id': port_config['network_id'],
|
||||||
|
'type': 'ipv%s' % subnet_config['ip_version'],
|
||||||
|
'link': port_id,
|
||||||
|
'ip_address': fixed_ip['ip_address'],
|
||||||
|
'netmask': netmask,
|
||||||
|
'routes': [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO(etingof): Adding default route if gateway is present.
|
||||||
|
# This is a hack, Neutron should have given us a route.
|
||||||
|
|
||||||
|
if subnet_config['gateway_ip']:
|
||||||
|
zero_addr = ('::0' if subnet_config['ip_version'] == 6
|
||||||
|
else '0.0.0.0')
|
||||||
|
|
||||||
|
route = {
|
||||||
|
'network': zero_addr,
|
||||||
|
'netmask': zero_addr,
|
||||||
|
'gateway': subnet_config['gateway_ip']
|
||||||
|
}
|
||||||
|
|
||||||
|
network['routes'].append(route)
|
||||||
|
|
||||||
|
for host_config in subnet_config['host_routes']:
|
||||||
|
subnet_network, netmask = _uncidr(
|
||||||
|
host_config['destination'],
|
||||||
|
subnet_config['ip_version'] == 6)
|
||||||
|
|
||||||
|
route = {
|
||||||
|
'network': subnet_network,
|
||||||
|
'netmask': netmask,
|
||||||
|
'gateway': host_config['nexthop']
|
||||||
|
}
|
||||||
|
|
||||||
|
network['routes'].append(route)
|
||||||
|
|
||||||
|
network_data['networks'].append(network)
|
||||||
|
|
||||||
|
return network_data
|
||||||
|
|
||||||
|
|
||||||
def get_node_portmap(task):
|
def get_node_portmap(task):
|
||||||
"""Extract the switch port information for the node.
|
"""Extract the switch port information for the node.
|
||||||
|
|
||||||
|
@ -410,7 +410,7 @@ class VIFPortIDMixin(object):
|
|||||||
or self._get_vif_id_by_port_like_obj(p_obj) or None)
|
or self._get_vif_id_by_port_like_obj(p_obj) or None)
|
||||||
|
|
||||||
def get_node_network_data(self, task):
|
def get_node_network_data(self, task):
|
||||||
"""Return network configuration for node NICs.
|
"""Get network configuration data for node's ports/portgroups.
|
||||||
|
|
||||||
Gather L2 and L3 network settings from ironic node `network_data`
|
Gather L2 and L3 network settings from ironic node `network_data`
|
||||||
field. Ironic would eventually pass network configuration to the node
|
field. Ironic would eventually pass network configuration to the node
|
||||||
@ -633,3 +633,51 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
|
|||||||
# DELETING state.
|
# DELETING state.
|
||||||
if task.node.provision_state in [states.ACTIVE, states.DELETING]:
|
if task.node.provision_state in [states.ACTIVE, states.DELETING]:
|
||||||
neutron.unbind_neutron_port(vif_id, context=task.context)
|
neutron.unbind_neutron_port(vif_id, context=task.context)
|
||||||
|
|
||||||
|
def get_node_network_data(self, task):
|
||||||
|
"""Get network configuration data for node ports.
|
||||||
|
|
||||||
|
Pull network data from ironic node object if present, otherwise
|
||||||
|
collect it for Neutron VIFs.
|
||||||
|
|
||||||
|
:param task: A TaskManager instance.
|
||||||
|
:raises: InvalidParameterValue, if the network interface configuration
|
||||||
|
is invalid.
|
||||||
|
:raises: MissingParameterValue, if some parameters are missing.
|
||||||
|
:returns: a dict holding network configuration information adhearing
|
||||||
|
Nova network metadata layout (`network_data.json`).
|
||||||
|
"""
|
||||||
|
# NOTE(etingof): static network data takes precedence
|
||||||
|
network_data = (
|
||||||
|
super(NeutronVIFPortIDMixin, self).get_node_network_data(task))
|
||||||
|
if network_data:
|
||||||
|
return network_data
|
||||||
|
|
||||||
|
node = task.node
|
||||||
|
|
||||||
|
LOG.debug('Gathering network data from ports of node '
|
||||||
|
'%(node)s', {'node': node.uuid})
|
||||||
|
|
||||||
|
network_data = collections.defaultdict(list)
|
||||||
|
|
||||||
|
for port_obj in task.ports:
|
||||||
|
vif_port_id = self.get_current_vif(task, port_obj)
|
||||||
|
|
||||||
|
LOG.debug('Considering node %(node)s port %(port)s, VIF %(vif)s',
|
||||||
|
{'node': node.uuid, 'port': port_obj.uuid,
|
||||||
|
'vif': vif_port_id})
|
||||||
|
|
||||||
|
if not vif_port_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
port_network_data = neutron.get_neutron_port_data(
|
||||||
|
port_obj.uuid, vif_port_id, context=task.context)
|
||||||
|
|
||||||
|
for field, field_data in port_network_data.items():
|
||||||
|
if field_data:
|
||||||
|
network_data[field].extend(field_data)
|
||||||
|
|
||||||
|
LOG.debug('Collected network data for node %(node)s: %(data)s',
|
||||||
|
{'node': node.uuid, 'data': network_data})
|
||||||
|
|
||||||
|
return network_data
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"network": {
|
||||||
|
"admin_state_up": true,
|
||||||
|
"availability_zone_hints": [],
|
||||||
|
"availability_zones": [
|
||||||
|
"nova"
|
||||||
|
],
|
||||||
|
"created_at": "2016-03-08T20:19:41",
|
||||||
|
"dns_domain": "my-domain.org.",
|
||||||
|
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
|
||||||
|
"ipv4_address_scope": null,
|
||||||
|
"ipv6_address_scope": null,
|
||||||
|
"l2_adjacency": false,
|
||||||
|
"mtu": 1500,
|
||||||
|
"name": "private-network",
|
||||||
|
"port_security_enabled": true,
|
||||||
|
"project_id": "4fd44f30292945e481c7b8a0c8908869",
|
||||||
|
"qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
|
||||||
|
"revision_number": 1,
|
||||||
|
"router:external": false,
|
||||||
|
"shared": true,
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"subnets": [
|
||||||
|
"54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
|
||||||
|
],
|
||||||
|
"tags": ["tag1,tag2"],
|
||||||
|
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
|
||||||
|
"updated_at": "2016-03-08T20:19:41",
|
||||||
|
"vlan_transparent": false,
|
||||||
|
"description": "",
|
||||||
|
"is_default": true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"network": {
|
||||||
|
"admin_state_up": true,
|
||||||
|
"availability_zone_hints": [],
|
||||||
|
"availability_zones": [
|
||||||
|
"nova"
|
||||||
|
],
|
||||||
|
"created_at": "2016-03-08T20:19:41",
|
||||||
|
"dns_domain": "my-domain.org.",
|
||||||
|
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
|
||||||
|
"ipv4_address_scope": null,
|
||||||
|
"ipv6_address_scope": null,
|
||||||
|
"l2_adjacency": false,
|
||||||
|
"mtu": 1500,
|
||||||
|
"name": "private-network",
|
||||||
|
"port_security_enabled": true,
|
||||||
|
"project_id": "5199666e520f4aed823710aec37cfd38",
|
||||||
|
"qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
|
||||||
|
"revision_number": 1,
|
||||||
|
"router:external": false,
|
||||||
|
"shared": true,
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"subnets": [
|
||||||
|
"54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
|
||||||
|
],
|
||||||
|
"tags": ["tag1,tag2"],
|
||||||
|
"tenant_id": "5199666e520f4aed823710aec37cfd38",
|
||||||
|
"updated_at": "2016-03-08T20:19:41",
|
||||||
|
"vlan_transparent": false,
|
||||||
|
"description": "",
|
||||||
|
"is_default": true
|
||||||
|
}
|
||||||
|
}
|
59
ironic/tests/unit/common/json_samples/neutron_port_show.json
Normal file
59
ironic/tests/unit/common/json_samples/neutron_port_show.json
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"port": {
|
||||||
|
"admin_state_up": true,
|
||||||
|
"allowed_address_pairs": [],
|
||||||
|
"binding:host_id": "devstack",
|
||||||
|
"binding:profile": {},
|
||||||
|
"binding:vif_details": {
|
||||||
|
"ovs_hybrid_plug": true,
|
||||||
|
"port_filter": true
|
||||||
|
},
|
||||||
|
"binding:vif_type": "ovs",
|
||||||
|
"binding:vnic_type": "normal",
|
||||||
|
"created_at": "2016-03-08T20:19:41",
|
||||||
|
"data_plane_status": "ACTIVE",
|
||||||
|
"description": "",
|
||||||
|
"device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e",
|
||||||
|
"device_owner": "network:router_interface",
|
||||||
|
"dns_assignment": {
|
||||||
|
"hostname": "myport",
|
||||||
|
"ip_address": "10.0.0.2",
|
||||||
|
"fqdn": "myport.my-domain.org"
|
||||||
|
},
|
||||||
|
"dns_domain": "my-domain.org.",
|
||||||
|
"dns_name": "myport",
|
||||||
|
"extra_dhcp_opts": [
|
||||||
|
{
|
||||||
|
"opt_value": "pxelinux.0",
|
||||||
|
"ip_version": 4,
|
||||||
|
"opt_name": "bootfile-name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fixed_ips": [
|
||||||
|
{
|
||||||
|
"ip_address": "10.0.0.2",
|
||||||
|
"subnet_id": "a0304c3a-4f08-4c43-88af-d796509c97d2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2",
|
||||||
|
"ip_allocation": "immediate",
|
||||||
|
"mac_address": "fa:16:3e:23:fd:d7",
|
||||||
|
"mac_learning_enabled": false,
|
||||||
|
"name": "",
|
||||||
|
"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
|
||||||
|
"port_security_enabled": false,
|
||||||
|
"project_id": "7e02058126cc4950b75f9970368ba177",
|
||||||
|
"revision_number": 1,
|
||||||
|
"security_groups": [],
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"tags": ["tag1,tag2"],
|
||||||
|
"tenant_id": "7e02058126cc4950b75f9970368ba177",
|
||||||
|
"updated_at": "2016-03-08T20:19:41",
|
||||||
|
"qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae",
|
||||||
|
"resource_request": {
|
||||||
|
"required": ["CUSTOM_PHYSNET_PUBLIC", "CUSTOM_VNIC_TYPE_NORMAL"],
|
||||||
|
"resources": {"NET_BW_EGR_KILOBIT_PER_SEC": 1000}
|
||||||
|
},
|
||||||
|
"uplink_status_propagation": false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"port": {
|
||||||
|
"admin_state_up": true,
|
||||||
|
"allowed_address_pairs": [],
|
||||||
|
"binding:host_id": "devstack",
|
||||||
|
"binding:profile": {},
|
||||||
|
"binding:vif_details": {
|
||||||
|
"ovs_hybrid_plug": true,
|
||||||
|
"port_filter": true
|
||||||
|
},
|
||||||
|
"binding:vif_type": "ovs",
|
||||||
|
"binding:vnic_type": "normal",
|
||||||
|
"created_at": "2016-03-08T20:19:41",
|
||||||
|
"data_plane_status": "ACTIVE",
|
||||||
|
"description": "",
|
||||||
|
"device_id": "5e3898d7-11be-483e-9732-b2f5eccd2b2e",
|
||||||
|
"device_owner": "network:router_interface",
|
||||||
|
"dns_assignment": {
|
||||||
|
"hostname": "myport",
|
||||||
|
"ip_address": "fd00:203:0:113::2",
|
||||||
|
"fqdn": "myport.my-domain.org"
|
||||||
|
},
|
||||||
|
"dns_domain": "my-domain.org.",
|
||||||
|
"dns_name": "myport",
|
||||||
|
"extra_dhcp_opts": [
|
||||||
|
{
|
||||||
|
"opt_value": "pxelinux.0",
|
||||||
|
"ip_version": 6,
|
||||||
|
"opt_name": "bootfile-name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fixed_ips": [
|
||||||
|
{
|
||||||
|
"ip_address": "fd00:203:0:113::2",
|
||||||
|
"subnet_id": "906e685a-b964-4d58-9939-9cf3af197c67"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "96d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb8",
|
||||||
|
"ip_allocation": "immediate",
|
||||||
|
"mac_address": "52:54:00:4f:ef:b7",
|
||||||
|
"mac_learning_enabled": false,
|
||||||
|
"name": "",
|
||||||
|
"network_id": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
|
||||||
|
"port_security_enabled": false,
|
||||||
|
"project_id": "7e02058126cc4950b75f9970368ba177",
|
||||||
|
"revision_number": 1,
|
||||||
|
"security_groups": [],
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"tags": ["tag1,tag2"],
|
||||||
|
"tenant_id": "7e02058126cc4950b75f9970368ba177",
|
||||||
|
"updated_at": "2016-03-08T20:19:41",
|
||||||
|
"qos_policy_id": "29d5e02e-d5ab-4929-bee4-4a9fc12e22ae",
|
||||||
|
"resource_request": {
|
||||||
|
"required": ["CUSTOM_PHYSNET_PUBLIC", "CUSTOM_VNIC_TYPE_NORMAL"],
|
||||||
|
"resources": {"NET_BW_EGR_KILOBIT_PER_SEC": 1000}
|
||||||
|
},
|
||||||
|
"uplink_status_propagation": false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"subnet": {
|
||||||
|
"name": "private-subnet",
|
||||||
|
"enable_dhcp": true,
|
||||||
|
"network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
|
||||||
|
"segment_id": null,
|
||||||
|
"project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
|
||||||
|
"tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
|
||||||
|
"dns_nameservers": [],
|
||||||
|
"dns_publish_fixed_ip": false,
|
||||||
|
"allocation_pools": [
|
||||||
|
{
|
||||||
|
"start": "10.0.0.2",
|
||||||
|
"end": "10.0.0.254"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"host_routes": [],
|
||||||
|
"ip_version": 4,
|
||||||
|
"gateway_ip": "10.0.0.1",
|
||||||
|
"cidr": "10.0.0.0/24",
|
||||||
|
"id": "08eae331-0402-425a-923c-34f7cfe39c1b",
|
||||||
|
"created_at": "2016-10-10T14:35:34Z",
|
||||||
|
"description": "",
|
||||||
|
"ipv6_address_mode": null,
|
||||||
|
"ipv6_ra_mode": null,
|
||||||
|
"revision_number": 2,
|
||||||
|
"service_types": [],
|
||||||
|
"subnetpool_id": null,
|
||||||
|
"tags": ["tag1,tag2"],
|
||||||
|
"updated_at": "2016-10-10T14:35:34Z"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"subnet": {
|
||||||
|
"name": "private-subnet",
|
||||||
|
"enable_dhcp": true,
|
||||||
|
"network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
|
||||||
|
"segment_id": null,
|
||||||
|
"project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
|
||||||
|
"tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
|
||||||
|
"dns_nameservers": [],
|
||||||
|
"dns_publish_fixed_ip": false,
|
||||||
|
"allocation_pools": [
|
||||||
|
{
|
||||||
|
"start": "fd00:203:0:113::2",
|
||||||
|
"end": "fd00:203:0:113:ffff:ffff:ffff:ffff"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"host_routes": [],
|
||||||
|
"ip_version": 6,
|
||||||
|
"gateway_ip": "fd00:203:0:113::1",
|
||||||
|
"cidr": "fd00:203:0:113::/64",
|
||||||
|
"id": "08eae331-0402-425a-923c-34f7cfe39c1b",
|
||||||
|
"created_at": "2016-10-10T14:35:34Z",
|
||||||
|
"description": "",
|
||||||
|
"ipv6_address_mode": "slaac",
|
||||||
|
"ipv6_ra_mode": null,
|
||||||
|
"revision_number": 2,
|
||||||
|
"service_types": [],
|
||||||
|
"subnetpool_id": null,
|
||||||
|
"tags": ["tag1,tag2"],
|
||||||
|
"updated_at": "2016-10-10T14:35:34Z"
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,8 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
@ -270,6 +272,30 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
|
|||||||
patcher.start()
|
patcher.start()
|
||||||
self.addCleanup(patcher.stop)
|
self.addCleanup(patcher.stop)
|
||||||
|
|
||||||
|
port_show_file = os.path.join(
|
||||||
|
os.path.dirname(__file__), 'json_samples',
|
||||||
|
'neutron_port_show.json')
|
||||||
|
with open(port_show_file, 'rb') as fl:
|
||||||
|
self.port_data = json.load(fl)
|
||||||
|
|
||||||
|
self.client_mock.show_port.return_value = self.port_data
|
||||||
|
|
||||||
|
network_show_file = os.path.join(
|
||||||
|
os.path.dirname(__file__), 'json_samples',
|
||||||
|
'neutron_network_show.json')
|
||||||
|
with open(network_show_file, 'rb') as fl:
|
||||||
|
self.network_data = json.load(fl)
|
||||||
|
|
||||||
|
self.client_mock.show_network.return_value = self.network_data
|
||||||
|
|
||||||
|
subnet_show_file = os.path.join(
|
||||||
|
os.path.dirname(__file__), 'json_samples',
|
||||||
|
'neutron_subnet_show.json')
|
||||||
|
with open(subnet_show_file, 'rb') as fl:
|
||||||
|
self.subnet_data = json.load(fl)
|
||||||
|
|
||||||
|
self.client_mock.show_subnet.return_value = self.subnet_data
|
||||||
|
|
||||||
@mock.patch.object(neutron, 'update_neutron_port', autospec=True)
|
@mock.patch.object(neutron, 'update_neutron_port', autospec=True)
|
||||||
def _test_add_ports_to_network(self, update_mock, is_client_id,
|
def _test_add_ports_to_network(self, update_mock, is_client_id,
|
||||||
security_groups=None,
|
security_groups=None,
|
||||||
@ -667,6 +693,103 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
|
|||||||
self.client_mock.delete_port.assert_called_once_with(
|
self.client_mock.delete_port.assert_called_once_with(
|
||||||
self.neutron_port['id'])
|
self.neutron_port['id'])
|
||||||
|
|
||||||
|
def test__uncidr_ipv4(self):
|
||||||
|
network, netmask = neutron._uncidr('10.0.0.0/24')
|
||||||
|
self.assertEqual('10.0.0.0', network)
|
||||||
|
self.assertEqual('255.255.255.0', netmask)
|
||||||
|
|
||||||
|
def test__uncidr_ipv6(self):
|
||||||
|
network, netmask = neutron._uncidr('::1/64', ipv6=True)
|
||||||
|
self.assertEqual('::', network)
|
||||||
|
self.assertEqual('ffff:ffff:ffff:ffff::', netmask)
|
||||||
|
|
||||||
|
def test_get_neutron_port_data(self):
|
||||||
|
|
||||||
|
network_data = neutron.get_neutron_port_data('port0', 'vif0')
|
||||||
|
|
||||||
|
expected_port = {
|
||||||
|
'id': 'port0',
|
||||||
|
'type': 'vif',
|
||||||
|
'ethernet_mac_address': 'fa:16:3e:23:fd:d7',
|
||||||
|
'vif_id': '46d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb2',
|
||||||
|
'mtu': 1500
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(expected_port, network_data['links'][0])
|
||||||
|
|
||||||
|
expected_network = {
|
||||||
|
'id': 'a0304c3a-4f08-4c43-88af-d796509c97d2',
|
||||||
|
'network_id': 'a87cc70a-3e15-4acf-8205-9b711a3531b7',
|
||||||
|
'type': 'ipv4',
|
||||||
|
'link': 'port0',
|
||||||
|
'ip_address': '10.0.0.2',
|
||||||
|
'netmask': '255.255.255.0',
|
||||||
|
'routes': [
|
||||||
|
{'gateway': '10.0.0.1',
|
||||||
|
'netmask': '0.0.0.0',
|
||||||
|
'network': '0.0.0.0'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(expected_network, network_data['networks'][0])
|
||||||
|
|
||||||
|
def load_ipv6_files(self):
|
||||||
|
port_show_file = os.path.join(
|
||||||
|
os.path.dirname(__file__), 'json_samples',
|
||||||
|
'neutron_port_show_ipv6.json')
|
||||||
|
with open(port_show_file, 'rb') as fl:
|
||||||
|
self.port_data = json.load(fl)
|
||||||
|
|
||||||
|
self.client_mock.show_port.return_value = self.port_data
|
||||||
|
|
||||||
|
network_show_file = os.path.join(
|
||||||
|
os.path.dirname(__file__), 'json_samples',
|
||||||
|
'neutron_network_show_ipv6.json')
|
||||||
|
with open(network_show_file, 'rb') as fl:
|
||||||
|
self.network_data = json.load(fl)
|
||||||
|
|
||||||
|
self.client_mock.show_network.return_value = self.network_data
|
||||||
|
|
||||||
|
subnet_show_file = os.path.join(
|
||||||
|
os.path.dirname(__file__), 'json_samples',
|
||||||
|
'neutron_subnet_show_ipv6.json')
|
||||||
|
with open(subnet_show_file, 'rb') as fl:
|
||||||
|
self.subnet_data = json.load(fl)
|
||||||
|
|
||||||
|
self.client_mock.show_subnet.return_value = self.subnet_data
|
||||||
|
|
||||||
|
def test_get_neutron_port_data_ipv6(self):
|
||||||
|
self.load_ipv6_files()
|
||||||
|
|
||||||
|
network_data = neutron.get_neutron_port_data('port1', 'vif1')
|
||||||
|
|
||||||
|
print(network_data)
|
||||||
|
expected_port = {
|
||||||
|
'id': 'port1',
|
||||||
|
'type': 'vif',
|
||||||
|
'ethernet_mac_address': '52:54:00:4f:ef:b7',
|
||||||
|
'vif_id': '96d4bfb9-b26e-41f3-bd2e-e6dcc1ccedb8',
|
||||||
|
'mtu': 1500
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(expected_port, network_data['links'][0])
|
||||||
|
|
||||||
|
expected_network = {
|
||||||
|
'id': '906e685a-b964-4d58-9939-9cf3af197c67',
|
||||||
|
'network_id': 'a87cc70a-3e15-4acf-8205-9b711a3531b7',
|
||||||
|
'type': 'ipv6',
|
||||||
|
'link': 'port1',
|
||||||
|
'ip_address': 'fd00:203:0:113::2',
|
||||||
|
'netmask': 'ffff:ffff:ffff:ffff::',
|
||||||
|
'routes': [
|
||||||
|
{'gateway': 'fd00:203:0:113::1',
|
||||||
|
'netmask': '::0',
|
||||||
|
'network': '::0'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(expected_network, network_data['networks'][0])
|
||||||
|
|
||||||
def test_get_node_portmap(self):
|
def test_get_node_portmap(self):
|
||||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
portmap = neutron.get_node_portmap(task)
|
portmap = neutron.get_node_portmap(task)
|
||||||
|
@ -339,7 +339,10 @@ class TestFlatInterface(db_base.DbTestCase):
|
|||||||
self.assertRaises(exception.UnsupportedDriverExtension,
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
self.interface.validate_inspection, task)
|
self.interface.validate_inspection, task)
|
||||||
|
|
||||||
def test_get_node_network_data(self):
|
@mock.patch.object(neutron, 'get_neutron_port_data', autospec=True)
|
||||||
|
def test_get_node_network_data(self, mock_gnpd):
|
||||||
|
mock_gnpd.return_value = {}
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.id) as task:
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
network_data = self.interface.get_node_network_data(task)
|
network_data = self.interface.get_node_network_data(task)
|
||||||
|
|
||||||
|
@ -699,13 +699,15 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
|
|||||||
self.node.save()
|
self.node.save()
|
||||||
self._test_configure_tenant_networks(is_client_id=True)
|
self._test_configure_tenant_networks(is_client_id=True)
|
||||||
|
|
||||||
|
@mock.patch.object(neutron_common, 'get_neutron_port_data', autospec=True)
|
||||||
@mock.patch.object(neutron_common, 'wait_for_host_agent', autospec=True)
|
@mock.patch.object(neutron_common, 'wait_for_host_agent', autospec=True)
|
||||||
@mock.patch.object(neutron_common, 'update_neutron_port', autospec=True)
|
@mock.patch.object(neutron_common, 'update_neutron_port', autospec=True)
|
||||||
@mock.patch.object(neutron_common, 'get_client', autospec=True)
|
@mock.patch.object(neutron_common, 'get_client', autospec=True)
|
||||||
@mock.patch.object(neutron_common, 'get_local_group_information',
|
@mock.patch.object(neutron_common, 'get_local_group_information',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_configure_tenant_networks_with_portgroups(
|
def test_configure_tenant_networks_with_portgroups(
|
||||||
self, glgi_mock, client_mock, update_mock, wait_agent_mock):
|
self, glgi_mock, client_mock, update_mock, wait_agent_mock,
|
||||||
|
port_data_mock):
|
||||||
pg = utils.create_test_portgroup(
|
pg = utils.create_test_portgroup(
|
||||||
self.context, node_id=self.node.id, address='ff:54:00:cf:2d:32',
|
self.context, node_id=self.node.id, address='ff:54:00:cf:2d:32',
|
||||||
extra={'vif_port_id': uuidutils.generate_uuid()})
|
extra={'vif_port_id': uuidutils.generate_uuid()})
|
||||||
@ -860,7 +862,10 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
|
|||||||
self.assertRaises(exception.UnsupportedDriverExtension,
|
self.assertRaises(exception.UnsupportedDriverExtension,
|
||||||
self.interface.validate_inspection, task)
|
self.interface.validate_inspection, task)
|
||||||
|
|
||||||
def test_get_node_network_data(self):
|
@mock.patch.object(neutron_common, 'get_neutron_port_data', autospec=True)
|
||||||
|
def test_get_node_network_data(self, mock_gnpd):
|
||||||
|
mock_gnpd.return_value = {}
|
||||||
|
|
||||||
with task_manager.acquire(self.context, self.node.id) as task:
|
with task_manager.acquire(self.context, self.node.id) as task:
|
||||||
network_data = self.interface.get_node_network_data(task)
|
network_data = self.interface.get_node_network_data(task)
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds `network_data` property to the node, a dictionary that represents the
|
||||||
|
node static network configuration. The Ironic API performs formal JSON
|
||||||
|
validation of node `network_data` content against user-supplied JSON schema
|
||||||
|
at driver validation step.
|
Loading…
x
Reference in New Issue
Block a user