Skip to content

Commit

Permalink
added zone_handler and multiple changes to deploy flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Costya-Y committed Sep 13, 2024
1 parent caf7e2b commit 3a70e0d
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 116 deletions.
26 changes: 21 additions & 5 deletions cloudshell/cp/gcp/flows/deploy_instance/base_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
from cloudshell.cp.gcp.actions.firewall_policy_actions import FirewallPolicyActions
from cloudshell.cp.gcp.handlers.ssh_keys import SSHKeysHandler
from cloudshell.cp.gcp.handlers.vpc import VPCHandler
from cloudshell.cp.gcp.handlers.zone import ZoneHandler
from cloudshell.cp.gcp.handlers.instance import InstanceHandler
from cloudshell.cp.gcp.helpers.interface_helper import InterfaceHelper
from cloudshell.cp.gcp.helpers.name_generator import GCPNameGenerator
from cloudshell.cp.gcp.actions.vm_details_actions import VMDetailsActions
from cloudshell.cp.gcp.flows.deploy_instance.commands import DeployInstanceCommand
from cloudshell.cp.gcp.helpers.network_tag_helper import get_network_tags, InboundPort

if TYPE_CHECKING:
from cloudshell.cp.gcp.handlers.instance import Instance, InstanceHandler
from cloudshell.cp.gcp.handlers.instance import Instance
from cloudshell.cp.gcp.models.deploy_app import BaseGCPDeployApp
from cloudshell.cp.gcp.resource_conf import GCPResourceConfig
from cloudshell.api.cloudshell_api import CloudShellAPISession, ReservationInfo
Expand Down Expand Up @@ -91,8 +93,11 @@ def _prepare_deploy_app_result(
)

@abstractmethod
def _create_instance(self, deploy_app: BaseGCPDeployApp, subnet_list: list[str],
tags: dict[str, str]) -> Instance:
def _create_instance(
self,
deploy_app: BaseGCPDeployApp,
subnet_list: list[str],
) -> Instance:
""""""
pass

Expand All @@ -110,7 +115,17 @@ def _deploy(self, request_actions: DeployVMRequestActions) -> DeployAppResult:
if not subnet_list:
subnet_list = network_handler.get_subnets()

if not deploy_app.machine_type or deploy_app.machine_type == "Inherited":
deploy_app.machine_type = self.resource_config.machine_type

deploy_app.custom_tags = self._get_tags(deploy_app)
deploy_app.zone = ZoneHandler(
credentials=self.resource_config.credentials,
zone=deploy_app.zone
).get_zone(
region=self.resource_config.region,
machine_type=deploy_app.machine_type
)
with self.cancellation_manager:
instance = self._create_instance(
deploy_app=deploy_app,
Expand All @@ -119,13 +134,14 @@ def _deploy(self, request_actions: DeployVMRequestActions) -> DeployAppResult:

net_tags = self._get_network_tags(instance, deploy_app)
if net_tags:
instance.tags = net_tags.keys()
instance.tags = InstanceHandler.add_network_tags(net_tags.keys())

with self._rollback_manager:
logger.info(f"Creating Instance {instance.name}")
deployed_instance = DeployInstanceCommand(
instance=instance,
credentials=self.resource_config.credentials,
zone=deploy_app.zone,
rollback_manager=self._rollback_manager,
cancellation_manager=self.cancellation_manager,
).execute()
Expand Down Expand Up @@ -175,7 +191,7 @@ def _get_network(self) -> VPCHandler:
)

def _get_tags(self, deploy_app: BaseGCPDeployApp) -> dict[str, str]:
tags = self.resource_config.custom_tags | deploy_app.custom_tags
tags = self.resource_config.tags | deploy_app.custom_tags
tags["ssh-keys"] = self._add_ssh_key(deploy_app)
if self._is_windows(deploy_app) and deploy_app.password:
tags["sysprep-specialize-script-ps1"] = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from contextlib import suppress
from typing import TYPE_CHECKING

from attr import define
from google.cloud.exceptions import NotFound

from cloudshell.cp.core.cancellation_manager import CancellationContextManager
Expand All @@ -17,9 +18,11 @@
from google.cloud.compute_v1.types import compute


@define
class DeployInstanceCommand(RollbackCommand, GlobalLock):
instance: compute.Instance
credentials: Credentials
zone: str
rollback_manager: RollbackCommandsManager
cancellation_manager: CancellationContextManager

Expand All @@ -30,11 +33,12 @@ def __attrs_post_init__(self):
)

@GlobalLock.lock
def _execute(self) -> str:
def _execute(self) -> compute.Instance:
try:
self._instance_handler = InstanceHandler.deploy(
instance=self.instance,
credentials=self.credentials
credentials=self.credentials,
zone=self.zone,
)
except Exception as e:
raise
Expand Down
142 changes: 41 additions & 101 deletions cloudshell/cp/gcp/handlers/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
from functools import cached_property
from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING

