diff --git a/AUTHORS b/AUTHORS index 41eb562fb8..4ad471152a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,3 +5,4 @@ Intel Inc Massachusetts Institute of Technology (MIT) Rackspace Inc Alibaba Inc +Tencent LTD \ No newline at end of file diff --git a/perfkitbenchmarker/providers/__init__.py b/perfkitbenchmarker/providers/__init__.py index c6001d4720..5468850eb8 100644 --- a/perfkitbenchmarker/providers/__init__.py +++ b/perfkitbenchmarker/providers/__init__.py @@ -37,12 +37,13 @@ RACKSPACE = 'Rackspace' MESOS = 'Mesos' PROFITBRICKS = 'ProfitBricks' +TENCENTCLOUD = 'TencentCloud' # Though Docker is not a cloud provider, it's inclusion is useful # for performing on premise to cloud benchmarks DOCKER = 'Docker' VALID_CLOUDS = (GCP, AZURE, AWS, IBMCLOUD, DIGITALOCEAN, KUBERNETES, OPENSTACK, - RACKSPACE, CLOUDSTACK, ALICLOUD, MESOS, PROFITBRICKS, DOCKER) + RACKSPACE, CLOUDSTACK, ALICLOUD, MESOS, PROFITBRICKS, DOCKER, TENCENTCLOUD) _imported_providers = set() diff --git a/perfkitbenchmarker/providers/alicloud/ali_network.py b/perfkitbenchmarker/providers/alicloud/ali_network.py index 79a9db3786..555fc45b58 100644 --- a/perfkitbenchmarker/providers/alicloud/ali_network.py +++ b/perfkitbenchmarker/providers/alicloud/ali_network.py @@ -48,6 +48,7 @@ def __init__(self, name, region): def _Create(self): """Creates the VPC.""" + 'aliyun ecs CreateVpc --VpcName xxx --RegionId xxx' create_cmd = util.ALI_PREFIX + [ 'ecs', 'CreateVpc', diff --git a/perfkitbenchmarker/providers/gcp/flags.py b/perfkitbenchmarker/providers/gcp/flags.py index 0c530d4a2c..0e234b6217 100644 --- a/perfkitbenchmarker/providers/gcp/flags.py +++ b/perfkitbenchmarker/providers/gcp/flags.py @@ -18,6 +18,7 @@ # Sentinel value for unspecified platform. GCP_MIN_CPU_PLATFORM_NONE = 'none' + flags.DEFINE_string('gcloud_path', 'gcloud', 'The path for the gcloud utility.') flags.DEFINE_list('additional_gcloud_flags', [], 'Additional flags to pass to gcloud.') diff --git a/perfkitbenchmarker/providers/tencentcloud/__init__.py b/perfkitbenchmarker/providers/tencentcloud/__init__.py new file mode 100644 index 0000000000..bc0b3fd957 --- /dev/null +++ b/perfkitbenchmarker/providers/tencentcloud/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2014 PerfKitBenchmarker Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Provider for Tencent Cloud.""" \ No newline at end of file diff --git a/perfkitbenchmarker/providers/tencentcloud/flags.py b/perfkitbenchmarker/providers/tencentcloud/flags.py new file mode 100644 index 0000000000..481815b9c6 --- /dev/null +++ b/perfkitbenchmarker/providers/tencentcloud/flags.py @@ -0,0 +1,31 @@ +# Copyright 2015 PerfKitBenchmarker Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module containing flags applicable across benchmark run on Tencent Cloud.""" + +from absl import flags + +flags.DEFINE_string('tencent_path', 'tccli', 'The path for the Tencent Cloud utility.') +flags.DEFINE_string('default_region', 'ap-guangzhou-3', + 'The path for the Tencent Cloud utility.') +flags.DEFINE_string('unfold', '--cli-unfold-argument', + 'Unfold complicated arguments, using . to access values of ' + 'complicated arguments') +flags.DEFINE_integer('bandwith_out', 100, + 'The maximum outbound bandwidth of the public network, in Mbps. ' + 'The default value is 100 Mbps. ') +flags.DEFINE_boolean('assign_public_ip', True, + 'If the public network bandwidth is greater than 0 Mbps, you can ' + 'choose whether to assign a public IP. If the public network ' + 'bandwidth is 0 Mbps, you will not be able to assign a public IP.') + diff --git a/perfkitbenchmarker/providers/tencentcloud/provider_info.py b/perfkitbenchmarker/providers/tencentcloud/provider_info.py new file mode 100644 index 0000000000..0798c99fdb --- /dev/null +++ b/perfkitbenchmarker/providers/tencentcloud/provider_info.py @@ -0,0 +1,24 @@ +# Copyright 2015 PerfKitBenchmarker Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Provider info for Tencent Cloud.""" + +from perfkitbenchmarker import provider_info +from perfkitbenchmarker import providers + + +class GCPProviderInfo(provider_info.BaseProviderInfo): + + UNSUPPORTED_BENCHMARKS = [] + CLOUD = providers.TENCENTCLOUD diff --git a/perfkitbenchmarker/providers/tencentcloud/tencentcloud_disk.py b/perfkitbenchmarker/providers/tencentcloud/tencentcloud_disk.py new file mode 100644 index 0000000000..b509669d59 --- /dev/null +++ b/perfkitbenchmarker/providers/tencentcloud/tencentcloud_disk.py @@ -0,0 +1,113 @@ +# Copyright 2014 PerfKitBenchmarker Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module containing classes related to Tencent Cloud disks. + +Disks can be created, deleted, attached to VMs, and detached from VMs. +Use 'DescribeDisks' to determine valid disk types. +""" + +import json +import logging +import string +import threading +from absl import flags +from perfkitbenchmarker import disk +from perfkitbenchmarker import vm_util +from perfkitbenchmarker.providers.tencentcloud import tencentcloud_network +from perfkitbenchmarker.providers.tencentcloud import util + +FLAGS = flags.FLAGS + +DISK_TYPE = {} + +class TencentDisk(disk.BaseDisk): + """Object representing an AliCloud Disk.""" + + _lock = threading.Lock() + vm_devices = {} + + def __init__(self, disk_spec, zone): + super(TencentDisk, self).__init__(disk_spec) + self.id = None + self.zone = zone + self.region = util.GetRegionByZone(self.zone) + self.attached_vm_id = None + + def _Create(self): + """Creates the disk.""" + create_cmd = '' + create_cmd = util.GetEncodedCmd(create_cmd) + stdout, _, _ = vm_util.IssueCommand(create_cmd, raise_on_failure=False) + response = json.loads(stdout) + self.id = response['DiskId'] + + def _Delete(self): + """Deletes the disk.""" + delete_cmd = '' + logging.info('Deleting Tencent Cloud disk %s. This may fail if the disk is not ' + 'yet detached, but will be retried.', self.id) + delete_cmd = util.GetEncodedCmd(delete_cmd) + vm_util.IssueRetryableCommand(delete_cmd) + + def Attach(self, vm): + """Attaches the disk to a VM. + + Args: + vm: The AliVirtualMachine instance to which the disk will be attached. + """ + with self._lock: + self.attached_vm_id = vm.id + if self.attached_vm_id not in TencentDisk.vm_devices: + TencentDisk.vm_devices[self.attached_vm_id] = set( + string.ascii_lowercase[1:]) + self.device_letter = min(TencentDisk.vm_devices[self.attached_vm_id]) + TencentDisk.vm_devices[self.attached_vm_id].remove(self.device_letter) + + attach_cmd = '' + attach_cmd = util.GetEncodedCmd(attach_cmd) + vm_util.IssueRetryableCommand(attach_cmd) + + def Detach(self): + """Detaches the disk from a VM.""" + detach_cmd = '' + detach_cmd = util.GetEncodedCmd(detach_cmd) + vm_util.IssueRetryableCommand(detach_cmd) + + with self._lock: + assert self.attached_vm_id in TencentDisk.vm_devices + TencentDisk.vm_devices[self.attached_vm_id].add(self.device_letter) + self.attached_vm_id = None + self.device_letter = None + + def GetDevicePath(self): + """Returns the path to the device inside the VM.""" + return '/dev/vd%s' % self.device_letter + + def GetVirtualDevicePath(self): + """Returns the path to the device visible to console users.""" + return '/dev/xvd%s' % self.device_letter + + @vm_util.Retry(poll_interval=5, max_retries=30, log_errors=False) + def WaitForDiskStatus(self, status_list): + """Waits until disk is attach to the instance""" + logging.info('Waits until the disk\'s status is one of statuses: %s', + status_list) + describe_cmd = '' + attach_cmd = util.GetEncodedCmd(describe_cmd) + stdout, _ = vm_util.IssueRetryableCommand(attach_cmd) + response = json.loads(stdout) + disk = response['Disks']['Disk'] + assert len(disk) == 1 + status = disk[0]['Status'] + assert status in status_list diff --git a/perfkitbenchmarker/providers/tencentcloud/tencentcloud_network.py b/perfkitbenchmarker/providers/tencentcloud/tencentcloud_network.py new file mode 100644 index 0000000000..d277ffbf59 --- /dev/null +++ b/perfkitbenchmarker/providers/tencentcloud/tencentcloud_network.py @@ -0,0 +1,167 @@ +# Copyright 2014 PerfKitBenchmarker Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Module containing classes related to GCE VM networking. + +The Firewall class provides a way of opening VM ports. The Network class allows +VMs to communicate via internal ips and isolates PerfKitBenchmarker VMs from +others in the +same project. See https://cloud.tencent.com/document/product/213/5220 for +more information about Tencent Cloud VM networking. +""" + + +import json +import logging +import threading +import uuid + +from absl import flags +from perfkitbenchmarker import network +from perfkitbenchmarker import providers +from perfkitbenchmarker import resource +from perfkitbenchmarker import vm_util +from perfkitbenchmarker.providers.alicloud import util +from six.moves import range + +FLAGS = flags.FLAGS +MAX_NAME_LENGTH = 128 + + +class TencentVpc(resource.BaseResource): + """An object representing an Tencent Cloud VPC.""" + + def __init__(self, name, region): + pass + + def _Create(self): + """Creates the VPC.""" + pass + + def _Exists(self): + """Returns true if the VPC exists.""" + pass + + @vm_util.Retry(poll_interval=5, max_retries=30, log_errors=False) + def _WaitForVpcStatus(self, status_list): + """Waits until VPC's status is in status_list""" + pass + + def _Delete(self): + """Delete's the VPC.""" + pass + + +class TencentVSwitch(resource.BaseResource): + """An object representing an Tencent Cloud VSwitch.""" + + def __init__(self, name, zone, vpc_id): + pass + + def _Create(self): + """Creates the VSwitch.""" + pass + + def _Delete(self): + """Deletes the VSwitch.""" + pass + + def _Exists(self): + """Returns true if the VSwitch exists.""" + pass + + +class TencentSecurityGroup(resource.BaseResource): + """Object representing an Tencent Cloud Security Group.""" + + def __init__(self, name, region, use_vpc=True, vpc_id=None): + pass + + def _Create(self): + """Creates the security group.""" + pass + + def _Delete(self): + """Deletes the security group.""" + pass + + def _Exists(self): + """Returns true if the security group exists.""" + pass + + +class TencentFirewall(network.BaseFirewall): + """An object representing the Tencent Cloud Firewall.""" + + CLOUD = providers.TENCENTCLOUD + + def __init__(self): + self.firewall_set = set() + self._lock = threading.Lock() + + def AllowIcmp(self, vm): + """Opens the ICMP protocol on the firewall. + + Args: + vm: The BaseVirtualMachine object to open the ICMP protocol for. + """ + pass + + def AllowPort(self, vm, start_port, end_port=None, source_range=None): + """Opens a port on the firewall. + + Args: + vm: The BaseVirtualMachine object to open the port for. + start_port: The first local port in a range of ports to open. + end_port: The last port in a range of ports to open. If None, only + start_port will be opened. + source_range: unsupported at present. + """ + pass + + def _AllowPort(self, vm, port): + """Opens a port on the firewall. + + Args: + vm: The BaseVirtualMachine object to open the port for. + port: The local port to open. + """ + pass + + def DisallowAllPorts(self): + """Closes all ports on the firewall.""" + pass + + +class TencentNetwork(network.BaseNetwork): + """Object representing a Tencent Cloud Network.""" + + CLOUD = providers.TENCENTCLOUD + + def __init__(self, spec): + self.name = None + self.region = util.GetRegionByZone(spec.zone) + self.use_vpc = FLAGS.ali_use_vpc + self.vpc = None + self.vswitch = None + self.security_group = None + pass + + @vm_util.Retry() + def Create(self): + """Creates the network.""" + pass + + def Delete(self): + """Deletes the network.""" + pass \ No newline at end of file diff --git a/perfkitbenchmarker/providers/tencentcloud/tencentcloud_virtual_machine.py b/perfkitbenchmarker/providers/tencentcloud/tencentcloud_virtual_machine.py new file mode 100644 index 0000000000..58a94f6e6a --- /dev/null +++ b/perfkitbenchmarker/providers/tencentcloud/tencentcloud_virtual_machine.py @@ -0,0 +1,232 @@ +# Copyright 2017 PerfKitBenchmarker Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Class to represent a GCE Virtual Machine object. + +All VM specifics are self-contained and the class provides methods to +operate on the VM: boot, shutdown, etc. +""" + +import base64 +import collections +import json +import logging +import threading +from tracemalloc import start +from urllib import response +from absl import flags +from PerfKitBenchmarker.perfkitbenchmarker.providers import tencentcloud +from perfkitbenchmarker import disk +from perfkitbenchmarker import linux_virtual_machine +from perfkitbenchmarker import providers +from perfkitbenchmarker import virtual_machine +from perfkitbenchmarker import vm_util +from perfkitbenchmarker.providers.tencentcloud import tencentcloud_disk +from perfkitbenchmarker.providers.tencentcloud import tencentcloud_network +from perfkitbenchmarker.providers.tencentcloud import util + +FLAGS = flags.FLAGS + +class TencentCloudVirtualMachine(virtual_machine.BaseVirtualMachine): + """Object representing an Tencent Cloud Virtual Machine(CVM).""" + + CLOUD = providers.TENCENTCLOUD + + _host_lock = threading.Lock() + deleted_hosts = set() + host_map = collections.defaultdict(list) + + def __init__(self, vm_spec): + """Initialize a Tencent Cloud virtual machine. + + Args: + vm_spec: virtual_machine.BaseVirtualMachineSpec object of the VM. + + Raises: + errors.Config.MissingOption: If the spec does not include a "machine_type" + or both "cpus" and "memory". + errors.Config.InvalidValue: If the spec contains both "machine_type" and + at least one of "cpus" or "memory". + """ + + super(TencentCloudVirtualMachine, self).__init__(vm_spec) + self.user_name = FLAGS.user_name + self.region = util.GetRegionFromZone(self.zone) + self.network = tencentcloud_network.TencentNetwork.GetNetwork(self) + self.firewall = tencentcloud_network.TencentFirewall.GetFirewall() + pass + + @vm_util.Retry(poll_interval=1, log_errors=False) + def _WaitForInstanceStatus(self, status_list): + """Waits until the instance's status is in status_list.""" + pass + + + def _CreateDependencies(self): + """Create VM dependencies.""" + # Calls tccli + self.image = self.image or util.GetDefaultImage(self.machine_type, + self.region) + + # Create user and add SSH key + with open(self.ssh_public_key) as f: + self.public_key = f.read().rstrip('\n') + self.key_pair_name, self.key_id = TencentKeyFileManager.ImportKeyfile( + self.region, self.public_key) + + self.AllowRemoteAccessPorts() + pass + + + def _DeleteDependencies(self): + """Delete VM dependencies.""" + pass + + + def _GenerateCreateCommand(self): + """Generates a command to create the VM instance. + + Returns: + TccliCommand. Command to issue in order to create the VM instance. + """ + cmd = util.TccliCommand(self, 'cvm', 'RunInstances') + cmd.flags['Placement.Zone'] = self.zone + cmd.flags['ImageId'] = self.image + cmd.flags['InstanceType'] = self.machine_type + cmd.flags['InstanceName'] = self.name + cmd.flags['InternetAccessible.InternetMaxBandwidthOut'] = FLAGS.bandwith_out + cmd.flags['InternetAccessible.PublicIpAssigned'] = FLAGS.assign_public_ip + cmd.flags['SecurityGroupIds'] = self.network.security_group.group_id + cmd.flags['LoginSettings.KeyIds'] = self.key_id + cmd.flags['SystemDisk.DiskType'] = self.disk.disk_type + cmd.flags['SystemDisk.DiskSize'] = self.disk.disk_size + + return cmd + + + def _Create(self): + """Create a VM instance.""" + create_cmd = self._GenerateCreateCommand(self) + stdout, stderr, retcode = create_cmd.Issue() + + response = json.loads(stdout) + self.id = response['InstanceIdSet'][0] + pass + + + + @vm_util.Retry() + def _PostCreate(self): + """Get data of the created instance.""" + describe_cmd = util.TccliCommand(self, 'cvm', 'DescribeInstances') + describe_cmd.flags['Region'] = self.region + describe_cmd.flags['InstanceIds'] = describe_cmd.toListFlag([self.id]) + + stdout, _, _ = describe_cmd.Issue() + response = json.loads(stdout) + + instance = response['InstanceSet'][0] + self.ip_address = instance['PublicIpAddresses'][0] + self.internal_ip = instance['PrivateIpAddresses'][0] + + + def _Start(self): + """Starts the VM.""" + start_cmd = util.TccliCommand(self, 'cvm', 'StartInstances') + start_cmd.flags['Region'] = self.region + start_cmd.flags['InstanceIds'] = start_cmd.toListFlag([self.id]) + start_cmd.Issue() + + + def _Stop(self): + """Stops the VM.""" + stop_cmd = util.TccliCommand(self, 'cvm', 'StopInstances') + stop_cmd.flags['Region'] = self.region + stop_cmd.flags['InstanceIds'] = stop_cmd.toListFlag([self.id]) + stop_cmd.Issue() + + + def _Delete(self): + """Delete a VM instance.""" + delete_cmd = util.TccliCommand(self, 'cvm', 'TerminateInstances') + delete_cmd.flags['Region'] = self.region + delete_cmd.flags['InstanceIds'] = delete_cmd.toListFlag([self.id]) + delete_cmd.Issue() + + + def _Exists(self): + """Returns true if the VM exists.""" + describe_cmd = util.TccliCommand(self, 'cvm', 'DescribeInstances') + describe_cmd.flags['Region'] = self.region + describe_cmd.flags['InstanceIds'] = describe_cmd.toListFlag([self.id]) + + stdout, _, _ = describe_cmd.Issue() + response = json.loads(stdout) + instance_cnt = response['TotalCount'] + + return instance_cnt == 1 + + + def CreateScratchDisk(self, disk_spec): + """Create a VM's scratch disk. + + Args: + disk_spec: virtual_machine.BaseDiskSpec object of the disk. + """ + pass + + + def AddMetadata(self, **kwargs): + """Adds metadata to the VM.""" + pass + + +class TencentKeyFileManager(object): + """Object for managing Tencent Cloud Keyfiles.""" + _lock = threading.Lock() + imported_keyfile_set = set() + deleted_keyfile_set = set() + + + @classmethod + def ImportKeyfile(cls, region, public_key): + """Imports the public keyfile to Tencent Cloud. + + Returns: key_name, key_id + """ + with cls._lock: + if (region, FLAGS.run_uri) in cls.imported_keyfile_set: + return + key_name = cls.GetKeyNameForRun() + + args = ['ecs', 'ImportKeyPair'] + import_cmd = util.TccliCommand(cls, args) + import_cmd.flags['Reigon'] = region + import_cmd.flags['KeyName'] = key_name + import_cmd.flags['ProjectId'] = 0 + import_cmd.flags['PublicKey'] = public_key + + stdout, _, _ = import_cmd.Issue() + key_id = json.loads(stdout) + + cls.run_uri_key_names[FLAGS.run_uri] = key_name + return key_name, key_id + + @classmethod + def DeleteKeyfile(cls, region, key_name): + """Deletes the imported KeyPair for a run_uri.""" + pass + + @classmethod + def GetKeyNameForRun(cls): + return 'perfkit-key-{0}'.format(FLAGS.run_uri) diff --git a/perfkitbenchmarker/providers/tencentcloud/util.py b/perfkitbenchmarker/providers/tencentcloud/util.py new file mode 100644 index 0000000000..96415b258e --- /dev/null +++ b/perfkitbenchmarker/providers/tencentcloud/util.py @@ -0,0 +1,123 @@ +# Copyright 2014 PerfKitBenchmarker Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Utilities for working with Tencent Cloud resources.""" + +import collections +import functools +import json +import logging +import re +from typing import Set +from absl import flags +from perfkitbenchmarker import context +from perfkitbenchmarker import errors +from perfkitbenchmarker import virtual_machine +from perfkitbenchmarker import vm_util +import six + +FLAGS = flags.FLAGS + +def GetDefaultImage(): + pass + +def GetRegionFromZone(): + pass + + +class TccliCommand(object): + """Object to handle a tccli command.""" + + def __init__(self, *args): + """Initializes a tccliCommand with the provided args and common flags. + + *args: sequence of strings. Non-flag args to pass to tccli, typically + specifying an operation to perform (e.g. ['compute', 'images', 'list'] + to list available images). + """ + self.args = list(args) + self.flags = collections.OrderedDict() + self.additional_flags = [] + self._AddCommonFlags() + + + def GetCommand(self): + """Generates a tccli command. + + Returns: + list of strings. When joined by spaces, forms the tccli shell command. + """ + cmd = [FLAGS.tencent_path, FLAGS.unfold] + cmd.extend(self.args) + for flag_name, values in sorted(self.flags.items()): + flag_name_str = '--{0}'.format(flag_name) + if values is True: + cmd.append(flag_name_str) + else: + values_iterable = values if isinstance(values, list) else [values] + for value in values_iterable: + cmd.append(flag_name_str) + cmd.append(str(value)) + cmd.extend(self.additional_flags) + + return cmd + + + def __repr__(self): + return '{0}({1})'.format(type(self).__name__, ' '.join(self.GetCommand())) + + + @vm_util.Retry() + def Issue(self, **kwargs): + """Tries to run the tccli command once.""" + try: + stdout, stderr, retcode = vm_util.IssueCommand(self.GetCommand(), **kwargs) + except errors.VmUtil.IssueCommandError as error: + raise error + + return stdout, stderr, retcode + + + def IssueRetryable(self, **kwargs): + """Tries running the tccli command until it succeeds or times out.""" + return vm_util.IssueRetryableCommand(self.GetCommand(), **kwargs) + + + def _AddCommonFlags(self): + """Adds common flags to the command. + + Adds common tccli flags derived from the PKB flags and provided resource. + """ + pass + + + def toListFlag(self, lst): + """Some tccli flag values require a list as input. This function + transform the list to a formatted string to be a part of tccli + command. + + Args: + lst: a list of strings + + Returns: + a string that can be straight insert into tccli command + """ + res = ["'[\""] + for _ in lst: + item = '"{}"'.format(_) + res.append(item) + res.append(',') + res[-1] = "\"]'" + + return ''.join(res) +