Skip to content

Commit 862c46b

Browse files
authored
Merge pull request #260 from cloudify-cosmo/allow-multiple-subnets-port
handle multiple subnets and ports on subnets
2 parents a9051d7 + 6847c8e commit 862c46b

5 files changed

Lines changed: 128 additions & 36 deletions

File tree

CHANGELOG.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2.9.8:
2+
- Add handling for bootable volume attribute.
3+
- Change port fixed ip handling code to permit ports on more than one subnet.
14
2.9.7:
25
- Add IPv6 Example
36
- Base internal volume bootable logic on API bootable flag.

neutron_plugin/port.py

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# * See the License for the specific language governing permissions and
1414
# * limitations under the License.
1515

16+
import netaddr
17+
1618
from cloudify import ctx
1719
from cloudify.decorators import operation
1820
from cloudify.exceptions import NonRecoverableError
@@ -91,7 +93,7 @@ def create(neutron_client, args, **kwargs):
9193
PORT_OPENSTACK_TYPE,
9294
args,
9395
{'network_id': net_id})
94-
_handle_fixed_ips(port)
96+
_handle_fixed_ips(port, neutron_client)
9597
_handle_security_groups(port)
9698

9799
p = neutron_client.create_port(
@@ -273,29 +275,101 @@ def _get_fixed_ip(port):
273275
return port['fixed_ips'][0]['ip_address'] if port['fixed_ips'] else None
274276

275277

276-
def _handle_fixed_ips(port):
277-
fixed_ips_element = {}
278+
def _valid_subnet_ip(ip_address, subnet_dict):
279+
"""Check if ip_address is valid for subnet_dict['cidr']
280+
281+
:param ip_address: string
282+
:param subnet_dict: dict with 'cidr' string
283+
:return: bool
284+
"""
278285

279-
# checking for fixed ip property
280-
if ctx.node.properties['fixed_ip']:
281-
fixed_ips_element['ip_address'] = ctx.node.properties['fixed_ip']
286+
try:
287+
if netaddr.IPAddress(ip_address) in \
288+
netaddr.IPNetwork(subnet_dict.get('cidr')):
289+
return True
290+
except TypeError:
291+
pass
292+
return False
293+
294+
295+
def _handle_fixed_ips(port, neutron_client):
296+
"""Combine IPs and Subnets for the Port fixed IPs list.
297+
298+
The Port object looks something this:
299+
{
300+
'port': {
301+
'id': 'some-id',
302+
'fixed_ips': [
303+
{'subnet_id': 'subnet1', 'ip_address': '1.2.3.4'},
304+
{'ip_address': '1.2.3.5'},
305+
{'subnet_id': 'subnet3'},
306+
]
307+
...snip...
308+
}
282309
283-
# checking for a connected subnet
284-
subnet_id = get_openstack_id_of_single_connected_node_by_openstack_type(
285-
ctx, SUBNET_OPENSTACK_TYPE, if_exists=True)
286-
if subnet_id:
287-
fixed_ips_element['subnet_id'] = subnet_id
310+
We need to combine subnets and ips from three sources:
311+
1) Fixed IPs and Subnets from the Port object.
312+
2) Subnets from relationships to subnets.
313+
3) A Fixed IP from node properties.
314+
315+
There are some issues:
316+
1) Users can provide both subnets and relationships to subnets.
317+
2) Recurrences of the subnet_id indicate a desire
318+
for multiple IPs on that subnet.
319+
3) If we provide a fixed_ip, we don't also know the
320+
target subnet because of how the node properties are.
321+
We should change that.
322+
Have not yet changed that.
323+
But will need to support both paths anyway.
324+
325+
:param port: An Openstack API Port Object.
326+
:param neutron_client: Openstack Neutron Client.
327+
:return: None
328+
"""
329+
330+
fixed_ips = port.get('fixed_ips', [])
331+
subnet_ids_from_port = [net.get('subnet_id') for net in fixed_ips]
332+
subnet_ids_from_rels = \
333+
get_openstack_ids_of_connected_nodes_by_openstack_type(
334+
ctx, SUBNET_OPENSTACK_TYPE)
335+
336+
# Add the subnets from relationships to the port subnets.
337+
for subnet_from_rel in subnet_ids_from_rels:
338+
if subnet_from_rel not in subnet_ids_from_port:
339+
fixed_ips.append({'subnet_id': subnet_from_rel})
288340

289-
fixed_ips = port.get(
290-
'fixed_ips',
291-
[] if not fixed_ips_element else [fixed_ips_element])
292341
addresses = [ip.get('ip_address') for ip in fixed_ips]
293-
subnets = [net.get('subnet_id') for net in fixed_ips]
294-
# applying fixed ip parameter, if available
295-
if fixed_ips_element and not \
296-
(fixed_ips_element.get('ip_address') in addresses or
297-
fixed_ips_element.get('subnet_id') in subnets):
298-
fixed_ips.append(fixed_ips_element)
342+
fixed_ip_from_props = ctx.node.properties['fixed_ip']
343+
344+
# If we have a fixed_ip from node props, we need to add it,
345+
# but first try to match it with one of our subnets.
346+
# The fixed_ip_element should be one of:
347+
# 1) {'ip_address': 'x.x.x.x'}
348+
# 2) {'subnet_id': '....'}
349+
# 3) {'ip_address': 'x.x.x.x', 'subnet_id': '....'}
350+
# show_subnet returns something like this:
351+
# subnet = {
352+
# 'subnet': {
353+
# 'id': 'subnet1',
354+
# 'cidr': '1.2.3.4/24',
355+
# 'allocation_pools': [],
356+
# ...snip...
357+
# }
358+
# }
359+
if fixed_ip_from_props and not (fixed_ip_from_props in addresses):
360+
fixed_ip_element = {'ip_address': fixed_ip_from_props}
361+
for fixed_ip in fixed_ips:
362+
subnet_id = fixed_ip.get('subnet_id')
363+
if not _valid_subnet_ip(
364+
fixed_ip_from_props,
365+
neutron_client.show_subnet(subnet_id)):
366+
continue
367+
fixed_ip_element['subnet_id'] = subnet_id
368+
del fixed_ips[fixed_ips.index(fixed_ip)]
369+
break
370+
fixed_ips.append(fixed_ip_element)
371+
372+
# Finally update the object.
299373
if fixed_ips:
300374
port['fixed_ips'] = fixed_ips
301375

neutron_plugin/tests/test_port.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# * limitations under the License.
1515

1616
import unittest
17-
1817
import mock
1918

2019
import neutron_plugin.port
@@ -32,75 +31,79 @@ class TestPort(unittest.TestCase):
3231

3332
def test_fixed_ips_no_fixed_ips(self):
3433
node_props = {'fixed_ip': ''}
34+
mock_neutron = MockNeutronClient(update=True)
3535

3636
with mock.patch(
3737
'neutron_plugin.port.'
3838
'get_openstack_id_of_single_connected_node_by_openstack_type',
39-
self._get_connected_subnet_mock(return_empty=True)):
39+
self._get_connected_subnets_mock(return_empty=True)):
4040
with mock.patch(
4141
'neutron_plugin.port.ctx',
4242
self._get_mock_ctx_with_node_properties(node_props)):
4343

