0a4ead08b27285efcfcf1ef4105da3480c6d86c8
[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 # "autoCreateSubnetworks": True,
323 # The network is created in AUTO mode (one subnet per region is created)
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 if not ip_profile:
340 ip_profile = {}
341
342 if not ip_profile.get("subnet_address"):
343 # Fake subnet is required
344 subnet_rand = random.randint(0, 255)
345 ip_profile["subnet_address"] = "192.168.{}.0/24".format(subnet_rand)
346
347 subnet_name = net_name + "-subnet"
348 subnet_id = self._new_subnet(
349 subnet_name, ip_profile, operation["targetLink"]
350 )
351
352 self.logger.debug("new_network Return: subnet_id: %s", subnet_id)
353 return subnet_id
354 except Exception as e:
355 self._format_vimconn_exception(e)
356
357 def _new_subnet(self, subnet_name, ip_profile, network):
358 """
359 Adds a tenant network to VIM. It creates a new subnet at existing base vnet
360 :param net_name: subnet name
361 :param ip_profile:
362 subnet-address: if it is not provided a subnet/24 in the default vnet is created,
363 otherwise it creates a subnet in the indicated address
364 :return: a tuple with the network identifier and created_items, or raises an exception on error
365 """
366 self.logger.debug(
367 "_new_subnet begin: subnet_name %s ip_profile %s network %s",
368 subnet_name,
369 ip_profile,
370 network,
371 )
372 self.logger.debug(
373 "create subnet name %s, ip_profile %s", subnet_name, ip_profile
374 )
375
376 try:
377
378 self.logger.debug("creating subnet_name: {}".format(subnet_name))
379
380 subnetwork_body = {
381 "name": str(subnet_name),
382 "description": subnet_name,
383 "network": network,
384 "ipCidrRange": ip_profile["subnet_address"],
385 }
386
387 operation = (
388 self.conn_compute.subnetworks()
389 .insert(
390 project=self.project,
391 region=self.region,
392 body=subnetwork_body,
393 )
394 .execute()
395 )
396 self._wait_for_region_operation(operation["name"])
397
398 self.logger.debug("created subnet_name: {}".format(subnet_name))
399
400 self.logger.debug(
401 "_new_subnet Return: (%s,%s)",
402 "regions/%s/subnetworks/%s" % (self.region, subnet_name),
403 None,
404 )
405 return "regions/%s/subnetworks/%s" % (self.region, subnet_name), None
406 except Exception as e:
407 self._format_vimconn_exception(e)
408
409 def get_network_list(self, filter_dict={}):
410 """Obtain tenant networks of VIM
411 Filter_dict can be:
412 name: network name
413 id: network id
414 shared: boolean, not implemented in GC
415 tenant_id: tenant, not used in GC, all networks same tenants
416 admin_state_up: boolean, not implemented in GC
417 status: 'ACTIVE', not implemented in GC #
418 Returns the network list of dictionaries
419 """
420 self.logger.debug("get_network_list begin: filter_dict %s", filter_dict)
421 self.logger.debug(
422 "Getting network (subnetwork) from VIM filter: {}".format(str(filter_dict))
423 )
424
425 try:
426
427 if self.reload_client:
428 self._reload_connection()
429
430 net_list = []
431
432 request = self.conn_compute.subnetworks().list(
433 project=self.project, region=self.region
434 )
435
436 while request is not None:
437 response = request.execute()
438 self.logger.debug("Network list: %s", response)
439 for net in response["items"]:
440 self.logger.debug("Network in list: {}".format(str(net["name"])))
441 if filter_dict is not None:
442 if "name" in filter_dict.keys():
443 if (
444 filter_dict["name"] == net["name"]
445 or filter_dict["name"] == net["selfLink"]
446 ):
447 self.logger.debug("Network found: %s", net["name"])
448 net_list.append(
449 {
450 "id": str(net["selfLink"]),
451 "name": str(net["name"]),
452 "network": str(net["network"]),
453 }
454 )
455 else:
456 net_list.append(
457 {
458 "id": str(net["selfLink"]),
459 "name": str(net["name"]),
460 "network": str(net["network"]),
461 }
462 )
463 request = self.conn_compute.subnetworks().list_next(
464 previous_request=request, previous_response=response
465 )
466
467 self.logger.debug("get_network_list Return: net_list %s", net_list)
468 return net_list
469
470 except Exception as e:
471 self.logger.error("Error in get_network_list()", exc_info=True)
472 raise vimconn.VimConnException(e)
473
474 def get_network(self, net_id):
475 self.logger.debug("get_network begin: net_id %s", net_id)
476 # res_name = self._get_resource_name_from_resource_id(net_id)
477 self._reload_connection()
478
479 self.logger.debug("Get network: %s", net_id)
480 filter_dict = {"name": net_id}
481 network_list = self.get_network_list(filter_dict)
482 self.logger.debug("Network list: %s", network_list)
483
484 if not network_list:
485 return []
486 else:
487 self.logger.debug("get_network Return: network_list[0] %s", network_list[0])
488 return network_list[0]
489
490 def delete_network(self, net_id, created_items=None):
491 """
492 Removes a tenant network from VIM and its associated elements
493 :param net_id: VIM identifier of the network, provided by method new_network
494 :param created_items: dictionary with extra items to be deleted. provided by method new_network
495 Returns the network identifier or raises an exception upon error or when network is not found
496 """
497
498 self.logger.debug(
499 "delete_network begin: net_id %s created_items %s",
500 net_id,
501 created_items,
502 )
503 self.logger.debug("Deleting network: {}".format(str(net_id)))
504
505 try:
506
507 net_name = self._get_resource_name_from_resource_id(net_id)
508
509 # Check associated VMs
510 self.conn_compute.instances().list(
511 project=self.project, zone=self.zone
512 ).execute()
513
514 net_id = self.delete_subnet(net_name, created_items)
515
516 self.logger.debug("delete_network Return: net_id %s", net_id)
517 return net_id
518
519 except Exception as e:
520 self.logger.error("Error in delete_network()", exc_info=True)
521 raise vimconn.VimConnException(e)
522
523 def delete_subnet(self, net_id, created_items=None):
524 """
525 Removes a tenant network from VIM and its associated elements
526 :param net_id: VIM identifier of the network, provided by method new_network
527 :param created_items: dictionary with extra items to be deleted. provided by method new_network
528 Returns the network identifier or raises an exception upon error or when network is not found
529 """
530
531 self.logger.debug(
532 "delete_subnet begin: net_id %s created_items %s",
533 net_id,
534 created_items,
535 )
536 self.logger.debug("Deleting subnetwork: {}".format(str(net_id)))
537
538 try:
539 # If the network has no more subnets, it will be deleted too
540 net_info = self.get_network(net_id)
541 # If the subnet is in use by another resource,
542 # the deletion will be retried N times before abort the operation
543 created_items = created_items or {}
544 created_items[net_id] = False
545
546 try:
547 operation = (
548 self.conn_compute.subnetworks()
549 .delete(
550 project=self.project,
551 region=self.region,
552 subnetwork=net_id,
553 )
554 .execute()
555 )
556 self._wait_for_region_operation(operation["name"])
557 if net_id in self.nets_to_be_deleted:
558 self.nets_to_be_deleted.remove(net_id)
559 except Exception as e:
560 if (
561 e.args[0]["status"] == "400"
562 ): # Resource in use, so the net is marked to be deleted
563 self.logger.debug("Subnet still in use")
564 self.nets_to_be_deleted.append(net_id)
565 else:
566 raise vimconn.VimConnException(e)
567
568 self.logger.debug("nets_to_be_deleted: %s", self.nets_to_be_deleted)
569
570 # If the network has no more subnets, it will be deleted too
571 # if "network" in net_info and net_id not in self.nets_to_be_deleted:
572 if "network" in net_info:
573 network_name = self._get_resource_name_from_resource_id(
574 net_info["network"]
575 )
576
577 try:
578 # Deletion of the associated firewall rules:
579 self._delete_firewall_rules(network_name)
580
581 operation = (
582 self.conn_compute.networks()
583 .delete(
584 project=self.project,
585 network=network_name,
586 )
587 .execute()
588 )
589 self._wait_for_global_operation(operation["name"])
590 except Exception as e:
591 self.logger.debug("error deleting associated network %s", e)
592
593 self.logger.debug("delete_subnet Return: net_id %s", net_id)
594 return net_id
595
596 except Exception as e:
597 self.logger.error("Error in delete_network()", exc_info=True)
598 raise vimconn.VimConnException(e)
599
600 def new_flavor(self, flavor_data):
601 """
602 It is not allowed to create new flavors (machine types) in Google Cloud, must always use an existing one
603 """
604 raise vimconn.VimConnNotImplemented(
605 "It is not possible to create new flavors in Google Cloud"
606 )
607
608 def new_tenant(self, tenant_name, tenant_description):
609 """
610 It is not allowed to create new tenants in Google Cloud
611 """
612 raise vimconn.VimConnNotImplemented(
613 "It is not possible to create a TENANT in Google Cloud"
614 )
615
616 def get_flavor(self, flavor_id):
617 """
618 Obtains the flavor_data from the flavor_id/machine type id
619 """
620 self.logger.debug("get_flavor begin: flavor_id %s", flavor_id)
621
622 try:
623 response = (
624 self.conn_compute.machineTypes()
625 .get(project=self.project, zone=self.zone, machineType=flavor_id)
626 .execute()
627 )
628 flavor_data = response
629 self.logger.debug("Machine type data: %s", flavor_data)
630
631 if flavor_data:
632 flavor = {
633 "id": flavor_data["id"],
634 "name": flavor_id,
635 "id_complete": flavor_data["selfLink"],
636 "ram": flavor_data["memoryMb"],
637 "vcpus": flavor_data["guestCpus"],
638 "disk": flavor_data["maximumPersistentDisksSizeGb"],
639 }
640
641 self.logger.debug("get_flavor Return: flavor %s", flavor)
642 return flavor
643 else:
644 raise vimconn.VimConnNotFoundException(
645 "flavor '{}' not found".format(flavor_id)
646 )
647 except Exception as e:
648 self._format_vimconn_exception(e)
649
650 # Google Cloud VM names can not have some special characters
651 def _check_vm_name(self, vm_name):
652 """
653 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
654 Only lowercase and hyphens are allowed
655 """
656 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
657
658 # First: the VM name max length is 64 characters
659 vm_name_aux = vm_name[:62]
660
661 # Second: replace not allowed characters
662 for elem in chars_not_allowed_list:
663 # Check if string is in the main string
664 if elem in vm_name_aux:
665 # self.logger.debug("Dentro del IF")
666 # Replace the string
667 vm_name_aux = vm_name_aux.replace(elem, "-")
668
669 return vm_name_aux.lower()
670
671 def get_flavor_id_from_data(self, flavor_dict):
672 self.logger.debug("get_flavor_id_from_data begin: flavor_dict %s", flavor_dict)
673 filter_dict = flavor_dict or {}
674
675 try:
676 response = (
677 self.conn_compute.machineTypes()
678 .list(project=self.project, zone=self.zone)
679 .execute()
680 )
681 machine_types_list = response["items"]
682 # self.logger.debug("List of machine types: %s", machine_types_list)
683
684 cpus = filter_dict.get("vcpus") or 0
685 memMB = filter_dict.get("ram") or 0
686 # Workaround (it should be 0)
687 numberInterfaces = len(filter_dict.get("interfaces", [])) or 4
688
689 # Filter
690 filtered_machines = []
691 for machine_type in machine_types_list:
692 if (
693 machine_type["guestCpus"] >= cpus
694 and machine_type["memoryMb"] >= memMB
695 # In Google Cloud the number of virtual network interfaces scales with
696 # the number of virtual CPUs with a minimum of 2 and a maximum of 8:
697 # https://cloud.google.com/vpc/docs/create-use-multiple-interfaces#max-interfaces
698 and machine_type["guestCpus"] >= numberInterfaces
699 ):
700 filtered_machines.append(machine_type)
701
702 # self.logger.debug("Filtered machines: %s", filtered_machines)
703
704 # Sort
705 listedFilteredMachines = sorted(
706 filtered_machines,
707 key=lambda k: (
708 int(k["guestCpus"]),
709 float(k["memoryMb"]),
710 int(k["maximumPersistentDisksSizeGb"]),
711 k["name"],
712 ),
713 )
714 # self.logger.debug("Sorted filtered machines: %s", listedFilteredMachines)
715
716 if listedFilteredMachines:
717 self.logger.debug(
718 "get_flavor_id_from_data Return: listedFilteredMachines[0][name] %s",
719 listedFilteredMachines[0]["name"],
720 )
721 return listedFilteredMachines[0]["name"]
722
723 raise vimconn.VimConnNotFoundException(
724 "Cannot find any flavor matching '{}'".format(str(flavor_dict))
725 )
726
727 except Exception as e:
728 self._format_vimconn_exception(e)
729
730 def delete_flavor(self, flavor_id):
731 raise vimconn.VimConnNotImplemented(
732 "It is not possible to delete a flavor in Google Cloud"
733 )
734
735 def delete_tenant(self, tenant_id):
736 raise vimconn.VimConnNotImplemented(
737 "It is not possible to delete a TENANT in Google Cloud"
738 )
739
740 def new_image(self, image_dict):
741 """
742 This function comes from the early days when we though the image could be embedded in the package.
743 Unless OSM manages VM images E2E from NBI to RO, this function does not make sense to be implemented.
744 """
745 raise vimconn.VimConnNotImplemented("Not implemented")
746
747 def get_image_id_from_path(self, path):
748 """
749 This function comes from the early days when we though the image could be embedded in the package.
750 Unless OSM manages VM images E2E from NBI to RO, this function does not make sense to be implemented.
751 """
752 raise vimconn.VimConnNotImplemented("Not implemented")
753
754 def get_image_list(self, filter_dict={}):
755 """Obtain tenant images from VIM
756 Filter_dict can be:
757 name: image name with the format: image project:image family:image version
758 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
759 for the provided publisher and offer
760 id: image uuid, currently not supported for azure
761 Returns the image list of dictionaries:
762 [{<the fields at Filter_dict plus some VIM specific>}, ...]
763 List can be empty
764 """
765 self.logger.debug("get_image_list begin: filter_dict %s", filter_dict)
766
767 try:
768 image_list = []
769 # Get image id from parameter image_id:
770 # <image Project>:image-family:<family> => Latest version of the family
771 # <image Project>:image:<image> => Specific image
772 # <image Project>:<image> => Specific image
773
774 image_info = filter_dict["name"].split(":")
775 image_project = image_info[0]
776 if len(image_info) == 2:
777 image_type = "image"
778 image_item = image_info[1]
779 if len(image_info) == 3:
780 image_type = image_info[1]
781 image_item = image_info[2]
782 else:
783 raise vimconn.VimConnNotFoundException("Wrong format for image")
784
785 image_response = {}
786 if image_type == "image-family":
787 image_response = (
788 self.conn_compute.images()
789 .getFromFamily(project=image_project, family=image_item)
790 .execute()
791 )
792 elif image_type == "image":
793 image_response = (
794 self.conn_compute.images()
795 .get(project=image_project, image=image_item)
796 .execute()
797 )
798 else:
799 raise vimconn.VimConnNotFoundException("Wrong format for image")
800 image_list.append(
801 {
802 "id": "projects/%s/global/images/%s"
803 % (image_project, image_response["name"]),
804 "name": ":".join(
805 [image_project, image_item, image_response["name"]]
806 ),
807 }
808 )
809
810 self.logger.debug("get_image_list Return: image_list %s", image_list)
811 return image_list
812
813 except Exception as e:
814 self._format_vimconn_exception(e)
815
816 def delete_inuse_nic(self, nic_name):
817 raise vimconn.VimConnNotImplemented("Not necessary")
818
819 def delete_image(self, image_id):
820 raise vimconn.VimConnNotImplemented("Not implemented")
821
822 def action_vminstance(self, vm_id, action_dict, created_items={}):
823 """Send and action over a VM instance from VIM
824 Returns the vm_id if the action was successfully sent to the VIM
825 """
826 raise vimconn.VimConnNotImplemented("Not necessary")
827
828 def _randomize_name(self, name):
829 """Adds a random string to allow requests with the same VM name
830 Returns the name with an additional random string (if the total size is bigger
831 than 62 the original name will be truncated)
832 """
833 random_name = name
834
835 while True:
836 try:
837 random_name = (
838 name[:49]
839 + "-"
840 + "".join(random_choice("0123456789abcdef") for _ in range(12))
841 )
842 self.conn_compute.instances().get(
843 project=self.project, zone=self.zone, instance=random_name
844 ).execute()
845 # If no exception is arisen, the random name exists for an instance,
846 # so a new random name must be generated
847
848 except Exception as e:
849 if e.args[0]["status"] == "404":
850 self.logger.debug("New random name: %s", random_name)
851 break
852 else:
853 self.logger.error(
854 "Exception generating random name (%s) for the instance", name
855 )
856 self._format_vimconn_exception(e)
857
858 return random_name
859
860 def new_vminstance(
861 self,
862 name,
863 description,
864 start,
865 image_id=None, # <image project>:(image|image-family):<image/family id>
866 flavor_id=None,
867 affinity_group_list=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
1010 # initial metadata
1011 metadata = {}
1012 metadata["items"] = []
1013
1014 # if there is a cloud-init load it
1015 if cloud_config:
1016 self.logger.debug("cloud config: %s", cloud_config)
1017 _, userdata = self._create_user_data(cloud_config)
1018 metadata["items"].append({"key": "user-data", "value": userdata})
1019
1020 # either password of ssh-keys are required
1021 # we will always use ssh-keys, in case it is not available we will generate it
1022 """
1023 if cloud_config and cloud_config.get("key-pairs"):
1024 key_data = ""
1025 key_pairs = {}
1026 if cloud_config.get("key-pairs"):
1027 if isinstance(cloud_config["key-pairs"], list):
1028 # Transform the format "<key> <user@host>" into "<user>:<key>"
1029 key_data = ""
1030 for key in cloud_config.get("key-pairs"):
1031 key_data = key_data + key + "\n"
1032 key_pairs = {
1033 "key": "ssh-keys",
1034 "value": key_data
1035 }
1036 else:
1037 # If there is no ssh key in cloud config, a new key is generated:
1038 _, key_data = self._generate_keys()
1039 key_pairs = {
1040 "key": "ssh-keys",
1041 "value": "" + key_data
1042 }
1043 self.logger.debug("generated keys: %s", key_data)
1044
1045 metadata["items"].append(key_pairs)
1046 """
1047 self.logger.debug("metadata: %s", metadata)
1048
1049 return metadata
1050
1051 def _generate_keys(self):
1052 """Method used to generate a pair of private/public keys.
1053 This method is used because to create a vm in Azure we always need a key or a password
1054 In some cases we may have a password in a cloud-init file but it may not be available
1055 """
1056 key = rsa.generate_private_key(
1057 backend=crypto_default_backend(), public_exponent=65537, key_size=2048
1058 )
1059 private_key = key.private_bytes(
1060 crypto_serialization.Encoding.PEM,
1061 crypto_serialization.PrivateFormat.PKCS8,
1062 crypto_serialization.NoEncryption(),
1063 )
1064 public_key = key.public_key().public_bytes(
1065 crypto_serialization.Encoding.OpenSSH,
1066 crypto_serialization.PublicFormat.OpenSSH,
1067 )
1068 private_key = private_key.decode("utf8")
1069 # Change first line because Paramiko needs a explicit start with 'BEGIN RSA PRIVATE KEY'
1070 i = private_key.find("\n")
1071 private_key = "-----BEGIN RSA PRIVATE KEY-----" + private_key[i:]
1072 public_key = public_key.decode("utf8")
1073
1074 return private_key, public_key
1075
1076 def _get_unused_vm_name(self, vm_name):
1077 """
1078 Checks the vm name and in case it is used adds a suffix to the name to allow creation
1079 :return:
1080 """
1081 all_vms = (
1082 self.conn_compute.instances()
1083 .list(project=self.project, zone=self.zone)
1084 .execute()
1085 )
1086 # Filter to vms starting with the indicated name
1087 vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms))
1088 vm_names = [str(vm.name) for vm in vms]
1089
1090 # get the name with the first not used suffix
1091 name_suffix = 0
1092 # name = subnet_name + "-" + str(name_suffix)
1093 name = vm_name # first subnet created will have no prefix
1094
1095 while name in vm_names:
1096 name_suffix += 1
1097 name = vm_name + "-" + str(name_suffix)
1098
1099 return name
1100
1101 def get_vminstance(self, vm_id):
1102 """
1103 Obtaing the vm instance data from v_id
1104 """
1105 self.logger.debug("get_vminstance begin: vm_id %s", vm_id)
1106 self._reload_connection()
1107 response = {}
1108 try:
1109 response = (
1110 self.conn_compute.instances()
1111 .get(project=self.project, zone=self.zone, instance=vm_id)
1112 .execute()
1113 )
1114 # vm = response["source"]
1115 except Exception as e:
1116 self._format_vimconn_exception(e)
1117
1118 self.logger.debug("get_vminstance Return: response %s", response)
1119 return response
1120
1121 def delete_vminstance(self, vm_id, created_items=None):
1122 """Deletes a vm instance from the vim."""
1123 self.logger.debug(
1124 "delete_vminstance begin: vm_id %s created_items %s",
1125 vm_id,
1126 created_items,
1127 )
1128 if self.reload_client:
1129 self._reload_connection()
1130
1131 created_items = created_items or {}
1132 try:
1133 vm = self.get_vminstance(vm_id)
1134
1135 operation = (
1136 self.conn_compute.instances()
1137 .delete(project=self.project, zone=self.zone, instance=vm_id)
1138 .execute()
1139 )
1140 self._wait_for_zone_operation(operation["name"])
1141
1142 # The associated subnets must be checked if they are marked to be deleted
1143 for netIface in vm["networkInterfaces"]:
1144 if (
1145 self._get_resource_name_from_resource_id(netIface["subnetwork"])
1146 in self.nets_to_be_deleted
1147 ):
1148 self._get_resource_name_from_resource_id(
1149 self.delete_network(netIface["subnetwork"])
1150 )
1151
1152 self.logger.debug("delete_vminstance end")
1153
1154 except Exception as e:
1155 # The VM can be deleted previously during network deletion
1156 if e.args[0]["status"] == "404":
1157 self.logger.debug("The VM doesn't exist or has been deleted")
1158 else:
1159 self._format_vimconn_exception(e)
1160
1161 def _get_net_name_from_resource_id(self, resource_id):
1162 try:
1163 net_name = str(resource_id.split("/")[-1])
1164
1165 return net_name
1166 except Exception:
1167 raise vimconn.VimConnException(
1168 "Unable to get google cloud net_name from invalid resource_id format '{}'".format(
1169 resource_id
1170 )
1171 )
1172
1173 def _get_resource_name_from_resource_id(self, resource_id):
1174 """
1175 Obtains resource_name from the google cloud complete identifier: resource_name will always be last item
1176 """
1177 self.logger.debug(
1178 "_get_resource_name_from_resource_id begin: resource_id %s",
1179 resource_id,
1180 )
1181 try:
1182 resource = str(resource_id.split("/")[-1])
1183
1184 self.logger.debug(
1185 "_get_resource_name_from_resource_id Return: resource %s",
1186 resource,
1187 )
1188 return resource
1189 except Exception as e:
1190 raise vimconn.VimConnException(
1191 "Unable to get resource name from resource_id '{}' Error: '{}'".format(
1192 resource_id, e
1193 )
1194 )
1195
1196 def refresh_nets_status(self, net_list):
1197 """Get the status of the networks
1198 Params: the list of network identifiers
1199 Returns a dictionary with:
1200 net_id: #VIM id of this network
1201 status: #Mandatory. Text with one of:
1202 # DELETED (not found at vim)
1203 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1204 # OTHER (Vim reported other status not understood)
1205 # ERROR (VIM indicates an ERROR status)
1206 # ACTIVE, INACTIVE, DOWN (admin down),
1207 # BUILD (on building process)
1208 #
1209 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1210 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1211 """
1212 self.logger.debug("refresh_nets_status begin: net_list %s", net_list)
1213 out_nets = {}
1214 self._reload_connection()
1215
1216 for net_id in net_list:
1217 try:
1218 resName = self._get_resource_name_from_resource_id(net_id)
1219
1220 net = (
1221 self.conn_compute.subnetworks()
1222 .get(project=self.project, region=self.region, subnetwork=resName)
1223 .execute()
1224 )
1225 self.logger.debug("get subnetwork: %s", net)
1226
1227 out_nets[net_id] = {
1228 "status": "ACTIVE", # Google Cloud does not provide the status in subnetworks getting
1229 "vim_info": str(net),
1230 }
1231 except vimconn.VimConnNotFoundException as e:
1232 self.logger.error(
1233 "VimConnNotFoundException %s when searching subnet", e
1234 )
1235 out_nets[net_id] = {
1236 "status": "DELETED",
1237 "error_msg": str(e),
1238 }
1239 except Exception as e:
1240 self.logger.error(
1241 "Exception %s when searching subnet", e, exc_info=True
1242 )
1243 out_nets[net_id] = {
1244 "status": "VIM_ERROR",
1245 "error_msg": str(e),
1246 }
1247
1248 self.logger.debug("refresh_nets_status Return: out_nets %s", out_nets)
1249 return out_nets
1250
1251 def refresh_vms_status(self, vm_list):
1252 """Get the status of the virtual machines and their interfaces/ports
1253 Params: the list of VM identifiers
1254 Returns a dictionary with:
1255 vm_id: # VIM id of this Virtual Machine
1256 status: # Mandatory. Text with one of:
1257 # DELETED (not found at vim)
1258 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1259 # OTHER (Vim reported other status not understood)
1260 # ERROR (VIM indicates an ERROR status)
1261 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1262 # BUILD (on building process), ERROR
1263 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1264 # (ACTIVE:NoMgmtIP is not returned for Azure)
1265 #
1266 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1267 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1268 interfaces: list with interface info. Each item a dictionary with:
1269 vim_interface_id - The ID of the interface
1270 mac_address - The MAC address of the interface.
1271 ip_address - The IP address of the interface within the subnet.
1272 """
1273 self.logger.debug("refresh_vms_status begin: vm_list %s", vm_list)
1274 out_vms = {}
1275 self._reload_connection()
1276
1277 search_vm_list = vm_list or {}
1278
1279 for vm_id in search_vm_list:
1280 out_vm = {}
1281 try:
1282 res_name = self._get_resource_name_from_resource_id(vm_id)
1283
1284 vm = (
1285 self.conn_compute.instances()
1286 .get(project=self.project, zone=self.zone, instance=res_name)
1287 .execute()
1288 )
1289
1290 out_vm["vim_info"] = str(vm["name"])
1291 out_vm["status"] = self.provision_state2osm.get(vm["status"], "OTHER")
1292
1293 # In Google Cloud the there is no difference between provision or power status,
1294 # so if provision status method doesn't return a specific state (OTHER), the
1295 # power method is called
1296 if out_vm["status"] == "OTHER":
1297 out_vm["status"] = self.power_state2osm.get(vm["status"], "OTHER")
1298
1299 network_interfaces = vm["networkInterfaces"]
1300 out_vm["interfaces"] = self._get_vm_interfaces_status(
1301 vm_id, network_interfaces
1302 )
1303 except Exception as e:
1304 self.logger.error("Exception %s refreshing vm_status", e, exc_info=True)
1305 out_vm["status"] = "VIM_ERROR"
1306 out_vm["error_msg"] = str(e)
1307 out_vm["vim_info"] = None
1308
1309 out_vms[vm_id] = out_vm
1310
1311 self.logger.debug("refresh_vms_status Return: out_vms %s", out_vms)
1312 return out_vms
1313
1314 def _get_vm_interfaces_status(self, vm_id, interfaces):
1315 """
1316 Gets the interfaces detail for a vm
1317 :param interfaces: List of interfaces.
1318 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1319 """
1320 self.logger.debug(
1321 "_get_vm_interfaces_status begin: vm_id %s interfaces %s",
1322 vm_id,
1323 interfaces,
1324 )
1325 try:
1326 interface_list = []
1327 for network_interface in interfaces:
1328 interface_dict = {}
1329 interface_dict["vim_interface_id"] = network_interface["name"]
1330
1331 ips = []
1332 ips.append(network_interface["networkIP"])
1333 interface_dict["ip_address"] = ";".join(ips)
1334 interface_list.append(interface_dict)
1335
1336 self.logger.debug(
1337 "_get_vm_interfaces_status Return: interface_list %s",
1338 interface_list,
1339 )
1340 return interface_list
1341 except Exception as e:
1342 self.logger.error(
1343 "Exception %s obtaining interface data for vm: %s",
1344 e,
1345 vm_id,
1346 exc_info=True,
1347 )
1348 self._format_vimconn_exception(e)
1349
1350 def _get_default_admin_user(self, image_id):
1351 if "ubuntu" in image_id.lower():
1352 return "ubuntu"
1353 else:
1354 return self._default_admin_user
1355
1356 def _create_firewall_rules(self, network):
1357 """
1358 Creates the necessary firewall rules to allow the traffic in the network
1359 (https://cloud.google.com/vpc/docs/firewalls)
1360 :param network.
1361 :return: a list with the names of the firewall rules
1362 """
1363 self.logger.debug("_create_firewall_rules begin: network %s", network)
1364 try:
1365 rules_list = []
1366
1367 # Adding firewall rule to allow http:
1368 self.logger.debug("creating firewall rule to allow http")
1369 firewall_rule_body = {
1370 "name": "fw-rule-http-" + network,
1371 "network": "global/networks/" + network,
1372 "allowed": [{"IPProtocol": "tcp", "ports": ["80"]}],
1373 }
1374 self.conn_compute.firewalls().insert(
1375 project=self.project, body=firewall_rule_body
1376 ).execute()
1377
1378 # Adding firewall rule to allow ssh:
1379 self.logger.debug("creating firewall rule to allow ssh")
1380 firewall_rule_body = {
1381 "name": "fw-rule-ssh-" + network,
1382 "network": "global/networks/" + network,
1383 "allowed": [{"IPProtocol": "tcp", "ports": ["22"]}],
1384 }
1385 self.conn_compute.firewalls().insert(
1386 project=self.project, body=firewall_rule_body
1387 ).execute()
1388
1389 # Adding firewall rule to allow ping:
1390 self.logger.debug("creating firewall rule to allow ping")
1391 firewall_rule_body = {
1392 "name": "fw-rule-icmp-" + network,
1393 "network": "global/networks/" + network,
1394 "allowed": [{"IPProtocol": "icmp"}],
1395 }
1396
1397 self.conn_compute.firewalls().insert(
1398 project=self.project, body=firewall_rule_body
1399 ).execute()
1400
1401 # Adding firewall rule to allow internal:
1402 self.logger.debug("creating firewall rule to allow internal")
1403 firewall_rule_body = {
1404 "name": "fw-rule-internal-" + network,
1405 "network": "global/networks/" + network,
1406 "allowed": [
1407 {"IPProtocol": "tcp", "ports": ["0-65535"]},
1408 {"IPProtocol": "udp", "ports": ["0-65535"]},
1409 {"IPProtocol": "icmp"},
1410 ],
1411 }
1412 self.conn_compute.firewalls().insert(
1413 project=self.project, body=firewall_rule_body
1414 ).execute()
1415
1416 # Adding firewall rule to allow microk8s:
1417 self.logger.debug("creating firewall rule to allow microk8s")
1418 firewall_rule_body = {
1419 "name": "fw-rule-microk8s-" + network,
1420 "network": "global/networks/" + network,
1421 "allowed": [{"IPProtocol": "tcp", "ports": ["16443"]}],
1422 }
1423 self.conn_compute.firewalls().insert(
1424 project=self.project, body=firewall_rule_body
1425 ).execute()
1426
1427 # Adding firewall rule to allow rdp:
1428 self.logger.debug("creating firewall rule to allow rdp")
1429 firewall_rule_body = {
1430 "name": "fw-rule-rdp-" + network,
1431 "network": "global/networks/" + network,
1432 "allowed": [{"IPProtocol": "tcp", "ports": ["3389"]}],
1433 }
1434 self.conn_compute.firewalls().insert(
1435 project=self.project, body=firewall_rule_body
1436 ).execute()
1437
1438 # Adding firewall rule to allow osm:
1439 self.logger.debug("creating firewall rule to allow osm")
1440 firewall_rule_body = {
1441 "name": "fw-rule-osm-" + network,
1442 "network": "global/networks/" + network,
1443 "allowed": [{"IPProtocol": "tcp", "ports": ["9001", "9999"]}],
1444 }
1445 self.conn_compute.firewalls().insert(
1446 project=self.project, body=firewall_rule_body
1447 ).execute()
1448
1449 self.logger.debug(
1450 "_create_firewall_rules Return: list_rules %s", rules_list
1451 )
1452 return rules_list
1453 except Exception as e:
1454 self.logger.error(
1455 "Unable to create google cloud firewall rules for network '{}'".format(
1456 network
1457 )
1458 )
1459 self._format_vimconn_exception(e)
1460
1461 def _delete_firewall_rules(self, network):
1462 """
1463 Deletes the associated firewall rules to the network
1464 :param network.
1465 :return: a list with the names of the firewall rules
1466 """
1467 self.logger.debug("_delete_firewall_rules begin: network %s", network)
1468 try:
1469 rules_list = []
1470
1471 rules_list = (
1472 self.conn_compute.firewalls().list(project=self.project).execute()
1473 )
1474 for item in rules_list["items"]:
1475 if network == self._get_resource_name_from_resource_id(item["network"]):
1476 self.conn_compute.firewalls().delete(
1477 project=self.project, firewall=item["name"]
1478 ).execute()
1479
1480 self.logger.debug("_delete_firewall_rules Return: list_rules %s", 0)
1481 return rules_list
1482 except Exception as e:
1483 self.logger.error(
1484 "Unable to delete google cloud firewall rules for network '{}'".format(
1485 network
1486 )
1487 )
1488 self._format_vimconn_exception(e)