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