4444
port = {}
45-
neutron_plugin.port._handle_fixed_ips(port)
45+
neutron_plugin.port._handle_fixed_ips(port, mock_neutron)
4646

4747
self.assertNotIn('fixed_ips', port)
4848

4949
def test_fixed_ips_subnet_only(self):
5050
node_props = {'fixed_ip': ''}
51+
mock_neutron = MockNeutronClient(update=True)
5152

5253
with mock.patch(
5354
'neutron_plugin.port.'
54-
'get_openstack_id_of_single_connected_node_by_openstack_type',
55-
self._get_connected_subnet_mock(return_empty=False)):
55+
'get_openstack_ids_of_connected_nodes_by_openstack_type',
56+
self._get_connected_subnets_mock(return_empty=False)):
5657
with mock.patch(
5758
'neutron_plugin.port.ctx',
5859
self._get_mock_ctx_with_node_properties(node_props)):
5960

6061
port = {}
61-
neutron_plugin.port._handle_fixed_ips(port)
62+
neutron_plugin.port._handle_fixed_ips(port, mock_neutron)
6263

6364
self.assertEquals([{'subnet_id': 'some-subnet-id'}],
6465
port.get('fixed_ips'))
6566

6667
def test_fixed_ips_ip_address_only(self):
6768
node_props = {'fixed_ip': '1.2.3.4'}
69+
mock_neutron = MockNeutronClient(update=True)
6870

