1 # Copyright (c) 2014 OpenStack Foundation, all rights reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
19 from testtools import matchers
21 from neutron.common import exceptions as exc
22 from neutron.db import api as db
23 from neutron.plugins.ml2 import driver_api as api
24 from neutron.plugins.ml2.drivers import type_tunnel
26 TUNNEL_IP_ONE = "10.10.10.10"
27 TUNNEL_IP_TWO = "10.10.10.20"
28 HOST_ONE = 'fake_host_one'
29 HOST_TWO = 'fake_host_two'
32 TUNNEL_RANGES = [(TUN_MIN, TUN_MAX)]
33 UPDATED_TUNNEL_RANGES = [(TUN_MIN + 5, TUN_MAX + 5)]
36 class TunnelTypeTestMixin(object):
41 super(TunnelTypeTestMixin, self).setUp()
42 self.driver = self.DRIVER_CLASS()
43 self.driver.tunnel_ranges = TUNNEL_RANGES
44 self.driver.sync_allocations()
45 self.session = db.get_session()
47 def test_tunnel_type(self):
48 self.assertEqual(self.TYPE, self.driver.get_type())
50 def test_validate_provider_segment(self):
51 segment = {api.NETWORK_TYPE: self.TYPE,
52 api.PHYSICAL_NETWORK: 'phys_net',
53 api.SEGMENTATION_ID: None}
55 with testtools.ExpectedException(exc.InvalidInput):
56 self.driver.validate_provider_segment(segment)
58 segment[api.PHYSICAL_NETWORK] = None
59 self.driver.validate_provider_segment(segment)
61 segment[api.SEGMENTATION_ID] = 1
62 self.driver.validate_provider_segment(segment)
64 def test_sync_tunnel_allocations(self):
66 self.driver.get_allocation(self.session, (TUN_MIN - 1)))
68 self.driver.get_allocation(self.session, (TUN_MIN)).allocated)
70 self.driver.get_allocation(self.session, (TUN_MIN + 1)).allocated)
72 self.driver.get_allocation(self.session, (TUN_MAX - 1)).allocated)
74 self.driver.get_allocation(self.session, (TUN_MAX)).allocated)
76 self.driver.get_allocation(self.session, (TUN_MAX + 1)))
78 self.driver.tunnel_ranges = UPDATED_TUNNEL_RANGES
79 self.driver.sync_allocations()
82 self.driver.get_allocation(self.session, (TUN_MIN + 5 - 1)))
84 self.driver.get_allocation(self.session, (TUN_MIN + 5)).allocated)
86 self.driver.get_allocation(self.session,
87 (TUN_MIN + 5 + 1)).allocated)
89 self.driver.get_allocation(self.session,
90 (TUN_MAX + 5 - 1)).allocated)
92 self.driver.get_allocation(self.session, (TUN_MAX + 5)).allocated)
94 self.driver.get_allocation(self.session, (TUN_MAX + 5 + 1)))
96 def _test_sync_allocations_and_allocated(self, tunnel_id):
97 segment = {api.NETWORK_TYPE: self.TYPE,
98 api.PHYSICAL_NETWORK: None,
99 api.SEGMENTATION_ID: tunnel_id}
100 self.driver.reserve_provider_segment(self.session, segment)
102 self.driver.tunnel_ranges = UPDATED_TUNNEL_RANGES
103 self.driver.sync_allocations()
106 self.driver.get_allocation(self.session, tunnel_id).allocated)
108 def test_sync_allocations_and_allocated_in_initial_range(self):
109 self._test_sync_allocations_and_allocated(TUN_MIN + 2)
111 def test_sync_allocations_and_allocated_in_final_range(self):
112 self._test_sync_allocations_and_allocated(TUN_MAX + 2)
114 def test_sync_allocations_no_op(self):
116 def verify_no_chunk(iterable, chunk_size):
117 # no segment removed/added
118 self.assertEqual(0, len(list(iterable)))
120 with mock.patch.object(
121 type_tunnel, 'chunks', side_effect=verify_no_chunk) as chunks:
122 self.driver.sync_allocations()
123 self.assertEqual(2, len(chunks.mock_calls))
125 def test_partial_segment_is_partial_segment(self):
126 segment = {api.NETWORK_TYPE: self.TYPE,
127 api.PHYSICAL_NETWORK: None,
128 api.SEGMENTATION_ID: None}
129 self.assertTrue(self.driver.is_partial_segment(segment))
131 def test_specific_segment_is_not_partial_segment(self):
132 segment = {api.NETWORK_TYPE: self.TYPE,
133 api.PHYSICAL_NETWORK: None,
134 api.SEGMENTATION_ID: 101}
135 self.assertFalse(self.driver.is_partial_segment(segment))
137 def test_reserve_provider_segment_full_specs(self):
138 segment = {api.NETWORK_TYPE: self.TYPE,
139 api.PHYSICAL_NETWORK: None,
140 api.SEGMENTATION_ID: 101}
141 observed = self.driver.reserve_provider_segment(self.session, segment)
142 alloc = self.driver.get_allocation(self.session,
143 observed[api.SEGMENTATION_ID])
144 self.assertTrue(alloc.allocated)
146 with testtools.ExpectedException(exc.TunnelIdInUse):
147 self.driver.reserve_provider_segment(self.session, segment)
149 self.driver.release_segment(self.session, segment)
150 alloc = self.driver.get_allocation(self.session,
151 observed[api.SEGMENTATION_ID])
152 self.assertFalse(alloc.allocated)
154 segment[api.SEGMENTATION_ID] = 1000
155 observed = self.driver.reserve_provider_segment(self.session, segment)
156 alloc = self.driver.get_allocation(self.session,
157 observed[api.SEGMENTATION_ID])
158 self.assertTrue(alloc.allocated)
160 self.driver.release_segment(self.session, segment)
161 alloc = self.driver.get_allocation(self.session,
162 observed[api.SEGMENTATION_ID])
163 self.assertIsNone(alloc)
165 def test_reserve_provider_segment(self):
167 specs = {api.NETWORK_TYPE: self.TYPE,
168 api.PHYSICAL_NETWORK: 'None',
169 api.SEGMENTATION_ID: None}
171 for x in moves.range(TUN_MIN, TUN_MAX + 1):
172 segment = self.driver.reserve_provider_segment(self.session,
174 self.assertEqual(self.TYPE, segment[api.NETWORK_TYPE])
175 self.assertThat(segment[api.SEGMENTATION_ID],
176 matchers.GreaterThan(TUN_MIN - 1))
177 self.assertThat(segment[api.SEGMENTATION_ID],
178 matchers.LessThan(TUN_MAX + 1))
179 tunnel_ids.add(segment[api.SEGMENTATION_ID])
181 with testtools.ExpectedException(exc.NoNetworkAvailable):
182 segment = self.driver.reserve_provider_segment(self.session,
185 segment = {api.NETWORK_TYPE: self.TYPE,
186 api.PHYSICAL_NETWORK: 'None',
187 api.SEGMENTATION_ID: tunnel_ids.pop()}
188 self.driver.release_segment(self.session, segment)
189 segment = self.driver.reserve_provider_segment(self.session, specs)
190 self.assertThat(segment[api.SEGMENTATION_ID],
191 matchers.GreaterThan(TUN_MIN - 1))
192 self.assertThat(segment[api.SEGMENTATION_ID],
193 matchers.LessThan(TUN_MAX + 1))
194 tunnel_ids.add(segment[api.SEGMENTATION_ID])
196 for tunnel_id in tunnel_ids:
197 segment[api.SEGMENTATION_ID] = tunnel_id
198 self.driver.release_segment(self.session, segment)
200 def test_allocate_tenant_segment(self):
202 for x in moves.range(TUN_MIN, TUN_MAX + 1):
203 segment = self.driver.allocate_tenant_segment(self.session)
204 self.assertThat(segment[api.SEGMENTATION_ID],
205 matchers.GreaterThan(TUN_MIN - 1))
206 self.assertThat(segment[api.SEGMENTATION_ID],
207 matchers.LessThan(TUN_MAX + 1))
208 tunnel_ids.add(segment[api.SEGMENTATION_ID])
210 segment = self.driver.allocate_tenant_segment(self.session)
211 self.assertIsNone(segment)
213 segment = {api.NETWORK_TYPE: self.TYPE,
214 api.PHYSICAL_NETWORK: 'None',
215 api.SEGMENTATION_ID: tunnel_ids.pop()}
216 self.driver.release_segment(self.session, segment)
217 segment = self.driver.allocate_tenant_segment(self.session)
218 self.assertThat(segment[api.SEGMENTATION_ID],
219 matchers.GreaterThan(TUN_MIN - 1))
220 self.assertThat(segment[api.SEGMENTATION_ID],
221 matchers.LessThan(TUN_MAX + 1))
222 tunnel_ids.add(segment[api.SEGMENTATION_ID])
224 for tunnel_id in tunnel_ids:
225 segment[api.SEGMENTATION_ID] = tunnel_id
226 self.driver.release_segment(self.session, segment)
228 def add_endpoint(self, ip=TUNNEL_IP_ONE, host=HOST_ONE):
229 return self.driver.add_endpoint(ip, host)
231 def test_add_endpoint(self):
232 endpoint = self.add_endpoint()
233 self.assertEqual(TUNNEL_IP_ONE, endpoint.ip_address)
234 self.assertEqual(HOST_ONE, endpoint.host)
237 def test_add_endpoint_for_existing_tunnel_ip(self):
240 with mock.patch.object(type_tunnel.LOG, 'warning') as log_warn:
242 log_warn.assert_called_once_with(mock.ANY, TUNNEL_IP_ONE)
244 def test_get_endpoint_by_host(self):
247 host_endpoint = self.driver.get_endpoint_by_host(HOST_ONE)
248 self.assertEqual(TUNNEL_IP_ONE, host_endpoint.ip_address)
251 def test_get_endpoint_by_host_for_not_existing_host(self):
252 ip_endpoint = self.driver.get_endpoint_by_host(HOST_TWO)
253 self.assertIsNone(ip_endpoint)
255 def test_get_endpoint_by_ip(self):
258 ip_endpoint = self.driver.get_endpoint_by_ip(TUNNEL_IP_ONE)
259 self.assertEqual(HOST_ONE, ip_endpoint.host)
262 def test_get_endpoint_by_ip_for_not_existing_tunnel_ip(self):
263 ip_endpoint = self.driver.get_endpoint_by_ip(TUNNEL_IP_TWO)
264 self.assertIsNone(ip_endpoint)
266 def test_delete_endpoint(self):
269 self.assertIsNone(self.driver.delete_endpoint(TUNNEL_IP_ONE))
270 # Get all the endpoints and verify its empty
271 endpoints = self.driver.get_endpoints()
272 self.assertNotIn(TUNNEL_IP_ONE, endpoints)
275 class TunnelTypeMultiRangeTestMixin(object):
282 TUNNEL_MULTI_RANGES = [(TUN_MIN0, TUN_MAX0), (TUN_MIN1, TUN_MAX1)]
285 super(TunnelTypeMultiRangeTestMixin, self).setUp()
286 self.driver = self.DRIVER_CLASS()
287 self.driver.tunnel_ranges = self.TUNNEL_MULTI_RANGES
288 self.driver.sync_allocations()
289 self.session = db.get_session()
291 def test_release_segment(self):
292 segments = [self.driver.allocate_tenant_segment(self.session)
295 # Release them in random order. No special meaning.
296 for i in (0, 2, 1, 3):
297 self.driver.release_segment(self.session, segments[i])
299 for key in (self.TUN_MIN0, self.TUN_MAX0,
300 self.TUN_MIN1, self.TUN_MAX1):
301 alloc = self.driver.get_allocation(self.session, key)
302 self.assertFalse(alloc.allocated)
305 class TunnelRpcCallbackTestMixin(object):
311 super(TunnelRpcCallbackTestMixin, self).setUp()
312 self.driver = self.DRIVER_CLASS()
314 def _test_tunnel_sync(self, kwargs, delete_tunnel=False):
315 with mock.patch.object(self.notifier,
316 'tunnel_update') as tunnel_update,\
317 mock.patch.object(self.notifier,
318 'tunnel_delete') as tunnel_delete:
319 details = self.callbacks.tunnel_sync('fake_context', **kwargs)
320 tunnels = details['tunnels']
321 for tunnel in tunnels:
322 self.assertEqual(kwargs['tunnel_ip'], tunnel['ip_address'])
323 self.assertEqual(kwargs['host'], tunnel['host'])
324 self.assertTrue(tunnel_update.called)
326 self.assertTrue(tunnel_delete.called)
328 self.assertFalse(tunnel_delete.called)
330 def _test_tunnel_sync_raises(self, kwargs):
331 with mock.patch.object(self.notifier,
332 'tunnel_update') as tunnel_update,\
333 mock.patch.object(self.notifier,
334 'tunnel_delete') as tunnel_delete:
335 self.assertRaises(exc.InvalidInput,
336 self.callbacks.tunnel_sync,
337 'fake_context', **kwargs)
338 self.assertFalse(tunnel_update.called)
339 self.assertFalse(tunnel_delete.called)
341 def test_tunnel_sync_called_without_host_passed(self):
342 kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'tunnel_type': self.TYPE,
344 self._test_tunnel_sync(kwargs)
346 def test_tunnel_sync_called_with_host_passed_for_existing_tunnel_ip(self):
347 self.driver.add_endpoint(TUNNEL_IP_ONE, None)
349 kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'tunnel_type': self.TYPE,
351 self._test_tunnel_sync(kwargs)
353 def test_tunnel_sync_called_with_host_passed(self):
354 kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'tunnel_type': self.TYPE,
356 self._test_tunnel_sync(kwargs)
358 def test_tunnel_sync_called_for_existing_endpoint(self):
359 self.driver.add_endpoint(TUNNEL_IP_ONE, HOST_ONE)
361 kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'tunnel_type': self.TYPE,
363 self._test_tunnel_sync(kwargs)
365 def test_tunnel_sync_called_for_existing_host_with_tunnel_ip_changed(self):
366 self.driver.add_endpoint(TUNNEL_IP_ONE, HOST_ONE)
368 kwargs = {'tunnel_ip': TUNNEL_IP_TWO, 'tunnel_type': self.TYPE,
370 self._test_tunnel_sync(kwargs, True)
372 def test_tunnel_sync_called_with_used_tunnel_ip_host_roaming(self):
373 self.driver.add_endpoint(TUNNEL_IP_ONE, HOST_ONE)
375 kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'tunnel_type': self.TYPE,
377 self._test_tunnel_sync(kwargs, False)
379 def test_tunnel_sync_called_with_used_tunnel_ip_roaming_case_two(self):
380 self.driver.add_endpoint(TUNNEL_IP_ONE, None)
381 self.driver.add_endpoint(TUNNEL_IP_TWO, HOST_TWO)
383 kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'tunnel_type': self.TYPE,
385 self._test_tunnel_sync(kwargs, False)
387 def test_tunnel_sync_called_without_tunnel_ip(self):
388 kwargs = {'tunnel_type': self.TYPE, 'host': None}
389 self._test_tunnel_sync_raises(kwargs)
391 def test_tunnel_sync_called_without_tunnel_type(self):
392 kwargs = {'tunnel_ip': TUNNEL_IP_ONE, 'host': None}
393 self._test_tunnel_sync_raises(kwargs)