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