6971
with mock.patch(
7072
'neutron_plugin.port.'
7173
'get_openstack_id_of_single_connected_node_by_openstack_type',
72-
self._get_connected_subnet_mock(return_empty=True)):
74+
self._get_connected_subnets_mock(return_empty=True)):
7375
with mock.patch(
7476
'neutron_plugin.port.ctx',
7577
self._get_mock_ctx_with_node_properties(node_props)):
7678

7779
port = {}
78-
neutron_plugin.port._handle_fixed_ips(port)
80+
neutron_plugin.port._handle_fixed_ips(port, mock_neutron)
7981

8082
self.assertEquals([{'ip_address': '1.2.3.4'}],
8183
port.get('fixed_ips'))
8284

8385
def test_fixed_ips_subnet_and_ip_address(self):
8486
node_props = {'fixed_ip': '1.2.3.4'}
87+
mock_neutron = MockNeutronClient(update=True)
8588

8689
with mock.patch(
8790
'neutron_plugin.port.'
88-
'get_openstack_id_of_single_connected_node_by_openstack_type',
89-
self._get_connected_subnet_mock(return_empty=False)):
91+
'get_openstack_ids_of_connected_nodes_by_openstack_type',
92+
self._get_connected_subnets_mock(return_empty=False)):
9093
with mock.patch(
9194
'neutron_plugin.port.ctx',
9295
self._get_mock_ctx_with_node_properties(node_props)):
9396

9497
port = {}
95-
neutron_plugin.port._handle_fixed_ips(port)
98+
neutron_plugin.port._handle_fixed_ips(port, mock_neutron)
9699

97100
self.assertEquals([{'ip_address': '1.2.3.4',
98101
'subnet_id': 'some-subnet-id'}],
99102
port.get('fixed_ips'))
100103

101104
@staticmethod
102-
def _get_connected_subnet_mock(return_empty=True):
103-
return lambda *args, **kw: None if return_empty else 'some-subnet-id'
105+
def _get_connected_subnets_mock(return_empty=True):
106+
return lambda *args, **kw: None if return_empty else ['some-subnet-id']
104107

105108
@staticmethod
106109
def _get_mock_ctx_with_node_properties(properties):
@@ -125,6 +128,18 @@ def update_port(self, _, b, **__):
125128
def cosmo_get(self, *_, **__):
126129
return self.body['port']
127130

131+
def show_subnet(self, subnet_id=None):
132+
subnet = {
133+
'subnet': {
134+
'id': subnet_id,
135+
}
136+
}
137+
if subnet_id == 'some-subnet-id':
138+
subnet['cidr'] = '1.2.3.0/24'
139+
else:
140+
subnet['cidr'] = '2.3.4.0/24'
141+
return subnet
142+
128143

129144
class TestPortSG(unittest.TestCase):
130145
@mock.patch('openstack_plugin_common._handle_kw')

plugin.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
plugins:
66
openstack:
77
executor: central_deployment_agent
8-
source: https://github.com/cloudify-cosmo/cloudify-openstack-plugin/archive/2.9.7.zip
8+
source: https://github.com/cloudify-cosmo/cloudify-openstack-plugin/archive/2.9.8.zip
99
package_name: cloudify-openstack-plugin
10-
package_version: '2.9.7'
10+
package_version: '2.9.8'
1111

1212
data_types:
1313
cloudify.openstack.types.custom_configuration:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
setup(
2020
zip_safe=True,
2121
name='cloudify-openstack-plugin',
22-
version='2.9.7',
22+
version='2.9.8',
2323
author='Cloudify',
2424
author_email='hello@cloudify.co',
2525
packages=[

0 commit comments

Comments
 (0)