Merge "Module: tripleo_composable_network"

This commit is contained in:
Zuul 2020-11-13 05:09:19 +00:00 committed by Gerrit Code Review
commit a0ee4a45ef
3 changed files with 1070 additions and 0 deletions

View File

@ -0,0 +1,14 @@
===================================
Module - tripleo_composable_network
===================================
This module provides for the following ansible plugin:
* tripleo_composable_network
.. ansibleautoplugin::
:module: tripleo_ansible/ansible_plugins/modules/tripleo_composable_network.py
:documentation: true
:examples: true

View File

@ -0,0 +1,460 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2018 OpenStack Foundation
# 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.
import yaml
try:
from ansible.module_utils import tripleo_common_utils as tc
except ImportError:
from tripleo_ansible.ansible_plugins.module_utils import tripleo_common_utils as tc
try:
from ansible.module_utils import network_data_v2
except ImportError:
from tripleo_ansible.ansible_plugins.module_utils import network_data_v2
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.openstack import openstack_full_argument_spec
from ansible.module_utils.openstack import openstack_module_kwargs
from ansible.module_utils.openstack import openstack_cloud_from_module
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: tripleo_composable_network
short_description: Create a TripleO Composable network
version_added: "2.8"
description:
- "Create a TripleO Composable network, a network, one or more segments and one or more subnets"
options:
net_data:
description:
- Structure describing a TripleO composable network
type: dict
author:
- Harald Jensås <hjensas@redhat.com>
'''
RETURN = '''
'''
EXAMPLES = '''
- name: Create composable networks
tripleo_composable_network:
net_data:
name: Storage
name_lower: storage
dns_domain: storage.localdomain.
mtu: 1442
subnets:
storage_subnet:
ip_subnet: 172.18.0.0/24
gateway_ip: 172.18.0.254
allocation_pools:
- start: 172.18.0.10
end: 172.18.0.250
routes:
- destination: 172.18.1.0/24
nexthop: 172.18.0.254
vip: true
vlan: 20
storage_leaf1:
ip_subnet: 172.18.1.0/24
gateway_ip: 172.18.1.254
allocation_pools:
- start: 172.18.1.10
end: 172.18.1.250
routes:
- destination: 172.18.0.0/24
nexthop: 172.18.1.254
vip: false
vlan: 21
'''
DEFAULT_ADMIN_STATE = False
DEFAULT_SHARED = False
DEFAULT_DOMAIN = 'localdomain.'
DEFAULT_NETWORK_TYPE = 'flat'
DEFAULT_MTU = 1500
DEFAULT_VLAN_ID = 1
def build_network_tag_field(net_data):
tags = []
service_net_map_replace = net_data.get('service_net_map_replace')
vip = net_data.get('vip')
if service_net_map_replace:
tags.append('='.join(['tripleo_service_net_map_replace',
service_net_map_replace]))
if vip:
tags.append('='.join(['tripleo_vip', 'true']))
return tags
def build_subnet_tag_field(subnet_data):
tags = []
vlan_id = subnet_data.get('vlan')
vlan_id = str(vlan_id) if vlan_id is not None else str(DEFAULT_VLAN_ID)
tags.append('='.join(['tripleo_vlan_id', vlan_id]))
return tags
def create_net_spec(net_data):
name_lower = net_data.get('name_lower', net_data['name'].lower())
net_spec = {
'admin_state_up': net_data.get('admin_state_up', DEFAULT_ADMIN_STATE),
'dns_domain': net_data.get(
'dns_domain', '.'.join([net_data['name'].lower(), DEFAULT_DOMAIN])
),
'mtu': net_data.get('mtu', DEFAULT_MTU),
'name': net_data['name'],
'shared': net_data.get('shared', DEFAULT_SHARED),
'provider:physical_network': name_lower,
'provider:network_type': DEFAULT_NETWORK_TYPE,
}
net_spec.update({'tags': build_network_tag_field(net_data)})
return net_spec
def validate_network_update(module, network, net_spec):
# Fail if updating read-only attributes
if (network.provider_network_type != net_spec.pop(
'provider:network_type')
and network.provider_network_type is not None):
module.fail_json(
msg='Cannot update provider:network_type in existing network')
# NOTE(hjensas): When a network have multiple segments,
# attributes provider:network_type, provider:physical_network is None
# for the network.
if (network.provider_physical_network != net_spec.pop(
'provider:physical_network')
and network.provider_physical_network is not None):
module.fail_json(
msg='Cannot update provider:physical_network in existing network')
# Remove fields that don't need update from spec
if network.is_admin_state_up == net_spec['admin_state_up']:
net_spec.pop('admin_state_up')
if network.dns_domain == net_spec['dns_domain']:
net_spec.pop('dns_domain')
if network.mtu == net_spec['mtu']:
net_spec.pop('mtu')
if network.name == net_spec['name']:
net_spec.pop('name')
if network.is_shared == net_spec['shared']:
net_spec.pop('shared')
return net_spec
def create_or_update_network(conn, module, net_spec):
changed = False
# Need to use set_tags for the tags ...
tags = net_spec.pop('tags')
network = conn.network.find_network(net_spec['name'])
if not network:
network = conn.network.create_network(**net_spec)
changed = True
else:
net_spec = validate_network_update(module, network, net_spec)
if net_spec:
network = conn.network.update_network(network.id, **net_spec)
changed = True
if network.tags != tags:
conn.network.set_tags(network, tags)
changed = True
return changed, network
def create_segment_spec(net_id, net_name, subnet_name, physical_network=None):
name = '_'.join([net_name, subnet_name])
if physical_network is None:
physical_network = name
else:
physical_network = physical_network
return {'network_id': net_id,
'physical_network': physical_network,
'name': name,
'network_type': DEFAULT_NETWORK_TYPE}
def validate_segment_update(module, segment, segment_spec):
# Fail if updating read-only attributes
if segment.network_id != segment_spec.pop('network_id'):
module.fail_json(
msg='Cannot update network_id in existing segment')
if segment.network_type != segment_spec.pop('network_type'):
module.fail_json(
msg='Cannot update network_type in existing segment')
if segment.physical_network != segment_spec.pop('physical_network'):
module.fail_json(
msg='Cannot update physical_network in existing segment')
# Remove fields that don't need update from spec
if segment.name == segment_spec['name']:
segment_spec.pop('name')
return segment_spec
def create_or_update_segment(conn, module, segment_spec, segment_id=None):
changed = False
if segment_id:
segment = conn.network.find_segment(segment_id)
else:
segment = conn.network.find_segment(
segment_spec['name'], network_id=segment_spec['network_id'])
if not segment:
segment = conn.network.create_segment(**segment_spec)
changed = True
else:
segment_spec = validate_segment_update(module, segment, segment_spec)
if segment_spec:
segment = conn.network.update_segment(segment.id, **segment_spec)
changed = True
return changed, segment
def create_subnet_spec(net_id, name, subnet_data):
tags = build_subnet_tag_field(subnet_data)
subnet_v4_spec = None
subnet_v6_spec = None
if subnet_data.get('ip_subnet'):
subnet_v4_spec = {
'ip_version': 4,
'name': name,
'network_id': net_id,
'enable_dhcp': subnet_data.get('enable_dhcp', False),
'gateway_ip': subnet_data.get('gateway_ip', None),
'cidr': subnet_data['ip_subnet'],
'allocation_pools': subnet_data.get('allocation_pools', []),
'host_routes': subnet_data.get('routes', []),
'tags': tags,
}
if subnet_data.get('ipv6_subnet'):
subnet_v6_spec = {
'ip_version': 6,
'name': name,
'network_id': net_id,
'enable_dhcp': subnet_data.get('enable_dhcp', False),
'ipv6_address_mode': subnet_data.get('ipv6_address_mode', None),
'ipv6_ra_mode': subnet_data.get('ipv6_ra_mode', None),
'gateway_ip': subnet_data.get('gateway_ipv6', None),
'cidr': subnet_data['ipv6_subnet'],
'allocation_pools': subnet_data.get('ipv6_allocation_pools', []),
'host_routes': subnet_data.get('routes_ipv6', []),
'tags': tags,
}
return subnet_v4_spec, subnet_v6_spec
def validate_subnet_update(module, subnet, subnet_spec):
# Fail if updating read-only attributes
if subnet.ip_version != subnet_spec.pop('ip_version'):
module.fail_json(
msg='Cannot update ip_version in existing subnet')
if subnet.network_id != subnet_spec.pop('network_id'):
module.fail_json(
msg='Cannot update network_id in existing subnet')
if subnet.cidr != subnet_spec.pop('cidr'):
module.fail_json(
msg='Cannot update cidr in existing subnet')
segment_id = subnet_spec.pop('segment_id')
if subnet.segment_id != segment_id:
module.fail_json(
msg='Cannot update segment_id in existing subnet, '
'Current segment_id: {} Update segment_id: {}'.format(
subnet.segment_id, segment_id))
# Remove fields that don't need update from spec
if subnet.name == subnet_spec['name']:
subnet_spec.pop('name')
if subnet.is_dhcp_enabled == subnet_spec['enable_dhcp']:
subnet_spec.pop('enable_dhcp')
if subnet.ipv6_address_mode == subnet_spec.get('ipv6_address_mode'):
try:
subnet_spec.pop('ipv6_address_mode')
except KeyError:
pass
if subnet.ipv6_ra_mode == subnet_spec.get('ipv6_ra_mode'):
try:
subnet_spec.pop('ipv6_ra_mode')
except KeyError:
pass
if subnet.gateway_ip == subnet_spec['gateway_ip']:
subnet_spec.pop('gateway_ip')
if subnet.allocation_pools == subnet_spec['allocation_pools']:
subnet_spec.pop('allocation_pools')
if subnet.host_routes == subnet_spec['host_routes']:
subnet_spec.pop('host_routes')
return subnet_spec
def create_or_update_subnet(conn, module, subnet_spec):
changed = False
# Need to use set_tags for the tags ...
tags = subnet_spec.pop('tags')
subnet = conn.network.find_subnet(subnet_spec['name'],
network_id=subnet_spec['network_id'])
if not subnet:
subnet = conn.network.create_subnet(**subnet_spec)
changed = True
else:
subnet_spec = validate_subnet_update(module, subnet, subnet_spec)
if subnet_spec:
subnet = conn.network.update_subnet(subnet.id, **subnet_spec)
changed = True
if subnet.tags != tags:
conn.network.set_tags(subnet, tags)
changed = True
return changed
def adopt_the_implicit_segment(conn, module, segments, subnets, network):
changed = False
# Check for implicit segment
implicit_segment = [s for s in segments if s['name'] is None]
if not implicit_segment:
return changed
if len(implicit_segment) > 1:
module.fail_json(msg='Multiple segments with no name attribute exist '
'on network {}, unable to reliably adopt the '
'implicit segment.'.format(network.id))
else:
implicit_segment = implicit_segment[0]
if implicit_segment and subnets:
subnet_associated = [s for s in subnets
if s.segment_id == implicit_segment.id][0]
segment_spec = create_segment_spec(
network.id, network.name, subnet_associated.name,
physical_network=implicit_segment.physical_network)
create_or_update_segment(conn, module, segment_spec,
segment_id=implicit_segment.id)
changed = True
return changed
elif implicit_segment and not subnets:
conn.network.delete_segment(implicit_segment.id)
changed = True
return changed
module.fail_json(msg='ERROR: Unable to reliably adopt the implicit '
'segment.')
def run_module():
result = dict(
success=False,
changed=False,
error="",
)
argument_spec = openstack_full_argument_spec(
**yaml.safe_load(DOCUMENTATION)['options']
)
module = AnsibleModule(
argument_spec,
supports_check_mode=False,
**openstack_module_kwargs()
)
net_data = module.params['net_data']
error_messages = network_data_v2.validate_json_schema(net_data)
if error_messages:
module.fail_json(msg='\n\n'.join(error_messages))
try:
_, conn = openstack_cloud_from_module(module)
# Create or update the network
net_spec = create_net_spec(net_data)
changed, network = create_or_update_network(conn, module, net_spec)
result['changed'] = changed if changed else result['changed']
# Get current segments and subnets on the network
segments = list(conn.network.segments(network_id=network.id))
subnets = list(conn.network.subnets(network_id=network.id))
changed = adopt_the_implicit_segment(conn, module, segments,
subnets, network)
result['changed'] = changed if changed else result['changed']
for subnet_name, subnet_data in net_data.get('subnets', {}).items():
segment_spec = create_segment_spec(
network.id, network.name, subnet_name,
physical_network=subnet_data.get('physical_network'))
subnet_v4_spec, subnet_v6_spec = create_subnet_spec(
network.id, subnet_name, subnet_data)
changed, segment = create_or_update_segment(
conn, module, segment_spec)
result['changed'] = changed if changed else result['changed']
if subnet_v4_spec:
subnet_v4_spec.update({'segment_id': segment.id})
changed = create_or_update_subnet(conn, module, subnet_v4_spec)
result['changed'] = changed if changed else result['changed']
if subnet_v6_spec:
subnet_v6_spec.update({'segment_id': segment.id})
changed = create_or_update_subnet(conn, module, subnet_v6_spec)
result['changed'] = changed if changed else result['changed']
result['success'] = True
module.exit_json(**result)
except Exception as err:
result['error'] = str(err)
result['msg'] = ("Error overcloud network provision failed!")
module.fail_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,596 @@
# Copyright 2019 Red Hat, 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.
import mock
import openstack
from tripleo_ansible.ansible_plugins.modules import (
tripleo_composable_network as plugin)
from tripleo_ansible.tests import base as tests_base
from tripleo_ansible.tests import stubs
class TestTripleoComposableNetwork(tests_base.TestCase):
def test_build_network_tag_field(self):
net_data = {'name': 'foo',
'service_net_map_replace': 'replacement',
'vip': True}
expected = ['tripleo_service_net_map_replace=replacement',
'tripleo_vip=true']
result = plugin.build_network_tag_field(net_data)
self.assertEqual(expected, result)
net_data = {'name': 'foo'}
expected = []
result = plugin.build_network_tag_field(net_data)
self.assertEqual(expected, result)
def test_build_subnet_tag_field(self):
# Default VLAN id 1
subnet_data = {}
expected = ["tripleo_vlan_id=1"]
result = plugin.build_subnet_tag_field(subnet_data)
self.assertEqual(expected, result)
subnet_data = {'vlan': 100}
expected = ["tripleo_vlan_id=100"]
result = plugin.build_subnet_tag_field(subnet_data)
self.assertEqual(expected, result)
def test_create_net_spec(self):
net_data = {'name': 'NetName'}
expected = {
'admin_state_up': plugin.DEFAULT_ADMIN_STATE,
'dns_domain': '.'.join(['netname', plugin.DEFAULT_DOMAIN]),
'mtu': plugin.DEFAULT_MTU,
'name': 'NetName',
'shared': plugin.DEFAULT_SHARED,
'provider:physical_network': 'netname',
'provider:network_type': plugin.DEFAULT_NETWORK_TYPE,
'tags': [],
}
result = plugin.create_net_spec(net_data)
self.assertEqual(expected, result)
def test_validate_network_update(self):
net_spec = {
'admin_state_up': True,
'dns_domain': 'netname.localdomain',
'mtu': 1450,
'name': 'new_name',
'shared': True,
'provider:physical_network': 'NEWNAME',
'provider:network_type': 'vlan',
'provider:segmentation_id': 101
}
fake_network = stubs.FakeNeutronNetwork(**{
'is_admin_state_up': False,
'mtu': 1500,
'is_shared': False,
'provider:network_type': 'flat',
'provider:physical_network': 'netname',
'provider:segmentation_id': 100,
'is_admin_state_up': False,
'dns_domain': 'netname.localdomain',
'name': 'netname'})
module = mock.Mock()
module.fail_json = mock.Mock()
result = plugin.validate_network_update(module, fake_network, net_spec)
module.fail_json.assert_has_calls([
mock.call(msg=('Cannot update provider:network_type in existing '
'network')),
mock.call(msg=('Cannot update provider:physical_network in '
'existing network'))
])
expected = {'mtu': 1450,
'shared': True,
'admin_state_up': True,
'name': 'new_name',
'provider:segmentation_id': 101}
self.assertEqual(expected, result)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_create_network(self, mock_conn):
mock_module = mock.Mock()
net_spec = {
'admin_state_up': plugin.DEFAULT_ADMIN_STATE,
'dns_domain': '.'.join(['netname', plugin.DEFAULT_DOMAIN]),
'mtu': plugin.DEFAULT_MTU,
'name': 'netname',
'shared': plugin.DEFAULT_SHARED,
'provider:physical_network': 'netname',
'provider:network_type': plugin.DEFAULT_NETWORK_TYPE,
'tags': ['tripleo_foo=bar'],
}
fake_network = stubs.FakeNeutronNetwork(
id='foo',
name='netname',
is_shared=False,
dns_domain='.'.join(['netname', plugin.DEFAULT_DOMAIN]),
mtu=plugin.DEFAULT_MTU,
is_admin_state_up=plugin.DEFAULT_ADMIN_STATE,
physical_network='netname',
network_type=plugin.DEFAULT_NETWORK_TYPE,
tags=[],
)
mock_conn.network.find_network.return_value = None
mock_conn.network.create_network.return_value = fake_network
changed, network = plugin.create_or_update_network(
mock_conn, mock_module, net_spec)
mock_conn.network.create_network.assert_called_once_with(**net_spec)
mock_conn.network.set_tags.assert_called_once_with(
network, ['tripleo_foo=bar'])
self.assertEqual(True, changed)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_update_network(self, mock_conn):
mock_module = mock.Mock()
net_spec = {
'admin_state_up': plugin.DEFAULT_ADMIN_STATE,
'dns_domain': '.'.join(['netname', plugin.DEFAULT_DOMAIN]),
'mtu': plugin.DEFAULT_MTU,
'name': 'new_name',
'shared': plugin.DEFAULT_SHARED,
'provider:physical_network': 'netname',
'provider:network_type': plugin.DEFAULT_NETWORK_TYPE,
'tags': ['tripleo_foo=bar'],
}
fake_network = stubs.FakeNeutronNetwork(
id='foo',
name='netname',
is_shared=False,
dns_domain='.'.join(['netname', plugin.DEFAULT_DOMAIN]),
mtu=plugin.DEFAULT_MTU,
is_admin_state_up=plugin.DEFAULT_ADMIN_STATE,
physical_network='netname',
network_type=plugin.DEFAULT_NETWORK_TYPE,
tags=[],
)
mock_conn.network.find_network.return_value = fake_network
changed, network = plugin.create_or_update_network(
mock_conn, mock_module, net_spec)
mock_conn.network.update_network.assert_called_once_with(
'foo', **{'name': 'new_name'})
mock_conn.network.set_tags.assert_called_once_with(
network, ['tripleo_foo=bar'])
self.assertEqual(True, changed)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_update_network_no_change(self, mock_conn):
mock_module = mock.Mock()
net_spec = {
'admin_state_up': plugin.DEFAULT_ADMIN_STATE,
'dns_domain': '.'.join(['netname', plugin.DEFAULT_DOMAIN]),
'mtu': plugin.DEFAULT_MTU,
'name': 'netname',
'shared': plugin.DEFAULT_SHARED,
'provider:physical_network': 'netname',
'provider:network_type': plugin.DEFAULT_NETWORK_TYPE,
'tags': ['tripleo_foo=bar'],
}
fake_network = stubs.FakeNeutronNetwork(
id='foo',
name='netname',
is_shared=False,
dns_domain='.'.join(['netname', plugin.DEFAULT_DOMAIN]),
mtu=plugin.DEFAULT_MTU,
is_admin_state_up=plugin.DEFAULT_ADMIN_STATE,
physical_network='netname',
network_type=plugin.DEFAULT_NETWORK_TYPE,
tags=['tripleo_foo=bar'],
)
mock_conn.network.find_network.return_value = fake_network
changed, network = plugin.create_or_update_network(
mock_conn, mock_module, net_spec)
mock_conn.network.create_network.assert_not_called()
mock_conn.network.update_network.assert_not_called()
mock_conn.network.set_tags.assert_not_called()
self.assertEqual(False, changed)
def test_create_segment_spec(self):
net_id = 'net_id'
net_name = 'net_name'
subnet_name = 'subnet_name'
expected = {'network_id': 'net_id', 'name': 'net_name_subnet_name',
'physical_network': 'net_name_subnet_name',
'network_type': plugin.DEFAULT_NETWORK_TYPE}
result = plugin.create_segment_spec(net_id, net_name, subnet_name)
self.assertEqual(expected, result)
def test_validate_segment_update(self):
segmnet_spec = {
'network_id': 'new_net_id',
'physical_network': 'new_physical_network',
'name': 'new_name',
'network_type': 'vlan',
}
fake_segment = stubs.FakeNeutronSegment(
name='net_name_subnet_name',
network_id='net_id',
network_type=plugin.DEFAULT_NETWORK_TYPE,
physical_network='net_name_subnet_name'
)
module = mock.Mock()
module.fail_json = mock.Mock()
result = plugin.validate_segment_update(
module, fake_segment, segmnet_spec)
module.fail_json.assert_has_calls([
mock.call(msg='Cannot update network_id in existing segment'),
mock.call(msg='Cannot update network_type in existing segment'),
mock.call(msg='Cannot update physical_network in existing segment')
])
expected = {'name': 'new_name'}
self.assertEqual(expected, result)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_create_segment(self, mock_conn):
mock_module = mock.Mock()
segment_spec = {'network_id': 'net_id', 'name': 'net_name_subnet_name',
'physical_network': 'net_name_subnet_name',
'network_type': plugin.DEFAULT_NETWORK_TYPE}
mock_conn.network.find_segment.return_value = None
changed, segment = plugin.create_or_update_segment(
mock_conn, mock_module, segment_spec)
mock_conn.network.create_segment.assert_called_once_with(
**segment_spec)
self.assertEqual(True, changed)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_update_segment(self, mock_conn):
mock_module = mock.Mock()
segment_spec = {'network_id': 'net_id',
'physical_network': 'net_name_subnet_name',
'name': 'NEW_NAME',
'network_type': plugin.DEFAULT_NETWORK_TYPE}
fake_segment = stubs.FakeNeutronSegment(
id='foo', name='net_name_subnet_name', network_id='net_id',
network_type=plugin.DEFAULT_NETWORK_TYPE,
physical_network='net_name_subnet_name')
mock_conn.network.find_segment.return_value = fake_segment
changed, segment = plugin.create_or_update_segment(
mock_conn, mock_module, segment_spec, segment_id='foo')
mock_conn.network.find_segment.assert_called_once_with('foo')
mock_conn.network.update_segment.assert_called_once_with(
fake_segment.id, **{'name': 'NEW_NAME'})
self.assertEqual(True, changed)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_update_segment_no_change(self, mock_conn):
mock_module = mock.Mock()
segment_spec = {'network_id': 'net_id',
'physical_network': 'net_name_subnet_name',
'name': 'net_name_subnet_name',
'network_type': plugin.DEFAULT_NETWORK_TYPE}
fake_segment = stubs.FakeNeutronSegment(
id='foo', name='net_name_subnet_name', network_id='net_id',
network_type=plugin.DEFAULT_NETWORK_TYPE,
physical_network='net_name_subnet_name')
mock_conn.network.find_segment.return_value = fake_segment
changed, segment = plugin.create_or_update_segment(
mock_conn, mock_module, segment_spec)
mock_conn.network.find_segment.assert_called_once_with(
'net_name_subnet_name', network_id='net_id')
mock_conn.network.create_segment.assert_not_called()
mock_conn.network.update_segment.assert_not_called()
self.assertEqual(False, changed)
def test_create_subnet_spec_ipv4(self):
net_id = 'net_id'
name = 'subnet0'
subnet_data = {
'ip_subnet': '192.168.24.0/24',
'gateway_ip': '192.168.24.1',
'allocation_pools': [
{'start': '192.168.24.100', 'end': '192.168.24.200'}
],
'routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.1'}
],
'vlan': 100,
}
expected = {
'ip_version': 4,
'name': name,
'network_id': net_id,
'enable_dhcp': False,
'gateway_ip': '192.168.24.1',
'cidr': '192.168.24.0/24',
'allocation_pools': [
{'start': '192.168.24.100', 'end': '192.168.24.200'}
],
'host_routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.1'}
],
'tags': ['tripleo_vlan_id=100'],
}
subnet_v4_spec, subnet_v6_spec = plugin.create_subnet_spec(
net_id, name, subnet_data)
self.assertEqual(expected, subnet_v4_spec)
self.assertEqual(None, subnet_v6_spec)
def test_create_subnet_spec_ipv6(self):
net_id = 'net_id'
name = 'subnet0'
subnet_data = {
'ipv6_subnet': '2001:db8:a::/64',
'gateway_ipv6': '2001:db8:a::1',
'vlan': 100,
}
expected = {
'ip_version': 6,
'name': name,
'network_id': net_id,
'enable_dhcp': False,
'gateway_ip': '2001:db8:a::1',
'cidr': '2001:db8:a::/64',
'allocation_pools': [],
'host_routes': [],
'ipv6_address_mode': None,
'ipv6_ra_mode': None,
'tags': ['tripleo_vlan_id=100'],
}
subnet_v4_spec, subnet_v6_spec = plugin.create_subnet_spec(
net_id, name, subnet_data)
self.assertEqual(None, subnet_v4_spec)
self.assertEqual(expected, subnet_v6_spec)
def test_create_subnet_spec_dual_stack(self):
net_id = 'net_id'
name = 'subnet0'
subnet_data = {
'ip_subnet': '192.168.24.0/24',
'gateway_ip': '192.168.24.1',
'allocation_pools': [
{'start': '192.168.24.100', 'end': '192.168.24.200'}],
'routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.1'}],
'ipv6_subnet': '2001:db8:a::/64',
'gateway_ipv6': '2001:db8:a::1',
'ipv6_allocation_pools': [
{'start': '2001:db8:a::0010', 'end': '2001:db8:a::fff9'}
],
'routes_ipv6': [
{'destination': '2001:db8:b::/64', 'nexthop': '2001:db8:a::1'}
],
'vlan': 100,
}
expected_ipv4 = {
'ip_version': 4,
'name': name,
'network_id': net_id,
'enable_dhcp': False,
'gateway_ip': '192.168.24.1',
'cidr': '192.168.24.0/24',
'allocation_pools': [
{'start': '192.168.24.100', 'end': '192.168.24.200'}
],
'host_routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.1'}
],
'tags': ['tripleo_vlan_id=100'],
}
expected_ipv6 = {
'ip_version': 6,
'name': name,
'network_id': net_id,
'enable_dhcp': False,
'gateway_ip': '2001:db8:a::1',
'cidr': '2001:db8:a::/64',
'allocation_pools': [
{'start': '2001:db8:a::0010', 'end': '2001:db8:a::fff9'}
],
'host_routes': [
{'destination': '2001:db8:b::/64', 'nexthop': '2001:db8:a::1'}
],
'ipv6_address_mode': None,
'ipv6_ra_mode': None,
'tags': ['tripleo_vlan_id=100'],
}
subnet_v4_spec, subnet_v6_spec = plugin.create_subnet_spec(
net_id, name, subnet_data)
self.assertEqual(expected_ipv4, subnet_v4_spec)
self.assertEqual(expected_ipv6, subnet_v6_spec)
def test_validate_subnet_update(self):
module = mock.Mock()
module.fail = mock.Mock()
subnet_spec = {
'ip_version': 6,
'network_id': 'new_net_id',
'cidr': '192.168.24.0/25',
'segment_id': 'new_segment_id',
'name': 'new_name',
'enable_dhcp': True,
'ipv6_address_mode': 'slaac',
'ipv6_ra_mode': 'slaac',
'gateway_ip': '192.168.24.254',
'allocation_pools': [{'start': '192.168.24.100', 'end': '200'}],
'host_routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.254'}
],
}
fake_subnet = stubs.FakeNeutronSubnet(
id='foo',
name='subnet01',
cidr='192.168.24.0/24',
gateway_ip='192.168.24.1',
allocation_pools=[{'start': '192.168.24.50', 'end': '99'}],
host_routes={'destination': '192.168.25.0/24',
'nexthop': '192.168.24.1'},
ip_version=4,
network_id='net_id',
segment_id='segment_id',
is_dhcp_enabled=False,
ipv6_address_mode=None,
ipv6_ra_mode=None,
)
result = plugin.validate_subnet_update(
module, fake_subnet, subnet_spec)
module.fail_json.assert_has_calls([
mock.call(msg='Cannot update ip_version in existing subnet'),
mock.call(msg='Cannot update network_id in existing subnet'),
mock.call(msg='Cannot update cidr in existing subnet'),
mock.call(
msg='Cannot update segment_id in existing subnet, Current '
'segment_id: {} Update segment_id: {}'.format(
'segment_id', 'new_segment_id')
),
])
expected_spec = {
'name': 'new_name',
'enable_dhcp': True,
'ipv6_address_mode': 'slaac',
'ipv6_ra_mode': 'slaac',
'gateway_ip': '192.168.24.254',
'allocation_pools': [{'start': '192.168.24.100', 'end': '200'}],
'host_routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.254'}
],
}
self.assertEqual(expected_spec, result)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_create_subnet(self, mock_conn):
mock_module = mock.Mock()
subnet_spec = {
'ip_version': 4,
'name': 'subnet_name',
'network_id': 'net_id',
'enable_dhcp': False,
'gateway_ip': '192.168.24.1',
'cidr': '192.168.24.0/24',
'allocation_pools': [
{'start': '192.168.24.100', 'end': '192.168.24.200'}
],
'host_routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.1'}
],
'tags': ['tripleo_vlan_id=100'],
}
mock_conn.network.find_subnet.return_value = None
changed = plugin.create_or_update_subnet(mock_conn, mock_module,
subnet_spec)
mock_conn.network.create_subnet.assert_called_once_with(**subnet_spec)
mock_conn.network.set_tags.assert_called_once_with(
mock.ANY, ['tripleo_vlan_id=100'])
self.assertEqual(True, changed)
@mock.patch.object(openstack.connection, 'Connection', autospec=True)
def test_update_subnet(self, mock_conn):
mock_module = mock.Mock()
subnet_spec = {
'ip_version': 4,
'name': 'subnet_name',
'network_id': 'net_id',
'enable_dhcp': False,
'gateway_ip': '192.168.24.1',
'cidr': '192.168.24.0/24',
'allocation_pools': [
{'start': '192.168.24.100', 'end': '192.168.24.200'}
],
'host_routes': [
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.1'}
],
'tags': ['tripleo_vlan_id=100'],
'segment_id': 'segment_id',
}
fake_subnet = stubs.FakeNeutronSubnet(
id='foo',
name='subnet_name',
network_id='net_id',
is_dhcp_enabled=False,
gateway_ip='192.168.24.254',
cidr='192.168.24.0/24',
allocation_pools=[
{'start': '192.168.24.100', 'end': '192.168.24.200'}],
host_routes=[
{'destination': '192.168.25.0/24', 'nexthop': '192.168.24.254'}
],
tags=['tripleo_vlan_id=100'],
segment_id='segment_id'
)
mock_conn.network.find_subnet.return_value = fake_subnet
changed = plugin.create_or_update_subnet(mock_conn, mock_module,
subnet_spec)
mock_conn.network.find_subnet.ssert_called_once_with(
'subnet_name', network_id='net_id')
mock_conn.network.create_subnet.assert_not_called()
mock_conn.network.update_subnet.assert_called_once_with(
'foo', **{'gateway_ip': '192.168.24.1',
'host_routes': [{'destination': '192.168.25.0/24',
'nexthop': '192.168.24.1'}]}
)
self.assertTrue(changed)
@mock.patch.object(plugin, 'create_segment_spec', autospec=True)
@mock.patch.object(plugin, 'create_or_update_segment', autospec=True)
def test_adopt_the_implicit_segment(self, mock_create_or_update_segment,
mock_create_segment_spec):
fake_network = stubs.FakeNeutronNetwork(id='net_id', name='net_name')
fake_segments = [
stubs.FakeNeutronSegment(id='segment_id', name=None,
physical_network='physical_net')]
fake_subnets = [
stubs.FakeNeutronSubnet(id='subnet_id', name='subnet_name',
segment_id='segment_id')]
changed = plugin.adopt_the_implicit_segment(
mock.ANY, mock.ANY, fake_segments, fake_subnets, fake_network)
mock_create_segment_spec.assert_called_once_with(
fake_network.id, fake_network.name, fake_subnets[0].name,
physical_network=fake_segments[0].physical_network)
mock_create_or_update_segment.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY, segment_id=fake_segments[0].id)
self.assertTrue(changed)
def test_implicit_segment_already_adopted(self):
fake_segments = [
stubs.FakeNeutronSegment(id='segment_id',
name='net_name_subnet_name',
physical_network='physical_net')]
changed = plugin.adopt_the_implicit_segment(
mock.ANY, mock.ANY, fake_segments, mock.ANY, mock.ANY)
self.assertFalse(changed)
def test_implicit_segment_unable_to_adopt(self):
mock_module = mock.Mock()
mock_module.fail_json = mock.Mock()
fake_network = stubs.FakeNeutronNetwork(id='net_id', name='net_name')
fake_segments = [
stubs.FakeNeutronSegment(id='segment_id_01',
name=None,
network_id='net_id',
physical_network='physical_net_01'),
stubs.FakeNeutronSegment(id='segment_id_02',
name=None,
network_id='net_id',
physical_network='physical_net_02')
]
fake_subnets = []
try:
plugin.adopt_the_implicit_segment(
mock.Mock(), mock_module, fake_segments, fake_subnets,
fake_network)
except AttributeError:
mock_module.fail_json.assert_called_once_with(
msg='Multiple segments with no name attribute exist on '
'network {}, unable to reliably adopt the implicit '
'segment.'.format(fake_network.id))