3b9c7eba975a6d0a9d118a64c94178e4e7b7590a
[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 random
24 import string
25 import time
26 import uuid
27
28 import os_client_config
29 import shade
30 from xtesting.core import testcase
31
32 from functest.utils import config
33 from functest.utils import env
34 from functest.utils import functest_utils
35
36
37 class NewProject():
38     """Ease creating new projects/users"""
39     # pylint: disable=too-many-instance-attributes
40
41     __logger = logging.getLogger(__name__)
42
43     def __init__(self, cloud, case_name, guid):
44         self.cloud = None
45         self.orig_cloud = cloud
46         self.case_name = case_name
47         self.guid = guid
48         self.project = None
49         self.user = None
50         self.password = None
51         self.domain = None
52         self.role_name = None
53         self.default_member = env.get('NEW_USER_ROLE')
54
55     def create(self):
56         """Create projects/users"""
57         assert self.orig_cloud
58         assert self.case_name
59         self.password = ''.join(random.choice(
60             string.ascii_letters + string.digits +
61             '!()*+,-.<=>?@[]^_{|}~') for _ in range(30))
62         self.__logger.debug("password: %s", self.password)
63         self.domain = self.orig_cloud.get_domain(
64             name_or_id=self.orig_cloud.auth.get(
65                 "project_domain_name", "Default"))
66         self.project = self.orig_cloud.create_project(
67             name='{}-project_{}'.format(self.case_name[:18], self.guid),
68             description="Created by OPNFV Functest: {}".format(
69                 self.case_name),
70             domain_id=self.domain.id)
71         self.__logger.debug("project: %s", self.project)
72         self.user = self.orig_cloud.create_user(
73             name='{}-user_{}'.format(self.case_name, self.guid),
74             password=self.password,
75             domain_id=self.domain.id)
76         self.__logger.debug("user: %s", self.user)
77         try:
78             if self.orig_cloud.get_role(self.default_member):
79                 self.role_name = self.default_member
80             elif self.orig_cloud.get_role(self.default_member.lower()):
81                 self.role_name = self.default_member.lower()
82             else:
83                 raise Exception("Cannot detect {}".format(self.default_member))
84         except Exception:  # pylint: disable=broad-except
85             self.__logger.info("Creating default role %s", self.default_member)
86             role = self.orig_cloud.create_role(self.default_member)
87             self.role_name = role.name
88             self.__logger.debug("role: %s", role)
89         self.orig_cloud.grant_role(
90             self.role_name, user=self.user.id, project=self.project.id,
91             domain=self.domain.id)
92         osconfig = os_client_config.config.OpenStackConfig()
93         osconfig.cloud_config[
94             'clouds']['envvars']['project_name'] = self.project.name
95         osconfig.cloud_config[
96             'clouds']['envvars']['project_id'] = self.project.id
97         osconfig.cloud_config['clouds']['envvars']['username'] = self.user.name
98         osconfig.cloud_config['clouds']['envvars']['password'] = self.password
99         self.__logger.debug("cloud_config %s", osconfig.cloud_config)
100         self.cloud = shade.OpenStackCloud(
101             cloud_config=osconfig.get_one_cloud())
102         self.__logger.debug("new cloud %s", self.cloud.auth)
103
104     def get_environ(self):
105         "Get new environ"
106         environ = dict(
107             os.environ,
108             OS_USERNAME=self.user.name,
109             OS_PROJECT_NAME=self.project.name,
110             OS_PROJECT_ID=self.project.id,
111             OS_PASSWORD=self.password)
112         try:
113             del environ['OS_TENANT_NAME']
114             del environ['OS_TENANT_ID']
115         except Exception:  # pylint: disable=broad-except
116             pass
117         return environ
118
119     def clean(self):
120         """Remove projects/users"""
121         try:
122             assert self.orig_cloud
123             if self.user:
124                 self.orig_cloud.delete_user(self.user.id)
125             if self.project:
126                 self.orig_cloud.delete_project(self.project.id)
127             secgroups = self.orig_cloud.list_security_groups(
128                 filters={'name': 'default',
129                          'project_id': self.project.id})
130             if secgroups:
131                 sec_id = secgroups[0].id
132                 self.orig_cloud.delete_security_group(sec_id)
133         except Exception:  # pylint: disable=broad-except
134             self.__logger.exception("Cannot clean all resources")
135
136
137 class TenantNetwork1(testcase.TestCase):
138     # pylint: disable=too-many-instance-attributes
139     """Create a tenant network (scenario1)
140
141     It creates and configures all tenant network resources required by
142     advanced testcases (subnet, network and router).
143
144     It ensures that all testcases inheriting from TenantNetwork1 could work
145     without network specific configurations (or at least read the same config
146     data).
147     """
148
149     __logger = logging.getLogger(__name__)
150     cidr = '192.168.120.0/24'
151     shared_network = False
152     allow_no_fip = False
153
154     def __init__(self, **kwargs):
155         if "case_name" not in kwargs:
156             kwargs["case_name"] = 'tenantnetwork1'
157         super(TenantNetwork1, self).__init__(**kwargs)
158         self.res_dir = os.path.join(
159             getattr(config.CONF, 'dir_results'), self.case_name)
160         try:
161             cloud_config = os_client_config.get_config()
162             self.cloud = self.orig_cloud = shade.OpenStackCloud(
163                 cloud_config=cloud_config)
164         except Exception:  # pylint: disable=broad-except
165             self.cloud = self.orig_cloud = None
166             self.ext_net = None
167             self.__logger.exception("Cannot connect to Cloud")
168         try:
169             self.ext_net = self.get_external_network(self.cloud)
170         except Exception:  # pylint: disable=broad-except
171             self.ext_net = None
172             self.__logger.exception("Cannot get the external network")
173         self.guid = str(uuid.uuid4())
174         self.network = None
175         self.subnet = None
176         self.router = None
177
178     @staticmethod
179     def get_external_network(cloud):
180         """
181         Return the configured external network name or
182         the first retrieved external network name
183         """
184         assert cloud
185         if env.get("EXTERNAL_NETWORK"):
186             network = cloud.get_network(
187                 env.get("EXTERNAL_NETWORK"), {"router:external": True})
188             if network:
189                 return network
190         networks = cloud.list_networks({"router:external": True})
191         if networks:
192             return networks[0]
193         return None
194
195     @staticmethod
196     def get_default_role(cloud, member="Member"):
197         """Get the default role
198
199         It also tests the role in lowercase to avoid possible conflicts.
200         """
201         role = cloud.get_role(member)
202         if not role:
203             role = cloud.get_role(member.lower())
204         return role
205
206     @staticmethod
207     def get_public_auth_url(cloud):
208         """Get Keystone public endpoint"""
209         keystone_id = functest_utils.search_services(cloud, 'keystone')[0].id
210         endpoint = cloud.search_endpoints(
211             filters={'interface': 'public',
212                      'service_id': keystone_id})[0].url
213         return endpoint
214
215     def create_network_resources(self):
216         """Create all tenant network resources
217
218         It creates a router which gateway is the external network detected.
219         The new subnet is attached to that router.
220
221         Raises: expection on error
222         """
223         assert self.cloud
224         if not self.allow_no_fip:
225             assert self.ext_net
226         provider = {}
227         if hasattr(config.CONF, '{}_network_type'.format(self.case_name)):
228             provider["network_type"] = getattr(
229                 config.CONF, '{}_network_type'.format(self.case_name))
230         if hasattr(config.CONF, '{}_physical_network'.format(self.case_name)):
231             provider["physical_network"] = getattr(
232                 config.CONF, '{}_physical_network'.format(self.case_name))
233         if hasattr(config.CONF, '{}_segmentation_id'.format(self.case_name)):
234             provider["segmentation_id"] = getattr(
235                 config.CONF, '{}_segmentation_id'.format(self.case_name))
236         domain = self.orig_cloud.get_domain(
237             name_or_id=self.orig_cloud.auth.get(
238                 "project_domain_name", "Default"))
239         project = self.orig_cloud.get_project(
240             self.cloud.auth['project_name'],
241             domain_id=domain.id)
242         self.network = self.orig_cloud.create_network(
243             '{}-net_{}'.format(self.case_name, self.guid),
244             provider=provider, project_id=project.id,
245             shared=self.shared_network)
246         self.__logger.debug("network: %s", self.network)
247
248         self.subnet = self.cloud.create_subnet(
249             self.network.id,
250             subnet_name='{}-subnet_{}'.format(self.case_name, self.guid),
251             cidr=getattr(
252                 config.CONF, '{}_private_subnet_cidr'.format(self.case_name),
253                 self.cidr),
254             enable_dhcp=True,
255             dns_nameservers=[env.get('NAMESERVER')])
256         self.__logger.debug("subnet: %s", self.subnet)
257
258         self.router = self.cloud.create_router(
259             name='{}-router_{}'.format(self.case_name, self.guid),
260             ext_gateway_net_id=self.ext_net.id if self.ext_net else None)
261         self.__logger.debug("router: %s", self.router)
262         self.cloud.add_router_interface(self.router, subnet_id=self.subnet.id)
263
264     def run(self, **kwargs):
265         status = testcase.TestCase.EX_RUN_ERROR
266         try:
267             assert self.cloud
268             self.start_time = time.time()
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")