2fb721df318809ae75565fcfc979a589fab70a28
[functest.git] / functest / core / tenantnetwork.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2018 Orange and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 """Ease deploying tenant networks
11
12 It offers a simple way to create all tenant network resources required by a
13 testcase (including all Functest ones):
14
15   - TenantNetwork1 selects the user and the project set as env vars
16   - TenantNetwork2 creates a user and project to isolate the same resources
17
18 This classes could be reused by more complexed scenarios (Single VM)
19 """
20
21 import logging
22 import os
23 import time
24 import uuid
25
26 import os_client_config
27 import shade
28 from tempest.lib.common.utils import data_utils
29 from xtesting.core import testcase
30
31 from functest.utils import config
32 from functest.utils import env
33 from functest.utils import functest_utils
34
35
36 class NewProject():
37     """Ease creating new projects/users"""
38     # pylint: disable=too-many-instance-attributes
39
40     __logger = logging.getLogger(__name__)
41
42     def __init__(self, cloud, case_name, guid):
43         self.cloud = None
44         self.orig_cloud = cloud
45         self.case_name = case_name
46         self.guid = guid
47         self.project = None
48         self.user = None
49         self.password = None
50         self.domain = None
51         self.role_name = None
52         self.default_member = env.get('NEW_USER_ROLE')
53
54     def create(self):
55         """Create projects/users"""
56         assert self.orig_cloud
57         assert self.case_name
58         self.password = data_utils.rand_password().replace('%', '!')
59         self.__logger.debug("password: %s", self.password)
60         self.domain = self.orig_cloud.get_domain(
61             name_or_id=self.orig_cloud.auth.get(
62                 "project_domain_name", "Default"))
63         self.project = self.orig_cloud.create_project(
64             name='{}-project_{}'.format(self.case_name[:18], self.guid),
65             description="Created by OPNFV Functest: {}".format(
66                 self.case_name),
67             domain_id=self.domain.id)
68         self.__logger.debug("project: %s", self.project)
69         self.user = self.orig_cloud.create_user(
70             name='{}-user_{}'.format(self.case_name, self.guid),
71             password=self.password,
72             domain_id=self.domain.id)
73         self.__logger.debug("user: %s", self.user)
74         try:
75             if self.orig_cloud.get_role(self.default_member):
76                 self.role_name = self.default_member
77             elif self.orig_cloud.get_role(self.default_member.lower()):
78                 self.role_name = self.default_member.lower()
79             else:
80                 raise Exception("Cannot detect {}".format(self.default_member))
81         except Exception:  # pylint: disable=broad-except
82             self.__logger.info("Creating default role %s", self.default_member)
83             role = self.orig_cloud.create_role(self.default_member)
84             self.role_name = role.name
85             self.__logger.debug("role: %s", role)
86         self.orig_cloud.grant_role(
87             self.role_name, user=self.user.id, project=self.project.id,
88             domain=self.domain.id)
89         osconfig = os_client_config.config.OpenStackConfig()
90         osconfig.cloud_config[
91             'clouds']['envvars']['project_name'] = self.project.name
92         osconfig.cloud_config[
93             'clouds']['envvars']['project_id'] = self.project.id
94         osconfig.cloud_config['clouds']['envvars']['username'] = self.user.name
95         osconfig.cloud_config['clouds']['envvars']['password'] = self.password
96         self.__logger.debug("cloud_config %s", osconfig.cloud_config)
97         self.cloud = shade.OpenStackCloud(
98             cloud_config=osconfig.get_one_cloud())
99         self.__logger.debug("new cloud %s", self.cloud.auth)
100
101     def get_environ(self):
102         "Get new environ"
103         environ = dict(
104             os.environ,
105             OS_USERNAME=self.user.name,
106             OS_PROJECT_NAME=self.project.name,
107             OS_PROJECT_ID=self.project.id,
108             OS_PASSWORD=self.password)
109         try:
110             del environ['OS_TENANT_NAME']
111             del environ['OS_TENANT_ID']
112         except Exception:  # pylint: disable=broad-except
113             pass
114         return environ
115
116     def clean(self):
117         """Remove projects/users"""
118         try:
119             assert self.orig_cloud
120             if self.user:
121                 self.orig_cloud.delete_user(self.user.id)
122             if self.project:
123                 self.orig_cloud.delete_project(self.project.id)
124             secgroups = self.orig_cloud.list_security_groups(
125                 filters={'name': 'default',
126                          'project_id': self.project.id})
127             if secgroups:
128                 sec_id = secgroups[0].id
129                 self.orig_cloud.delete_security_group(sec_id)
130         except Exception:  # pylint: disable=broad-except
131             self.__logger.exception("Cannot clean all resources")
132
133
134 class TenantNetwork1(testcase.TestCase):
135     # pylint: disable=too-many-instance-attributes
136     """Create a tenant network (scenario1)
137
138     It creates and configures all tenant network resources required by
139     advanced testcases (subnet, network and router).
140
141     It ensures that all testcases inheriting from TenantNetwork1 could work
142     without network specific configurations (or at least read the same config
143     data).
144     """
145
146     __logger = logging.getLogger(__name__)
147     cidr = '192.168.120.0/24'
148     shared_network = False
149     allow_no_fip = False
150
151     def __init__(self, **kwargs):
152         if "case_name" not in kwargs:
153             kwargs["case_name"] = 'tenantnetwork1'
154         super(TenantNetwork1, self).__init__(**kwargs)
155         self.dir_results = os.path.join(getattr(config.CONF, 'dir_results'))
156         self.res_dir = os.path.join(self.dir_results, self.case_name)
157         self.output_log_name = 'functest.log'
158         self.output_debug_log_name = 'functest.debug.log'
159         try:
160             cloud_config = os_client_config.get_config()
161             self.cloud = self.orig_cloud = shade.OpenStackCloud(
162                 cloud_config=cloud_config)
163         except Exception:  # pylint: disable=broad-except
164             self.cloud = self.orig_cloud = None
165             self.ext_net = None
166             self.__logger.exception("Cannot connect to Cloud")
167         try:
168             self.ext_net = self.get_external_network(self.cloud)
169         except Exception:  # pylint: disable=broad-except
170             self.ext_net = None
171             self.__logger.exception("Cannot get the external network")
172         self.guid = str(uuid.uuid4())
173         self.network = None
174         self.subnet = None
175         self.router = None
176
177     @staticmethod
178     def get_external_network(cloud):
179         """
180         Return the configured external network name or
181         the first retrieved external network name
182         """
183         assert cloud
184         if env.get("EXTERNAL_NETWORK"):
185             network = cloud.get_network(
186                 env.get("EXTERNAL_NETWORK"), {"router:external": True})
187             if network:
188                 return network
189         networks = cloud.list_networks({"router:external": True})
190         if networks:
191             return networks[0]
192         return None
193
194     @staticmethod
195     def get_default_role(cloud, member="Member"):
196         """Get the default role
197
198         It also tests the role in lowercase to avoid possible conflicts.
199         """
200         role = cloud.get_role(member)
201         if not role:
202             role = cloud.get_role(member.lower())
203         return role
204
205     @staticmethod
206     def get_public_auth_url(cloud):
207         """Get Keystone public endpoint"""
208         keystone_id = functest_utils.search_services(cloud, 'keystone')[0].id
209         endpoint = cloud.search_endpoints(
210             filters={'interface': 'public',
211                      'service_id': keystone_id})[0].url
212         return endpoint
213
214     def create_network_resources(self):
215         """Create all tenant network resources
216
217         It creates a router which gateway is the external network detected.
218         The new subnet is attached to that router.
219
220         Raises: expection on error
221         """
222         assert self.cloud
223         if not self.allow_no_fip:
224             assert self.ext_net
225         provider = {}
226         if hasattr(config.CONF, '{}_network_type'.format(self.case_name)):
227             provider["network_type"] = getattr(
228                 config.CONF, '{}_network_type'.format(self.case_name))
229         if hasattr(config.CONF, '{}_physical_network'.format(self.case_name)):
230             provider["physical_network"] = getattr(
231                 config.CONF, '{}_physical_network'.format(self.case_name))
232         if hasattr(config.CONF, '{}_segmentation_id'.format(self.case_name)):
233             provider["segmentation_id"] = getattr(
234                 config.CONF, '{}_segmentation_id'.format(self.case_name))
235         domain = self.orig_cloud.get_domain(
236             name_or_id=self.orig_cloud.auth.get(
237                 "project_domain_name", "Default"))
238         project = self.orig_cloud.get_project(
239             self.cloud.auth['project_name'],
240             domain_id=domain.id)
241         self.network = self.orig_cloud.create_network(
242             '{}-net_{}'.format(self.case_name, self.guid),
243             provider=provider, project_id=project.id,
244             shared=self.shared_network)
245         self.__logger.debug("network: %s", self.network)
246
247         self.subnet = self.cloud.create_subnet(
248             self.network.id,
249             subnet_name='{}-subnet_{}'.format(self.case_name, self.guid),
250             cidr=getattr(
251                 config.CONF, '{}_private_subnet_cidr'.format(self.case_name),
252                 self.cidr),
253             enable_dhcp=True,
254             dns_nameservers=[env.get('NAMESERVER')])
255         self.__logger.debug("subnet: %s", self.subnet)
256
257         self.router = self.cloud.create_router(
258             name='{}-router_{}'.format(self.case_name, self.guid),
259             ext_gateway_net_id=self.ext_net.id if self.ext_net else None)
260         self.__logger.debug("router: %s", self.router)
261         self.cloud.add_router_interface(self.router, subnet_id=self.subnet.id)
262
263     def run(self, **kwargs):
264         status = testcase.TestCase.EX_RUN_ERROR
265         try:
266             assert self.cloud
267             self.start_time = time.time()
268             if env.get('NO_TENANT_NETWORK').lower() != 'true':
269                 self.create_network_resources()
270             self.result = 100
271             status = testcase.TestCase.EX_OK
272         except Exception:  # pylint: disable=broad-except
273             self.__logger.exception('Cannot run %s', self.case_name)
274         finally:
275             self.stop_time = time.time()
276         return status
277
278     def clean(self):
279         try:
280             assert self.cloud
281             if self.router:
282                 if self.subnet:
283                     self.cloud.remove_router_interface(
284                         self.router, self.subnet.id)
285                 self.cloud.delete_router(self.router.id)
286             if self.subnet:
287                 self.cloud.delete_subnet(self.subnet.id)
288             if self.network:
289                 self.cloud.delete_network(self.network.id)
290         except Exception:  # pylint: disable=broad-except
291             self.__logger.exception("cannot clean all resources")
292
293
294 class TenantNetwork2(TenantNetwork1):
295     """Create a tenant network (scenario2)
296
297     It creates new user/project before creating and configuring all tenant
298     network resources required by a testcase (subnet, network and router).
299
300     It ensures that all testcases inheriting from TenantNetwork2 could work
301     without network specific configurations (or at least read the same config
302     data).
303     """
304
305     __logger = logging.getLogger(__name__)
306
307     def __init__(self, **kwargs):
308         if "case_name" not in kwargs:
309             kwargs["case_name"] = 'tenantnetwork2'
310         super(TenantNetwork2, self).__init__(**kwargs)
311         try:
312             assert self.cloud
313             self.project = NewProject(
314                 self.cloud, self.case_name, self.guid)
315             self.project.create()
316             self.cloud = self.project.cloud
317         except Exception:  # pylint: disable=broad-except
318             self.__logger.exception("Cannot create user or project")
319             self.cloud = None
320             self.project = None
321
322     def clean(self):
323         try:
324             super(TenantNetwork2, self).clean()
325             assert self.project
326             self.project.clean()
327         except Exception:  # pylint: disable=broad-except
328             self.__logger.exception("Cannot clean all resources")