7b2bf8799b4e2f1fc246806295d057c4781d234b
[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
35
36 class NewProject(object):
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 = ''.join(random.choice(
59             string.ascii_letters + string.digits) for _ in range(30))
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
150     def __init__(self, **kwargs):
151         if "case_name" not in kwargs:
152             kwargs["case_name"] = 'tenantnetwork1'
153         super(TenantNetwork1, self).__init__(**kwargs)
154         self.res_dir = os.path.join(
155             getattr(config.CONF, 'dir_results'), self.case_name)
156         try:
157             cloud_config = os_client_config.get_config()
158             self.cloud = self.orig_cloud = shade.OpenStackCloud(
159                 cloud_config=cloud_config)
160         except Exception:  # pylint: disable=broad-except
161             self.cloud = self.orig_cloud = None
162             self.ext_net = None
163             self.__logger.exception("Cannot connect to Cloud")
164         try:
165             self.ext_net = self.get_external_network(self.cloud)
166         except Exception:  # pylint: disable=broad-except
167             self.__logger.exception("Cannot get the external network")
168         self.guid = str(uuid.uuid4())
169         self.network = None
170         self.subnet = None
171         self.router = None
172
173     @staticmethod
174     def get_external_network(cloud):
175         """
176         Return the configured external network name or
177         the first retrieved external network name
178         """
179         assert cloud
180         if env.get("EXTERNAL_NETWORK"):
181             network = cloud.get_network(
182                 env.get("EXTERNAL_NETWORK"), {"router:external": True})
183             if network:
184                 return network
185         networks = cloud.list_networks({"router:external": True})
186         if networks:
187             return networks[0]
188         return None
189
190     @staticmethod
191     def get_default_role(cloud, member="Member"):
192         """Get the default role
193
194         It also tests the role in lowercase to avoid possible conflicts.
195         """
196         role = cloud.get_role(member)
197         if not role:
198             role = cloud.get_role(member.lower())
199         return role
200
201     @staticmethod
202     def get_public_auth_url(cloud):
203         """Get Keystone public endpoint"""
204         keystone_id = cloud.search_services('keystone')[0].id
205         endpoint = cloud.search_endpoints(
206             filters={'interface': 'public',
207                      'service_id': keystone_id})[0].url
208         return endpoint
209
210     def create_network_resources(self):
211         """Create all tenant network resources
212
213         It creates a router which gateway is the external network detected.
214         The new subnet is attached to that router.
215
216         Raises: expection on error
217         """
218         assert self.cloud
219         assert self.ext_net
220         provider = {}
221         if hasattr(config.CONF, '{}_network_type'.format(self.case_name)):
222             provider["network_type"] = getattr(
223                 config.CONF, '{}_network_type'.format(self.case_name))
224         if hasattr(config.CONF, '{}_physical_network'.format(self.case_name)):
225             provider["physical_network"] = getattr(
226                 config.CONF, '{}_physical_network'.format(self.case_name))
227         if hasattr(config.CONF, '{}_segmentation_id'.format(self.case_name)):
228             provider["segmentation_id"] = getattr(
229                 config.CONF, '{}_segmentation_id'.format(self.case_name))
230         domain = self.orig_cloud.get_domain(
231             name_or_id=self.orig_cloud.auth.get(
232                 "project_domain_name", "Default"))
233         project = self.orig_cloud.get_project(
234             self.cloud.auth['project_name'],
235             domain_id=domain.id)
236         self.network = self.orig_cloud.create_network(
237             '{}-net_{}'.format(self.case_name, self.guid),
238             provider=provider, project_id=project.id,
239             shared=self.shared_network)
240         self.__logger.debug("network: %s", self.network)
241
242         self.subnet = self.cloud.create_subnet(
243             self.network.id,
244             subnet_name='{}-subnet_{}'.format(self.case_name, self.guid),
245             cidr=getattr(
246                 config.CONF, '{}_private_subnet_cidr'.format(self.case_name),
247                 self.cidr),
248             enable_dhcp=True,
249             dns_nameservers=[env.get('NAMESERVER')])
250         self.__logger.debug("subnet: %s", self.subnet)
251
252         self.router = self.cloud.create_router(
253             name='{}-router_{}'.format(self.case_name, self.guid),
254             ext_gateway_net_id=self.ext_net.id)
255         self.__logger.debug("router: %s", self.router)
256         self.cloud.add_router_interface(self.router, subnet_id=self.subnet.id)
257
258     def run(self, **kwargs):
259         status = testcase.TestCase.EX_RUN_ERROR
260         try:
261             assert self.cloud
262             self.start_time = time.time()
263             self.create_network_resources()
264             self.result = 100
265             status = testcase.TestCase.EX_OK
266         except Exception:  # pylint: disable=broad-except
267             self.__logger.exception('Cannot run %s', self.case_name)
268         finally:
269             self.stop_time = time.time()
270         return status
271
272     def clean(self):
273         try:
274             assert self.cloud
275             if self.router:
276                 if self.subnet:
277                     self.cloud.remove_router_interface(
278                         self.router, self.subnet.id)
279                 self.cloud.delete_router(self.router.id)
280             if self.subnet:
281                 self.cloud.delete_subnet(self.subnet.id)
282             if self.network:
283                 self.cloud.delete_network(self.network.id)
284         except Exception:  # pylint: disable=broad-except
285             self.__logger.exception("cannot clean all resources")
286
287
288 class TenantNetwork2(TenantNetwork1):
289     """Create a tenant network (scenario2)
290
291     It creates new user/project before creating and configuring all tenant
292     network resources required by a testcase (subnet, network and router).
293
294     It ensures that all testcases inheriting from TenantNetwork2 could work
295     without network specific configurations (or at least read the same config
296     data).
297     """
298
299     __logger = logging.getLogger(__name__)
300
301     def __init__(self, **kwargs):
302         if "case_name" not in kwargs:
303             kwargs["case_name"] = 'tenantnetwork2'
304         super(TenantNetwork2, self).__init__(**kwargs)
305         try:
306             assert self.cloud
307             self.project = NewProject(
308                 self.cloud, self.case_name, self.guid)
309             self.project.create()
310             self.cloud = self.project.cloud
311         except Exception:  # pylint: disable=broad-except
312             self.__logger.exception("Cannot create user or project")
313             self.cloud = None
314             self.project = None
315
316     def clean(self):
317         try:
318             super(TenantNetwork2, self).clean()
319             assert self.project
320             self.project.clean()
321         except Exception:  # pylint: disable=broad-except
322             self.__logger.exception("Cannot clean all resources")