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