from attrs import define
from google.cloud import compute_v1
Expand All @@ -19,7 +19,7 @@

if TYPE_CHECKING:
from google.auth.credentials import Credentials
from typing_extensions import Self
from typing_extensions import Self, Union, Iterable
from cloudshell.cp.gcp.resource_conf import GCPResourceConfig

logger = logging.getLogger(__name__)
Expand All @@ -32,41 +32,6 @@ class Instance:
resource_config: GCPResourceConfig
subnet_list: list[str]

@property
def zone_name(self):
"""Determine short zone.
Zone value depends on provided zone, region and/or machine type.
"""
if not self.deploy_app.zone:
region_client = compute_v1.RegionsClient(
credentials=self.resource_config.credentials
)
region_info = region_client.get(
project=self.resource_config.credentials.project_id,
region=self.resource_config.region
)

zones = [zone.split('/')[-1] for zone in region_info.zones]
else:
zones = [self.deploy_app.zone]

machine_type_client = compute_v1.MachineTypesClient(
credentials=self.resource_config.credentials
)

# List all machine types in the specified zone
for zone in zones:
machine_types = machine_type_client.list(
project=self.resource_config.credentials.project_id,
zone=zone
)
for machine_type in machine_types:
if self.deploy_app.machine_type == machine_type.name:
return zone

raise AttributeGCPError("Incompatible zone and machine type values.")

def from_scratch(self):
"""Build GCP Instance from scratch."""
# Define the VM settings
Expand All @@ -76,27 +41,28 @@ def from_scratch(self):
generate=self.deploy_app.autogenerated_name
)
instance.can_ip_forward = self.deploy_app.ip_forwarding
instance.machine_type = f"zones/{self.zone_name}/machineTypes/{self.deploy_app.machine_type}"
instance.machine_type = f"zones/{self.deploy_app.zone}/machineTypes/{self.deploy_app.machine_type}"

# instance.tags = ["str", "str"]

scheduling = compute_v1.Scheduling()
scheduling.automatic_restart = self.deploy_app.auto_restart
scheduling.on_host_maintenance = self.deploy_app.maintenance
scheduling.on_host_maintenance = self.deploy_app.maintenance.upper()
instance.scheduling = scheduling

# Create boot disk
boot_disk = compute_v1.AttachedDisk()
boot_disk.boot = True
boot_disk.disk_name = GCPNameGenerator().instance_disk(
boot_disk.device_name = GCPNameGenerator().instance_disk(
instance_name=instance.name,
disk_num=0
)
# boot_disk.auto_delete = self.deploy_app.disk_rule
disk_initialize_params = compute_v1.AttachedDiskInitializeParams()
disk_initialize_params.disk_size_gb = self.deploy_app.disk_size
disk_initialize_params.disk_type = f"zones/{self.zone_name}/diskTypes/{self.deploy_app.disk_type}"
disk_initialize_params.source_image = f"projects/{self.deploy_app.project_cloud}/global/images/family/{self.deploy_app.disk_image}"
disk_initialize_params.disk_type = f"zones/{self.deploy_app.zone}/diskTypes/{self.deploy_app.disk_type}"
# projects/debian-cloud/global/images/debian-12-bookworm-v20240910
disk_initialize_params.source_image = f"projects/{self.deploy_app.project_cloud}/global/images/{self.deploy_app.disk_image}"
boot_disk.initialize_params = disk_initialize_params
instance.disks = [boot_disk]

Expand Down Expand Up @@ -136,7 +102,7 @@ def from_template(self):
)

instance.can_ip_forward = self.deploy_app.ip_forwarding or instance_template.properties.can_ip_forward
instance.machine_type = f"zones/{self.zone_name}/machineTypes/{self.deploy_app.machine_type}" or instance_template.properties.machine_type
instance.machine_type = f"zones/{self.deploy_app.zone}/machineTypes/{self.deploy_app.machine_type}" or instance_template.properties.machine_type

# instance.tags = ["str", "str"]

Expand All @@ -155,12 +121,12 @@ def from_template(self):
disk_initialize_params = compute_v1.AttachedDiskInitializeParams()
disk_initialize_params.disk_size_gb = self.deploy_app.disk_size or disk.initialize_params.disk_size_gb
if self.deploy_app.disk_type:
disk_initialize_params.disk_type = f"zones/{self.zone_name}/diskTypes/{self.deploy_app.disk_type}"
disk_initialize_params.disk_type = f"zones/{self.deploy_app.zone}/diskTypes/{self.deploy_app.disk_type}"
else:
disk_initialize_params.disk_type = disk.initialize_params.disk_type

if self.deploy_app.disk_image:
disk_initialize_params.source_image = f"projects/{self.deploy_app.project_cloud}/global/images/family/{self.deploy_app.disk_image}"
disk_initialize_params.source_image = f"projects/{self.deploy_app.project_cloud}/global/images/{self.deploy_app.disk_image}"
else:
disk_initialize_params.source_image = disk.initialize_params.source_image

