blob: faba9870634f6d98ceb5161bb7377470f534adef [file] [log] [blame]
Yair Fried4d7efa62013-11-17 17:12:29 +02001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2013 Red Hat, Inc.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18from tempest.common import debug
19from tempest.common.utils import data_utils
20from tempest import exceptions
21from tempest.openstack.common import log as logging
22from tempest.scenario import manager
23from tempest.scenario.manager import OfficialClientManager
24from tempest.test import attr
25from tempest.test import call_until_true
26from tempest.test import services
27
28LOG = logging.getLogger(__name__)
29
30
31class TestNetworkCrossTenant(manager.NetworkScenarioTest):
32
33 """
34 This test suite assumes that Nova has been configured to
35 boot VM's with Neutron-managed networking, and attempts to
36 verify cross tenant connectivity as follows
37
38 ssh:
39 in order to overcome "ip namespace", each tenant has an "access point"
40 VM with floating-ip open to incoming ssh connection allowing network
41 commands (ping/ssh) to be executed from within the
42 tenant-network-namespace
43 Tempest host performs key-based authentication to the ssh server via
44 floating IP address
45
46 connectivity test is done by pinging destination server via source server
47 ssh connection.
48 success - ping returns
49 failure - ping_timeout reached
50
51 setup:
52 for each tenant (demo and alt):
53 1. create a network&subnet
54 2. create a router (if public router isn't configured)
55 3. connect tenant network to public network via router
56 4. create an access point:
57 a. a security group open to incoming ssh connection
58 b. a VM with a floating ip
59 5. create a general empty security group (same as "default", but
60 without rules allowing in-tenant traffic)
61 6. for demo tenant - create another server to test in-tenant
62 connections
63
64 tests:
65 1. _verify_network_details
66 2. _verify_mac_addr: for each access point verify that
67 (subnet, fix_ip, mac address) are as defined in the port list
68 3. _test_in_tenant_block: test that in-tenant traffic is disabled
69 without rules allowing it
70 4. _test_in_tenant_allow: test that in-tenant traffic is enabled
71 once an appropriate rule has been created
72 5. _test_cross_tenant_block: test that cross-tenant traffic is disabled
73 without a rule allowing it on destination tenant
74 6. _test_cross_tenant_allow:
75 * test that cross-tenant traffic is enabled once an appropriate
76 rule has been created on destination tenant.
77 * test that reverse traffic is still blocked
78 * test than revesre traffic is enabled once an appropriate rule has
79 been created on source tenant
80
81 assumptions:
82 1. alt_tenant/user existed and is different from demo_tenant/user
83 2. Public network is defined and reachable from the Tempest host
84 3. Public router can either be:
85 * defined, in which case all tenants networks can connect directly
86 to it, and cross tenant check will be done on the private IP of the
87 destination tenant
88 or
89 * not defined (empty string), in which case each tanant will have
90 its own router connected to the public network
91 """
92
93 class TenantProperties():
94 '''
95 helper class to save tenant details
96 id
97 credentials
98 network
99 subnet
100 security groups
101 servers
102 access point
103 '''
104
105 def __init__(self, tenant_id, tenant_user, tenant_pass, tenant_name):
106 self.manager = OfficialClientManager(
107 tenant_user,
108 tenant_pass,
109 tenant_name
110 )
111 self.tenant_id = tenant_id
112 self.tenant_name = tenant_name
113 self.tenant_user = tenant_user
114 self.tenant_pass = tenant_pass
115 self.network = None
116 self.subnet = None
117 self.router = None
118 self.security_groups = {}
119 self.servers = list()
120
121 def _set_network(self, network, subnet, router):
122 self.network = network
123 self.subnet = subnet
124 self.router = router
125
126 def _get_tenant_credentials(self):
127 return self.tenant_user, self.tenant_pass, self.tenant_name
128
129 @classmethod
130 def check_preconditions(cls):
131 super(TestNetworkCrossTenant, cls).check_preconditions()
132 if (cls.alt_tenant_id is None) or (cls.tenant_id is cls.alt_tenant_id):
133 msg = 'No alt_tenant defined'
134 cls.enabled = False
135 raise cls.skipException(msg)
136 cfg = cls.config.network
137 if not (cfg.tenant_networks_reachable or cfg.public_network_id):
138 msg = ('Either tenant_networks_reachable must be "true", or '
139 'public_network_id must be defined.')
140 cls.enabled = False
141 raise cls.skipException(msg)
142
143 @classmethod
144 def setUpClass(cls):
145 super(TestNetworkCrossTenant, cls).setUpClass()
Yair Fried769bbff2013-12-18 16:33:17 +0200146 alt_creds = cls.alt_credentials()
Yair Fried4d7efa62013-11-17 17:12:29 +0200147 cls.alt_tenant_id = cls.manager._get_identity_client(
Yair Fried769bbff2013-12-18 16:33:17 +0200148 *alt_creds
Yair Fried4d7efa62013-11-17 17:12:29 +0200149 ).tenant_id
150 cls.check_preconditions()
151 # TODO(mnewby) Consider looking up entities as needed instead
152 # of storing them as collections on the class.
153 cls.keypairs = {}
154 cls.security_groups = {}
155 cls.networks = []
156 cls.subnets = []
157 cls.routers = []
158 cls.servers = []
159 cls.floating_ips = {}
160 cls.tenants = {}
161 cls.demo_tenant = cls.TenantProperties(
162 cls.tenant_id,
Yair Fried769bbff2013-12-18 16:33:17 +0200163 *cls.credentials()
Yair Fried4d7efa62013-11-17 17:12:29 +0200164 )
165 cls.alt_tenant = cls.TenantProperties(
166 cls.alt_tenant_id,
Yair Fried769bbff2013-12-18 16:33:17 +0200167 *alt_creds
Yair Fried4d7efa62013-11-17 17:12:29 +0200168 )
169 for tenant in [cls.demo_tenant, cls.alt_tenant]:
170 cls.tenants[tenant.tenant_id] = tenant
171 if not cls.config.network.public_router_id:
172 cls.floating_ip_access = True
173 else:
174 cls.floating_ip_access = False
175
176 @classmethod
177 def tearDownClass(cls):
178 super(TestNetworkCrossTenant, cls).tearDownClass()
179
180 def _create_tenant_keypairs(self, tenant_id):
181 self.keypairs[tenant_id] = self.create_keypair(
182 name=data_utils.rand_name('keypair-smoke-'))
183
184 def _create_tenant_security_groups(self, tenant):
185 self.security_groups.setdefault(self.tenant_id, [])
186 access_sg = self._create_empty_security_group(
187 namestart='secgroup_access-',
188 tenant_id=tenant.tenant_id
189 )
190 # don't use default secgroup since it allows in-tenant traffic
191 def_sg = self._create_empty_security_group(
192 namestart='secgroup_general-',
193 tenant_id=tenant.tenant_id
194 )
195 tenant.security_groups.update(access=access_sg, default=def_sg)
196 ssh_rule = dict(
197 protocol='tcp',
198 port_range_min=22,
199 port_range_max=22,
200 direction='ingress',
201 )
202 self._create_security_group_rule(secgroup=access_sg,
203 **ssh_rule
204 )
205
206 def _verify_network_details(self, tenant):
207 # Checks that we see the newly created network/subnet/router via
208 # checking the result of list_[networks,routers,subnets]
209 # Check that (router, subnet) couple exist in port_list
210 seen_nets = self._list_networks()
211 seen_names = [n['name'] for n in seen_nets]
212 seen_ids = [n['id'] for n in seen_nets]
213
214 self.assertIn(tenant.network.name, seen_names)
215 self.assertIn(tenant.network.id, seen_ids)
216
217 seen_subnets = [(n['id'], n['cidr'], n['network_id'])
218 for n in self._list_subnets()]
219 mysubnet = (tenant.subnet.id, tenant.subnet.cidr, tenant.network.id)
220 self.assertIn(mysubnet, seen_subnets)
221
222 seen_routers = self._list_routers()
223 seen_router_ids = [n['id'] for n in seen_routers]
224 seen_router_names = [n['name'] for n in seen_routers]
225
226 self.assertIn(tenant.router.name, seen_router_names)
227 self.assertIn(tenant.router.id, seen_router_ids)
228
229 myport = (tenant.router.id, tenant.subnet.id)
230 router_ports = [(i['device_id'], i['fixed_ips'][0]['subnet_id']) for i
231 in self.network_client.list_ports()['ports']
232 if i['device_owner'] == 'network:router_interface']
233
234 self.assertIn(myport, router_ports)
235
236 def _create_server(self, name, tenant, security_groups=None):
237 """
238 creates a server and assigns to security group
239 """
240 self._set_compute_context(tenant)
241 if security_groups is None:
242 security_groups = [tenant.security_groups['default'].name]
243 create_kwargs = {
244 'nics': [
245 {'net-id': tenant.network.id},
246 ],
247 'key_name': self.keypairs[tenant.tenant_id].name,
248 'security_groups': security_groups,
249 'tenant_id': tenant.tenant_id
250 }
251 server = self.create_server(name=name, create_kwargs=create_kwargs)
252 return server
253
254 def _create_tenant_servers(self, tenant, num=1):
255 for i in range(num):
256 name = 'server-{tenant}-gen-{num}-'.format(
257 tenant=tenant.tenant_name,
258 num=i
259 )
260 name = data_utils.rand_name(name)
261 server = self._create_server(name, tenant)
262 self.servers.append(server)
263 tenant.servers.append(server)
264
265 def _set_access_point(self, tenant):
266 """
267 creates a server in a secgroup with rule allowing external ssh
268 in order to access tenant internal network
269 workaround ip namespace
270 """
271 secgroups = [sg.name for sg in tenant.security_groups.values()]
272 name = 'server-{tenant}-access_point-'.format(tenant=tenant.tenant_name
273 )
274 name = data_utils.rand_name(name)
275 server = self._create_server(name, tenant,
276 security_groups=secgroups)
277 self.servers.append(server)
278 tenant.access_point = server
279 self._assign_floating_ips(server)
280
281 def _assign_floating_ips(self, server):
282 public_network_id = self.config.network.public_network_id
283 floating_ip = self._create_floating_ip(server, public_network_id)
284 self.floating_ips.setdefault(server, floating_ip)
285
286 def _create_tenant_network(self, tenant):
287 tenant._set_network(*self._create_networks(tenant.tenant_id))
288
289 def _set_compute_context(self, tenant):
290 self.compute_client = tenant.manager.compute_client
291 return self.compute_client
292
293 def _deploy_tenant(self, tenant_or_id):
294 """
295 creates:
296 network
297 subnet
298 router (if public not defined)
299 access security group
300 access-point server
301 for demo_tenant:
302 creates general server to test against
303 """
304 if not isinstance(tenant_or_id, self.TenantProperties):
305 tenant = self.tenants[tenant_or_id]
306 tenant_id = tenant_or_id
307 else:
308 tenant = tenant_or_id
309 tenant_id = tenant.tenant_id
310 self._set_compute_context(tenant)
311 self._create_tenant_keypairs(tenant_id)
312 self._create_tenant_network(tenant)
313 self._create_tenant_security_groups(tenant)
314 if tenant is self.demo_tenant:
315 self._create_tenant_servers(tenant, num=1)
316 self._set_access_point(tenant)
317
318 def _get_server_ip(self, server, floating=False):
319 '''
320 returns the ip (floating/internal) of a server
321 '''
322 if floating:
323 return self.floating_ips[server].floating_ip_address
324 else:
325 network_name = self.tenants[server.tenant_id].network.name
326 return server.networks[network_name][0]
327
328 def _connect_to_access_point(self, tenant):
329 """
330 create ssh connection to tenant access point
331 """
332 access_point_ssh = \
333 self.floating_ips[tenant.access_point].floating_ip_address
334 private_key = self.keypairs[tenant.tenant_id].private_key
335 access_point_ssh = self._ssh_to_server(access_point_ssh,
336 private_key=private_key)
337 return access_point_ssh
338
339 def _test_remote_connectivity(self, source, dest, should_succeed=True):
340 """
341 check ping server via source ssh connection
342
343 :param source: RemoteClient: an ssh connection from which to ping
344 :param dest: and IP to ping against
345 :param should_succeed: boolean should ping succeed or not
346 :returns: boolean -- should_succeed == ping
347 :returns: ping is false if ping failed
348 """
349 def ping_remote():
350 try:
351 source.ping_host(dest)
352 except exceptions.SSHExecCommandFailed as ex:
353 LOG.debug(ex)
354 return not should_succeed
355 return should_succeed
356
357 return call_until_true(ping_remote,
358 self.config.compute.ping_timeout,
359 1)
360
361 def _check_connectivity(self, access_point, ip, should_succeed=True):
362 if should_succeed:
363 msg = "Timed out waiting for %s to become reachable" % ip
364 else:
365 # todo(yfried): remove this line when bug 1252620 is fixed
366 return True
367 msg = "%s is reachable" % ip
368 try:
369 self.assertTrue(self._test_remote_connectivity(access_point, ip,
370 should_succeed),
371 msg)
372 except Exception:
373 debug.log_ip_ns()
374 raise
375
376 def _test_in_tenant_block(self, tenant):
377 access_point_ssh = self._connect_to_access_point(tenant)
378 for server in tenant.servers:
379 self._check_connectivity(access_point=access_point_ssh,
380 ip=self._get_server_ip(server),
381 should_succeed=False)
382
383 def _test_in_tenant_allow(self, tenant):
384 ruleset = dict(
385 protocol='icmp',
386 remote_group_id=tenant.security_groups['default'].id,
387 direction='ingress'
388 )
389 rule = self._create_security_group_rule(
390 secgroup=tenant.security_groups['default'],
391 **ruleset
392 )
393 access_point_ssh = self._connect_to_access_point(tenant)
394 for server in tenant.servers:
395 self._check_connectivity(access_point=access_point_ssh,
396 ip=self._get_server_ip(server))
397 rule.delete()
398
399 def _test_cross_tenant_block(self, source_tenant, dest_tenant):
400 '''
401 if public router isn't defined, then dest_tenant access is via
402 floating-ip
403 '''
404 access_point_ssh = self._connect_to_access_point(source_tenant)
405 ip = self._get_server_ip(dest_tenant.access_point,
406 floating=self.floating_ip_access)
407 self._check_connectivity(access_point=access_point_ssh, ip=ip,
408 should_succeed=False)
409
410 def _test_cross_tenant_allow(self, source_tenant, dest_tenant):
411 '''
412 check for each direction:
413 creating rule for tenant incoming traffic enables only 1way traffic
414 '''
415 ruleset = dict(
416 protocol='icmp',
417 direction='ingress'
418 )
419 rule_s2d = self._create_security_group_rule(
420 secgroup=dest_tenant.security_groups['default'],
421 **ruleset
422 )
423 try:
424 access_point_ssh = self._connect_to_access_point(source_tenant)
425 ip = self._get_server_ip(dest_tenant.access_point,
426 floating=self.floating_ip_access)
427 self._check_connectivity(access_point_ssh, ip)
428
429 # test that reverse traffic is still blocked
430 self._test_cross_tenant_block(dest_tenant, source_tenant)
431
432 # allow reverse traffic and check
433 rule_d2s = self._create_security_group_rule(
434 secgroup=source_tenant.security_groups['default'],
435 **ruleset
436 )
437 try:
438 access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
439 ip = self._get_server_ip(source_tenant.access_point,
440 floating=self.floating_ip_access)
441 self._check_connectivity(access_point_ssh_2, ip)
442
443 # clean_rules
444 rule_s2d.delete()
445 rule_d2s.delete()
446
447 except Exception as e:
448 rule_d2s.delete()
449 raise e
450
451 except Exception as e:
452 rule_s2d.delete()
453 raise e
454
455 def _verify_mac_addr(self, tenant):
456 """
457 verify that VM (tenant's access point) has the same ip,mac as listed in
458 port list
459 """
460 access_point_ssh = self._connect_to_access_point(tenant)
461 mac_addr = access_point_ssh.get_mac_address()
462 mac_addr = mac_addr.strip().lower()
463 port_list = self.network_client.list_ports()['ports']
464 port_detail_list = [
465 (port['fixed_ips'][0]['subnet_id'],
466 port['fixed_ips'][0]['ip_address'],
467 port['mac_address'].lower()) for port in port_list
468 ]
469 server_ip = self._get_server_ip(tenant.access_point)
470 subnet_id = tenant.subnet.id
471 self.assertIn((subnet_id, server_ip, mac_addr), port_detail_list)
472
473 @attr(type='smoke')
474 @services('compute', 'network')
475 def test_cross_tenant_traffic(self):
476 for tenant_id in self.tenants.keys():
477 self._deploy_tenant(tenant_id)
478 self._verify_network_details(self.tenants[tenant_id])
479 self._verify_mac_addr(self.tenants[tenant_id])
480
481 # in-tenant check
482 self._test_in_tenant_block(self.demo_tenant)
483 self._test_in_tenant_allow(self.demo_tenant)
484
485 # cross tenant check
486 source_tenant = self.demo_tenant
487 dest_tenant = self.alt_tenant
488 self._test_cross_tenant_block(source_tenant, dest_tenant)
489 self._test_cross_tenant_allow(source_tenant, dest_tenant)