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