blob: ad2c27141fbb99dc48c41a80e57e0a910200f137 [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()
146 cls.alt_tenant_id = cls.manager._get_identity_client(
147 cls.config.identity.alt_username,
148 cls.config.identity.alt_password,
149 cls.config.identity.alt_tenant_name
150 ).tenant_id
151 cls.check_preconditions()
152 # TODO(mnewby) Consider looking up entities as needed instead
153 # of storing them as collections on the class.
154 cls.keypairs = {}
155 cls.security_groups = {}
156 cls.networks = []
157 cls.subnets = []
158 cls.routers = []
159 cls.servers = []
160 cls.floating_ips = {}
161 cls.tenants = {}
162 cls.demo_tenant = cls.TenantProperties(
163 cls.tenant_id,
164 cls.config.identity.username,
165 cls.config.identity.password,
166 cls.config.identity.tenant_name
167 )
168 cls.alt_tenant = cls.TenantProperties(
169 cls.alt_tenant_id,
170 cls.config.identity.alt_username,
171 cls.config.identity.alt_password,
172 cls.config.identity.alt_tenant_name
173 )
174 for tenant in [cls.demo_tenant, cls.alt_tenant]:
175 cls.tenants[tenant.tenant_id] = tenant
176 if not cls.config.network.public_router_id:
177 cls.floating_ip_access = True
178 else:
179 cls.floating_ip_access = False
180
181 @classmethod
182 def tearDownClass(cls):
183 super(TestNetworkCrossTenant, cls).tearDownClass()
184
185 def _create_tenant_keypairs(self, tenant_id):
186 self.keypairs[tenant_id] = self.create_keypair(
187 name=data_utils.rand_name('keypair-smoke-'))
188
189 def _create_tenant_security_groups(self, tenant):
190 self.security_groups.setdefault(self.tenant_id, [])
191 access_sg = self._create_empty_security_group(
192 namestart='secgroup_access-',
193 tenant_id=tenant.tenant_id
194 )
195 # don't use default secgroup since it allows in-tenant traffic
196 def_sg = self._create_empty_security_group(
197 namestart='secgroup_general-',
198 tenant_id=tenant.tenant_id
199 )
200 tenant.security_groups.update(access=access_sg, default=def_sg)
201 ssh_rule = dict(
202 protocol='tcp',
203 port_range_min=22,
204 port_range_max=22,
205 direction='ingress',
206 )
207 self._create_security_group_rule(secgroup=access_sg,
208 **ssh_rule
209 )
210
211 def _verify_network_details(self, tenant):
212 # Checks that we see the newly created network/subnet/router via
213 # checking the result of list_[networks,routers,subnets]
214 # Check that (router, subnet) couple exist in port_list
215 seen_nets = self._list_networks()
216 seen_names = [n['name'] for n in seen_nets]
217 seen_ids = [n['id'] for n in seen_nets]
218
219 self.assertIn(tenant.network.name, seen_names)
220 self.assertIn(tenant.network.id, seen_ids)
221
222 seen_subnets = [(n['id'], n['cidr'], n['network_id'])
223 for n in self._list_subnets()]
224 mysubnet = (tenant.subnet.id, tenant.subnet.cidr, tenant.network.id)
225 self.assertIn(mysubnet, seen_subnets)
226
227 seen_routers = self._list_routers()
228 seen_router_ids = [n['id'] for n in seen_routers]
229 seen_router_names = [n['name'] for n in seen_routers]
230
231 self.assertIn(tenant.router.name, seen_router_names)
232 self.assertIn(tenant.router.id, seen_router_ids)
233
234 myport = (tenant.router.id, tenant.subnet.id)
235 router_ports = [(i['device_id'], i['fixed_ips'][0]['subnet_id']) for i
236 in self.network_client.list_ports()['ports']
237 if i['device_owner'] == 'network:router_interface']
238
239 self.assertIn(myport, router_ports)
240
241 def _create_server(self, name, tenant, security_groups=None):
242 """
243 creates a server and assigns to security group
244 """
245 self._set_compute_context(tenant)
246 if security_groups is None:
247 security_groups = [tenant.security_groups['default'].name]
248 create_kwargs = {
249 'nics': [
250 {'net-id': tenant.network.id},
251 ],
252 'key_name': self.keypairs[tenant.tenant_id].name,
253 'security_groups': security_groups,
254 'tenant_id': tenant.tenant_id
255 }
256 server = self.create_server(name=name, create_kwargs=create_kwargs)
257 return server
258
259 def _create_tenant_servers(self, tenant, num=1):
260 for i in range(num):
261 name = 'server-{tenant}-gen-{num}-'.format(
262 tenant=tenant.tenant_name,
263 num=i
264 )
265 name = data_utils.rand_name(name)
266 server = self._create_server(name, tenant)
267 self.servers.append(server)
268 tenant.servers.append(server)
269
270 def _set_access_point(self, tenant):
271 """
272 creates a server in a secgroup with rule allowing external ssh
273 in order to access tenant internal network
274 workaround ip namespace
275 """
276 secgroups = [sg.name for sg in tenant.security_groups.values()]
277 name = 'server-{tenant}-access_point-'.format(tenant=tenant.tenant_name
278 )
279 name = data_utils.rand_name(name)
280 server = self._create_server(name, tenant,
281 security_groups=secgroups)
282 self.servers.append(server)
283 tenant.access_point = server
284 self._assign_floating_ips(server)
285
286 def _assign_floating_ips(self, server):
287 public_network_id = self.config.network.public_network_id
288 floating_ip = self._create_floating_ip(server, public_network_id)
289 self.floating_ips.setdefault(server, floating_ip)
290
291 def _create_tenant_network(self, tenant):
292 tenant._set_network(*self._create_networks(tenant.tenant_id))
293
294 def _set_compute_context(self, tenant):
295 self.compute_client = tenant.manager.compute_client
296 return self.compute_client
297
298 def _deploy_tenant(self, tenant_or_id):
299 """
300 creates:
301 network
302 subnet
303 router (if public not defined)
304 access security group
305 access-point server
306 for demo_tenant:
307 creates general server to test against
308 """
309 if not isinstance(tenant_or_id, self.TenantProperties):
310 tenant = self.tenants[tenant_or_id]
311 tenant_id = tenant_or_id
312 else:
313 tenant = tenant_or_id
314 tenant_id = tenant.tenant_id
315 self._set_compute_context(tenant)
316 self._create_tenant_keypairs(tenant_id)
317 self._create_tenant_network(tenant)
318 self._create_tenant_security_groups(tenant)
319 if tenant is self.demo_tenant:
320 self._create_tenant_servers(tenant, num=1)
321 self._set_access_point(tenant)
322
323 def _get_server_ip(self, server, floating=False):
324 '''
325 returns the ip (floating/internal) of a server
326 '''
327 if floating:
328 return self.floating_ips[server].floating_ip_address
329 else:
330 network_name = self.tenants[server.tenant_id].network.name
331 return server.networks[network_name][0]
332
333 def _connect_to_access_point(self, tenant):
334 """
335 create ssh connection to tenant access point
336 """
337 access_point_ssh = \
338 self.floating_ips[tenant.access_point].floating_ip_address
339 private_key = self.keypairs[tenant.tenant_id].private_key
340 access_point_ssh = self._ssh_to_server(access_point_ssh,
341 private_key=private_key)
342 return access_point_ssh
343
344 def _test_remote_connectivity(self, source, dest, should_succeed=True):
345 """
346 check ping server via source ssh connection
347
348 :param source: RemoteClient: an ssh connection from which to ping
349 :param dest: and IP to ping against
350 :param should_succeed: boolean should ping succeed or not
351 :returns: boolean -- should_succeed == ping
352 :returns: ping is false if ping failed
353 """
354 def ping_remote():
355 try:
356 source.ping_host(dest)
357 except exceptions.SSHExecCommandFailed as ex:
358 LOG.debug(ex)
359 return not should_succeed
360 return should_succeed
361
362 return call_until_true(ping_remote,
363 self.config.compute.ping_timeout,
364 1)
365
366 def _check_connectivity(self, access_point, ip, should_succeed=True):
367 if should_succeed:
368 msg = "Timed out waiting for %s to become reachable" % ip
369 else:
370 # todo(yfried): remove this line when bug 1252620 is fixed
371 return True
372 msg = "%s is reachable" % ip
373 try:
374 self.assertTrue(self._test_remote_connectivity(access_point, ip,
375 should_succeed),
376 msg)
377 except Exception:
378 debug.log_ip_ns()
379 raise
380
381 def _test_in_tenant_block(self, tenant):
382 access_point_ssh = self._connect_to_access_point(tenant)
383 for server in tenant.servers:
384 self._check_connectivity(access_point=access_point_ssh,
385 ip=self._get_server_ip(server),
386 should_succeed=False)
387
388 def _test_in_tenant_allow(self, tenant):
389 ruleset = dict(
390 protocol='icmp',
391 remote_group_id=tenant.security_groups['default'].id,
392 direction='ingress'
393 )
394 rule = self._create_security_group_rule(
395 secgroup=tenant.security_groups['default'],
396 **ruleset
397 )
398 access_point_ssh = self._connect_to_access_point(tenant)
399 for server in tenant.servers:
400 self._check_connectivity(access_point=access_point_ssh,
401 ip=self._get_server_ip(server))
402 rule.delete()
403
404 def _test_cross_tenant_block(self, source_tenant, dest_tenant):
405 '''
406 if public router isn't defined, then dest_tenant access is via
407 floating-ip
408 '''
409 access_point_ssh = self._connect_to_access_point(source_tenant)
410 ip = self._get_server_ip(dest_tenant.access_point,
411 floating=self.floating_ip_access)
412 self._check_connectivity(access_point=access_point_ssh, ip=ip,
413 should_succeed=False)
414
415 def _test_cross_tenant_allow(self, source_tenant, dest_tenant):
416 '''
417 check for each direction:
418 creating rule for tenant incoming traffic enables only 1way traffic
419 '''
420 ruleset = dict(
421 protocol='icmp',
422 direction='ingress'
423 )
424 rule_s2d = self._create_security_group_rule(
425 secgroup=dest_tenant.security_groups['default'],
426 **ruleset
427 )
428 try:
429 access_point_ssh = self._connect_to_access_point(source_tenant)
430 ip = self._get_server_ip(dest_tenant.access_point,
431 floating=self.floating_ip_access)
432 self._check_connectivity(access_point_ssh, ip)
433
434 # test that reverse traffic is still blocked
435 self._test_cross_tenant_block(dest_tenant, source_tenant)
436
437 # allow reverse traffic and check
438 rule_d2s = self._create_security_group_rule(
439 secgroup=source_tenant.security_groups['default'],
440 **ruleset
441 )
442 try:
443 access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
444 ip = self._get_server_ip(source_tenant.access_point,
445 floating=self.floating_ip_access)
446 self._check_connectivity(access_point_ssh_2, ip)
447
448 # clean_rules
449 rule_s2d.delete()
450 rule_d2s.delete()
451
452 except Exception as e:
453 rule_d2s.delete()
454 raise e
455
456 except Exception as e:
457 rule_s2d.delete()
458 raise e
459
460 def _verify_mac_addr(self, tenant):
461 """
462 verify that VM (tenant's access point) has the same ip,mac as listed in
463 port list
464 """
465 access_point_ssh = self._connect_to_access_point(tenant)
466 mac_addr = access_point_ssh.get_mac_address()
467 mac_addr = mac_addr.strip().lower()
468 port_list = self.network_client.list_ports()['ports']
469 port_detail_list = [
470 (port['fixed_ips'][0]['subnet_id'],
471 port['fixed_ips'][0]['ip_address'],
472 port['mac_address'].lower()) for port in port_list
473 ]
474 server_ip = self._get_server_ip(tenant.access_point)
475 subnet_id = tenant.subnet.id
476 self.assertIn((subnet_id, server_ip, mac_addr), port_detail_list)
477
478 @attr(type='smoke')
479 @services('compute', 'network')
480 def test_cross_tenant_traffic(self):
481 for tenant_id in self.tenants.keys():
482 self._deploy_tenant(tenant_id)
483 self._verify_network_details(self.tenants[tenant_id])
484 self._verify_mac_addr(self.tenants[tenant_id])
485
486 # in-tenant check
487 self._test_in_tenant_block(self.demo_tenant)
488 self._test_in_tenant_allow(self.demo_tenant)
489
490 # cross tenant check
491 source_tenant = self.demo_tenant
492 dest_tenant = self.alt_tenant
493 self._test_cross_tenant_block(source_tenant, dest_tenant)
494 self._test_cross_tenant_allow(source_tenant, dest_tenant)