ecfdb0682c2cb071a574714c84084c838788fa01
[osm/RO.git] / RO-VIM-gcp / osm_rovim_gcp / vimconn_gcp.py
1 # -*- coding: utf-8 -*-
2 ##
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
13 # under the License.
14 ##
15
16 import base64
17 from osm_ro_plugin import vimconn
18 import logging
19 import time
20 import random
21 from random import choice as random_choice
22 from os import getenv
23
24 from google.api_core.exceptions import NotFound
25 import googleapiclient.discovery
26 from google.oauth2 import service_account
27
28 from cryptography.hazmat.primitives import serialization as crypto_serialization
29 from cryptography.hazmat.primitives.asymmetric import rsa
30 from cryptography.hazmat.backends import default_backend as crypto_default_backend
31
32 import logging
33
34 __author__ = "Sergio Gallardo Ruiz"
35 __date__ = "$11-aug-2021 08:30:00$"
36
37
38 if getenv("OSMRO_PDB_DEBUG"):
39 import sys
40
41 print(sys.path)
42 import pdb
43
44 pdb.set_trace()
45
46
47 class vimconnector(vimconn.VimConnector):
48
49 # Translate Google Cloud provisioning state to OSM provision state
50 # The first three ones are the transitional status once a user initiated action has been requested
51 # Once the operation is complete, it will transition into the states Succeeded or Failed
52 # https://cloud.google.com/compute/docs/instances/instance-life-cycle
53 provision_state2osm = {
54 "PROVISIONING": "BUILD",
55 "REPAIRING": "ERROR",
56 }
57
58 # Translate azure power state to OSM provision state
59 power_state2osm = {
60 "STAGING": "BUILD",
61 "RUNNING": "ACTIVE",
62 "STOPPING": "INACTIVE",
63 "SUSPENDING": "INACTIVE",
64 "SUSPENDED": "INACTIVE",
65 "TERMINATED": "INACTIVE",
66 }
67
68 # If a net or subnet is tried to be deleted and it has an associated resource, the net is marked "to be deleted"
69 # (incluid it's name in the following list). When the instance is deleted, its associated net will be deleted if
70 # they are present in that list
71 nets_to_be_deleted = []
72
73 def __init__(
74 self,
75 uuid,
76 name,
77 tenant_id,
78 tenant_name,
79 url,
80 url_admin=None,
81 user=None,
82 passwd=None,
83 log_level=None,
84 config={},
85 persistent_info={},
86 ):
87 """
88 Constructor of VIM. Raise an exception is some needed parameter is missing, but it must not do any connectivity
89 checking against the VIM
90 Using common constructor parameters.
91 In this case: config must include the following parameters:
92 subscription_id: assigned subscription identifier
93 region_name: current region for network
94 config may also include the following parameter:
95 flavors_pattern: pattern that will be used to select a range of vm sizes, for example
96 "^((?!Standard_B).)*$" will filter out Standard_B range that is cheap but is very overused
97 "^Standard_B" will select a serie B maybe for test environment
98 """
99 vimconn.VimConnector.__init__(
100 self,
101 uuid,
102 name,
103 tenant_id,
104 tenant_name,
105 url,
106 url_admin,
107 user,
108 passwd,
109 log_level,
110 config,
111 persistent_info,
112 )
113
114 # Variable that indicates if client must be reloaded or initialized
115 self.reload_client = False
116
117 # LOGGER
118
119 log_format_simple = (
120 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
121 )
122 log_format_complete = "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(funcName)s(): %(message)s"
123 log_formatter_simple = logging.Formatter(
124 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
125 )
126 self.handler = logging.StreamHandler()
127 self.handler.setFormatter(log_formatter_simple)
128
129 self.logger = logging.getLogger("ro.vim.gcp")
130 self.logger.addHandler(self.handler)
131 if log_level:
132 self.logger.setLevel(getattr(logging, log_level))
133
134 if self.logger.getEffectiveLevel() == logging.DEBUG:
135 log_formatter = logging.Formatter(
136 log_format_complete, datefmt="%Y-%m-%dT%H:%M:%S"
137 )
138 self.handler.setFormatter(log_formatter)
139
140 self.logger.debug("Google Cloud connection init")
141
142 self.project = tenant_id or tenant_name
143
144 # REGION - Google Cloud considers regions and zones. A specific region can have more than one zone
145 # (for instance: region us-west1 with the zones us-west1-a, us-west1-b and us-west1-c)
146 # So the region name specified in the config will be considered as a specific zone for GC and
147 # the region will be calculated from that without the preffix.
148 if "region_name" in config:
149 self.zone = config.get("region_name")
150 self.region = self.zone.rsplit("-", 1)[0]
151 else:
152 raise vimconn.VimConnException(
153 "Google Cloud region_name is not specified at config"
154 )
155
156 # Credentials
157 self.logger.debug("Config: %s", config)
158 scopes = ["https://www.googleapis.com/auth/cloud-platform"]
159 self.credentials = None
160 if (
161 "credentials" in config
162 ):
163 self.logger.debug("Setting credentials")
164 # Settings Google Cloud credentials dict
165 credentials_body = config["credentials"]
166 # self.logger.debug("Credentials filtered: %s", credentials_body)
167 credentials = service_account.Credentials.from_service_account_info(
168 credentials_body
169 )
170 if "sa_file" in config:
171 credentials = service_account.Credentials.from_service_account_file(
172 config.get("sa_file"), scopes=scopes
173 )
174 self.logger.debug("Credentials: %s", credentials)
175 # Construct a Resource for interacting with an API.
176 self.credentials = credentials
177 try:
178 self.conn_compute = googleapiclient.discovery.build(
179 "compute", "v1", credentials=credentials
180 )
181 except Exception as e:
182 self._format_vimconn_exception(e)
183 else:
184 raise vimconn.VimConnException(
185 "It is not possible to init GCP with no credentials"
186 )
187
188 def _reload_connection(self):
189 """
190 Called before any operation, checks python Google Cloud clientsself.reload_client
191 """
192 if self.reload_client:
193 self.logger.debug("reloading google cloud client")
194
195 try:
196 # Set to client created
197 self.conn_compute = googleapiclient.discovery.build("compute", "v1")
198 except Exception as e:
199 self._format_vimconn_exception(e)
200
201 def _format_vimconn_exception(self, e):
202 """
203 Transforms a generic exception to a vimConnException
204 """
205 self.logger.error("Google Cloud plugin error: {}".format(e))
206 if isinstance(e, vimconn.VimConnException):
207 raise e
208 else:
209 # In case of generic error recreate client
210 self.reload_client = True
211 raise vimconn.VimConnException(type(e).__name__ + ": " + str(e))
212
213 def _wait_for_global_operation(self, operation):
214 """
215 Waits for the end of the specific operation
216 :operation: operation name
217 """
218
219 self.logger.debug("Waiting for operation %s", operation)
220
221 while True:
222 result = (
223 self.conn_compute.globalOperations()
224 .get(project=self.project, operation=operation)
225 .execute()
226 )
227
228 if result["status"] == "DONE":
229 if "error" in result:
230 raise vimconn.VimConnException(result["error"])
231 return result
232
233 time.sleep(1)
234
235 def _wait_for_zone_operation(self, operation):
236 """
237 Waits for the end of the specific operation
238 :operation: operation name
239 """
240
241 self.logger.debug("Waiting for operation %s", operation)
242
243 while True:
244 result = (
245 self.conn_compute.zoneOperations()
246 .get(project=self.project, operation=operation, zone=self.zone)
247 .execute()
248 )
249
250 if result["status"] == "DONE":
251 if "error" in result:
252 raise vimconn.VimConnException(result["error"])
253 return result
254
255 time.sleep(1)
256
257 def _wait_for_region_operation(self, operation):
258 """
259 Waits for the end of the specific operation
260 :operation: operation name
261 """
262
263 self.logger.debug("Waiting for operation %s", operation)
264
265 while True:
266 result = (
267 self.conn_compute.regionOperations()
268 .get(project=self.project, operation=operation, region=self.region)
269 .execute()
270 )
271
272 if result["status"] == "DONE":
273 if "error" in result:
274 raise vimconn.VimConnException(result["error"])
275 return result
276
277 time.sleep(1)
278
279 def new_network(
280 self,
281 net_name,
282 net_type,
283 ip_profile=None,
284 shared=False,
285 provider_network_profile=None,
286 ):
287 """
288 Adds a network to VIM
289 :param net_name: name of the network
290 :param net_type: not used for Google Cloud networks
291 :param ip_profile: not used for Google Cloud networks
292 :param shared: Not allowed for Google Cloud Connector
293 :param provider_network_profile: (optional)
294
295 contains {segmentation-id: vlan, provider-network: vim_netowrk}
296 :return: a tuple with the network identifier and created_items, or raises an exception on error
297 created_items can be None or a dictionary where this method can include key-values that will be passed to
298 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
299 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
300 as not present.
301 """
302
303 self.logger.debug(
304 "new_network begin: net_name %s net_type %s ip_profile %s shared %s provider_network_profile %s",
305 net_name,
306 net_type,
307 ip_profile,
308 shared,
309 provider_network_profile,
310 )
311 net_name = self._check_vm_name(net_name)
312 net_name = self._randomize_name(net_name)
313 self.logger.debug("create network name %s, ip_profile %s", net_name, ip_profile)
314
315 try:
316
317 self.logger.debug("creating network_name: {}".format(net_name))
318
319 network = "projects/{}/global/networks/default".format(self.project)
320 subnet_address = ""
321 if ip_profile is not None:
322 if "subnet_address" in ip_profile:
323 subnet_address = ip_profile["subnet_address"]
324 network_body = {
325 "name": str(net_name),
326 "description": net_name,
327 "network": network,
328 "ipCidrRange": subnet_address,
329 # "autoCreateSubnetworks": True, # The network is created in AUTO mode (one subnet per region is created)
330 "autoCreateSubnetworks": False,
331 }
332
333 operation = (
334 self.conn_compute.networks()
335 .insert(project=self.project, body=network_body)
336 .execute()
337 )
338 self._wait_for_global_operation(operation["name"])
339 self.logger.debug("created network_name: {}".format(net_name))
340
341 # Adding firewall rules to allow the traffic in the network:
342 rules_list = self._create_firewall_rules(net_name)
343
344 # create subnetwork, even if there is no profile
345
346 if not ip_profile:
347 ip_profile = {}
348
349 if not ip_profile.get("subnet_address"):
350 # Fake subnet is required
351 subnet_rand = random.randint(0, 255)
352 ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand)
353
354 subnet_name = net_name + "-subnet"
355 subnet_id = self._new_subnet(
356 subnet_name, ip_profile, operation["targetLink"]
357 )
358
359 self.logger.debug("new_network Return: subnet_id: %s", subnet_id)
360 return subnet_id
361 except Exception as e:
362 self._format_vimconn_exception(e)
363
364 def _new_subnet(self, subnet_name, ip_profile, network):
365 """
366 Adds a tenant network to VIM. It creates a new subnet at existing base vnet
367 :param net_name: subnet name
368 :param ip_profile:
369 subnet-address: if it is not provided a subnet/24 in the default vnet is created,
370 otherwise it creates a subnet in the indicated address
371 :return: a tuple with the network identifier and created_items, or raises an exception on error
372 """
373 self.logger.debug(
374 "_new_subnet begin: subnet_name %s ip_profile %s network %s",
375 subnet_name,
376 ip_profile,
377 network,
378 )
379 self.logger.debug(
380 "create subnet name %s, ip_profile %s", subnet_name, ip_profile
381 )
382
383 try:
384
385 self.logger.debug("creating subnet_name: {}".format(subnet_name))
386
387 subnetwork_body = {
388 "name": str(subnet_name),
389 "description": subnet_name,
390 "network": network,
391 "ipCidrRange": ip_profile["subnet_address"],
392 }
393
394 operation = (
395 self.conn_compute.subnetworks()
396 .insert(
397 project=self.project,
398 region=self.region,
399 body=subnetwork_body,
400 )
401 .execute()
402 )
403 self._wait_for_region_operation(operation["name"])
404
405 self.logger.debug("created subnet_name: {}".format(subnet_name))
406
407 self.logger.debug(
408 "_new_subnet Return: (%s,%s)",
409 "regions/%s/subnetworks/%s" % (self.region, subnet_name),
410 None,
411 )
412 return "regions/%s/subnetworks/%s" % (self.region, subnet_name), None
413 except Exception as e:
414 self._format_vimconn_exception(e)
415
416 def get_network_list(self, filter_dict={}):
417 """Obtain tenant networks of VIM
418 Filter_dict can be:
419 name: network name
420 id: network id
421 shared: boolean, not implemented in GC
422 tenant_id: tenant, not used in GC, all networks same tenants
423 admin_state_up: boolean, not implemented in GC
424 status: 'ACTIVE', not implemented in GC #
425 Returns the network list of dictionaries
426 """
427 self.logger.debug("get_network_list begin: filter_dict %s", filter_dict)
428 self.logger.debug(
429 "Getting network (subnetwork) from VIM filter: {}".format(str(filter_dict))
430 )
431
432 try:
433
434 if self.reload_client:
435 self._reload_connection()
436
437 net_list = []
438
439 request = self.conn_compute.subnetworks().list(
440 project=self.project, region=self.region
441 )
442
443 while request is not None:
444 response = request.execute()
445 self.logger.debug("Network list: %s", response)
446 for net in response["items"]:
447 self.logger.debug("Network in list: {}".format(str(net["name"])))
448 if filter_dict is not None:
449 if "name" in filter_dict.keys():
450 if (
451 filter_dict["name"] == net["name"]
452 or filter_dict["name"] == net["selfLink"]
453 ):
454 self.logger.debug("Network found: %s", net["name"])
455 net_list.append(
456 {
457 "id": str(net["selfLink"]),
458 "name": str(net["name"]),
459 "network": str(net["network"]),
460 }
461 )
462 else:
463 net_list.append(
464 {
465 "id": str(net["selfLink"]),
466 "name": str(net["name"]),
467 "network": str(net["network"]),
468 }
469 )
470 request = self.conn_compute.subnetworks().list_next(
471 previous_request=request, previous_response=response
472 )
473
474 self.logger.debug("get_network_list Return: net_list %s", net_list)
475 return net_list
476
477 except Exception as e:
478 self.logger.error("Error in get_network_list()", exc_info=True)
479 raise vimconn.VimConnException(e)
480
481 def get_network(self, net_id):
482 self.logger.debug("get_network begin: net_id %s", net_id)
483 # res_name = self._get_resource_name_from_resource_id(net_id)
484 self._reload_connection()
485
486 self.logger.debug("Get network: %s", net_id)
487 filter_dict = {"name": net_id}
488 network_list = self.get_network_list(filter_dict)
489 self.logger.debug("Network list: %s", network_list)
490
491 if not network_list:
492 return []
493 else:
494 self.logger.debug(
495 "get_network Return: network_list[0] %s", network_list[0]
496 )
497 return network_list[0]
498
499 def delete_network(self, net_id, created_items=None):
500 """
501 Removes a tenant network from VIM and its associated elements
502 :param net_id: VIM identifier of the network, provided by method new_network
503 :param created_items: dictionary with extra items to be deleted. provided by method new_network
504 Returns the network identifier or raises an exception upon error or when network is not found
505 """
506
507 self.logger.debug(
508 "delete_network begin: net_id %s created_items %s",
509 net_id,
510 created_items,
511 )
512 self.logger.debug("Deleting network: {}".format(str(net_id)))
513
514 try:
515
516 net_name = self._get_resource_name_from_resource_id(net_id)
517
518 # Check associated VMs
519 vms = (
520 self.conn_compute.instances()
521 .list(project=self.project, zone=self.zone)
522 .execute()
523 )
524
525 net_id = self.delete_subnet(net_name, created_items)
526
527 self.logger.debug("delete_network Return: net_id %s", net_id)
528 return net_id
529
530 except Exception as e:
531 self.logger.error("Error in delete_network()", exc_info=True)
532 raise vimconn.VimConnException(e)
533
534 def delete_subnet(self, net_id, created_items=None):
535 """
536 Removes a tenant network from VIM and its associated elements
537 :param net_id: VIM identifier of the network, provided by method new_network
538 :param created_items: dictionary with extra items to be deleted. provided by method new_network
539 Returns the network identifier or raises an exception upon error or when network is not found
540 """
541
542 self.logger.debug(
543 "delete_subnet begin: net_id %s created_items %s",
544 net_id,
545 created_items,
546 )
547 self.logger.debug("Deleting subnetwork: {}".format(str(net_id)))
548
549 try:
550 # If the network has no more subnets, it will be deleted too
551 net_info = self.get_network(net_id)
552 # If the subnet is in use by another resource, the deletion will be retried N times before abort the operation
553 created_items = created_items or {}
554 created_items[net_id] = False
555
556 try:
557 operation = (
558 self.conn_compute.subnetworks()
559 .delete(
560 project=self.project,
561 region=self.region,
562 subnetwork=net_id,
563 )
564 .execute()
565 )
566 self._wait_for_region_operation(operation["name"])
567 if net_id in self.nets_to_be_deleted:
568 self.nets_to_be_deleted.remove(net_id)
569 except Exception as e:
570 if (
571 e.args[0]["status"] == "400"
572 ): # Resource in use, so the net is marked to be deleted
573 self.logger.debug("Subnet still in use")
574 self.nets_to_be_deleted.append(net_id)
575 else:
576 raise vimconn.VimConnException(e)
577
578 self.logger.debug("nets_to_be_deleted: %s", self.nets_to_be_deleted)
579
580 # If the network has no more subnets, it will be deleted too
581 # if "network" in net_info and net_id not in self.nets_to_be_deleted:
582 if "network" in net_info:
583 network_name = self._get_resource_name_from_resource_id(
584 net_info["network"]
585 )
586
587 try:
588 # Deletion of the associated firewall rules:
589 rules_list = self._delete_firewall_rules(network_name)
590
591 operation = (
592 self.conn_compute.networks()
593 .delete(
594 project=self.project,
595 network=network_name,
596 )
597 .execute()
598 )
599 self._wait_for_global_operation(operation["name"])
600 except Exception as e:
601 self.logger.debug("error deleting associated network %s", e)
602
603 self.logger.debug("delete_subnet Return: net_id %s", net_id)
604 return net_id
605
606 except Exception as e:
607 self.logger.error("Error in delete_network()", exc_info=True)
608 raise vimconn.VimConnException(e)
609
610 def new_flavor(self, flavor_data):
611 """
612 It is not allowed to create new flavors (machine types) in Google Cloud, must always use an existing one
613 """
614 raise vimconn.VimConnNotImplemented(
615 "It is not possible to create new flavors in Google Cloud"
616 )
617
618 def new_tenant(self, tenant_name, tenant_description):
619 """
620 It is not allowed to create new tenants in Google Cloud
621 """
622 raise vimconn.VimConnNotImplemented(
623 "It is not possible to create a TENANT in Google Cloud"
624 )
625
626 def get_flavor(self, flavor_id):
627 """
628 Obtains the flavor_data from the flavor_id/machine type id
629 """
630 self.logger.debug("get_flavor begin: flavor_id %s", flavor_id)
631
632 try:
633 response = (
634 self.conn_compute.machineTypes()
635 .get(project=self.project, zone=self.zone, machineType=flavor_id)
636 .execute()
637 )
638 flavor_data = response
639 self.logger.debug("Machine type data: %s", flavor_data)
640
641 if flavor_data:
642 flavor = {
643 "id": flavor_data["id"],
644 "name": flavor_id,
645 "id_complete": flavor_data["selfLink"],
646 "ram": flavor_data["memoryMb"],
647 "vcpus": flavor_data["guestCpus"],
648 "disk": flavor_data["maximumPersistentDisksSizeGb"],
649 }
650
651 self.logger.debug("get_flavor Return: flavor %s", flavor)
652 return flavor
653 else:
654 raise vimconn.VimConnNotFoundException(
655 "flavor '{}' not found".format(flavor_id)
656 )
657 except Exception as e:
658 self._format_vimconn_exception(e)
659
660 # Google Cloud VM names can not have some special characters
661 def _check_vm_name(self, vm_name):
662 """
663 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
664 Only lowercase and hyphens are allowed
665 """
666 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
667
668 # First: the VM name max length is 64 characters
669 vm_name_aux = vm_name[:62]
670
671 # Second: replace not allowed characters
672 for elem in chars_not_allowed_list:
673 # Check if string is in the main string
674 if elem in vm_name_aux:
675 # self.logger.debug("Dentro del IF")
676 # Replace the string
677 vm_name_aux = vm_name_aux.replace(elem, "-")
678
679 return vm_name_aux.lower()
680
681 def get_flavor_id_from_data(self, flavor_dict):
682 self.logger.debug(
683 "get_flavor_id_from_data begin: flavor_dict %s", flavor_dict
684 )
685 filter_dict = flavor_dict or {}
686
687 try:
688 response = (
689 self.conn_compute.machineTypes()
690 .list(project=self.project, zone=self.zone)
691 .execute()
692 )
693 machine_types_list = response["items"]
694 # self.logger.debug("List of machine types: %s", machine_types_list)
695
696 cpus = filter_dict.get("vcpus") or 0
697 memMB = filter_dict.get("ram") or 0
698 numberInterfaces = len(filter_dict.get("interfaces", [])) or 4 # Workaround (it should be 0)
699
700 # Filter
701 filtered_machines = []
702 for machine_type in machine_types_list:
703 if (
704 machine_type["guestCpus"] >= cpus
705 and machine_type["memoryMb"] >= memMB
706 # In Google Cloud the number of virtual network interfaces scales with
707 # the number of virtual CPUs with a minimum of 2 and a maximum of 8:
708 # https://cloud.google.com/vpc/docs/create-use-multiple-interfaces#max-interfaces
709 and machine_type["guestCpus"] >= numberInterfaces
710 ):
711 filtered_machines.append(machine_type)
712
713 # self.logger.debug("Filtered machines: %s", filtered_machines)
714
715 # Sort
716 listedFilteredMachines = sorted(
717 filtered_machines,
718 key=lambda k: (
719 int(k["guestCpus"]),
720 float(k["memoryMb"]),
721 int(k["maximumPersistentDisksSizeGb"]),
722 k["name"],
723 ),
724 )
725 # self.logger.debug("Sorted filtered machines: %s", listedFilteredMachines)
726
727 if listedFilteredMachines:
728 self.logger.debug(
729 "get_flavor_id_from_data Return: listedFilteredMachines[0][name] %s",
730 listedFilteredMachines[0]["name"],
731 )
732 return listedFilteredMachines[0]["name"]
733
734 raise vimconn.VimConnNotFoundException(
735 "Cannot find any flavor matching '{}'".format(str(flavor_dict))
736 )
737
738 except Exception as e:
739 self._format_vimconn_exception(e)
740
741 def delete_flavor(self, flavor_id):
742 raise vimconn.VimConnNotImplemented(
743 "It is not possible to delete a flavor in Google Cloud"
744 )
745
746 def delete_tenant(self, tenant_id):
747 raise vimconn.VimConnNotImplemented(
748 "It is not possible to delete a TENANT in Google Cloud"
749 )
750
751 def new_image(self, image_dict):
752 """
753 This function comes from the early days when we though the image could be embedded in the package.
754 Unless OSM manages VM images E2E from NBI to RO, this function does not make sense to be implemented.
755 """
756 raise vimconn.VimConnNotImplemented("Not implemented")
757
758 def get_image_id_from_path(self, path):
759 """
760 This function comes from the early days when we though the image could be embedded in the package.
761 Unless OSM manages VM images E2E from NBI to RO, this function does not make sense to be implemented.
762 """
763 raise vimconn.VimConnNotImplemented("Not implemented")
764
765 def get_image_list(self, filter_dict={}):
766 """Obtain tenant images from VIM
767 Filter_dict can be:
768 name: image name with the format: image project:image family:image version
769 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
770 for the provided publisher and offer
771 id: image uuid, currently not supported for azure
772 Returns the image list of dictionaries:
773 [{<the fields at Filter_dict plus some VIM specific>}, ...]
774 List can be empty
775 """
776 self.logger.debug("get_image_list begin: filter_dict %s", filter_dict)
777
778 try:
779 image_list = []
780 # Get image id from parameter image_id:
781 # <image Project>:image-family:<family> => Latest version of the family
782 # <image Project>:image:<image> => Specific image
783 # <image Project>:<image> => Specific image
784
785 image_info = filter_dict["name"].split(":")
786 image_project = image_info[0]
787 if len(image_info) == 2:
788 image_type = "image"
789 image_item = image_info[1]
790 if len(image_info) == 3:
791 image_type = image_info[1]
792 image_item = image_info[2]
793 else:
794 raise vimconn.VimConnNotFoundException("Wrong format for image")
795
796 image_response = {}
797 if image_type == "image-family":
798 image_response = (
799 self.conn_compute.images()
800 .getFromFamily(project=image_project, family=image_item)
801 .execute()
802 )
803 elif image_type == "image":
804 image_response = (
805 self.conn_compute.images()
806 .get(project=image_project, image=image_item)
807 .execute()
808 )
809 else:
810 raise vimconn.VimConnNotFoundException("Wrong format for image")
811 image_list.append(
812 {
813 "id": "projects/%s/global/images/%s"
814 % (image_project, image_response["name"]),
815 "name": ":".join(
816 [image_project, image_item, image_response["name"]]
817 ),
818 }
819 )
820
821 self.logger.debug("get_image_list Return: image_list %s", image_list)
822 return image_list
823
824 except Exception as e:
825 self._format_vimconn_exception(e)
826
827 def delete_inuse_nic(self, nic_name):
828 raise vimconn.VimConnNotImplemented("Not necessary")
829
830 def delete_image(self, image_id):
831 raise vimconn.VimConnNotImplemented("Not implemented")
832
833 def action_vminstance(self, vm_id, action_dict, created_items={}):
834 """Send and action over a VM instance from VIM
835 Returns the vm_id if the action was successfully sent to the VIM
836 """
837 raise vimconn.VimConnNotImplemented("Not necessary")
838
839 def _randomize_name(self, name):
840 """Adds a random string to allow requests with the same VM name
841 Returns the name with an additional random string (if the total size is bigger
842 than 62 the original name will be truncated)
843 """
844 random_name = name
845
846 while True:
847 try:
848 random_name = (
849 name[:49]
850 + "-"
851 + "".join(random_choice("0123456789abcdef") for _ in range(12))
852 )
853 response = (
854 self.conn_compute.instances()
855 .get(project=self.project, zone=self.zone, instance=random_name)
856 .execute()
857 )
858 # If no exception is arisen, the random name exists for an instance, so a new random name must be generated
859
860 except Exception as e:
861 if e.args[0]["status"] == "404":
862 self.logger.debug("New random name: %s", random_name)
863 break
864 else:
865 self.logger.error("Exception generating random name (%s) for the instance", name)
866 self._format_vimconn_exception(e)
867
868 return random_name
869
870 def new_vminstance(
871 self,
872 name,
873 description,
874 start,
875 image_id=None, # <image project>:(image|image-family):<image/family id>
876 flavor_id=None,
877 net_list=None,
878 cloud_config=None,
879 disk_list=None,
880 availability_zone_index=None,
881 availability_zone_list=None,
882 ):
883 self.logger.debug(
884 "new_vminstance begin: name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
885 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
886 name,
887 image_id,
888 flavor_id,
889 net_list,
890 cloud_config,
891 disk_list,
892 availability_zone_index,
893 availability_zone_list,
894 )
895
896 if self.reload_client:
897 self._reload_connection()
898
899 # Validate input data is valid
900 # # First of all, the name must be adapted because Google Cloud only allows names consist of
901 # lowercase letters (a-z), numbers and hyphens (?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)
902 vm_name = self._check_vm_name(name)
903 vm_name = self._randomize_name(vm_name)
904 vm_id = None
905
906 # At least one network must be provided
907 if not net_list:
908 raise vimconn.VimConnException(
909 "At least one net must be provided to create a new VM"
910 )
911
912 try:
913 created_items = {}
914 metadata = self._build_metadata(vm_name, cloud_config)
915
916 # Building network interfaces list
917 network_interfaces = []
918 for net in net_list:
919 net_iface = {}
920 if not net.get("net_id"):
921 if not net.get("name"):
922 continue
923 else:
924 net_iface[
925 "subnetwork"
926 ] = "regions/%s/subnetworks/" % self.region + net.get("name")
927 else:
928 net_iface["subnetwork"] = net.get("net_id")
929 # In order to get an external IP address, the key "accessConfigs" must be used
930 # in the interace. It has to be of type "ONE_TO_ONE_NAT" and name "External NAT"
931 if net.get("floating_ip", False) or (net["use"] == "mgmt" and self.config.get("use_floating_ip")):
932 net_iface["accessConfigs"] = [
933 {"type": "ONE_TO_ONE_NAT", "name": "External NAT"}
934 ]
935
936 network_interfaces.append(net_iface)
937
938 self.logger.debug("Network interfaces: %s", network_interfaces)
939
940 self.logger.debug("Source image: %s", image_id)
941
942 vm_parameters = {
943 "name": vm_name,
944 "machineType": self.get_flavor(flavor_id)["id_complete"],
945 # Specify the boot disk and the image to use as a source.
946 "disks": [
947 {
948 "boot": True,
949 "autoDelete": True,
950 "initializeParams": {
951 "sourceImage": image_id,
952 },
953 }
954 ],
955 # Specify the network interfaces
956 "networkInterfaces": network_interfaces,
957 "metadata": metadata,
958 }
959
960 response = (
961 self.conn_compute.instances()
962 .insert(project=self.project, zone=self.zone, body=vm_parameters)
963 .execute()
964 )
965 self._wait_for_zone_operation(response["name"])
966
967 # The created instance info is obtained to get the name of the generated network interfaces (nic0, nic1...)
968 response = (
969 self.conn_compute.instances()
970 .get(project=self.project, zone=self.zone, instance=vm_name)
971 .execute()
972 )
973 self.logger.debug("instance get: %s", response)
974 vm_id = response["name"]
975
976 # The generated network interfaces in the instance are include in net_list:
977 for _, net in enumerate(net_list):
978 for net_ifaces in response["networkInterfaces"]:
979 network_id = ""
980 if "net_id" in net:
981 network_id = self._get_resource_name_from_resource_id(
982 net["net_id"]
983 )
984 else:
985 network_id = self._get_resource_name_from_resource_id(
986 net["name"]
987 )
988 if network_id == self._get_resource_name_from_resource_id(
989 net_ifaces["subnetwork"]
990 ):
991 net["vim_id"] = net_ifaces["name"]
992
993 self.logger.debug(
994 "new_vminstance Return: (name %s, created_items %s)",
995 vm_name,
996 created_items,
997 )
998 return vm_name, created_items
999
1000 except Exception as e:
1001 # Rollback vm creacion
1002 if vm_id is not None:
1003 try:
1004 self.logger.debug("exception creating vm try to rollback")
1005 self.delete_vminstance(vm_id, created_items)
1006 except Exception as e2:
1007 self.logger.error("new_vminstance rollback fail {}".format(e2))
1008
1009 else:
1010 self.logger.debug("Exception creating new vminstance: %s", e, exc_info=True)
1011 self._format_vimconn_exception(e)
1012
1013
1014 def _build_metadata(self, vm_name, cloud_config):
1015
1016 # initial metadata
1017 metadata = {}
1018 metadata["items"] = []
1019 key_pairs = {}
1020
1021 # if there is a cloud-init load it
1022 if cloud_config:
1023 self.logger.debug("cloud config: %s", cloud_config)
1024 _, userdata = self._create_user_data(cloud_config)
1025 metadata["items"].append(
1026 {"key": "user-data", "value": userdata}
1027 )
1028
1029 # either password of ssh-keys are required
1030 # we will always use ssh-keys, in case it is not available we will generate it
1031 """
1032 if cloud_config and cloud_config.get("key-pairs"):
1033 key_data = ""
1034 key_pairs = {}
1035 if cloud_config.get("key-pairs"):
1036 if isinstance(cloud_config["key-pairs"], list):
1037 # Transform the format "<key> <user@host>" into "<user>:<key>"
1038 key_data = ""
1039 for key in cloud_config.get("key-pairs"):
1040 key_data = key_data + key + "\n"
1041 key_pairs = {
1042 "key": "ssh-keys",
1043 "value": key_data
1044 }
1045 else:
1046 # If there is no ssh key in cloud config, a new key is generated:
1047 _, key_data = self._generate_keys()
1048 key_pairs = {
1049 "key": "ssh-keys",
1050 "value": "" + key_data
1051 }
1052 self.logger.debug("generated keys: %s", key_data)
1053
1054 metadata["items"].append(key_pairs)
1055 """
1056 self.logger.debug("metadata: %s", metadata)
1057
1058 return metadata
1059
1060
1061 def _generate_keys(self):
1062 """Method used to generate a pair of private/public keys.
1063 This method is used because to create a vm in Azure we always need a key or a password
1064 In some cases we may have a password in a cloud-init file but it may not be available
1065 """
1066 key = rsa.generate_private_key(
1067 backend=crypto_default_backend(), public_exponent=65537, key_size=2048
1068 )
1069 private_key = key.private_bytes(
1070 crypto_serialization.Encoding.PEM,
1071 crypto_serialization.PrivateFormat.PKCS8,
1072 crypto_serialization.NoEncryption(),
1073 )
1074 public_key = key.public_key().public_bytes(
1075 crypto_serialization.Encoding.OpenSSH,
1076 crypto_serialization.PublicFormat.OpenSSH,
1077 )
1078 private_key = private_key.decode("utf8")
1079 # Change first line because Paramiko needs a explicit start with 'BEGIN RSA PRIVATE KEY'
1080 i = private_key.find("\n")
1081 private_key = "-----BEGIN RSA PRIVATE KEY-----" + private_key[i:]
1082 public_key = public_key.decode("utf8")
1083
1084 return private_key, public_key
1085
1086
1087 def _get_unused_vm_name(self, vm_name):
1088 """
1089 Checks the vm name and in case it is used adds a suffix to the name to allow creation
1090 :return:
1091 """
1092 all_vms = (
1093 self.conn_compute.instances()
1094 .list(project=self.project, zone=self.zone)
1095 .execute()
1096 )
1097 # Filter to vms starting with the indicated name
1098 vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms))
1099 vm_names = [str(vm.name) for vm in vms]
1100
1101 # get the name with the first not used suffix
1102 name_suffix = 0
1103 # name = subnet_name + "-" + str(name_suffix)
1104 name = vm_name # first subnet created will have no prefix
1105
1106 while name in vm_names:
1107 name_suffix += 1
1108 name = vm_name + "-" + str(name_suffix)
1109
1110 return name
1111
1112 def get_vminstance(self, vm_id):
1113 """
1114 Obtaing the vm instance data from v_id
1115 """
1116 self.logger.debug("get_vminstance begin: vm_id %s", vm_id)
1117 self._reload_connection()
1118 response = {}
1119 try:
1120 response = (
1121 self.conn_compute.instances()
1122 .get(project=self.project, zone=self.zone, instance=vm_id)
1123 .execute()
1124 )
1125 # vm = response["source"]
1126 except Exception as e:
1127 self._format_vimconn_exception(e)
1128
1129 self.logger.debug("get_vminstance Return: response %s", response)
1130 return response
1131
1132 def delete_vminstance(self, vm_id, created_items=None):
1133 """Deletes a vm instance from the vim."""
1134 self.logger.debug(
1135 "delete_vminstance begin: vm_id %s created_items %s",
1136 vm_id,
1137 created_items,
1138 )
1139 if self.reload_client:
1140 self._reload_connection()
1141
1142 created_items = created_items or {}
1143 try:
1144 vm = self.get_vminstance(vm_id)
1145
1146 operation = (
1147 self.conn_compute.instances()
1148 .delete(project=self.project, zone=self.zone, instance=vm_id)
1149 .execute()
1150 )
1151 self._wait_for_zone_operation(operation["name"])
1152
1153 # The associated subnets must be checked if they are marked to be deleted
1154 for netIface in vm["networkInterfaces"]:
1155 if (
1156 self._get_resource_name_from_resource_id(netIface["subnetwork"])
1157 in self.nets_to_be_deleted
1158 ):
1159 net_id = self._get_resource_name_from_resource_id(
1160 self.delete_network(netIface["subnetwork"])
1161 )
1162
1163 self.logger.debug("delete_vminstance end")
1164
1165 except Exception as e:
1166 # The VM can be deleted previously during network deletion
1167 if e.args[0]["status"] == "404":
1168 self.logger.debug("The VM doesn't exist or has been deleted")
1169 else:
1170 self._format_vimconn_exception(e)
1171
1172 def _get_net_name_from_resource_id(self, resource_id):
1173 try:
1174 net_name = str(resource_id.split("/")[-1])
1175
1176 return net_name
1177 except Exception:
1178 raise vimconn.VimConnException(
1179 "Unable to get google cloud net_name from invalid resource_id format '{}'".format(
1180 resource_id
1181 )
1182 )
1183
1184 def _get_resource_name_from_resource_id(self, resource_id):
1185 """
1186 Obtains resource_name from the google cloud complete identifier: resource_name will always be last item
1187 """
1188 self.logger.debug(
1189 "_get_resource_name_from_resource_id begin: resource_id %s",
1190 resource_id,
1191 )
1192 try:
1193 resource = str(resource_id.split("/")[-1])
1194
1195 self.logger.debug(
1196 "_get_resource_name_from_resource_id Return: resource %s",
1197 resource,
1198 )
1199 return resource
1200 except Exception as e:
1201 raise vimconn.VimConnException(
1202 "Unable to get resource name from resource_id '{}' Error: '{}'".format(
1203 resource_id, e
1204 )
1205 )
1206
1207 def refresh_nets_status(self, net_list):
1208 """Get the status of the networks
1209 Params: the list of network identifiers
1210 Returns a dictionary with:
1211 net_id: #VIM id of this network
1212 status: #Mandatory. Text with one of:
1213 # DELETED (not found at vim)
1214 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1215 # OTHER (Vim reported other status not understood)
1216 # ERROR (VIM indicates an ERROR status)
1217 # ACTIVE, INACTIVE, DOWN (admin down),
1218 # BUILD (on building process)
1219 #
1220 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1221 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1222 """
1223 self.logger.debug("refresh_nets_status begin: net_list %s", net_list)
1224 out_nets = {}
1225 self._reload_connection()
1226
1227 for net_id in net_list:
1228 try:
1229 netName = self._get_net_name_from_resource_id(net_id)
1230 resName = self._get_resource_name_from_resource_id(net_id)
1231
1232 net = (
1233 self.conn_compute.subnetworks()
1234 .get(project=self.project, region=self.region, subnetwork=resName)
1235 .execute()
1236 )
1237 self.logger.debug("get subnetwork: %s", net)
1238
1239 out_nets[net_id] = {
1240 "status": "ACTIVE", # Google Cloud does not provide the status in subnetworks getting
1241 "vim_info": str(net),
1242 }
1243 except vimconn.VimConnNotFoundException as e:
1244 self.logger.error(
1245 "VimConnNotFoundException %s when searching subnet", e
1246 )
1247 out_nets[net_id] = {
1248 "status": "DELETED",
1249 "error_msg": str(e),
1250 }
1251 except Exception as e:
1252 self.logger.error(
1253 "Exception %s when searching subnet", e, exc_info=True
1254 )
1255 out_nets[net_id] = {
1256 "status": "VIM_ERROR",
1257 "error_msg": str(e),
1258 }
1259
1260 self.logger.debug("refresh_nets_status Return: out_nets %s", out_nets)
1261 return out_nets
1262
1263 def refresh_vms_status(self, vm_list):
1264 """Get the status of the virtual machines and their interfaces/ports
1265 Params: the list of VM identifiers
1266 Returns a dictionary with:
1267 vm_id: # VIM id of this Virtual Machine
1268 status: # Mandatory. Text with one of:
1269 # DELETED (not found at vim)
1270 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1271 # OTHER (Vim reported other status not understood)
1272 # ERROR (VIM indicates an ERROR status)
1273 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1274 # BUILD (on building process), ERROR
1275 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1276 # (ACTIVE:NoMgmtIP is not returned for Azure)
1277 #
1278 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1279 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1280 interfaces: list with interface info. Each item a dictionary with:
1281 vim_interface_id - The ID of the interface
1282 mac_address - The MAC address of the interface.
1283 ip_address - The IP address of the interface within the subnet.
1284 """
1285 self.logger.debug("refresh_vms_status begin: vm_list %s", vm_list)
1286 out_vms = {}
1287 self._reload_connection()
1288
1289 search_vm_list = vm_list or {}
1290
1291 for vm_id in search_vm_list:
1292 out_vm = {}
1293 try:
1294 res_name = self._get_resource_name_from_resource_id(vm_id)
1295
1296 vm = (
1297 self.conn_compute.instances()
1298 .get(project=self.project, zone=self.zone, instance=res_name)
1299 .execute()
1300 )
1301
1302 out_vm["vim_info"] = str(vm["name"])
1303 out_vm["status"] = self.provision_state2osm.get(vm["status"], "OTHER")
1304
1305 # In Google Cloud the there is no difference between provision or power status,
1306 # so if provision status method doesn't return a specific state (OTHER), the
1307 # power method is called
1308 if out_vm["status"] == "OTHER":
1309 out_vm["status"] = self.power_state2osm.get(vm["status"], "OTHER")
1310
1311 network_interfaces = vm["networkInterfaces"]
1312 out_vm["interfaces"] = self._get_vm_interfaces_status(
1313 vm_id, network_interfaces
1314 )
1315 except Exception as e:
1316 self.logger.error("Exception %s refreshing vm_status", e, exc_info=True)
1317 out_vm["status"] = "VIM_ERROR"
1318 out_vm["error_msg"] = str(e)
1319 out_vm["vim_info"] = None
1320
1321 out_vms[vm_id] = out_vm
1322
1323 self.logger.debug("refresh_vms_status Return: out_vms %s", out_vms)
1324 return out_vms
1325
1326 def _get_vm_interfaces_status(self, vm_id, interfaces):
1327 """
1328 Gets the interfaces detail for a vm
1329 :param interfaces: List of interfaces.
1330 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1331 """
1332 self.logger.debug(
1333 "_get_vm_interfaces_status begin: vm_id %s interfaces %s",
1334 vm_id,
1335 interfaces,
1336 )
1337 try:
1338 interface_list = []
1339 for network_interface in interfaces:
1340 interface_dict = {}
1341 nic_name = network_interface["name"]
1342 interface_dict["vim_interface_id"] = network_interface["name"]
1343
1344 ips = []
1345 ips.append(network_interface["networkIP"])
1346 interface_dict["ip_address"] = ";".join(ips)
1347 interface_list.append(interface_dict)
1348
1349 self.logger.debug(
1350 "_get_vm_interfaces_status Return: interface_list %s",
1351 interface_list,
1352 )
1353 return interface_list
1354 except Exception as e:
1355 self.logger.error(
1356 "Exception %s obtaining interface data for vm: %s",
1357 e,
1358 vm_id,
1359 exc_info=True,
1360 )
1361 self._format_vimconn_exception(e)
1362
1363 def _get_default_admin_user(self, image_id):
1364 if "ubuntu" in image_id.lower():
1365 return "ubuntu"
1366 else:
1367 return self._default_admin_user
1368
1369 def _create_firewall_rules(self, network):
1370 """
1371 Creates the necessary firewall rules to allow the traffic in the network
1372 (https://cloud.google.com/vpc/docs/firewalls)
1373 :param network.
1374 :return: a list with the names of the firewall rules
1375 """
1376 self.logger.debug("_create_firewall_rules begin: network %s", network)
1377 try:
1378 rules_list = []
1379
1380 # Adding firewall rule to allow http:
1381 self.logger.debug("creating firewall rule to allow http")
1382 firewall_rule_body = {
1383 "name": "fw-rule-http-" + network,
1384 "network": "global/networks/" + network,
1385 "allowed": [{"IPProtocol": "tcp", "ports": ["80"]}],
1386 }
1387 operation_firewall = (
1388 self.conn_compute.firewalls()
1389 .insert(project=self.project, body=firewall_rule_body)
1390 .execute()
1391 )
1392
1393 # Adding firewall rule to allow ssh:
1394 self.logger.debug("creating firewall rule to allow ssh")
1395 firewall_rule_body = {
1396 "name": "fw-rule-ssh-" + network,
1397 "network": "global/networks/" + network,
1398 "allowed": [{"IPProtocol": "tcp", "ports": ["22"]}],
1399 }
1400 operation_firewall = (
1401 self.conn_compute.firewalls()
1402 .insert(project=self.project, body=firewall_rule_body)
1403 .execute()
1404 )
1405
1406 # Adding firewall rule to allow ping:
1407 self.logger.debug("creating firewall rule to allow ping")
1408 firewall_rule_body = {
1409 "name": "fw-rule-icmp-" + network,
1410 "network": "global/networks/" + network,
1411 "allowed": [{"IPProtocol": "icmp"}],
1412 }
1413 operation_firewall = (
1414 self.conn_compute.firewalls()
1415 .insert(project=self.project, body=firewall_rule_body)
1416 .execute()
1417 )
1418
1419 # Adding firewall rule to allow internal:
1420 self.logger.debug("creating firewall rule to allow internal")
1421 firewall_rule_body = {
1422 "name": "fw-rule-internal-" + network,
1423 "network": "global/networks/" + network,
1424 "allowed": [
1425 {"IPProtocol": "tcp", "ports": ["0-65535"]},
1426 {"IPProtocol": "udp", "ports": ["0-65535"]},
1427 {"IPProtocol": "icmp"},
1428 ],
1429 }
1430 operation_firewall = (
1431 self.conn_compute.firewalls()
1432 .insert(project=self.project, body=firewall_rule_body)
1433 .execute()
1434 )
1435
1436 # Adding firewall rule to allow microk8s:
1437 self.logger.debug("creating firewall rule to allow microk8s")
1438 firewall_rule_body = {
1439 "name": "fw-rule-microk8s-" + network,
1440 "network": "global/networks/" + network,
1441 "allowed": [{"IPProtocol": "tcp", "ports": ["16443"]}],
1442 }
1443 operation_firewall = (
1444 self.conn_compute.firewalls()
1445 .insert(project=self.project, body=firewall_rule_body)
1446 .execute()
1447 )
1448
1449 # Adding firewall rule to allow rdp:
1450 self.logger.debug("creating firewall rule to allow rdp")
1451 firewall_rule_body = {
1452 "name": "fw-rule-rdp-" + network,
1453 "network": "global/networks/" + network,
1454 "allowed": [{"IPProtocol": "tcp", "ports": ["3389"]}],
1455 }
1456 operation_firewall = (
1457 self.conn_compute.firewalls()
1458 .insert(project=self.project, body=firewall_rule_body)
1459 .execute()
1460 )
1461
1462 # Adding firewall rule to allow osm:
1463 self.logger.debug("creating firewall rule to allow osm")
1464 firewall_rule_body = {
1465 "name": "fw-rule-osm-" + network,
1466 "network": "global/networks/" + network,
1467 "allowed": [{"IPProtocol": "tcp", "ports": ["9001", "9999"]}],
1468 }
1469 operation_firewall = (
1470 self.conn_compute.firewalls()
1471 .insert(project=self.project, body=firewall_rule_body)
1472 .execute()
1473 )
1474
1475 self.logger.debug(
1476 "_create_firewall_rules Return: list_rules %s", rules_list
1477 )
1478 return rules_list
1479 except Exception as e:
1480 self.logger.error(
1481 "Unable to create google cloud firewall rules for network '{}'".format(
1482 network
1483 )
1484 )
1485 self._format_vimconn_exception(e)
1486
1487 def _delete_firewall_rules(self, network):
1488 """
1489 Deletes the associated firewall rules to the network
1490 :param network.
1491 :return: a list with the names of the firewall rules
1492 """
1493 self.logger.debug("_delete_firewall_rules begin: network %s", network)
1494 try:
1495 rules_list = []
1496
1497 rules_list = (
1498 self.conn_compute.firewalls().list(project=self.project).execute()
1499 )
1500 for item in rules_list["items"]:
1501 if network == self._get_resource_name_from_resource_id(item["network"]):
1502 operation_firewall = (
1503 self.conn_compute.firewalls()
1504 .delete(project=self.project, firewall=item["name"])
1505 .execute()
1506 )
1507
1508 self.logger.debug("_delete_firewall_rules Return: list_rules %s", 0)
1509 return rules_list
1510 except Exception as e:
1511 self.logger.error(
1512 "Unable to delete google cloud firewall rules for network '{}'".format(
1513 network
1514 )
1515 )
1516 self._format_vimconn_exception(e)
1517