import json import logging from typing import List, Optional from aliyunsdkcore import client from aliyunsdkcore.acs_exception.exceptions import ClientException, ServerException from aliyunsdkecs.request.v20140526.AllocatePublicIpAddressRequest import ( AllocatePublicIpAddressRequest, ) from aliyunsdkecs.request.v20140526.AuthorizeSecurityGroupRequest import ( AuthorizeSecurityGroupRequest, ) from aliyunsdkecs.request.v20140526.CreateInstanceRequest import CreateInstanceRequest from aliyunsdkecs.request.v20140526.CreateKeyPairRequest import CreateKeyPairRequest from aliyunsdkecs.request.v20140526.CreateSecurityGroupRequest import ( CreateSecurityGroupRequest, ) from aliyunsdkecs.request.v20140526.CreateVpcRequest import CreateVpcRequest from aliyunsdkecs.request.v20140526.CreateVSwitchRequest import CreateVSwitchRequest from aliyunsdkecs.request.v20140526.DeleteInstanceRequest import DeleteInstanceRequest from aliyunsdkecs.request.v20140526.DeleteInstancesRequest import DeleteInstancesRequest from aliyunsdkecs.request.v20140526.DeleteKeyPairsRequest import DeleteKeyPairsRequest from aliyunsdkecs.request.v20140526.DescribeInstancesRequest import ( DescribeInstancesRequest, ) from aliyunsdkecs.request.v20140526.DescribeKeyPairsRequest import ( DescribeKeyPairsRequest, ) from aliyunsdkecs.request.v20140526.DescribeSecurityGroupsRequest import ( DescribeSecurityGroupsRequest, ) from aliyunsdkecs.request.v20140526.DescribeVpcsRequest import DescribeVpcsRequest from aliyunsdkecs.request.v20140526.DescribeVSwitchesRequest import ( DescribeVSwitchesRequest, ) from aliyunsdkecs.request.v20140526.ImportKeyPairRequest import ImportKeyPairRequest from aliyunsdkecs.request.v20140526.RunInstancesRequest import RunInstancesRequest from aliyunsdkecs.request.v20140526.StartInstanceRequest import StartInstanceRequest from aliyunsdkecs.request.v20140526.StopInstanceRequest import StopInstanceRequest from aliyunsdkecs.request.v20140526.StopInstancesRequest import StopInstancesRequest from aliyunsdkecs.request.v20140526.TagResourcesRequest import TagResourcesRequest class AcsClient: """ A wrapper around Aliyun SDK. We use this wrapper in aliyun node provider. Parameters: access_key: The AccessKey ID of your aliyun account. access_key_secret: The AccessKey secret of your aliyun account. region_id: A region is a geographic area where a data center resides. Region_id is the ID of region (e.g., cn-hangzhou, us-west-1, etc.) max_retries: The maximum number of retries each connection. """ def __init__( self, access_key: str, access_key_secret: str, region_id: str, max_retries: int, ): self.cli = client.AcsClient( ak=access_key, secret=access_key_secret, max_retry_time=max_retries, region_id=region_id, ) def describe_instances( self, tags: Optional[List[dict]] = None, instance_ids: Optional[List[str]] = None, ) -> Optional[list]: """Query the details of one or more Elastic Compute Service (ECS) instances. Args: tags: The tags of the instance. instance_ids: The IDs of ECS instances. Returns: ECS instance list. """ request = DescribeInstancesRequest() if tags is not None: request.set_Tags(tags) if instance_ids is not None: request.set_InstanceIds(instance_ids) response = self._send_request(request) if response is not None: instance_list = response.get("Instances").get("Instance") return instance_list return None def create_instance( self, instance_type: str, image_id: str, tags: List[dict], key_pair_name: str, optimized: str = "optimized", instance_charge_type: str = "PostPaid", spot_strategy: str = "SpotWithPriceLimit", internet_charge_type: str = "PayByTraffic", internet_max_bandwidth_out: int = 5, ) -> Optional[str]: """Create a subscription or pay-as-you-go ECS instance. Args: instance_type: The instance type of the ECS. image_id: The ID of the image used to create the instance. tags: The tags of the instance. key_pair_name: The name of the key pair to be bound to the instance. optimized: Specifies whether the instance is I/O optimized. instance_charge_type: The billing method of the instance. Default value: PostPaid. spot_strategy: The preemption policy for the pay-as-you-go instance. internet_charge_type: The billing method for network usage. Default value: PayByTraffic. internet_max_bandwidth_out: The maximum inbound public bandwidth. Unit: Mbit/s. Returns: The created instance ID. """ request = CreateInstanceRequest() request.set_InstanceType(instance_type) request.set_ImageId(image_id) request.set_IoOptimized(optimized) request.set_InstanceChargeType(instance_charge_type) request.set_SpotStrategy(spot_strategy) request.set_InternetChargeType(internet_charge_type) request.set_InternetMaxBandwidthOut(internet_max_bandwidth_out) request.set_KeyPairName(key_pair_name) request.set_Tags(tags) response = self._send_request(request) if response is not None: instance_id = response.get("InstanceId") logging.info("instance %s created task submit successfully.", instance_id) return instance_id logging.error("instance created failed.") return None def run_instances( self, instance_type: str, image_id: str, tags: List[dict], security_group_id: str, vswitch_id: str, key_pair_name: str, amount: int = 1, optimized: str = "optimized", instance_charge_type: str = "PostPaid", spot_strategy: str = "SpotWithPriceLimit", internet_charge_type: str = "PayByTraffic", internet_max_bandwidth_out: int = 1, ) -> Optional[List[str]]: """Create one or more pay-as-you-go or subscription ECS instances. Args: instance_type: The instance type of the ECS. image_id: The ID of the image used to create the instance. tags: The tags of the instance. security_group_id: The ID of the security group to which to assign the instance. Instances in the same security group can communicate with each other. vswitch_id: The ID of the vSwitch to which to connect the instance. key_pair_name: The name of the key pair to be bound to the instance. amount: The number of instances that you want to create. optimized: Specifies whether the instance is I/O optimized. instance_charge_type: The billing method of the instance. Default value: PostPaid. spot_strategy: The preemption policy for the pay-as-you-go instance. internet_charge_type: The billing method for network usage. Default value: PayByTraffic. internet_max_bandwidth_out: The maximum inbound public bandwidth. Unit: Mbit/s. Returns: The created instance IDs. """ request = RunInstancesRequest() request.set_InstanceType(instance_type) request.set_ImageId(image_id) request.set_IoOptimized(optimized) request.set_InstanceChargeType(instance_charge_type) request.set_SpotStrategy(spot_strategy) request.set_InternetChargeType(internet_charge_type) request.set_InternetMaxBandwidthOut(internet_max_bandwidth_out) request.set_Tags(tags) request.set_Amount(amount) request.set_SecurityGroupId(security_group_id) request.set_VSwitchId(vswitch_id) request.set_KeyPairName(key_pair_name) response = self._send_request(request) if response is not None: instance_ids = response.get("InstanceIdSets").get("InstanceIdSet") return instance_ids logging.error("instance created failed.") return None def create_security_group(self, vpc_id: str) -> Optional[str]: """Create a security group. Args: vpc_id: The ID of the VPC in which to create the security group. Returns: The created security group ID. """ request = CreateSecurityGroupRequest() request.set_VpcId(vpc_id) response = self._send_request(request) if response is not None: security_group_id = response.get("SecurityGroupId") return security_group_id return None def describe_security_groups( self, vpc_id: Optional[str] = None, tags: Optional[List[dict]] = None, ) -> Optional[list]: """Query basic information of security groups. Args: vpc_id: The ID of the VPC to which the security group belongs. tags: The tags of the security group. Returns: Security group list. """ request = DescribeSecurityGroupsRequest() if vpc_id is not None: request.set_VpcId(vpc_id) if tags is not None: request.set_Tags(tags) response = self._send_request(request) if response is not None: security_groups = response.get("SecurityGroups").get("SecurityGroup") return security_groups logging.error("describe security group failed.") return None def authorize_security_group( self, ip_protocol: str, port_range: str, security_group_id: str, source_cidr_ip: str, ) -> None: """Create an inbound security group rule. Args: ip_protocol: The transport layer protocol. port_range: The range of destination ports relevant to the transport layer protocol. security_group_id: The ID of the destination security group. source_cidr_ip: The range of source IPv4 addresses. CIDR blocks and IPv4 addresses are supported. """ request = AuthorizeSecurityGroupRequest() request.set_IpProtocol(ip_protocol) request.set_PortRange(port_range) request.set_SecurityGroupId(security_group_id) request.set_SourceCidrIp(source_cidr_ip) self._send_request(request) def create_v_switch( self, vpc_id: str, zone_id: str, cidr_block: str ) -> Optional[str]: """Create vSwitches to divide the VPC into one or more subnets. Args: vpc_id: The ID of the VPC to which the VSwitch belongs. zone_id: The ID of the zone to which the target VSwitch belongs. cidr_block: The CIDR block of the VSwitch. Returns: The created VSwitch ID, or None if the request failed. """ request = CreateVSwitchRequest() request.set_ZoneId(zone_id) request.set_VpcId(vpc_id) request.set_CidrBlock(cidr_block) response = self._send_request(request) if response is not None: return response.get("VSwitchId") else: logging.error("create_v_switch vpc_id %s failed.", vpc_id) return None def create_vpc(self) -> Optional[str]: """Create a virtual private cloud (VPC). Returns: The created VPC ID. """ request = CreateVpcRequest() response = self._send_request(request) if response is not None: return response.get("VpcId") return None def describe_vpcs(self) -> Optional[list]: """Query one or more VPCs in a region. Returns: VPC list. """ request = DescribeVpcsRequest() response = self._send_request(request) if response is not None: return response.get("Vpcs").get("Vpc") return None def tag_resource( self, resource_ids: List[str], tags: List[dict], resource_type: str = "instance", ) -> None: """Create and bind tags to specified ECS resources. Args: resource_ids: The IDs of N resources. tags: The tags of the resource. resource_type: The type of the resource. """ request = TagResourcesRequest() request.set_Tags(tags) request.set_ResourceType(resource_type) request.set_ResourceIds(resource_ids) response = self._send_request(request) if response is not None: logging.info("instance %s create tag successfully.", resource_ids) else: logging.error("instance %s create tag failed.", resource_ids) def start_instance(self, instance_id: str) -> None: """Start an ECS instance. Args: instance_id: The ECS instance ID. """ request = StartInstanceRequest() request.set_InstanceId(instance_id) response = self._send_request(request) if response is not None: logging.info("instance %s start successfully.", instance_id) else: logging.error("instance %s start failed.", instance_id) def stop_instance(self, instance_id: str, force_stop: bool = False) -> None: """Stop an ECS instance that is in the Running state. Args: instance_id: The ECS instance ID. force_stop: Specifies whether to forcibly stop the instance. """ request = StopInstanceRequest() request.set_InstanceId(instance_id) request.set_ForceStop(force_stop) logging.info("Stop %s command submit successfully.", instance_id) self._send_request(request) def stop_instances( self, instance_ids: List[str], stopped_mode: str = "StopCharging" ) -> None: """Stop one or more ECS instances that are in the Running state. Args: instance_ids: The IDs of instances. stopped_mode: Specifies whether billing for the instance continues after the instance is stopped. """ request = StopInstancesRequest() request.set_InstanceIds(instance_ids) request.set_StoppedMode(stopped_mode) response = self._send_request(request) if response is None: logging.error("stop_instances failed") def delete_instance(self, instance_id: str) -> None: """Release a pay-as-you-go instance or an expired subscription instance. Args: instance_id: The ID of the instance that you want to release. """ request = DeleteInstanceRequest() request.set_InstanceId(instance_id) request.set_Force(True) logging.info("Delete %s command submit successfully", instance_id) self._send_request(request) def delete_instances(self, instance_ids: List[str]) -> None: """Release one or more pay-as-you-go or expired subscription instances. Args: instance_ids: The IDs of instances that you want to release. """ request = DeleteInstancesRequest() request.set_Force(True) request.set_InstanceIds(instance_ids) self._send_request(request) def allocate_public_address(self, instance_id: str) -> Optional[str]: """Assign a public IP address to an ECS instance. Args: instance_id: The ID of the instance to which you want to assign a public IP address. Returns: The assigned IP address. """ request = AllocatePublicIpAddressRequest() request.set_InstanceId(instance_id) response = self._send_request(request) if response is not None: return response.get("IpAddress") def create_key_pair(self, key_pair_name: str) -> Optional[dict]: """Create an SSH key pair. Args: key_pair_name: The name of the key pair. Returns: The created keypair data. """ request = CreateKeyPairRequest() request.set_KeyPairName(key_pair_name) response = self._send_request(request) if response is not None: logging.info("Create Key Pair %s Successfully", response.get("KeyPairId")) return response else: logging.error("Create Key Pair Failed") return None def import_key_pair(self, key_pair_name: str, public_key_body: str) -> None: """Import the public key of an RSA-encrypted key pair. Args: key_pair_name: The name of the key pair. public_key_body: The public key of the key pair. """ request = ImportKeyPairRequest() request.set_KeyPairName(key_pair_name) request.set_PublicKeyBody(public_key_body) self._send_request(request) def delete_key_pairs(self, key_pair_names: List[str]) -> None: """Delete one or more SSH key pairs. Args: key_pair_names: The names of the key pairs. """ request = DeleteKeyPairsRequest() request.set_KeyPairNames(key_pair_names) self._send_request(request) def describe_key_pairs(self, key_pair_name: Optional[str] = None) -> Optional[list]: """Query one or more key pairs. Args: key_pair_name: The name of the key pair. Returns: Key pair list, or None if the request failed. """ request = DescribeKeyPairsRequest() if key_pair_name is not None: request.set_KeyPairName(key_pair_name) response = self._send_request(request) if response is not None: return response.get("KeyPairs").get("KeyPair") else: return None def describe_v_switches(self, vpc_id: Optional[str] = None) -> Optional[list]: """Query one or more VSwitches. Args: vpc_id: The ID of the VPC to which the VSwitch belongs. Returns: VSwitch list. """ request = DescribeVSwitchesRequest() if vpc_id is not None: request.set_VpcId(vpc_id) response = self._send_request(request) if response is not None: return response.get("VSwitches").get("VSwitch") else: logging.error("Describe VSwitches Failed.") return None def _send_request(self, request): """send open api request""" request.set_accept_format("json") try: response_str = self.cli.do_action_with_exception(request) response_detail = json.loads(response_str) return response_detail except (ClientException, ServerException) as e: logging.error(request.get_action_name()) logging.error(e) return None