Expand Down Expand Up @@ -210,13 +176,14 @@ def from_machine_image(self): # machine_image.source_instance_properties
)

instance.can_ip_forward = self.deploy_app.ip_forwarding or machine_image.source_instance_properties.can_ip_forward
instance.machine_type = f"zones/{self.zone_name}/machineTypes/{self.deploy_app.machine_type}" or machine_image.source_instance_properties.properties.machine_type
instance.machine_type = f"zones/{self.deploy_app.zone}/machineTypes/{self.deploy_app.machine_type}" or machine_image.source_instance_properties.properties.machine_type

# instance.tags = ["str", "str"]

scheduling = compute_v1.Scheduling()
scheduling.automatic_restart = self.deploy_app.auto_restart or machine_image.source_instance_properties.scheduling.automatic_restart
scheduling.on_host_maintenance = self.deploy_app.maintenance or machine_image.source_instance_properties.scheduling.on_host_maintenance
scheduling.on_host_maintenance = (self.deploy_app.maintenance or
machine_image.source_instance_properties.scheduling.on_host_maintenance).upper()
instance.scheduling = scheduling

# Create disks
Expand Down Expand Up @@ -256,7 +223,6 @@ def _add_interfaces(self, instance):
instance.network_interfaces.append(network_interface)



@define
class InstanceHandler(BaseGCPHandler):
instance: Instance
Expand All @@ -266,30 +232,40 @@ def instance_client(self):
return compute_v1.InstancesClient(credentials=self.credentials)

@classmethod
def deploy(cls, instance: Instance, credentials: Credentials) -> Self:
def deploy(
cls,
instance: compute.Instance,
credentials: Credentials,
zone: str
) -> Self:
"""Get instance object from GCP and create InstanceHandler object."""
logger.info("Start deploying Instance.")
client = compute_v1.InstancesClient(credentials=credentials)

operation = client.insert(
project=credentials.project_id,
zone=instance.zone_name,
zone=zone,
instance_resource=instance
)

# Wait for the operation to complete
BaseGCPHandler(credentials=credentials).wait_for_operation(
name=operation.name,
zone=instance.zone_name
operation_client = compute_v1.ZoneOperationsClient(
credentials=credentials
)
logger.info(f"Instance '{instance.name}' created successfully.")

instance = client.get(
operation_client.wait(
project=credentials.project_id,
zone=instance.zone_name,
instance=instance.name
zone=zone,
operation=operation.name
)
logger.info(f"Instance '{instance.name}' created successfully.")
if operation.done and operation.error:
raise Exception(f"Instance {instance.name} deployment failed: "
f"{operation.error_code} - {operation.error_message}")
return cls.get(
credentials=credentials,
zone=zone,
instance_name=instance.name
)
return cls(instance=instance, credentials=credentials)

@classmethod
def get(cls, instance_name: str, zone: str, credentials: Credentials) -> Self:
Expand Down Expand Up @@ -360,48 +336,6 @@ def stop(self) -> None:

logger.info(f"VM '{self.instance.name}' stopped successfully.")

def add_tag(self) -> None:
"""Add tag to existed Virtual Machine."""
# Get the existing VM
vm = self.get_vm_by_name(vm_name, zone)

# Add the new tag
vm.tags.append(tag)

# Update the VM
operation = self.instance_client.update(
project=self.credentials.project_id,
zone=self._zone,
instance=self.instance.name,
instance_resource=vm,
)

# Wait for the operation to complete
self.wait_for_operation(name=operation.name, zone=self._zone)

logger.info(f"Tag '{tag}' added to VM '{self.instance.name}'.")

def remove_tag(self, vm_name: str, tag: str, *, zone: str) -> None:
"""Remove tag."""
# Get the existing VM
vm = self.get_vm_by_name(vm_name, zone)

# Remove the tag
vm.tags.remove(tag)

# Update the VM
operation = self.instance_client.update(
project=self.credentials.project_id,
zone=self._zone,
instance=self.instance.name,
instance_resource=vm,
)

# Wait for the operation to complete
self.wait_for_operation(name=operation.name, zone=self._zone)

logger.info(f"Tag '{tag}' removed from VM '{self.instance.name}'.")

def add_metadata(self, vm_name: str, key: str, value: str, *, zone: str) -> None:
"""Add metadata record."""
# Get the existing VM
Expand Down Expand Up @@ -793,6 +727,12 @@ def remove_public_static_ip(self, vm_name: str, ip: str, *, zone: str) -> None:

logger.info(f"Public static IP removed from VM '{self.instance.name}'.")

@staticmethod
def add_network_tags(tags: Iterable[str]) -> compute.Tags:
"""Add network tags to the instance."""
gcp_tags = compute_v1.Tags()
gcp_tags.items = list(tags)
return gcp_tags

"""
class Instance(compute.Instance):
Expand Down
Loading

0 comments on commit 3a70e0d

Please sign in to comment.