blob: 03462dc1bb1b8c65053a4d3be417a0365fedf77b [file] [log] [blame]
Ievgeniia Zadorozhna97dfde42022-06-17 20:05:09 +03001import logging
2import os
3import random
4import sys
5import time
6
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +02007from cinderclient import client as cinder_client
8from glanceclient import client as glance_client
9from keystoneauth1 import identity as keystone_identity
10from keystoneauth1 import session as keystone_session
11from keystoneclient.v3 import client as keystone_client
12from neutronclient.v2_0 import client as neutron_client
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +030013from neutronclient import common as neutron_common
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +020014from novaclient import client as novaclient
15
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +020016import utils
17
18logger = logging.getLogger(__name__)
19
20
21class OfficialClientManager(object):
22 """Manager that provides access to the official python clients for
23 calling various OpenStack APIs.
24 """
25
26 CINDERCLIENT_VERSION = 3
27 GLANCECLIENT_VERSION = 2
28 KEYSTONECLIENT_VERSION = 3
29 NEUTRONCLIENT_VERSION = 2
30 NOVACLIENT_VERSION = 2
31 INTERFACE = 'admin'
32 if "OS_ENDPOINT_TYPE" in list(os.environ.keys()):
33 INTERFACE = os.environ["OS_ENDPOINT_TYPE"]
34
35 def __init__(self, username=None, password=None,
36 tenant_name=None, auth_url=None, endpoint_type="internalURL",
37 cert=False, domain="Default", **kwargs):
38 self.traceback = ""
39
40 self.client_attr_names = [
41 "auth",
42 "compute",
43 "network",
44 "volume",
45 "image",
46 ]
47 self.username = username
48 self.password = password
49 self.tenant_name = tenant_name
50 self.project_name = tenant_name
51 self.auth_url = auth_url
52 self.endpoint_type = endpoint_type
53 self.cert = cert
54 self.domain = domain
55 self.kwargs = kwargs
56
57 # Lazy clients
58 self._auth = None
59 self._compute = None
60 self._network = None
61 self._volume = None
62 self._image = None
63
64 @classmethod
65 def _get_auth_session(cls, username=None, password=None,
66 tenant_name=None, auth_url=None, cert=None,
67 domain='Default'):
68 if None in (username, password, tenant_name):
Ievgeniia Zadorozhna97dfde42022-06-17 20:05:09 +030069 sys.stdout.write((username, password, tenant_name))
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +020070 msg = ("Missing required credentials for identity client. "
71 "username: {username}, password: {password}, "
72 "tenant_name: {tenant_name}").format(
73 username=username,
74 password=password,
75 tenant_name=tenant_name
76 )
77 raise msg
78
79 if cert and "https" not in auth_url:
80 auth_url = auth_url.replace("http", "https")
81
82 if "v2" in auth_url:
83 raise BaseException("Keystone v2 is deprecated since OpenStack"
84 "Queens release. So current OS_AUTH_URL {} "
85 "is not valid. Please use Keystone v3."
86 "".format(auth_url))
87 else:
88 auth_url = auth_url if ("v3" in auth_url) else "{}{}".format(
89 auth_url, "/v3")
90 auth = keystone_identity.v3.Password(
91 auth_url=auth_url,
92 user_domain_name=domain,
93 username=username,
94 password=password,
95 project_domain_name=domain,
96 project_name=tenant_name)
97
98 auth_session = keystone_session.Session(auth=auth, verify=cert)
99 # auth_session.get_auth_headers()
100 return auth_session
101
102 @classmethod
103 def get_auth_client(cls, username=None, password=None,
104 tenant_name=None, auth_url=None, cert=None,
105 domain='Default', **kwargs):
106 session = cls._get_auth_session(
107 username=username,
108 password=password,
109 tenant_name=tenant_name,
110 auth_url=auth_url,
111 cert=cert,
112 domain=domain)
113 keystone = keystone_client.Client(version=cls.KEYSTONECLIENT_VERSION,
114 session=session, **kwargs)
115 keystone.management_url = auth_url
116 return keystone
117
118 @classmethod
119 def get_compute_client(cls, username=None, password=None,
120 tenant_name=None, auth_url=None, cert=None,
121 domain='Default', **kwargs):
122 session = cls._get_auth_session(
123 username=username, password=password, tenant_name=tenant_name,
124 auth_url=auth_url, cert=cert, domain=domain)
125 service_type = 'compute'
126 compute_client = novaclient.Client(
127 version=cls.NOVACLIENT_VERSION, session=session,
128 service_type=service_type, os_cache=False, **kwargs)
129 return compute_client
130
131 @classmethod
132 def get_network_client(cls, username=None, password=None,
133 tenant_name=None, auth_url=None, cert=None,
134 domain='Default', **kwargs):
135 session = cls._get_auth_session(
136 username=username, password=password, tenant_name=tenant_name,
137 auth_url=auth_url, cert=cert, domain=domain)
138 service_type = 'network'
139 return neutron_client.Client(
140 service_type=service_type, session=session,
141 interface=cls.INTERFACE, **kwargs)
142
143 @classmethod
144 def get_volume_client(cls, username=None, password=None,
145 tenant_name=None, auth_url=None, cert=None,
146 domain='Default', **kwargs):
147 session = cls._get_auth_session(
148 username=username, password=password, tenant_name=tenant_name,
149 auth_url=auth_url, cert=cert, domain=domain)
150 service_type = 'volume'
151 return cinder_client.Client(
152 version=cls.CINDERCLIENT_VERSION,
153 service_type=service_type,
154 interface=cls.INTERFACE,
155 session=session, **kwargs)
156
157 @classmethod
158 def get_image_client(cls, username=None, password=None,
159 tenant_name=None, auth_url=None, cert=None,
160 domain='Default', **kwargs):
161 session = cls._get_auth_session(
162 username=username, password=password, tenant_name=tenant_name,
163 auth_url=auth_url, cert=cert, domain=domain)
164 service_type = 'image'
165 return glance_client.Client(
166 version=cls.GLANCECLIENT_VERSION,
167 service_type=service_type,
168 session=session, interface=cls.INTERFACE,
169 **kwargs)
170
171 @property
172 def auth(self):
173 if self._auth is None:
174 self._auth = self.get_auth_client(
175 self.username, self.password, self.tenant_name, self.auth_url,
176 self.cert, self.domain, endpoint_type=self.endpoint_type
177 )
178 return self._auth
179
180 @property
181 def compute(self):
182 if self._compute is None:
183 self._compute = self.get_compute_client(
184 self.username, self.password, self.tenant_name, self.auth_url,
185 self.cert, self.domain, endpoint_type=self.endpoint_type
186 )
187 return self._compute
188
189 @property
190 def network(self):
191 if self._network is None:
192 self._network = self.get_network_client(
193 self.username, self.password, self.tenant_name, self.auth_url,
194 self.cert, self.domain, endpoint_type=self.endpoint_type
195 )
196 return self._network
197
198 @property
199 def volume(self):
200 if self._volume is None:
201 self._volume = self.get_volume_client(
202 self.username, self.password, self.tenant_name, self.auth_url,
203 self.cert, self.domain, endpoint_type=self.endpoint_type
204 )
205 return self._volume
206
207 @property
208 def image(self):
209
210 if self._image is None:
211 self._image = self.get_image_client(
212 self.username, self.password, self.tenant_name, self.auth_url,
213 self.cert, self.domain
214 )
215 return self._image
216
217
218class OSCliActions(object):
219 def __init__(self, os_clients):
220 self.os_clients = os_clients
221 self.create_fake_ext_net = False
222
223 def get_admin_tenant(self):
224 # TODO Keystone v3 doesnt have tenants attribute
225 return self.os_clients.auth.projects.find(name="admin")
226
227 def get_internal_network(self):
228 networks = [
229 net for net in self.os_clients.network.list_networks()["networks"]
230 if net["admin_state_up"] and not net["router:external"] and
231 len(net["subnets"])
232 ]
233 if networks:
234 net = networks[0]
235 else:
236 net = self.create_network_resources()
237 return net
238
239 def create_fake_external_network(self):
240 logger.info(
241 "Could not find any external network, creating a fake one...")
242 net_name = "spt-ext-net-{}".format(random.randrange(100, 999))
243 net_body = {"network": {"name": net_name,
244 "router:external": True,
245 "provider:network_type": "local"}}
246 try:
247 ext_net = \
248 self.os_clients.network.create_network(net_body)['network']
249 logger.info("Created a fake external net {}".format(net_name))
250 except Exception as e:
251 # in case 'local' net type is absent, create with default type
252 net_body["network"].pop('provider:network_type', None)
253 ext_net = \
254 self.os_clients.network.create_network(net_body)['network']
255 subnet_name = "spt-ext-subnet-{}".format(random.randrange(100, 999))
256 subnet_body = {
257 "subnet": {
258 "name": subnet_name,
259 "network_id": ext_net["id"],
260 "ip_version": 4,
261 "cidr": "10.255.255.0/24",
262 "allocation_pools": [{"start": "10.255.255.100",
263 "end": "10.255.255.200"}]
264 }
265 }
266 self.os_clients.network.create_subnet(subnet_body)
267 self.create_fake_ext_net = True
268 return ext_net
269
270 def get_external_network(self):
271 config = utils.get_configuration()
272 ext_net = config.get('external_network') or ''
273 if not ext_net:
274 networks = [
275 net for net in
276 self.os_clients.network.list_networks()["networks"]
277 if net["admin_state_up"] and net["router:external"] and
278 len(net["subnets"])
279 ]
280 else:
281 networks = [net for net in
282 self.os_clients.network.list_networks()["networks"]
283 if net["name"] == ext_net]
284
285 if networks:
286 ext_net = networks[0]
287 logger.info("Using external net '{}'.".format(ext_net["name"]))
288 else:
289 ext_net = self.create_fake_external_network()
290 return ext_net
291
292 def create_flavor(self, name, ram=256, vcpus=1, disk=2):
293 logger.info("Creating a flavor {}".format(name))
294 return self.os_clients.compute.flavors.create(name, ram, vcpus, disk)
295
296 def create_sec_group(self, rulesets=None):
297 if rulesets is None:
298 rulesets = [
299 {
300 # ssh
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300301 'protocol': 'tcp',
302 'port_range_max': 22,
303 'port_range_min': 22,
304 'remote_ip_prefix': '0.0.0.0/0',
305 'direction': 'ingress'
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200306 },
307 {
308 # iperf3
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300309 'protocol': 'tcp',
310 'port_range_max': 5201,
311 'port_range_min': 5201,
312 'remote_ip_prefix': '0.0.0.0/0',
313 'direction': 'ingress'
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200314 },
315 {
Ievgeniia Zadorozhna5ed74e22022-07-26 16:56:23 +0300316 # iperf
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300317 'protocol': 'tcp',
318 'port_range_max': 5001,
319 'port_range_min': 5001,
320 'remote_ip_prefix': '0.0.0.0/0',
321 'direction': 'ingress'
Ievgeniia Zadorozhna5ed74e22022-07-26 16:56:23 +0300322 },
323 {
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200324 # ping
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300325 'protocol': 'icmp',
326 'remote_ip_prefix': '0.0.0.0/0',
327 'direction': 'ingress'
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200328 }
329 ]
330 sg_name = "spt-test-secgroup-{}".format(random.randrange(100, 999))
331 sg_desc = sg_name + " SPT"
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300332 body = {"security_group": {"name": sg_name, "description": sg_desc}}
333 secgroup = self.os_clients.network.create_security_group(body=body)
334
335 rule_body_teplate = {"security_group_rule": {}}
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200336 for ruleset in rulesets:
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300337 rule_body_teplate["security_group_rule"] = ruleset
338 rule_body_teplate["security_group_rule"]["security_group_id"] = \
339 secgroup['security_group']['id']
340 self.os_clients.network.create_security_group_rule(
341 body=rule_body_teplate)
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200342 logger.info("Created a security group {}".format(sg_name))
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300343 return secgroup['security_group']
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200344
345 def create_basic_server(self, image=None, flavor=None, net=None,
346 availability_zone=None, sec_groups=(),
347 keypair=None):
348 os_conn = self.os_clients
349 net = net or self.get_internal_network()
350 kwargs = {}
351 if sec_groups:
352 kwargs['security_groups'] = sec_groups
353 server = os_conn.compute.servers.create(
354 "spt-test-server-{}".format(random.randrange(100, 999)),
355 image, flavor, nics=[{"net-id": net["id"]}],
356 availability_zone=availability_zone, key_name=keypair, **kwargs)
357
358 return server
359
360 def get_vm(self, vm_id):
361 os_conn = self.os_clients
362 try:
363 vm = os_conn.compute.servers.find(id=vm_id)
364 except Exception as e:
365 raise Exception(
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300366 "Could not get the VM \"{}\": {}".format(
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200367 vm_id, e))
368 return vm
369
370 def check_vm_is_active(self, vm_uuid, retry_delay=5, timeout=500):
371 vm = None
372 timeout_reached = False
373 start_time = time.time()
374 expected_state = 'ACTIVE'
375 while not timeout_reached:
376 vm = self.get_vm(vm_uuid)
377 if vm.status == expected_state:
378 logger.info(
379 "VM {} is in {} status.".format(vm_uuid, vm.status))
380 break
381 if vm.status == 'ERROR':
382 break
383 time.sleep(retry_delay)
384 timeout_reached = (time.time() - start_time) > timeout
385 if vm.status != expected_state:
386 logger.info("VM {} is in {} status.".format(vm_uuid, vm.status))
387 raise TimeoutError(
388 "VM {vm_uuid} on is expected to be in '{expected_state}' "
389 "state, but is in '{actual}' state instead.".format(
390 vm_uuid=vm_uuid, expected_state=expected_state,
391 actual=vm.status))
392
393 def create_network(self, tenant_id):
394 net_name = "spt-test-net-{}".format(random.randrange(100, 999))
Ievgeniia Zadorozhnafa13ba62022-06-18 02:03:09 +0300395 config = utils.get_configuration()
396 mtu = config.get('custom_mtu') or 'default'
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200397 net_body = {
398 'network': {
399 'name': net_name,
400 'tenant_id': tenant_id
401 }
402 }
Ievgeniia Zadorozhnafa13ba62022-06-18 02:03:09 +0300403 if mtu != 'default':
404 try:
405 net_body['network']['mtu'] = int(mtu)
406 except ValueError as e:
407 raise ValueError("MTU value '{}' is not correct. "
408 "Must be an integer at 'custom_mtu' in "
409 "global_config.yaml.\n{}".format(mtu, e))
Ievgeniia Zadorozhna84023022021-12-30 13:00:41 +0200410 net = self.os_clients.network.create_network(net_body)['network']
411 logger.info("Created internal network {}".format(net_name))
412 return net
413
414 def create_subnet(self, net, tenant_id, cidr=None):
415 subnet_name = "spt-test-subnet-{}".format(random.randrange(100, 999))
416 subnet_body = {
417 'subnet': {
418 "name": subnet_name,
419 'network_id': net['id'],
420 'ip_version': 4,
421 'cidr': cidr if cidr else '10.1.7.0/24',
422 'tenant_id': tenant_id
423 }
424 }
425 subnet = self.os_clients.network.create_subnet(subnet_body)['subnet']
426 logger.info("Created subnet {}".format(subnet_name))
427 return subnet
428
429 def create_router(self, ext_net, tenant_id):
430 name = 'spt-test-router-{}'.format(random.randrange(100, 999))
431 router_body = {
432 'router': {
433 'name': name,
434 'external_gateway_info': {
435 'network_id': ext_net['id']
436 },
437 'tenant_id': tenant_id
438 }
439 }
440 logger.info("Created a router {}".format(name))
441 router = self.os_clients.network.create_router(router_body)['router']
442 return router
443
444 def create_network_resources(self):
445 tenant_id = self.get_admin_tenant().id
446 self.get_external_network()
447 net = self.create_network(tenant_id)
448 self.create_subnet(net, tenant_id)
449 return net
450
451 def list_nova_computes(self):
452 nova_services = self.os_clients.compute.hosts.list()
453 computes_list = [h for h in nova_services if h.service == "compute"]
454 return computes_list
Ievgeniia Zadorozhna2c6469d2022-08-10 17:21:10 +0300455
456 def create_floating_ip(self, floating_net_id):
457 fip = self.os_clients.network.create_floatingip({"floatingip": {
458 "floating_network_id": floating_net_id}})
459 return fip['floatingip']
460
461 def delete_floating_ip(self, floatingip_id):
462 try:
463 return self.os_clients.network.delete_floatingip(floatingip_id)
464 except neutron_common.exceptions.NotFound as e:
465 msg = "Could not delete a Floating IP, UUID {}. Error: {}" \
466 "".format(floatingip_id, e)
467 logger.info(msg)