├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── NOTICE ├── README.rst ├── qingcloud ├── __init__.py ├── app │ ├── __init__.py │ ├── connection.py │ └── constants.py ├── conn │ ├── __init__.py │ ├── auth.py │ └── connection.py ├── iaas │ ├── __init__.py │ ├── actions │ │ ├── __init__.py │ │ ├── alarm_policy.py │ │ ├── cluster.py │ │ ├── eip.py │ │ ├── image.py │ │ ├── instance.py │ │ ├── instance_groups.py │ │ ├── keypair.py │ │ ├── loadbalancer.py │ │ ├── migrate.py │ │ ├── nic.py │ │ ├── router.py │ │ ├── s2.py │ │ ├── sdwan.py │ │ ├── security_group.py │ │ ├── snapshot.py │ │ ├── tag.py │ │ ├── volume.py │ │ ├── vpc_border.py │ │ └── vxnet.py │ ├── connection.py │ ├── consolidator.py │ ├── constants.py │ ├── errors.py │ ├── lb_backend.py │ ├── lb_listener.py │ ├── monitor.py │ ├── router_static.py │ └── sg_rule.py ├── misc │ ├── __init__.py │ ├── json_tool.py │ └── utils.py ├── qai │ ├── __init__.py │ ├── connection.py │ └── constants.py └── qingstor │ ├── __init__.py │ ├── acl.py │ ├── bucket.py │ ├── connection.py │ ├── exception.py │ ├── key.py │ ├── multipart.py │ └── util.py ├── setup.py ├── tests ├── __init__.py ├── actions │ ├── 0test_instance_groups_action.py │ ├── __init__.py │ └── test_instance_groups_action_mock.py ├── qingstor │ ├── test_bucket.py │ ├── test_connection.py │ ├── test_key.py │ └── test_multipart.py ├── test_app_auth.py ├── test_consolidator.py ├── test_json_tool.py ├── test_lb_backend.py ├── test_lb_listener.py ├── test_monitor.py ├── test_router_static.py ├── test_sg_rule.py └── test_utils.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | .cache/ 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | 39 | # IDE 40 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | # - "2.6" 4 | - "2.7" 5 | # - "3.2" 6 | # - "3.3" 7 | - "3.4" 8 | - "3.5" 9 | - "3.6" 10 | 11 | #before_install: 12 | # - sudo apt-get update 13 | # - sudo apt-get install -y python-logilab-common 14 | 15 | install: 16 | - pip install future 17 | - pip install mock 18 | - python setup.py install 19 | 20 | script: pytest -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | recursive-include qingcloud *.py 3 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | SDK for QingCloud 2 | Copyright 2013 Yunify, Inc. 3 | 4 | This product includes software developed at 5 | Yunify, Inc. (http://www.yunify.com/). 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | QingCloud Python SDK 3 | ===================== 4 | 5 | This repository allows you to access `QingCloud `_ 6 | and control your resources from your applications. 7 | 8 | This SDK is licensed under 9 | `Apache Licence, Version 2.0 `_. 10 | 11 | .. note:: 12 | Requires Python 2.6 or higher, compatible with Python 3, 13 | for more information please see 14 | `QingCloud SDK Documentation `_ 15 | 16 | 17 | ------------ 18 | Installation 19 | ------------ 20 | 21 | Install via `pip `_ :: 22 | 23 | $ pip install qingcloud-sdk 24 | 25 | Upgrade to the latest version :: 26 | 27 | $ pip install --upgrade qingcloud-sdk 28 | 29 | Install from source :: 30 | 31 | git clone https://github.com/yunify/qingcloud-sdk-python.git 32 | cd qingcloud-sdk-python 33 | python setup.py install 34 | 35 | 36 | --------------- 37 | Getting Started 38 | --------------- 39 | 40 | In order to operate QingCloud IaaS or QingStor (QingCloud Object Storage), 41 | you need apply **access key** on `qingcloud console `_ first. 42 | 43 | 44 | QingCloud IaaS API 45 | ''''''''''''''''''' 46 | 1. Pass access key id and secret key into method ``connect_to_zone`` to create connection :: 47 | 48 | >>> import qingcloud.iaas 49 | >>> conn = qingcloud.iaas.connect_to_zone( 50 | 'zone id', 51 | 'access key id', 52 | 'secret access key' 53 | ) 54 | 55 | 56 | 2. Call API by using IAM role 57 | 58 | If you would like to call our APIs without access key and secret key (bad things would happen if they were lost or leaked) 59 | or if you want a finer access control over your instances, there is a easy way to do it :P 60 | 61 | - Go to our IAM service, create an instance role and attach it to your instance. 62 | - Create connection without access key and secret key. :: 63 | 64 | >>> import qingcloud.iaas 65 | >>> conn = qingcloud.iaas.connect_to_zone( 66 | 'zone id', 67 | None, 68 | None 69 | ) 70 | 71 | 72 | The variable ``conn`` is the instance of ``qingcloud.iaas.connection.APIConnection``, 73 | we can use it to call resource related methods. Example:: 74 | 75 | # launch instances 76 | >>> ret = conn.run_instances( 77 | image_id='img-xxxxxxxx', 78 | cpu=1, 79 | memory=1024, 80 | vxnets=['vxnet-0'], 81 | login_mode='passwd', 82 | login_passwd='Passw0rd@()' 83 | ) 84 | 85 | # stop instances 86 | >>> ret = conn.stop_instances( 87 | instances=['i-xxxxxxxx'], 88 | force=True 89 | ) 90 | 91 | # describe instances 92 | >>> ret = conn.describe_instances( 93 | status=['running', 'stopped'] 94 | ) 95 | 96 | QingCloud QingStor API 97 | ''''''''''''''''''''''' 98 | Pass access key id and secret key into method ``connect`` to create connection :: 99 | 100 | >>> import qingcloud.qingstor 101 | >>> conn = qingcloud.qingstor.connect( 102 | 'pek3a.qingstor.com', 103 | 'access key id', 104 | 'secret access key' 105 | ) 106 | 107 | The variable ``conn`` is the instance of ``qingcloud.qingstor.connection.QSConnection``, 108 | we can use it to create Bucket which is used for generating Key and MultiPartUpload. 109 | 110 | Example:: 111 | 112 | # Create a bucket 113 | >>> bucket = conn.create_bucket('mybucket') 114 | 115 | # Create a key 116 | >>> key = bucket.new_key('myobject') 117 | >>> with open('/tmp/myfile') as f: 118 | >>> key.send_file(f) 119 | 120 | # Delete the key 121 | >>> bucket.delete_key('myobject') 122 | 123 | 124 | Coreshub AICP API 125 | ''''''''''''''''''''''' 126 | Pass access key id and secret key into method ``connect`` to create connection :: 127 | 128 | >>> import qingcloud.qai 129 | >>> conn = qingcloud.qai.connect( 130 | 'access key id', 131 | 'secret access key', 132 | 'zone_id' 133 | ) 134 | 135 | The variable ``conn`` is the instance of ``qingcloud.qai.connection.QAIConnection``, 136 | we can use it to connect to aicp server. 137 | 138 | Example:: 139 | 140 | # Get user information. 141 | >>> user_info = conn.get_user_info() 142 | 143 | # Get trains. 144 | >>> trains = conn.get_trains() 145 | 146 | # Get the metrics of trains. 147 | >>> conn.trains_metrics(['tn-xxx', 'tn-xxx']) 148 | 149 | 150 | -------------------------------------------------------------------------------- /qingcloud/__init__.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | __import__('pkg_resources').declare_namespace(__name__) 18 | -------------------------------------------------------------------------------- /qingcloud/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunify/qingcloud-sdk-python/50e61f57bcb70e88e8847099958dd5f7c5520199/qingcloud/app/__init__.py -------------------------------------------------------------------------------- /qingcloud/app/connection.py: -------------------------------------------------------------------------------- 1 | from qingcloud.iaas.connection import APIConnection 2 | from qingcloud.conn import auth 3 | from . import constants as const 4 | from __builtin__ import str 5 | 6 | 7 | class AppConnection(APIConnection): 8 | 9 | def __init__(self, app_id, secret_app_key, zone, 10 | host='api.qingcloud.com', port=443, protocol='https', 11 | pool=None, expires=None, retry_time=3, 12 | http_socket_timeout=10, access_token=None): 13 | """ 14 | @param app_id 15 | @param secret_app_key 16 | @param zone - the zone id to access 17 | @param host - the host to make the connection to 18 | @param port - the port to use when connect to host 19 | @param protocol - the protocol to access to web server, "http" or "https" 20 | @param pool - the connection pool 21 | @param retry_time - the retry_time when message send fail 22 | """ 23 | APIConnection.__init__(self, app_id, secret_app_key, zone, host, port, 24 | protocol, pool, expires, retry_time, 25 | http_socket_timeout) 26 | self._auth_handler = auth.AppSignatureAuthHandler(app_id, 27 | secret_app_key, 28 | access_token) 29 | 30 | def send_request(self, action, body, url='/app/', verb='GET'): 31 | """ Send request 32 | """ 33 | return super(AppConnection, self).send_request(action, body, url, verb) 34 | 35 | def describe_users(self, **ignore): 36 | """ get current app user info 37 | """ 38 | action = const.ACTION_DESCRIBE_USERS 39 | body = {} 40 | 41 | return self.send_request(action, body) 42 | 43 | def lease_app(self, service, resource=None): 44 | """ start lease app 45 | @param service: service to lease 46 | @param resource: related qingcloud resource 47 | """ 48 | action = const.ACTION_LEASE_APP 49 | body = {"service": service} 50 | if resource: 51 | body["resource"] = resource 52 | 53 | return self.send_request(action, body) 54 | 55 | def unlease_app(self, resources): 56 | """ start lease app 57 | @param resources: list of resource ids to unlease. 58 | It can be id of user, app, service or appr. 59 | For user id, unlease all app services for this user 60 | For app id, unlease all services for this app 61 | For service id, unlease all services 62 | user id and other id can be conbined to unlease service for specified user 63 | """ 64 | action = const.ACTION_UNLEASE_APP 65 | 66 | if isinstance(resources, str): 67 | resources = [resources] 68 | 69 | if not isinstance(resources, list): 70 | return None 71 | 72 | body = {"resources": resources} 73 | 74 | return self.send_request(action, body) 75 | -------------------------------------------------------------------------------- /qingcloud/app/constants.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | ########## App Actions ########## 18 | # jobs 19 | ACTION_DESCRIBE_USERS = "DescribeUsers" 20 | ACTION_LEASE_APP = "LeaseApp" 21 | ACTION_UNLEASE_APP = "UnLeaseApp" 22 | -------------------------------------------------------------------------------- /qingcloud/conn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunify/qingcloud-sdk-python/50e61f57bcb70e88e8847099958dd5f7c5520199/qingcloud/conn/__init__.py -------------------------------------------------------------------------------- /qingcloud/iaas/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | interface to the IaaS service from QingCloud. 3 | """ 4 | 5 | from qingcloud.iaas.connection import APIConnection 6 | 7 | 8 | def connect_to_zone(zone, access_key_id, secret_access_key, lowercase=True): 9 | """ Connect to one of zones in qingcloud by access key. 10 | """ 11 | if lowercase: 12 | zone = zone.strip().lower() 13 | return APIConnection(access_key_id, secret_access_key, zone) 14 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunify/qingcloud-sdk-python/50e61f57bcb70e88e8847099958dd5f7c5520199/qingcloud/iaas/actions/__init__.py -------------------------------------------------------------------------------- /qingcloud/iaas/actions/eip.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class EipAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_eips(self, eips=None, 27 | status=None, 28 | instance_id=None, 29 | search_word=None, 30 | owner=None, 31 | offset=None, 32 | limit=None, 33 | tags=None, 34 | **ignore): 35 | """ Describe eips filtered by condition. 36 | @param eips: IDs of the eip you want describe. 37 | @param status: filter eips by status 38 | @param instance_id: filter eips by instance. 39 | @param search_word: search word column. 40 | @param offset: the starting offset of the returning results. 41 | @param limit: specify the number of the returning results. 42 | @param tags : the array of IDs of tags. 43 | """ 44 | action = const.ACTION_DESCRIBE_EIPS 45 | valid_keys = ['eips', 'status', 'instance_id', 'search_word', 46 | 'offset', 'limit', 'tags', 'owner'] 47 | body = filter_out_none(locals(), valid_keys) 48 | if not self.conn.req_checker.check_params(body, 49 | required_params=[], 50 | integer_params=[ 51 | 'offset', 'limit'], 52 | list_params=[ 53 | 'status', 'eips', 'tags'] 54 | ): 55 | return None 56 | 57 | return self.conn.send_request(action, body) 58 | 59 | def associate_eip(self, eip, 60 | instance, 61 | **ignore): 62 | """ Associate an eip on an instance. 63 | @param eip: The id of eip you want to associate with instance. 64 | @param instance: the id of instance you want to associate eip. 65 | """ 66 | action = const.ACTION_ASSOCIATE_EIP 67 | body = {'eip': eip, 'instance': instance} 68 | if not self.conn.req_checker.check_params(body, 69 | required_params=[ 70 | 'eip', 'instance'], 71 | integer_params=[], 72 | list_params=[] 73 | ): 74 | return None 75 | 76 | return self.conn.send_request(action, body) 77 | 78 | def dissociate_eips(self, eips, 79 | **ignore): 80 | """ Dissociate one or more eips. 81 | @param eips: The ids of eips you want to dissociate with instance. 82 | """ 83 | action = const.ACTION_DISSOCIATE_EIPS 84 | body = {'eips': eips} 85 | if not self.conn.req_checker.check_params(body, 86 | required_params=['eips'], 87 | integer_params=[], 88 | list_params=['eips'] 89 | ): 90 | return None 91 | 92 | return self.conn.send_request(action, body) 93 | 94 | def allocate_eips(self, bandwidth, 95 | billing_mode=const.EIP_BILLING_MODE_BANDWIDTH, 96 | count=1, 97 | need_icp=0, 98 | eip_name='', 99 | target_user=None, 100 | associate_mode=0, 101 | **ignore): 102 | """ Allocate one or more eips. 103 | @param count: the number of eips you want to allocate. 104 | @param bandwidth: the bandwidth of the eip in Mbps. 105 | @param need_icp: 0 - no need, 1 - need 106 | @param eip_name : the short name of eip 107 | @param target_user: ID of user who will own this resource, should be one of your sub-accounts 108 | @param associate_mode: 0 - associate ip addr to virtual gateway, 1 - associate ip addr to vm 109 | """ 110 | action = const.ACTION_ALLOCATE_EIPS 111 | valid_keys = ['bandwidth', 'billing_mode', 112 | 'count', 'need_icp', 'eip_name', 113 | 'target_user', 'associate_mode'] 114 | body = filter_out_none(locals(), valid_keys) 115 | if not self.conn.req_checker.check_params(body, 116 | required_params=['bandwidth'], 117 | integer_params=['bandwidth', 'count', 118 | 'need_icp', 'associate_mode'], 119 | list_params=[] 120 | ): 121 | return None 122 | 123 | return self.conn.send_request(action, body) 124 | 125 | def release_eips(self, eips, 126 | force=0, 127 | **ignore): 128 | """ Release one or more eips. 129 | @param eips : The ids of eips that you want to release 130 | @param force : Whether to force release the eip that needs icp codes. 131 | """ 132 | action = const.ACTION_RELEASE_EIPS 133 | body = {'eips': eips, 'force': int(force != 0)} 134 | if not self.conn.req_checker.check_params(body, 135 | required_params=['eips'], 136 | integer_params=['force'], 137 | list_params=['eips'] 138 | ): 139 | return None 140 | 141 | return self.conn.send_request(action, body) 142 | 143 | def change_eips_bandwidth(self, eips, 144 | bandwidth, 145 | **ignore): 146 | """ Change one or more eips bandwidth. 147 | @param eips: The IDs of the eips whose bandwidth you want to change. 148 | @param bandwidth: the new bandwidth of the eip in MB. 149 | """ 150 | action = const.ACTION_CHANGE_EIPS_BANDWIDTH 151 | body = {'eips': eips, 'bandwidth': bandwidth} 152 | if not self.conn.req_checker.check_params(body, 153 | required_params=[ 154 | 'eips', 'bandwidth'], 155 | integer_params=['bandwidth'], 156 | list_params=['eips'] 157 | ): 158 | return None 159 | 160 | return self.conn.send_request(action, body) 161 | 162 | def change_eips_billing_mode(self, eips, 163 | billing_mode, 164 | **ignore): 165 | """ Change one or more eips billing mode. 166 | @param eips: The IDs of the eips whose billing mode you want to change. 167 | @param billing_mode: the new billing mode, "bandwidth" or "traffic". 168 | """ 169 | action = const.ACTION_CHANGE_EIPS_BILLING_MODE 170 | body = {'eips': eips, 'billing_mode': billing_mode} 171 | if not self.conn.req_checker.check_params(body, 172 | required_params=[ 173 | 'eips', 'billing_mode'], 174 | list_params=['eips'] 175 | ): 176 | return None 177 | 178 | return self.conn.send_request(action, body) 179 | 180 | def modify_eip_attributes(self, eip, 181 | eip_name=None, 182 | description=None, 183 | **ignore): 184 | """ Modify eip attributes. 185 | If you want to modify eip's bandwidth, use `change_eips_bandwidth`. 186 | @param eip : the ID of eip that you want to modify 187 | @param eip_name : the name of eip 188 | @param description : the eip description 189 | """ 190 | action = const.ACTION_MODIFY_EIP_ATTRIBUTES 191 | valid_keys = ['eip', 'eip_name', 'description'] 192 | body = filter_out_none(locals(), valid_keys) 193 | if not self.conn.req_checker.check_params(body, 194 | required_params=['eip'], 195 | integer_params=[], 196 | list_params=[] 197 | ): 198 | return None 199 | 200 | return self.conn.send_request(action, body) 201 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/image.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class ImageAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_images(self, images=None, 27 | tags=None, 28 | os_family=None, 29 | processor_type=None, 30 | status=None, 31 | visibility=None, 32 | provider=None, 33 | verbose=0, 34 | search_word=None, 35 | owner=None, 36 | offset=None, 37 | limit=None, 38 | **ignore): 39 | """ Describe images filtered by condition. 40 | @param images: an array including IDs of the images you want to list. 41 | No ID specified means list all. 42 | @param tags: the array of IDs of tags. 43 | @param os_family: os family, windows/debian/centos/ubuntu. 44 | @param processor_type: supported processor types are `64bit` and `32bit`. 45 | @param status: valid values include pending, available, deleted, ceased. 46 | @param visibility: who can see and use this image. Valid values include public, private. 47 | @param provider: who provide this image, self, system. 48 | @param verbose: the number to specify the verbose level, 49 | larger the number, the more detailed information will be returned. 50 | @param search_word: the search word. 51 | @param offset: the starting offset of the returning results. 52 | @param limit: specify the number of the returning results. 53 | """ 54 | 55 | action = const.ACTION_DESCRIBE_IMAGES 56 | valid_keys = ['images', 'os_family', 'processor_type', 'status', 'visibility', 57 | 'provider', 'verbose', 'search_word', 'offset', 'limit', 'owner', 58 | 'tags'] 59 | body = filter_out_none(locals(), valid_keys) 60 | if not self.conn.req_checker.check_params(body, 61 | required_params=[], 62 | integer_params=[ 63 | "offset", "limit", "verbose"], 64 | list_params=["images", "tags"] 65 | ): 66 | return None 67 | 68 | return self.conn.send_request(action, body) 69 | 70 | def capture_instance(self, instance, 71 | image_name="", 72 | **ignore): 73 | """ Capture an instance and make it available as an image for reuse. 74 | @param instance: ID of the instance you want to capture. 75 | @param image_name: short name of the image. 76 | """ 77 | action = const.ACTION_CAPTURE_INSTANCE 78 | valid_keys = ['instance', 'image_name'] 79 | body = filter_out_none(locals(), valid_keys) 80 | if not self.conn.req_checker.check_params(body, 81 | required_params=['instance'], 82 | integer_params=[], 83 | list_params=[] 84 | ): 85 | return None 86 | 87 | return self.conn.send_request(action, body) 88 | 89 | def delete_images(self, images, 90 | **ignore): 91 | """ Delete one or more images whose provider is `self`. 92 | @param images: ID of the images you want to delete. 93 | """ 94 | action = const.ACTION_DELETE_IMAGES 95 | body = {'images': images} 96 | if not self.conn.req_checker.check_params(body, 97 | required_params=['images'], 98 | integer_params=[], 99 | list_params=[] 100 | ): 101 | return None 102 | 103 | return self.conn.send_request(action, body) 104 | 105 | def modify_image_attributes(self, image, 106 | image_name=None, 107 | description=None, 108 | **ignore): 109 | """ Modify image attributes. 110 | @param image: the ID of image whose attributes you want to modify. 111 | @param image_name: Name of the image. It's a short name for the image 112 | that more meaningful than image id. 113 | @param description: The detailed description of the image. 114 | """ 115 | action = const.ACTION_MODIFY_IMAGE_ATTRIBUTES 116 | valid_keys = ['image', 'image_name', 'description'] 117 | body = filter_out_none(locals(), valid_keys) 118 | if not self.conn.req_checker.check_params(body, 119 | required_params=['image'], 120 | integer_params=[], 121 | list_params=[] 122 | ): 123 | return None 124 | 125 | return self.conn.send_request(action, body) 126 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/instance_groups.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class InstanceGroupsAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def create_instance_groups(self, relation, 27 | instance_group_name=None, 28 | description=None, 29 | **ignore): 30 | """ Create an instance group. 31 | @param relation: Define the relation between instances in the same group. 32 | "repel" means these instances prefer distributing on the different physical units. 33 | "attract" means these instances prefer converging on the same physical unit. 34 | @param instance_group_name: The name of this group. 35 | @param description: The description of this group. 36 | """ 37 | action = const.ACTION_CREATE_INSTANCE_GROUPS 38 | valid_keys = ['relation', 'instance_group_name', 'description'] 39 | body = filter_out_none(locals(), valid_keys) 40 | if not self.conn.req_checker.check_params(body, 41 | required_params=['relation'], 42 | ): 43 | return None 44 | 45 | return self.conn.send_request(action, body) 46 | 47 | def delete_instance_groups(self, instance_groups, 48 | **ignore): 49 | """ Delete the specific instance group. 50 | @param instance_groups: An id list contains the group(s) id which will be deleted. 51 | """ 52 | action = const.ACTION_DELETE_INSTANCE_GROUPS 53 | valid_keys = ['instance_groups'] 54 | body = filter_out_none(locals(), valid_keys) 55 | if not self.conn.req_checker.check_params(body, 56 | required_params=['instance_groups'], 57 | list_params=['instance_groups'] 58 | ): 59 | return None 60 | 61 | return self.conn.send_request(action, body) 62 | 63 | def join_instance_group(self, instances, 64 | instance_group, 65 | **ignore): 66 | """ Add the instance(s) to the instance group. 67 | @param instances: An id list contains the instances(s) that will be added in the specific group. 68 | @param instance_group: The group id. 69 | """ 70 | action = const.ACTION_JOIN_INSTANCE_GROUP 71 | valid_keys = ['instances', 'instance_group'] 72 | body = filter_out_none(locals(), valid_keys) 73 | if not self.conn.req_checker.check_params(body, 74 | required_params=['instances', 'instance_group'], 75 | list_params=['instances'] 76 | ): 77 | return None 78 | 79 | return self.conn.send_request(action, body) 80 | 81 | def leave_instance_group(self, instances, 82 | instance_group, 83 | **ignore): 84 | """ Delete the specific instance(s) from the group. 85 | @param instances: An id list contains the instance(s) who want to leave the instance group. 86 | @param instance_group: The instance group id. 87 | """ 88 | action = const.ACTION_LEAVE_INSTANCE_GROUP 89 | valid_keys = ['instances', 'instance_group'] 90 | body = filter_out_none(locals(), valid_keys) 91 | if not self.conn.req_checker.check_params(body, 92 | required_params=['instances', 'instance_group'], 93 | list_params=['instances'] 94 | ): 95 | return None 96 | 97 | return self.conn.send_request(action, body) 98 | 99 | def describe_instance_groups(self, instance_groups=[], 100 | relation=None, 101 | tags=None, 102 | owner=None, 103 | verbose=0, 104 | offset=0, 105 | limit=20, 106 | **ignore): 107 | """ Describe the instance groups filtered by conditions. 108 | @param instance_groups: If this param was given, only return the group(s) info in this given list. 109 | @param relation: Filter by the relation type. 110 | @param tags: Filter by the tag id. 111 | @param owner: Filter by the owner id. 112 | @param verbose: Whether return the verbose information. 113 | @param offset: The offset of the item cursor and its default value is 0. 114 | @param limit: The number of items that will be displayed. Default is 20, maximum is 100. 115 | """ 116 | action = const.ACTION_DESCRIBE_INSTANCE_GROUPS 117 | valid_keys = ['instance_groups', 'relation', 'tags', 'owner', 118 | 'verbose', 'offset', 'limit'] 119 | body = filter_out_none(locals(), valid_keys) 120 | if not self.conn.req_checker.check_params(body, 121 | list_params=['instance_groups', 'tags'], 122 | integer_params=['limit', 'verbose', 'offset'] 123 | ): 124 | return None 125 | 126 | return self.conn.send_request(action, body) 127 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/keypair.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class KeypairAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_key_pairs(self, keypairs=None, 27 | encrypt_method=None, 28 | search_word=None, 29 | owner=None, 30 | verbose=0, 31 | offset=None, 32 | limit=None, 33 | tags=None, 34 | **ignore): 35 | """ Describe key-pairs filtered by condition 36 | @param keypairs: IDs of the keypairs you want to describe. 37 | @param encrypt_method: encrypt method. 38 | @param verbose: the number to specify the verbose level, larger the number, the more detailed information will be returned. 39 | @param offset: the starting offset of the returning results. 40 | @param limit: specify the number of the returning results. 41 | @param tags : the array of IDs of tags. 42 | """ 43 | action = const.ACTION_DESCRIBE_KEY_PAIRS 44 | valid_keys = ['keypairs', 'encrypt_method', 'search_word', 'verbose', 45 | 'offset', 'limit', 'tags', 'owner'] 46 | body = filter_out_none(locals(), valid_keys) 47 | if not self.conn.req_checker.check_params(body, 48 | required_params=[], 49 | integer_params=[ 50 | 'offset', 'limit', 'verbose'], 51 | list_params=['keypairs', 'tags'] 52 | ): 53 | return None 54 | 55 | return self.conn.send_request(action, body) 56 | 57 | def attach_keypairs(self, keypairs, 58 | instances, 59 | **ignore): 60 | """ Attach one or more keypairs to instances. 61 | @param keypairs: IDs of the keypairs you want to attach to instance . 62 | @param instances: IDs of the instances the keypairs will be attached to. 63 | """ 64 | action = const.ACTION_ATTACH_KEY_PAIRS 65 | valid_keys = ['keypairs', 'instances'] 66 | body = filter_out_none(locals(), valid_keys) 67 | if not self.conn.req_checker.check_params(body, 68 | required_params=[ 69 | 'keypairs', 'instances'], 70 | integer_params=[], 71 | list_params=[ 72 | 'keypairs', 'instances'] 73 | ): 74 | return None 75 | 76 | return self.conn.send_request(action, body) 77 | 78 | def detach_keypairs(self, keypairs, 79 | instances, 80 | **ignore): 81 | """ Detach one or more keypairs from instances. 82 | @param keypairs: IDs of the keypairs you want to detach from instance . 83 | @param instances: IDs of the instances the keypairs will be detached from. 84 | """ 85 | action = const.ACTION_DETACH_KEY_PAIRS 86 | valid_keys = ['keypairs', 'instances'] 87 | body = filter_out_none(locals(), valid_keys) 88 | if not self.conn.req_checker.check_params(body, 89 | required_params=[ 90 | "keypairs", "instances"], 91 | integer_params=[], 92 | list_params=[ 93 | "keypairs", "instances"] 94 | ): 95 | return None 96 | 97 | return self.conn.send_request(action, body) 98 | 99 | def create_keypair(self, keypair_name, 100 | mode='system', 101 | encrypt_method="ssh-rsa", 102 | public_key=None, 103 | target_user=None, 104 | **ignore): 105 | """ Create a keypair. 106 | @param keypair_name: the name of the keypair you want to create. 107 | @param mode: the keypair creation mode, "system" or "user". 108 | @param encrypt_method: the encrypt method, supported methods "ssh-rsa", "ssh-dss". 109 | @param public_key: provide your public key. (need "user" mode) 110 | @param target_user: ID of user who will own this resource, should be one of your sub-accounts 111 | """ 112 | action = const.ACTION_CREATE_KEY_PAIR 113 | valid_keys = ['keypair_name', 'mode', 'encrypt_method', 'public_key', 'target_user'] 114 | body = filter_out_none(locals(), valid_keys) 115 | if not self.conn.req_checker.check_params(body, 116 | required_params=['keypair_name'], 117 | integer_params=[], 118 | list_params=[] 119 | ): 120 | return None 121 | 122 | return self.conn.send_request(action, body) 123 | 124 | def delete_keypairs(self, keypairs, 125 | **ignore): 126 | """ Delete one or more keypairs. 127 | @param keypairs: IDs of the keypairs you want to delete. 128 | """ 129 | action = const.ACTION_DELETE_KEY_PAIRS 130 | body = {'keypairs': keypairs} 131 | if not self.conn.req_checker.check_params(body, 132 | required_params=['keypairs'], 133 | integer_params=[], 134 | list_params=['keypairs'] 135 | ): 136 | return None 137 | 138 | return self.conn.send_request(action, body) 139 | 140 | def modify_keypair_attributes(self, keypair, 141 | keypair_name=None, 142 | description=None, 143 | **ignore): 144 | """ Modify keypair attributes. 145 | @param keypair: the ID of keypair you want to modify its attributes. 146 | @param keypair_name: the new name of keypair. 147 | @param description: The detailed description of the resource. 148 | """ 149 | action = const.ACTION_MODIFY_KEYPAIR_ATTRIBUTES 150 | valid_keys = ['keypair', 'keypair_name', 'description'] 151 | body = filter_out_none(locals(), valid_keys) 152 | if not self.conn.req_checker.check_params(body, 153 | required_params=['keypair'], 154 | integer_params=[], 155 | list_params=[] 156 | ): 157 | return None 158 | 159 | return self.conn.send_request(action, body) 160 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/migrate.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class MigrateAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def migrate_resources(self, resources, 27 | src_zone, 28 | dst_zone, 29 | **ignore): 30 | """ Migrate resources. 31 | @param resources: the IDs of resources you want to migrate. 32 | @param src_zone: the zone of the resources. 33 | @param dst_zone: the destination zone of the resources migrate. 34 | """ 35 | action = const.ACTION_MIGRATE_RESOURCES 36 | valid_keys = ['resources', 'src_zone', 'dst_zone'] 37 | body = filter_out_none(locals(), valid_keys) 38 | if not self.conn.req_checker.check_params(body, 39 | required_params=["resources", "src_zone", "dst_zone"], 40 | integer_params=[], 41 | list_params=[] 42 | ): 43 | return None 44 | 45 | return self.conn.send_request(action, body) 46 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/nic.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class NicAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_nics(self, 27 | nics=None, 28 | nic_name=None, 29 | status=None, 30 | vxnets=None, 31 | vxnet_type=None, 32 | offset=None, 33 | limit=None, 34 | **ignore): 35 | """ Describe nics 36 | 37 | @param nics: the IDs of nic you want to describe. 38 | @param nic_name: the name of nic. 39 | @param status: valid values include available, in-use. 40 | @param vxnets: the IDs of vxnet. 41 | @param vxnet_type: vxnet type, 0: unmanaged, 1: managed. 42 | @param offset: the starting offset of the returning results. 43 | @param limit: specify the number of the returning results. 44 | """ 45 | action = const.ACTION_DESCRIBE_NICS 46 | valid_keys = [ 47 | "nics", "nic_name", "status", 48 | "vxnets", "vxnet_type", "offset", "limit", 49 | ] 50 | body = filter_out_none(locals(), valid_keys) 51 | if not self.conn.req_checker.check_params( 52 | body, 53 | integer_params=["offset", "limit"], 54 | list_params=["nics", "vxnets"], 55 | ): 56 | return None 57 | 58 | return self.conn.send_request(action, body) 59 | 60 | def create_nics(self, 61 | vxnet, 62 | nic_name=None, 63 | count=1, 64 | private_ips=None, 65 | **ignore): 66 | """ Create nics 67 | 68 | @param nic_name: the name of nic. 69 | @param vxnet: the ID of vxnet. 70 | @param count : the number of nics to create. 71 | @param private_ips: set nic"s ip, like ["192.168.100.14","192.168.100.17"] 72 | """ 73 | action = const.ACTION_CREATE_NICS 74 | valid_keys = [ 75 | "nic_name", "vxnet", 76 | "count", "private_ips", 77 | ] 78 | body = filter_out_none(locals(), valid_keys) 79 | if not self.conn.req_checker.check_params( 80 | body, 81 | required_params=["vxnet"], 82 | integer_params=["count"], 83 | list_params=["private_ips"], 84 | ): 85 | return None 86 | 87 | return self.conn.send_request(action, body) 88 | 89 | def attach_nics(self, 90 | nics, 91 | instance, 92 | **ignore): 93 | """ Attach one or more nics to instance. 94 | 95 | @param nics: the IDs of nics. 96 | @param instance: the ID of instance. 97 | """ 98 | action = const.ACTION_ATTACH_NICS 99 | valid_keys = ["nics", "instance"] 100 | body = filter_out_none(locals(), valid_keys) 101 | if not self.conn.req_checker.check_params( 102 | body, 103 | required_params=["nics", "instance"], 104 | list_params=["nics"] 105 | ): 106 | return None 107 | 108 | return self.conn.send_request(action, body) 109 | 110 | def detach_nics(self, 111 | nics, 112 | **ignore): 113 | """ Detach one or more nics from instance. 114 | 115 | @param nics: the IDs of nics you want to detach. 116 | """ 117 | action = const.ACTION_DETACH_NICS 118 | valid_keys = ["nics"] 119 | body = filter_out_none(locals(), valid_keys) 120 | if not self.conn.req_checker.check_params( 121 | body, 122 | required_params=["nics"], 123 | list_params=["nics"] 124 | ): 125 | return None 126 | 127 | return self.conn.send_request(action, body) 128 | 129 | def modify_nic_attributes(self, 130 | nic, 131 | nic_name=None, 132 | private_ip=None, 133 | **ignore): 134 | """ Modify one nic's attributes 135 | 136 | @param nic: the ID of nic you want to modify. 137 | @param nic_name: the new name of nic. 138 | @param private_ip: the new ip address for this nic. 139 | """ 140 | action = const.ACTION_MODIFY_NIC_ATTRIBUTES 141 | valid_keys = ["nic", "nic_name", "private_ip"] 142 | body = filter_out_none(locals(), valid_keys) 143 | if not self.conn.req_checker.check_params( 144 | body, 145 | required_params=["nic"], 146 | ): 147 | return None 148 | 149 | return self.conn.send_request(action, body) 150 | 151 | def delete_nics(self, 152 | nics, 153 | **ignore): 154 | """ Detach one or more nics from instance. 155 | 156 | @param nics: the IDs of nics you want to detach. 157 | """ 158 | action = const.ACTION_DELETE_NICS 159 | valid_keys = ["nics"] 160 | body = filter_out_none(locals(), valid_keys) 161 | if not self.conn.req_checker.check_params( 162 | body, 163 | required_params=["nics"], 164 | list_params=["nics"] 165 | ): 166 | return None 167 | 168 | return self.conn.send_request(action, body) 169 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/sdwan.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class SdwanAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_wan_accesss(self, 27 | wan_accesss=None, 28 | wan_access_name=None, 29 | wan_nets=None, 30 | wan_pops=None, 31 | status=None, 32 | access_type=None, 33 | location_nation=None, 34 | location_province=None, 35 | location_city=None, 36 | owner=None, 37 | search_word=None, 38 | offset=None, 39 | limit=None, 40 | verbose=0, 41 | **params): 42 | ''' Action: DescribeWanAccesss 43 | @param wan_accesss: IDs of the wan accesss you want describe. 44 | @param wan_access_name: the name of the wan access. 45 | @param wan_nets: ID of wan net which wan accesss belong to 46 | @param wan_pops: ID of wan pop which wan accesss belong to 47 | @param status: status of wan access 48 | @param access_type: access type eg: line,vpc,cpe. 49 | @param location_nation: The nation of access location. 50 | @param location_province: The province of access location. 51 | @param location_city: The city of access location. 52 | @param owner: the owner IDs of resource. 53 | @param search_word: the search_word of resource 54 | @param offset: the starting offset of the returning results. 55 | @param limit: specify the number of the returning results. 56 | @param verbose: the number to specify the verbose level. eg: 0/1 57 | ''' 58 | action = const.ACTION_DESCRIBE_WAN_ACCESS 59 | valid_keys = ['wan_accesss', 'wan_access_name', 'wan_nets', 60 | 'wan_pops', 'status', 'access_type', 'location_nation', 61 | 'location_province', 'location_city', 'owner', 62 | 'search_word', 'offset', 'limit', 'verbose'] 63 | body = filter_out_none(locals(), valid_keys) 64 | if not self.conn.req_checker.check_params( 65 | body, 66 | integer_params=["offset", "limit", "verbose"], 67 | list_params=["wan_accesss", 68 | "wan_nets", 69 | "wan_pops", 70 | "access_type", "status"]): 71 | return None 72 | 73 | return self.conn.send_request(action, body) 74 | 75 | def change_wan_access_bandwidth(self, 76 | wan_access, 77 | bandwidth_type, 78 | bandwidth=None, 79 | **params): 80 | """ change wan accesss bandwidth. 81 | @param wan_access: the IDs of wan access. 82 | @param bandwidth_type: wan access bandwitdth type eg: elastic. 83 | @param bandwidth: the new bandwidth for all, unit in Mbps. 84 | """ 85 | action = const.ACTION_CHANGE_WAN_ACCESS_BANDWIDTH 86 | valid_keys = ['wan_access', 'bandwidth_type', 'bandwidth'] 87 | body = filter_out_none(locals(), valid_keys) 88 | if not self.conn.req_checker.check_params( 89 | body, 90 | required_params=['wan_access', 91 | 'bandwidth_type'], 92 | integer_params=['bandwidth'] 93 | ): 94 | return None 95 | 96 | return self.conn.send_request(action, body) 97 | 98 | def upgrade_wan_access(self, 99 | wan_accesss, 100 | bandwidth=None, 101 | **params): 102 | """ upgrade_wan_access. 103 | @param wan_access: the IDs of wan access. 104 | @param bandwidth: the new bandwidth for all, unit in Mbps. 105 | unit in Mbps. 106 | """ 107 | action = const.ACTION_UPGRADE_WAN_ACCESS 108 | valid_keys = ['wan_accesss', 'bandwidth'] 109 | body = filter_out_none(locals(), valid_keys) 110 | if not self.conn.req_checker.check_params( 111 | body, 112 | required_params=['wan_accesss'], 113 | integer_params=['bandwidth'], 114 | ): 115 | return None 116 | 117 | return self.conn.send_request(action, body) 118 | 119 | def get_wan_monitor(self, 120 | resource=None, 121 | access_type=None, 122 | meters=None, 123 | step=None, 124 | start_time=None, 125 | end_time=None, 126 | interface_name=None, 127 | monitor_type=None, 128 | ha_member_index=None, 129 | **params): 130 | """ Action: GetWanMonitor 131 | @param resource: the ID of resource whose monitoring data 132 | you want to get. 133 | @param access_type: the wan access type. eg: line, vpc, cpe. 134 | @param meters: a list of metering types you want to get. 135 | e.g. "flow", "pps" 136 | @param step: the metering time step. e.g. "1m", "5m", "15m", 137 | "30m", "1h", "2h", "1d" 138 | @param start_time: the starting time stamp. 139 | @param end_time: the ending time stamp. 140 | @param interface_name: interface name, eg: eth0, eth1 141 | @param monitor_type: CPE's monitor type, eg: internet, pop 142 | @param ha_member_index: the ha member index. eg: 0/1 143 | """ 144 | action = const.ACTION_GET_WAN_MONITOR 145 | valid_keys = ['resource', 'access_type', 'meters', 'step', 146 | 'start_time', 'end_time', 'interface_name', 147 | 'monitor_type', 'ha_member_index'] 148 | body = filter_out_none(locals(), valid_keys) 149 | if not self.conn.req_checker.check_params( 150 | body, 151 | required_params=["resource", "access_type", 152 | "meters", "step", 153 | "start_time", "end_time"], 154 | list_params=["meters"], 155 | datetime_params=["start_time", "end_time"] 156 | ): 157 | return None 158 | 159 | return self.conn.send_request(action, body) 160 | 161 | def get_wan_info(self, 162 | resources=None, 163 | info_type=None, 164 | **params): 165 | """ Action: GetWanInfo 166 | @param resources: the comma separated IDs of wan resource. 167 | @param info_type: the info type. eg: cpe_mobile_info. 168 | """ 169 | action = const.ACTION_GET_WAN_INFO 170 | valid_keys = ['resources', 'info_type'] 171 | body = filter_out_none(locals(), valid_keys) 172 | if not self.conn.req_checker.check_params( 173 | body, 174 | required_params=["resources", "info_type"], 175 | list_params=["resources"], 176 | ): 177 | return None 178 | 179 | return self.conn.send_request(action, body) 180 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/snapshot.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class SnapshotAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_snapshots(self, snapshots=None, 27 | resource_id=None, 28 | snapshot_type=None, 29 | root_id=None, 30 | owner=None, 31 | status=None, 32 | verbose=0, 33 | search_word=None, 34 | offset=None, 35 | limit=None, 36 | tags=None, 37 | is_manually=None, 38 | **ignore): 39 | """ Describe snapshots filtered by condition. 40 | @param snapshots: an array including IDs of the snapshots you want to list. 41 | No ID specified means list all. 42 | @param resource_id: filter by resource ID. 43 | @param snapshot_type: filter by snapshot type. 0: incremantal snapshot, 1: full snapshot. 44 | @param root_id: filter by snapshot root ID. 45 | @param status: valid values include pending, available, suspended, deleted, ceased. 46 | @param verbose: the number to specify the verbose level, 47 | larger the number, the more detailed information will be returned. 48 | @param search_word: the search word. 49 | @param offset: the starting offset of the returning results. 50 | @param limit: specify the number of the returning results. 51 | @param tags : the array of IDs of tags. 52 | """ 53 | action = const.ACTION_DESCRIBE_SNAPSHOTS 54 | valid_keys = ['snapshots', 'resource_id', 'snapshot_type', 'root_id', 'status', 55 | 'verbose', 'search_word', 'offset', 'limit', 'tags', 'owner', 'is_manually'] 56 | body = filter_out_none(locals(), valid_keys) 57 | if not self.conn.req_checker.check_params(body, 58 | required_params=[], 59 | integer_params=[ 60 | "offset", "limit", "verbose", "snapshot_type"], 61 | list_params=["snapshots", "tags"] 62 | ): 63 | return None 64 | 65 | return self.conn.send_request(action, body) 66 | 67 | def create_snapshots(self, resources, 68 | snapshot_name=None, 69 | is_full=0, 70 | backstore_type=None, 71 | scheduler_id="", 72 | **ignore): 73 | """ Create snapshots. 74 | @param resources: the IDs of resources you want to create snapshot for, the supported resource types are instance/volume. 75 | @param snapshot_name: the name of the snapshot. 76 | @param is_full: whether to create a full snapshot. 0: determined by the system. 1: should create full snapshot. 77 | @param backstore_type: the backstore type used to store the snapshot. 78 | """ 79 | action = const.ACTION_CREATE_SNAPSHOTS 80 | valid_keys = ['resources', 'snapshot_name', 'is_full', 'backstore_type', 'scheduler_id'] 81 | body = filter_out_none(locals(), valid_keys) 82 | if not self.conn.req_checker.check_params(body, 83 | required_params=["resources"], 84 | integer_params=["is_full", 'backstore_type'], 85 | list_params=["resources"] 86 | ): 87 | return None 88 | 89 | return self.conn.send_request(action, body) 90 | 91 | def delete_snapshots(self, snapshots, merge_action=None, 92 | **ignore): 93 | """ Delete snapshots. 94 | @param snapshots: the IDs of snapshots you want to delete. 95 | @param merge_action: commit, merge the specified increment snapshot to parent snapshot. 96 | """ 97 | action = const.ACTION_DELETE_SNAPSHOTS 98 | valid_keys = ['snapshots', 'merge_action'] 99 | body = filter_out_none(locals(), valid_keys) 100 | if not self.conn.req_checker.check_params(body, 101 | required_params=["snapshots"], 102 | integer_params=[], 103 | list_params=["snapshots"] 104 | ): 105 | return None 106 | 107 | return self.conn.send_request(action, body) 108 | 109 | def apply_snapshots(self, snapshots, 110 | **ignore): 111 | """ Apply snapshots. 112 | @param snapshots: the IDs of snapshots you want to apply. 113 | """ 114 | action = const.ACTION_APPLY_SNAPSHOTS 115 | valid_keys = ['snapshots'] 116 | body = filter_out_none(locals(), valid_keys) 117 | if not self.conn.req_checker.check_params(body, 118 | required_params=["snapshots"], 119 | integer_params=[], 120 | list_params=["snapshots"] 121 | ): 122 | return None 123 | 124 | return self.conn.send_request(action, body) 125 | 126 | def modify_snapshot_attributes(self, snapshot, 127 | snapshot_name=None, 128 | description=None, 129 | scheduler_id=None, 130 | **ignore): 131 | """ Modify snapshot attributes. 132 | @param snapshot: the ID of snapshot whose attributes you want to modify. 133 | @param snapshot_name: the new snapshot name. 134 | @param description: the new snapshot description. 135 | """ 136 | action = const.ACTION_MODIFY_SNAPSHOT_ATTRIBUTES 137 | valid_keys = ['snapshot', 'snapshot_name', 'description', 'scheduler_id'] 138 | body = filter_out_none(locals(), valid_keys) 139 | if not self.conn.req_checker.check_params(body, 140 | required_params=["snapshot"], 141 | integer_params=[], 142 | list_params=[] 143 | ): 144 | return None 145 | 146 | return self.conn.send_request(action, body) 147 | 148 | def capture_instance_from_snapshot(self, snapshot, 149 | image_name=None, 150 | **ignore): 151 | """ Capture instance from snapshot. 152 | @param snapshot: the ID of snapshot you want to export as an image, this snapshot should be created from an instance. 153 | @param image_name: the image name. 154 | """ 155 | action = const.ACTION_CAPTURE_INSTANCE_FROM_SNAPSHOT 156 | valid_keys = ['snapshot', 'image_name'] 157 | body = filter_out_none(locals(), valid_keys) 158 | if not self.conn.req_checker.check_params(body, 159 | required_params=["snapshot"], 160 | integer_params=[], 161 | list_params=[] 162 | ): 163 | return None 164 | 165 | return self.conn.send_request(action, body) 166 | 167 | def create_volume_from_snapshot(self, snapshot, 168 | volume_name=None, 169 | **ignore): 170 | """ Create volume from snapshot. 171 | @param snapshot: the ID of snapshot you want to export as an volume, this snapshot should be created from a volume. 172 | @param volume_name: the volume name. 173 | """ 174 | action = const.ACTION_CREATE_VOLUME_FROM_SNAPSHOT 175 | valid_keys = ['snapshot', 'volume_name'] 176 | body = filter_out_none(locals(), valid_keys) 177 | if not self.conn.req_checker.check_params(body, 178 | required_params=["snapshot"], 179 | integer_params=[], 180 | list_params=[] 181 | ): 182 | return None 183 | 184 | return self.conn.send_request(action, body) 185 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/tag.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class TagAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_tags(self, tags=None, 27 | resources=None, 28 | search_word=None, 29 | owner=None, 30 | verbose=0, 31 | offset=None, 32 | limit=None, 33 | **ignore): 34 | """ Describe tags filtered by condition 35 | @param tags: IDs of the tags you want to describe. 36 | @param resources: IDs of the resources. 37 | @param verbose: the number to specify the verbose level, larger the number, the more detailed information will be returned. 38 | @param offset: the starting offset of the returning results. 39 | @param limit: specify the number of the returning results. 40 | """ 41 | action = const.ACTION_DESCRIBE_TAGS 42 | valid_keys = ['tags', 'search_word', 43 | 'verbose', 'offset', 'limit', 'owner', 'resources'] 44 | body = filter_out_none(locals(), valid_keys) 45 | if not self.conn.req_checker.check_params(body, 46 | required_params=[], 47 | integer_params=[ 48 | 'offset', 'limit', 'verbose'], 49 | list_params=['tags', 'resources']): 50 | return None 51 | 52 | return self.conn.send_request(action, body) 53 | 54 | def create_tag(self, tag_name, **ignore): 55 | """ Create a tag. 56 | @param tag_name: the name of the tag you want to create. 57 | """ 58 | action = const.ACTION_CREATE_TAG 59 | valid_keys = ['tag_name'] 60 | body = filter_out_none(locals(), valid_keys) 61 | if not self.conn.req_checker.check_params(body, required_params=['tag_name']): 62 | return None 63 | return self.conn.send_request(action, body) 64 | 65 | def delete_tags(self, tags, **ignore): 66 | """ Delete one or more tags. 67 | @param tags: IDs of the tags you want to delete. 68 | """ 69 | action = const.ACTION_DELETE_TAGS 70 | body = {'tags': tags} 71 | if not self.conn.req_checker.check_params(body, 72 | required_params=['tags'], 73 | list_params=['tags']): 74 | return None 75 | return self.conn.send_request(action, body) 76 | 77 | def modify_tag_attributes(self, tag, tag_name=None, description=None, **ignore): 78 | """ Modify tag attributes. 79 | @param tag: the ID of tag you want to modify its attributes. 80 | @param tag_name: the new name of tag. 81 | @param description: The detailed description of the resource. 82 | """ 83 | action = const.ACTION_MODIFY_TAG_ATTRIBUTES 84 | valid_keys = ['tag', 'tag_name', 'description'] 85 | body = filter_out_none(locals(), valid_keys) 86 | if not self.conn.req_checker.check_params(body, 87 | required_params=['tag']): 88 | return None 89 | return self.conn.send_request(action, body) 90 | 91 | def attach_tags(self, resource_tag_pairs, **ignore): 92 | """ Attach one or more tags to resources. 93 | @param resource_tag_pairs: the pair of resource and tag. 94 | it's a list-dict, such as: 95 | [{ 96 | 'tag_id': 'tag-hp55o9i5', 97 | 'resource_type': 'instance', 98 | 'resource_id': 'i-5yn6js06' 99 | }] 100 | """ 101 | action = const.ACTION_ATTACH_TAGS 102 | valid_keys = ['resource_tag_pairs'] 103 | body = filter_out_none(locals(), valid_keys) 104 | if not self.conn.req_checker.check_params(body, 105 | required_params=[ 106 | 'resource_tag_pairs'], 107 | list_params=['resource_tag_pairs']): 108 | return None 109 | for pair in resource_tag_pairs: 110 | if not isinstance(pair, dict): 111 | return None 112 | for key in ['tag_id', 'resource_id', 'resource_type']: 113 | if key not in pair: 114 | return None 115 | 116 | return self.conn.send_request(action, body) 117 | 118 | def detach_tags(self, resource_tag_pairs, **ignore): 119 | """ Detach one or more tags to resources. 120 | @param resource_tag_pairs: the pair of resource and tag. 121 | it's a list-dict, such as: 122 | [{ 123 | 'tag_id': 'tag-hp55o9i5', 124 | 'resource_type': 'instance', 125 | 'resource_id': 'i-5yn6js06' 126 | }] 127 | """ 128 | action = const.ACTION_DETACH_TAGS 129 | valid_keys = ['resource_tag_pairs'] 130 | body = filter_out_none(locals(), valid_keys) 131 | if not self.conn.req_checker.check_params(body, 132 | required_params=[ 133 | 'resource_tag_pairs'], 134 | list_params=['resource_tag_pairs']): 135 | return None 136 | for pair in resource_tag_pairs: 137 | if not isinstance(pair, dict): 138 | return None 139 | for key in ['tag_id', 'resource_id', 'resource_type']: 140 | if key not in pair: 141 | return None 142 | 143 | return self.conn.send_request(action, body) 144 | -------------------------------------------------------------------------------- /qingcloud/iaas/actions/vxnet.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from qingcloud.iaas import constants as const 18 | from qingcloud.misc.utils import filter_out_none 19 | 20 | 21 | class VxnetAction(object): 22 | 23 | def __init__(self, conn): 24 | self.conn = conn 25 | 26 | def describe_vxnets(self, vxnets=None, 27 | search_word=None, 28 | verbose=0, 29 | owner=None, 30 | limit=None, 31 | offset=None, 32 | tags=None, 33 | vxnet_type=None, 34 | mode=None, 35 | **ignore): 36 | """ Describe vxnets filtered by condition. 37 | @param vxnets: the IDs of vxnets you want to describe. 38 | @param verbose: the number to specify the verbose level, larger the number, the more detailed information will be returned. 39 | @param offset: the starting offset of the returning results. 40 | @param limit: specify the number of the returning results. 41 | @param tags : the array of IDs of tags. 42 | @param vxnet_type: the vxnet of type you want to describe. 43 | @param mode: the vxnet mode. 0: gre+ovs, 1: vxlan+bridge. 44 | """ 45 | action = const.ACTION_DESCRIBE_VXNETS 46 | valid_keys = ['vxnets', 'search_word', 'verbose', 'limit', 'offset', 47 | 'tags', 'vxnet_type', 'owner', 'mode'] 48 | body = filter_out_none(locals(), valid_keys) 49 | if not self.conn.req_checker.check_params(body, 50 | required_params=[], 51 | integer_params=[ 52 | 'limit', 'offset', 'verbose', 53 | 'vxnet_type', 'mode', 54 | ], 55 | list_params=['vxnets', 'tags'] 56 | ): 57 | return None 58 | 59 | return self.conn.send_request(action, body) 60 | 61 | def create_vxnets(self, vxnet_name=None, 62 | vxnet_type=const.VXNET_TYPE_MANAGED, 63 | count=1, 64 | mode=0, 65 | **ignore): 66 | """ Create one or more vxnets. 67 | @param vxnet_name: the name of vxnet you want to create. 68 | @param vxnet_type: vxnet type: unmanaged or managed. 69 | @param count : the number of vxnet you want to create. 70 | @param mode: the vxnet mode. 0: gre+ovs, 1: vxlan+bridge. 71 | """ 72 | action = const.ACTION_CREATE_VXNETS 73 | valid_keys = ['vxnet_name', 'vxnet_type', 'count', 'mode'] 74 | body = filter_out_none(locals(), valid_keys) 75 | if not self.conn.req_checker.check_params(body, 76 | required_params=['vxnet_type'], 77 | integer_params=[ 78 | 'vxnet_type', 'count', 'mode', 79 | ], 80 | list_params=[] 81 | ): 82 | return None 83 | 84 | return self.conn.send_request(action, body) 85 | 86 | def join_vxnet(self, vxnet, 87 | instances, 88 | **ignore): 89 | """ One or more instances join the vxnet. 90 | @param vxnet : the id of vxnet you want the instances to join. 91 | @param instances : the IDs of instances that will join vxnet. 92 | """ 93 | 94 | action = const.ACTION_JOIN_VXNET 95 | valid_keys = ['vxnet', 'instances'] 96 | body = filter_out_none(locals(), valid_keys) 97 | if not self.conn.req_checker.check_params(body, 98 | required_params=[ 99 | 'vxnet', 'instances'], 100 | integer_params=[], 101 | list_params=['instances'] 102 | ): 103 | return None 104 | 105 | return self.conn.send_request(action, body) 106 | 107 | def leave_vxnet(self, vxnet, 108 | instances, 109 | **ignore): 110 | """ One or more instances leave the vxnet. 111 | @param vxnet : The id of vxnet that the instances will leave. 112 | @param instances : the IDs of instances that will leave vxnet. 113 | """ 114 | action = const.ACTION_LEAVE_VXNET 115 | valid_keys = ['vxnet', 'instances'] 116 | body = filter_out_none(locals(), valid_keys) 117 | if not self.conn.req_checker.check_params(body, 118 | required_params=[ 119 | 'vxnet', 'instances'], 120 | integer_params=[], 121 | list_params=['instances'] 122 | ): 123 | return None 124 | 125 | return self.conn.send_request(action, body) 126 | 127 | def delete_vxnets(self, vxnets, 128 | **ignore): 129 | """ Delete one or more vxnets. 130 | @param vxnets: the IDs of vxnets you want to delete. 131 | """ 132 | action = const.ACTION_DELETE_VXNETS 133 | body = {'vxnets': vxnets} 134 | if not self.conn.req_checker.check_params(body, 135 | required_params=['vxnets'], 136 | integer_params=[], 137 | list_params=['vxnets'] 138 | ): 139 | return None 140 | 141 | return self.conn.send_request(action, body) 142 | 143 | def modify_vxnet_attributes(self, vxnet, 144 | vxnet_name=None, 145 | description=None, 146 | **ignore): 147 | """ Modify vxnet attributes 148 | @param vxnet: the ID of vxnet you want to modify its attributes. 149 | @param vxnet_name: the new name of vxnet. 150 | @param description: The detailed description of the resource. 151 | """ 152 | action = const.ACTION_MODIFY_VXNET_ATTRIBUTES 153 | valid_keys = ['vxnet', 'vxnet_name', 'description'] 154 | body = filter_out_none(locals(), valid_keys) 155 | if not self.conn.req_checker.check_params(body, 156 | required_params=['vxnet'], 157 | integer_params=[], 158 | list_params=[] 159 | ): 160 | return None 161 | 162 | return self.conn.send_request(action, body) 163 | 164 | def describe_vxnet_instances(self, vxnet, 165 | instances=None, 166 | image=None, 167 | instance_type=None, 168 | status=None, 169 | limit=None, 170 | offset=None, 171 | **ignore): 172 | """ Describe instances in vxnet. 173 | @param vxnet: the ID of vxnet whose instances you want to describe. 174 | @param image: filter by image ID. 175 | @param instances: filter by instance ID. 176 | @param instance_type: filter by instance type 177 | See: https://docs.qingcloud.com/api/common/includes/instance_type.html 178 | @param status: filter by status 179 | @param offset: the starting offset of the returning results. 180 | @param limit: specify the number of the returning results. 181 | """ 182 | action = const.ACTION_DESCRIBE_VXNET_INSTANCES 183 | valid_keys = ['vxnet', 'instances', 'image', 'instance_type', 'status', 184 | 'limit', 'offset'] 185 | body = filter_out_none(locals(), valid_keys) 186 | if not self.conn.req_checker.check_params(body, 187 | required_params=['vxnet'], 188 | integer_params=[ 189 | 'limit', 'offset'], 190 | list_params=['instances'] 191 | ): 192 | return None 193 | 194 | return self.conn.send_request(action, body) 195 | -------------------------------------------------------------------------------- /qingcloud/iaas/consolidator.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | """ 18 | Check parameters in request 19 | """ 20 | import re 21 | 22 | from qingcloud.iaas.errors import InvalidParameterError 23 | from qingcloud.iaas.router_static import RouterStaticFactory 24 | from qingcloud.misc.utils import parse_ts 25 | 26 | 27 | class RequestChecker(object): 28 | 29 | def err_occur(self, error_msg): 30 | raise InvalidParameterError(error_msg) 31 | 32 | def is_integer(self, value): 33 | try: 34 | int(value) 35 | except: 36 | return False 37 | return True 38 | 39 | def check_integer_params(self, directive, params): 40 | """ Specified params should be `int` type if in directive 41 | @param directive: the directive to check 42 | @param params: the params that should be `int` type. 43 | """ 44 | for param in params: 45 | if param not in directive: 46 | continue 47 | val = directive[param] 48 | if self.is_integer(val): 49 | directive[param] = int(val) 50 | else: 51 | self.err_occur( 52 | "parameter [%s] should be integer in directive [%s]" % (param, directive)) 53 | 54 | def check_list_params(self, directive, params): 55 | """ Specified params should be `list` type if in directive 56 | @param directive: the directive to check 57 | @param params: the params that should be `list` type. 58 | """ 59 | for param in params: 60 | if param not in directive: 61 | continue 62 | if not isinstance(directive[param], list): 63 | self.err_occur( 64 | "parameter [%s] should be list in directive [%s]" % (param, directive)) 65 | 66 | def check_required_params(self, directive, params): 67 | """ Specified params should be in directive 68 | @param directive: the directive to check 69 | @param params: the params that should be in directive. 70 | """ 71 | for param in params: 72 | if param not in directive: 73 | self.err_occur( 74 | "[%s] should be specified in directive [%s]" % (param, directive)) 75 | 76 | def check_datetime_params(self, directive, params): 77 | """ Specified params should be `date` type if in directive 78 | @param directive: the directive to check 79 | @param params: the params that should be `date` type. 80 | """ 81 | for param in params: 82 | if param not in directive: 83 | continue 84 | if not parse_ts(directive[param]): 85 | self.err_occur( 86 | "[%s] should be 'YYYY-MM-DDThh:mm:ssZ' in directive [%s]" % (param, directive)) 87 | 88 | def check_params(self, directive, required_params=None, 89 | integer_params=None, list_params=None, datetime_params=None): 90 | """ Check parameters in directive 91 | @param directive: the directive to check, should be `dict` type. 92 | @param required_params: a list of parameter that should be in directive. 93 | @param integer_params: a list of parameter that should be `integer` type 94 | if it exists in directive. 95 | @param list_params: a list of parameter that should be `list` type 96 | if it exists in directive. 97 | @param datetime_params: a list of parameter that should be `date` type 98 | if it exists in directive. 99 | """ 100 | if not isinstance(directive, dict): 101 | self.err_occur('[%s] should be dict type' % directive) 102 | return False 103 | 104 | if required_params: 105 | self.check_required_params(directive, required_params) 106 | if integer_params: 107 | self.check_integer_params(directive, integer_params) 108 | if list_params: 109 | self.check_list_params(directive, list_params) 110 | if datetime_params: 111 | self.check_datetime_params(directive, datetime_params) 112 | return True 113 | 114 | def check_sg_rules(self, rules): 115 | return all(self.check_params(rule, 116 | required_params=['priority', 'protocol'], 117 | integer_params=['priority', 'direction'], 118 | list_params=[] 119 | ) for rule in rules) 120 | 121 | def check_router_statics(self, statics): 122 | def check_router_static(static): 123 | required_params = ['static_type'] 124 | integer_params = [] 125 | static_type = static.get('static_type') 126 | if static_type == RouterStaticFactory.TYPE_PORT_FORWARDING: 127 | # src port, dst ip, dst port 128 | required_params.extend(['val1', 'val2', 'val3']) 129 | integer_params = [] 130 | elif static_type == RouterStaticFactory.TYPE_VPN: 131 | # vpn type 132 | required_params.extend(['val1']) 133 | elif static_type == RouterStaticFactory.TYPE_TUNNEL: 134 | required_params.extend(['vxnet_id', 'val1']) 135 | elif static_type == RouterStaticFactory.TYPE_FILTERING: 136 | integer_params = [] 137 | else: 138 | integer_params = [] 139 | 140 | return self.check_params(static, required_params, integer_params) 141 | 142 | return all(check_router_static(static) for static in statics) 143 | 144 | def check_lb_listener_port(self, port): 145 | if port in [25, 80, 443] or 1024 <= port <= 65535: 146 | return 147 | self.err_occur( 148 | 'illegal port[%s], valid ones are [25, 80, 443, 1024~65535]' % port) 149 | 150 | def check_lb_listener_healthy_check_method(self, method): 151 | # valid methods: "tcp", "http|/url", "http|/url|host" 152 | if method == 'tcp' or re.match('http\|\/[^|]*(\|.+)?', method): 153 | return 154 | self.err_occur('illegal healthy check method[%s]' % method) 155 | 156 | def check_lb_listener_healthy_check_option(self, options): 157 | # options format string: inter | timeout | fall | rise 158 | items = options.split('|') 159 | if len(items) != 4: 160 | self.err_occur('illegal healthy check options[%s]' % options) 161 | 162 | inter, timeout, fall, rise = [int(item) for item in items] 163 | if not 2 <= inter <= 60: 164 | self.err_occur( 165 | 'illegal inter[%s], should be between 2 and 60' % inter) 166 | if not 5 <= timeout <= 300: 167 | self.err_occur( 168 | 'illegal timeout[%s], should be between 5 and 300' % timeout) 169 | if not 2 <= fall <= 10: 170 | self.err_occur( 171 | 'illegal fall[%s], should be between 2 and 10' % fall) 172 | if not 2 <= rise <= 10: 173 | self.err_occur( 174 | 'illegal rise[%s], should be between 2 and 10' % rise) 175 | 176 | def check_lb_backend_port(self, port): 177 | if 1 <= port <= 65535: 178 | return 179 | self.err_occur( 180 | 'illegal port[%s], should be between 1 and 65535' % port) 181 | 182 | def check_lb_backend_weight(self, weight): 183 | if 1 <= weight <= 100: 184 | return 185 | self.err_occur( 186 | 'illegal weight[%s], should be between 1 and 100' % weight) 187 | 188 | def check_lb_listeners(self, listeners): 189 | required_params = ['listener_protocol', 190 | 'listener_port', 'backend_protocol'] 191 | integer_params = ['forwardfor', 'listener_port'] 192 | for listener in listeners: 193 | self.check_params(listener, 194 | required_params=required_params, 195 | integer_params=integer_params, 196 | ) 197 | self.check_lb_listener_port(listener['listener_port']) 198 | if 'healthy_check_method' in listener: 199 | self.check_lb_listener_healthy_check_method( 200 | listener['healthy_check_method']) 201 | if 'healthy_check_option' in listener: 202 | self.check_lb_listener_healthy_check_option( 203 | listener['healthy_check_option']) 204 | 205 | def check_lb_backends(self, backends): 206 | required_params = ['resource_id', 'port'] 207 | integer_params = ['weight', 'port'] 208 | for backend in backends: 209 | self.check_params(backend, 210 | required_params=required_params, 211 | integer_params=integer_params, 212 | ) 213 | self.check_lb_backend_port(backend['port']) 214 | if 'weight' in backend: 215 | self.check_lb_backend_weight(backend['weight']) 216 | -------------------------------------------------------------------------------- /qingcloud/iaas/errors.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | """ 18 | Exception classes 19 | """ 20 | 21 | 22 | class InvalidParameterError(Exception): 23 | """ Error when invalid parameter found in request 24 | """ 25 | pass 26 | 27 | 28 | class APIError(Exception): 29 | """ Error in response from api 30 | """ 31 | 32 | def __init__(self, err_code, err_msg): 33 | super(APIError, self).__init__(self) 34 | self.err_code = err_code 35 | self.err_msg = err_msg 36 | 37 | def __repr__(self): 38 | return '%s: %s-%s' % (self.__class__.__name__, 39 | self.err_code, self.err_msg) 40 | 41 | def __str__(self): 42 | return '%s: %s-%s' % (self.__class__.__name__, 43 | self.err_code, self.err_msg) 44 | 45 | 46 | class InvalidRouterStatic(Exception): 47 | pass 48 | 49 | 50 | class InvalidSecurityGroupRule(Exception): 51 | pass 52 | 53 | 54 | class InvalidAction(Exception): 55 | pass 56 | -------------------------------------------------------------------------------- /qingcloud/iaas/lb_backend.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import json 18 | from past.builtins import basestring 19 | 20 | 21 | class LoadBalancerBackend(object): 22 | """ LoadBalancerBackend is used to define backend in load balancer listener. 23 | """ 24 | resource_id = None 25 | loadbalancer_backend_name = None 26 | port = None 27 | weight = None 28 | 29 | def __init__(self, resource_id, port, weight=1, 30 | loadbalancer_backend_id=None, loadbalancer_backend_name=None, 31 | **kw): 32 | self.resource_id = resource_id 33 | self.port = port 34 | self.weight = weight 35 | self.loadbalancer_backend_name = loadbalancer_backend_name 36 | if loadbalancer_backend_id: 37 | self.loadbalancer_backend_id = loadbalancer_backend_id 38 | 39 | def __repr__(self): 40 | return '<%s>%s' % (self.__class__.__name__, self.to_json()) 41 | 42 | @classmethod 43 | def create_from_string(cls, string): 44 | """ Create load balancer backend from json formatted string. 45 | """ 46 | if not isinstance(string, basestring): 47 | return string 48 | data = json.loads(string) 49 | if isinstance(data, dict): 50 | return cls(**data) 51 | if isinstance(data, list): 52 | return [cls(**item) for item in data] 53 | 54 | def to_json(self): 55 | return { 56 | 'resource_id': self.resource_id, 57 | 'loadbalancer_backend_name': self.loadbalancer_backend_name, 58 | 'port': self.port, 59 | 'weight': self.weight, 60 | } 61 | -------------------------------------------------------------------------------- /qingcloud/iaas/lb_listener.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import json 18 | 19 | from qingcloud.iaas.constants import HEADER_X_FORWARD_FOR, HEADER_QC_LBID, HEADER_QC_LBIP 20 | 21 | HEADERS = { 22 | 'X-FORWARD-FOR': HEADER_X_FORWARD_FOR, 23 | 'QC-LBID': HEADER_QC_LBID, 24 | 'QC-LBIP': HEADER_QC_LBIP, 25 | } 26 | 27 | 28 | class LoadBalancerListener(object): 29 | """ LoadBalancerListener is used to define listener in load balancer. 30 | """ 31 | loadbalancer_listener_id = None 32 | loadbalancer_listener_name = None 33 | listener_port = None 34 | listener_protocol = None 35 | backend_protocol = None 36 | balance_mode = None 37 | forwardfor = None 38 | session_sticky = None 39 | healthy_check_method = None 40 | healthy_check_option = None 41 | 42 | def __init__(self, listener_port, listener_protocol, backend_protocol, 43 | balance_mode='roundrobin', forwardfor=None, headers=None, session_sticky='', 44 | healthy_check_method='tcp', healthy_check_option='10|5|2|5', 45 | loadbalancer_listener_name=None, loadbalancer_listener_id=None, 46 | **kw): 47 | self.listener_port = listener_port 48 | self.listener_protocol = listener_protocol 49 | self.backend_protocol = backend_protocol 50 | self.balance_mode = balance_mode 51 | self.forwardfor = forwardfor or LoadBalancerListener.get_forwardfor( 52 | headers) 53 | self.session_sticky = session_sticky 54 | self.healthy_check_method = healthy_check_method 55 | self.healthy_check_option = healthy_check_option 56 | self.loadbalancer_listener_name = loadbalancer_listener_name 57 | if loadbalancer_listener_id: 58 | self.loadbalancer_listener_id = loadbalancer_listener_id 59 | 60 | def __repr__(self): 61 | return '<%s>%s' % (self.__class__.__name__, self.to_json()) 62 | 63 | @staticmethod 64 | def get_forwardfor(headers): 65 | """ Get forwardfor from header array. 66 | """ 67 | if not headers: 68 | return 0 69 | 70 | forwardfor = 0 71 | for head in headers: 72 | if head in HEADERS: 73 | forwardfor |= HEADERS[head] 74 | return forwardfor 75 | 76 | @classmethod 77 | def create_from_string(cls, string): 78 | """ Create load balancer listener from json formatted string. 79 | """ 80 | data = json.loads(string) 81 | if isinstance(data, dict): 82 | return cls(**data) 83 | if isinstance(data, list): 84 | return [cls(**item) for item in data] 85 | 86 | def to_json(self): 87 | return { 88 | 'loadbalancer_listener_name': self.loadbalancer_listener_name, 89 | 'listener_port': self.listener_port, 90 | 'listener_protocol': self.listener_protocol, 91 | 'backend_protocol': self.backend_protocol, 92 | 'balance_mode': self.balance_mode, 93 | 'forwardfor': self.forwardfor, 94 | 'session_sticky': self.session_sticky, 95 | 'healthy_check_method': self.healthy_check_method, 96 | 'healthy_check_option': self.healthy_check_option, 97 | } 98 | -------------------------------------------------------------------------------- /qingcloud/iaas/monitor.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from copy import deepcopy 18 | from qingcloud.misc.utils import local_ts 19 | 20 | NA = 'NA' 21 | STEPS = { 22 | '5m': 300, 23 | '15m': 900, 24 | '30m': 1800, 25 | '1h': 3600, 26 | '2h': 7200, 27 | '1d': 24 * 3600, 28 | } 29 | 30 | 31 | class MonitorProcessor(object): 32 | """ Process monitoring data. 33 | """ 34 | 35 | def __init__(self, raw_meter_set, start_time, end_time, step): 36 | self.raw_meter_set = raw_meter_set 37 | self.start_time = local_ts(start_time) 38 | self.end_time = local_ts(end_time) 39 | self.step = STEPS.get(step) 40 | 41 | def _is_invalid(self, value): 42 | if isinstance(value, list): 43 | return any(v == NA for v in value) 44 | else: 45 | return value == NA 46 | 47 | def _get_empty_item(self, sample_item): 48 | """ Return empty item which is used as supplemental data. 49 | """ 50 | if isinstance(sample_item, list): 51 | return [None] * len(sample_item) 52 | else: 53 | return None 54 | 55 | def _fill_vacancies(self, value, from_ts, to_ts): 56 | ret = [] 57 | t = from_ts 58 | while t < to_ts: 59 | ret.append([t, value]) 60 | t += self.step 61 | return ret 62 | 63 | def _decompress_meter_data(self, data): 64 | """ Decompress meter data like: 65 | [ 66 | [1391854500, 3], # first item with timestamp 67 | 4, # normal value 68 | [200, 3], # [timestamp_offset, value] 69 | NA, # Not Avaliable 70 | .... 71 | ] 72 | """ 73 | if not data or not self.step or not self.start_time: 74 | return data 75 | 76 | empty_item = self._get_empty_item(data[0][1]) 77 | first_time = data[0][0] 78 | decompress_data = self._fill_vacancies( 79 | empty_item, self.start_time, first_time) 80 | 81 | decompress_data.append(data[0]) 82 | t = first_time + self.step 83 | for item in data[1:]: 84 | if self._is_invalid(item): 85 | item = empty_item 86 | 87 | # sometimes item like [timestamp_offset, value] 88 | elif isinstance(item, list) and len(item) > 1: 89 | if not isinstance(empty_item, list) or isinstance(item[1], list): 90 | t -= self.step 91 | decompress_data += self._fill_vacancies( 92 | empty_item, t + self.step, t + item[0]) 93 | t += item[0] 94 | item = item[1] 95 | 96 | decompress_data.append([t, item]) 97 | t += self.step 98 | 99 | return decompress_data 100 | 101 | def decompress_monitoring_data(self): 102 | """ Decompress instance/eip/volume monitoring data. 103 | """ 104 | meter_set = deepcopy(self.raw_meter_set) 105 | for meter in meter_set: 106 | data = meter['data'] 107 | if not data: 108 | continue 109 | meter['data'] = self._decompress_meter_data(data) 110 | return meter_set 111 | 112 | def decompress_lb_monitoring_data(self): 113 | """ Decompress load balancer related monitoring data. 114 | """ 115 | meter_set = deepcopy(self.raw_meter_set) 116 | for meter in meter_set: 117 | for data_item in meter['data_set']: 118 | data = data_item['data'] 119 | if not data: 120 | continue 121 | data_item['data'] = self._decompress_meter_data(data) 122 | return meter_set 123 | -------------------------------------------------------------------------------- /qingcloud/iaas/sg_rule.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import json 18 | 19 | from qingcloud.iaas.errors import InvalidSecurityGroupRule 20 | 21 | 22 | class SecurityGroupRuleFactory(object): 23 | """ Factory for security group rule 24 | 25 | Example: 26 | conn = qingcloud.iaas.connect_to_zone(....) 27 | security_group_id = 'sg-xxxxx' 28 | 29 | # Add security group rule 30 | ping_rule = SecurityGroupRuleFactory.create( 31 | protocol = SecurityGroupRuleFactory.PROTOCOL_ICMP, 32 | priority = 1, 33 | direction = SecurityGroupRuleFactory.INBOUND, 34 | action = 'accept', 35 | security_group_rule_name = 'ECHO', 36 | val1 = '8', 37 | val2 = '0' 38 | ) 39 | ping_rule = ping_rule.to_json() 40 | conn.add_security_group_rules(security_group_id, ping_rule) 41 | 42 | # Modify security group rule 43 | rules = conn.describe_security_group_rules(security_group_id) 44 | assert(rules['security_group_rule_set']) 45 | rule = rules['security_group_rule_set'][0] 46 | conn.modify_security_group_rule_attributes( 47 | rule['security_group_rule_id'], 48 | priority=5, 49 | ) 50 | 51 | """ 52 | 53 | PROTOCOL_TCP = 'tcp' 54 | PROTOCOL_UDP = 'udp' 55 | PROTOCOL_ICMP = 'icmp' 56 | PROTOCOL_GRE = 'gre' 57 | 58 | INBOUND = 0 59 | OUTBOUND = 1 60 | 61 | @classmethod 62 | def create(cls, protocol, priority, direction=INBOUND, action='accept', 63 | security_group_rule_id='', security_group_rule_name='', 64 | **kw): 65 | """ Create security group rule. 66 | @param protocol: support protocol. 67 | @param priority: should be between 0 and 100. 68 | """ 69 | if protocol not in RULE_MAPPER: 70 | raise InvalidSecurityGroupRule("invalid protocol[%s]" % protocol) 71 | if not isinstance(priority, int) or priority < 0 or priority > 100: 72 | raise InvalidSecurityGroupRule("invalid priority[%s]" % priority) 73 | 74 | clazz = RULE_MAPPER[protocol] 75 | inst = clazz(**kw) 76 | inst.priority = priority 77 | inst.direction = direction 78 | inst.action = action 79 | inst.security_group_rule_id = security_group_rule_id 80 | inst.security_group_rule_name = security_group_rule_name 81 | return inst 82 | 83 | @classmethod 84 | def create_from_string(cls, string): 85 | """ Create security group rule from json formatted string. 86 | """ 87 | data = json.loads(string) 88 | if isinstance(data, dict): 89 | return cls.create(**data) 90 | if isinstance(data, list): 91 | return [cls.create(**item) for item in data] 92 | 93 | 94 | class _SecurityGroupRule(object): 95 | """ _SecurityGroupRule is used to define a rule in security group. 96 | """ 97 | 98 | security_group_rule_id = None 99 | security_group_rule_name = None 100 | priority = None 101 | direction = None 102 | action = None 103 | protocol = None 104 | 105 | def extra_props(self): 106 | raise NotImplementedError 107 | 108 | def to_json(self): 109 | """ Format SecurityGroupRule to JSON string 110 | NOTE: call this method when passing SecurityGroupRule instance as API parameter 111 | """ 112 | props = { 113 | 'security_group_rule_id': self.security_group_rule_id, 114 | 'security_group_rule_name': self.security_group_rule_name, 115 | 'priority': self.priority, 116 | 'direction': self.direction, 117 | 'action': self.action, 118 | 'protocol': self.protocol, 119 | } 120 | props.update(self.extra_props()) 121 | return props 122 | 123 | def __repr__(self): 124 | return '<%s>%s' % (self.__class__.__name__, self.to_json()) 125 | 126 | 127 | class _RuleForTCP(_SecurityGroupRule): 128 | 129 | protocol = SecurityGroupRuleFactory.PROTOCOL_TCP 130 | 131 | def __init__(self, start_port='', end_port='', ip_network='', **kw): 132 | super(_RuleForTCP, self).__init__() 133 | self.start_port = start_port if start_port != '' else kw.get( 134 | 'val1', '') 135 | self.end_port = end_port if end_port != '' else kw.get('val2', '') 136 | self.ip_network = ip_network if ip_network != '' else kw.get( 137 | 'val3', '') 138 | 139 | def extra_props(self): 140 | return { 141 | 'val1': self.start_port, 142 | 'val2': self.end_port, 143 | 'val3': self.ip_network, 144 | } 145 | 146 | 147 | class _RuleForUDP(_SecurityGroupRule): 148 | 149 | protocol = SecurityGroupRuleFactory.PROTOCOL_UDP 150 | 151 | def __init__(self, start_port='', end_port='', ip_network='', **kw): 152 | super(_RuleForUDP, self).__init__() 153 | self.start_port = start_port if start_port != '' else kw.get( 154 | 'val1', '') 155 | self.end_port = end_port if end_port != '' else kw.get('val2', '') 156 | self.ip_network = ip_network if ip_network != '' else kw.get( 157 | 'val3', '') 158 | 159 | def extra_props(self): 160 | return { 161 | 'val1': self.start_port, 162 | 'val2': self.end_port, 163 | 'val3': self.ip_network, 164 | } 165 | 166 | 167 | class _RuleForICMP(_SecurityGroupRule): 168 | 169 | protocol = SecurityGroupRuleFactory.PROTOCOL_ICMP 170 | 171 | def __init__(self, icmp_type='', icmp_code='', ip_network='', **kw): 172 | super(_RuleForICMP, self).__init__() 173 | self.icmp_type = icmp_type if icmp_type != '' else kw.get('val1', '') 174 | self.icmp_code = icmp_code if icmp_code != '' else kw.get('val2', '') 175 | self.ip_network = ip_network if ip_network != '' else kw.get( 176 | 'val3', '') 177 | 178 | def extra_props(self): 179 | return { 180 | 'val1': self.icmp_type, 181 | 'val2': self.icmp_code, 182 | 'val3': self.ip_network, 183 | } 184 | 185 | 186 | class _RuleForGRE(_SecurityGroupRule): 187 | 188 | protocol = SecurityGroupRuleFactory.PROTOCOL_GRE 189 | 190 | def __init__(self, ip_network='', **kw): 191 | super(_RuleForGRE, self).__init__() 192 | self.ip_network = ip_network or kw.get('val3', '') 193 | 194 | def extra_props(self): 195 | return { 196 | 'val3': self.ip_network, 197 | } 198 | 199 | RULE_MAPPER = { 200 | SecurityGroupRuleFactory.PROTOCOL_TCP: _RuleForTCP, 201 | SecurityGroupRuleFactory.PROTOCOL_UDP: _RuleForUDP, 202 | SecurityGroupRuleFactory.PROTOCOL_ICMP: _RuleForICMP, 203 | SecurityGroupRuleFactory.PROTOCOL_GRE: _RuleForGRE, 204 | } 205 | -------------------------------------------------------------------------------- /qingcloud/misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunify/qingcloud-sdk-python/50e61f57bcb70e88e8847099958dd5f7c5520199/qingcloud/misc/__init__.py -------------------------------------------------------------------------------- /qingcloud/misc/json_tool.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | 18 | import json as jsmod 19 | 20 | 21 | def json_dump(obj, indent=None): 22 | """ Dump an object to json string, only basic types are supported. 23 | @return json string or `None` if failed 24 | 25 | >>> json_dump({'int': 1, 'none': None, 'str': 'string'}) 26 | '{"int":1,"none":null,"str":"string"}' 27 | """ 28 | try: 29 | jstr = jsmod.dumps(obj, separators=(',', ':'), 30 | indent=indent, sort_keys=True) 31 | except: 32 | jstr = None 33 | return jstr 34 | 35 | 36 | def json_load(json): 37 | """ Load from json string and create a new python object 38 | @return object or `None` if failed 39 | 40 | >>> json_load('{"int":1,"none":null,"str":"string"}') 41 | {u'int': 1, u'none': None, u'str': u'string'} 42 | """ 43 | try: 44 | obj = jsmod.loads(json) 45 | except: 46 | obj = None 47 | return obj 48 | 49 | __all__ = [json_dump, json_load] 50 | -------------------------------------------------------------------------------- /qingcloud/misc/utils.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import sys 18 | import time 19 | import base64 20 | 21 | 22 | def get_utf8_value(value): 23 | if sys.version < "3": 24 | if isinstance(value, unicode): 25 | return value.encode('utf-8') 26 | if not isinstance(value, str): 27 | value = str(value) 28 | return value 29 | else: 30 | return str(value) 31 | 32 | 33 | def filter_out_none(dictionary, keys=None): 34 | """ Filter out items whose value is None. 35 | If `keys` specified, only return non-None items with matched key. 36 | """ 37 | ret = {} 38 | if keys is None: 39 | keys = [] 40 | for key, value in dictionary.items(): 41 | if value is None or key not in keys: 42 | continue 43 | ret[key] = value 44 | return ret 45 | 46 | 47 | ISO8601 = '%Y-%m-%dT%H:%M:%SZ' 48 | ISO8601_MS = '%Y-%m-%dT%H:%M:%S.%fZ' 49 | 50 | 51 | def get_ts(ts=None): 52 | """ Get formatted time 53 | """ 54 | if not ts: 55 | ts = time.gmtime() 56 | return time.strftime(ISO8601, ts) 57 | 58 | 59 | def parse_ts(ts): 60 | """ Return as timestamp 61 | """ 62 | ts = ts.strip() 63 | try: 64 | ts_s = time.strptime(ts, ISO8601) 65 | return time.mktime(ts_s) 66 | except ValueError: 67 | try: 68 | ts_s = time.strptime(ts, ISO8601_MS) 69 | return time.mktime(ts_s) 70 | except ValueError: 71 | return 0 72 | 73 | 74 | def local_ts(utc_ts): 75 | ts = parse_ts(utc_ts) 76 | if ts: 77 | return ts - time.timezone 78 | else: 79 | return 0 80 | 81 | 82 | def read_file(file_name, mode='r'): 83 | """ read file content 84 | """ 85 | try: 86 | with open(file_name, mode) as f: 87 | content = f.read() 88 | except Exception: 89 | return None 90 | return content 91 | 92 | 93 | def encode_base64(content): 94 | try: 95 | base64str = base64.standard_b64encode(content) 96 | return base64str 97 | except Exception: 98 | return '' 99 | 100 | 101 | def decode_base64(base64str): 102 | try: 103 | decodestr = base64.standard_b64decode(base64str) 104 | return decodestr 105 | except Exception: 106 | return '' 107 | 108 | 109 | def base64_url_decode(inp): 110 | if sys.version > "3": 111 | if isinstance(inp, bytes): 112 | inp = inp.decode() 113 | return base64.urlsafe_b64decode(inp + '=' * (4 - len(inp) % 4)).decode() 114 | else: 115 | return base64.urlsafe_b64decode(str(inp + '=' * (4 - len(inp) % 4))) 116 | 117 | 118 | def base64_url_encode(inp): 119 | if sys.version > "3": 120 | if isinstance(inp, str): 121 | inp = inp.encode() 122 | return bytes.decode(base64.urlsafe_b64encode(inp).rstrip(b'=')) 123 | else: 124 | return base64.urlsafe_b64encode(str(inp)).rstrip(b'=') 125 | 126 | 127 | def wait_job(conn, job_id, timeout=60): 128 | """ waiting for job complete (success or fail) until timeout 129 | """ 130 | def describe_job(job_id): 131 | ret = conn.describe_jobs([job_id]) 132 | if not ret or not ret.get('job_set'): 133 | return None 134 | return ret['job_set'][0] 135 | 136 | deadline = time.time() + timeout 137 | while time.time() <= deadline: 138 | time.sleep(2) 139 | job = describe_job(job_id) 140 | if not job: 141 | continue 142 | if job['status'] not in ('pending', 'working'): 143 | if conn.debug: 144 | print('job is %s: %s' % (job['status'], job_id)) 145 | sys.stdout.flush() 146 | return True 147 | 148 | if conn.debug: 149 | print('timeout for job: %s' % job_id) 150 | sys.stdout.flush() 151 | return False 152 | -------------------------------------------------------------------------------- /qingcloud/qai/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Connect to QAI. 3 | """ 4 | from qingcloud.qai.connection import QAIConnection 5 | 6 | 7 | def connect(access_key_id, secret_access_key, zone): 8 | return QAIConnection(access_key_id, secret_access_key, zone) -------------------------------------------------------------------------------- /qingcloud/qai/connection.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional, List 3 | import requests 4 | import hashlib 5 | import base64 6 | import hmac 7 | from collections import OrderedDict 8 | from hashlib import sha256 9 | from urllib import parse 10 | 11 | import qingcloud.qai 12 | from qingcloud.misc.json_tool import json_dump 13 | from qingcloud.qai.constants import GET_TRAINS, WORK_GROUP, TRAINS_METRICS, GET_RESOURCE_GROUP, SHARE_RESOURCE_GROUP 14 | 15 | 16 | class QAIConnection(): 17 | """ 18 | Public connection to QAI. 19 | """ 20 | def __init__(self, qy_access_key_id, qy_secret_access_key, zone, host="ai.coreshub.cn", port=443, 21 | protocol="https"): 22 | self.qy_access_key_id = qy_access_key_id 23 | self.qy_secret_access_key = qy_secret_access_key 24 | self.zone = zone 25 | self.host = host 26 | self.port = port 27 | self.protocol = protocol 28 | 29 | # Send request to QAI. 30 | def send_request(self, url="", method="", params=None, body=None, headers=None, timeout=5): 31 | if headers: 32 | headers["Channel"] = "api" 33 | else: 34 | headers = {"Channel": "api"} 35 | signature = QAISignatureAuthHandler.generate_signature(method=method, url=url, ak=self.qy_access_key_id, 36 | sk=self.qy_secret_access_key, 37 | params=params) 38 | try: 39 | if method == "GET": 40 | path = f"{self.protocol}://{self.host}:{self.port}{url}?{signature}" 41 | response = requests.get(path, headers=headers, timeout=timeout) 42 | return response.text 43 | if method == "POST": 44 | path = f"{self.protocol}://{self.host}:{self.port}{url}?{signature}" 45 | response = requests.post(path, headers=headers, json=body, timeout=timeout) 46 | return response.text 47 | if method == "DELETE": 48 | path = f"{self.protocol}://{self.host}:{self.port}{url}?{signature}" 49 | response = requests.delete(path, headers=headers, timeout=timeout) 50 | return response.text 51 | except requests.exceptions.Timeout: 52 | print("Connection timed out.") 53 | raise Exception("Connection timed out.") 54 | except requests.exceptions.RequestException: 55 | print("Connection failed.") 56 | raise Exception("Connection failed.") 57 | except Exception as e: 58 | raise e 59 | 60 | # User 61 | def get_user_info(self): 62 | url = WORK_GROUP 63 | params = { 64 | 'zone': self.zone 65 | } 66 | resp = self.send_request(url=url, method="GET", params=params) 67 | return resp 68 | 69 | # Resource Group 70 | def get_resource_groups(self, offset: int = 0, limit: int = 20, reverse: bool = False, order_by: str = "created_at", search_word: str = ""): 71 | url = GET_RESOURCE_GROUP 72 | params = { 73 | 'zone': self.zone, 74 | 'offset': offset, 75 | 'limit': limit, 76 | 'reverse': reverse, 77 | 'order_by': order_by, 78 | 'search_word': search_word 79 | } 80 | resp = self.send_request(url=url, method="GET", params=params) 81 | return resp 82 | 83 | def get_share_users(self, rg_id: str = "", offset: int = 0, limit: int = 20): 84 | url = SHARE_RESOURCE_GROUP 85 | params = { 86 | 'zone': self.zone, 87 | 'rg_id': rg_id, 88 | 'offset': offset, 89 | 'limit': limit, 90 | } 91 | resp = self.send_request(url=url, method="GET", params=params) 92 | return resp 93 | 94 | def share_resource_group(self, rg_id: str, is_all: int = 1, share_user_ids: Optional[List[str]] = []): 95 | """ 96 | @param rg_id: The id of resource group you want to select. 97 | @param is_all: It has two values, 0 and 1. 1 represents sharing all sub accounts, 98 | and 0 represents sharing sub accounts under share_user_ids 99 | @param share_user_ids: Share sub accounts under share_user_ids. 100 | """ 101 | url = SHARE_RESOURCE_GROUP 102 | params = { 103 | 'zone': self.zone, 104 | } 105 | body = { 106 | 'rg_id': rg_id, 107 | 'is_all': is_all, 108 | 'share_user_ids': share_user_ids 109 | } 110 | resp = self.send_request(url=url, method="POST", params=params, body=body) 111 | return resp 112 | 113 | def remove_shared_resource_group(self, rg_id: str, is_all: int = 0, share_user_ids: Optional[List[str]] = []): 114 | """ 115 | @param rg_id: The id of resource group you want to select. 116 | @param is_all: It has two values, 0 and 1. 1 represents remove all sub accounts, 117 | and 0 represents remove sub accounts under share_user_ids 118 | @param share_user_ids: Reomve shared sub accounts under share_user_ids. 119 | """ 120 | url = SHARE_RESOURCE_GROUP 121 | params = { 122 | 'zone': self.zone, 123 | 'rg_id': rg_id, 124 | 'is_all': is_all, 125 | 'share_user_ids': share_user_ids 126 | } 127 | resp = self.send_request(url=url, method="DELETE", params=params) 128 | return resp 129 | 130 | # Train 131 | def get_trains(self, namespace: str = "ALL", name: str = '', image_name: str = '', reverse: bool = False, offset: int = 0, limit: int = 100, order_by: Optional[str] = None, 132 | status: Optional[List[str]] = None, endpoints: Optional[List[str]] = None, start_at: Optional[datetime] = None, 133 | end_at: Optional[datetime] = None, owner: Optional[str] = None): 134 | url = GET_TRAINS.format(namespace) 135 | params = { 136 | 'namespace': namespace, 137 | 'zone': self.zone, 138 | 'name': name, 139 | 'image_name': image_name, 140 | 'reverse': reverse, 141 | 'offset': offset, 142 | 'limit': limit, 143 | 'order_by': order_by, 144 | 'status': status, 145 | 'endpoints': endpoints, 146 | 'start_at': start_at, 147 | 'end_at': end_at, 148 | 'owner': owner 149 | } 150 | # Remove keys with a value of None 151 | params = {k: v for k, v in params.items() if v is not None} 152 | resp = self.send_request(url=url, method="GET", params=params) 153 | return resp 154 | 155 | def trains_metrics(self, resource_ids: List[str], namespace: str = "ALL"): 156 | if len(resource_ids) == 0: 157 | raise Exception("resource_ids cannot be empty.") 158 | url = TRAINS_METRICS.format(namespace) 159 | params = { 160 | 'namespace': namespace, 161 | 'zone': self.zone, 162 | 'resource_ids': resource_ids 163 | } 164 | # Remove keys with a value of None 165 | params = {k: v for k, v in params.items() if v is not None} 166 | resp = self.send_request(url=url, method="GET", params=params) 167 | return resp 168 | 169 | 170 | class QAISignatureAuthHandler(): 171 | """ 172 | QAISignatureAuthHandler is used to authenticate QAI. 173 | """ 174 | @staticmethod 175 | def generate_signature(method: str, url: str, ak: str, sk: str, params: dict): 176 | """ 177 | :param url: /api/test/ must be end / 178 | :param ak: access_key_id 179 | :param sk: secure_key 180 | :param params: dict type 181 | :param method: method GET POST PUT DELETE 182 | :return: 183 | """ 184 | url += "/" if not url.endswith("/") else "" 185 | params["access_key_id"] = ak 186 | sorted_param = OrderedDict() 187 | keys = sorted(params.keys()) 188 | for key in keys: 189 | if isinstance(params[key], list): 190 | sorted_param[key] = sorted(params[key]) 191 | else: 192 | sorted_param[key] = params[key] 193 | 194 | # generate url. 195 | url_param_parts = [] 196 | for key, values in sorted_param.items(): 197 | if not isinstance(values, list): 198 | url_param_parts.append(f"{key}={values}") 199 | else: 200 | for value in values: 201 | url_param_parts.append(f"{key}={value}") 202 | 203 | url_param = '&'.join(url_param_parts) 204 | string_to_sign = method + "\n" + url + "\n" + url_param + "\n" + hex_encode_md5_hash("") 205 | 206 | h = hmac.new(sk.encode(encoding="utf-8"), digestmod=sha256) 207 | h.update(string_to_sign.encode(encoding="utf-8")) 208 | sign = base64.b64encode(h.digest()).strip() 209 | signature = parse.quote_plus(sign) 210 | url_param += "&signature=%s" % signature 211 | return url_param 212 | 213 | 214 | def hex_encode_md5_hash(data): 215 | if not data: 216 | data = "".encode("utf-8") 217 | else: 218 | data = data.encode("utf-8") 219 | md5 = hashlib.md5() 220 | md5.update(data) 221 | return md5.hexdigest() 222 | 223 | -------------------------------------------------------------------------------- /qingcloud/qai/constants.py: -------------------------------------------------------------------------------- 1 | # user 2 | WORK_GROUP = "/aicp/user/workgroups" 3 | # train 4 | GET_TRAINS = "/aicp/trains/namespaces/{}/trains" 5 | TRAINS_METRICS = "/aicp/trains/namespaces/{}/trains/metrics" 6 | # resource_group 7 | GET_RESOURCE_GROUP = "/aicp/resource/resource_group" 8 | SHARE_RESOURCE_GROUP = "/aicp/resource/resource_group/share/user" 9 | -------------------------------------------------------------------------------- /qingcloud/qingstor/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface to QingStor (QingCloud Object Storage). 3 | """ 4 | 5 | from qingcloud.qingstor.connection import QSConnection 6 | 7 | 8 | def connect(host, access_key_id=None, secret_access_key=None): 9 | """ Connect to qingstor by access key. 10 | """ 11 | return QSConnection(access_key_id, secret_access_key, host) 12 | -------------------------------------------------------------------------------- /qingcloud/qingstor/acl.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2016 Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | class ACL(object): 18 | 19 | def __init__(self, bucket=None, acl=None): 20 | """ 21 | @param bucket - The bucket 22 | @param acl - The access control list of the bucket 23 | """ 24 | self.bucket = bucket 25 | self.acl = acl or [] 26 | self.grants = [] 27 | for item in self.acl: 28 | grantee = item["grantee"] 29 | if grantee["type"] == "user": 30 | grant = Grant( 31 | permission=item["permission"], 32 | type=grantee["type"], 33 | id=grantee["id"], 34 | name=grantee["name"] 35 | ) 36 | else: 37 | grant = Grant( 38 | permission=item["permission"], 39 | type=grantee["type"], 40 | name=grantee["name"] 41 | ) 42 | self.add_grant(grant) 43 | 44 | def add_grant(self, grant): 45 | self.grants.append(grant) 46 | 47 | def __repr__(self): 48 | return str(self.grants) 49 | 50 | 51 | class Grant(object): 52 | 53 | def __init__(self, permission, type, id=None, name=None): 54 | """ 55 | @param permission - The grant permission 56 | @param type - The grantee type 57 | @param id - The grantee user id 58 | @param name - The grantee name 59 | """ 60 | self.permission = permission 61 | self.type = type 62 | self.id = id 63 | self.name = name 64 | 65 | def __repr__(self): 66 | if self.type== "user": 67 | args = (self.id, self.permission) 68 | else: 69 | args = (self.name, self.permission) 70 | return "" % args 71 | 72 | def to_dict(self): 73 | 74 | if self.type == "user": 75 | grantee = { 76 | "type": self.type, 77 | "id": self.id, 78 | "name": self.name or "" 79 | } 80 | else: 81 | grantee = { 82 | "type": self.type, 83 | "name": self.name 84 | } 85 | 86 | return { 87 | "grantee": grantee, 88 | "permission": self.permission 89 | } 90 | -------------------------------------------------------------------------------- /qingcloud/qingstor/exception.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2015 Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from .util import load_data 18 | 19 | 20 | class QSResponseError(Exception): 21 | 22 | def __init__(self, status, body=None, request_id=None, 23 | code=None, message=None, url=None): 24 | self.status = status 25 | self.body = body or '' 26 | self.request_id = request_id 27 | self.code = code 28 | self.message = message 29 | self.url = url 30 | 31 | def __repr__(self): 32 | return "%s: %s %s\n%s" % (self.__class__.__name__, 33 | self.status, self.code, self.body) 34 | 35 | def __str__(self): 36 | return "%s: %s %s\n%s" % (self.__class__.__name__, 37 | self.status, self.code, self.body) 38 | 39 | 40 | def get_response_error(response, body=None): 41 | if not body: 42 | body = response.read() 43 | args = { 44 | "status": response.status, 45 | "body": body, 46 | "request_id": response.getheader("request_id") 47 | } 48 | if body: 49 | try: 50 | resp = load_data(body) 51 | args["code"] = resp["code"] 52 | args["message"] = resp["message"] 53 | args["url"] = resp["url"] 54 | except ValueError: 55 | pass 56 | return QSResponseError(**args) 57 | -------------------------------------------------------------------------------- /qingcloud/qingstor/key.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2015 Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from .exception import get_response_error 18 | 19 | 20 | class Key(object): 21 | 22 | DefaultContentType = "application/oct-stream" 23 | 24 | def __init__(self, bucket=None, name=None): 25 | 26 | self.bucket = bucket 27 | self.name = name 28 | self.resp = None 29 | self.content_type = self.DefaultContentType 30 | 31 | def __repr__(self): 32 | return '' % (self.name, self.bucket.name) 33 | 34 | def close(self): 35 | if self.resp: 36 | self.resp.read() 37 | self.resp = None 38 | 39 | def open_read(self, headers=None): 40 | """ Open this key for reading. 41 | """ 42 | if self.resp is None: 43 | self.resp = self.bucket.connection.make_request( 44 | "GET", self.bucket.name, self.name, headers=headers) 45 | if self.resp.status != 200 and self.resp.status != 206: 46 | err = get_response_error(self.resp) 47 | raise err 48 | 49 | def open(self, mode="r", headers=None): 50 | if mode == "r": 51 | self.open_read(headers) 52 | else: 53 | raise Exception("Not implement mode %s yet" % mode) 54 | 55 | def read(self, size=0): 56 | if size == 0: 57 | self.open_read() 58 | else: 59 | headers = {"Range": "bytes=0-%d" % size} 60 | self.open_read(headers) 61 | data = self.resp.read() 62 | if not data: 63 | self.resp.close() 64 | return data 65 | 66 | def send_file(self, fp, content_type=None): 67 | """ Upload a file to a key into the bucket. 68 | 69 | Keyword arguments: 70 | content_type - The content type of the object 71 | """ 72 | headers = { 73 | "Content-Type": content_type or self.content_type 74 | } 75 | response = self.bucket.connection.make_request( 76 | "PUT", self.bucket.name, self.name, data=fp, headers=headers) 77 | if response.status == 201: 78 | self.close() 79 | return True 80 | else: 81 | err = get_response_error(response) 82 | raise err 83 | 84 | def exists(self): 85 | """ Check whether the object exists or not. 86 | """ 87 | response = self.bucket.connection.make_request("HEAD", self.bucket.name, 88 | self.name) 89 | if response.status == 200: 90 | return True 91 | elif response.status == 404: 92 | return False 93 | else: 94 | err = get_response_error(response) 95 | raise err 96 | -------------------------------------------------------------------------------- /qingcloud/qingstor/multipart.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2015 Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | from .exception import get_response_error 18 | from .util import load_data 19 | 20 | 21 | class Part(object): 22 | 23 | def __init__(self, bucket, key_name, part_number, size=None, created=None): 24 | """ 25 | @param bucket - The name of the bucket 26 | @param key_name - The name of the object 27 | @param part_number - The number of the multipart 28 | @param size - The size of the multipart 29 | @param created - The creation time of the multipart 30 | """ 31 | self.bucket = bucket 32 | self.key_name = key_name 33 | self.part_number = part_number 34 | self.size = size 35 | self.created = created 36 | 37 | def __repr__(self): 38 | return "" % ( 39 | self.part_number, self.key_name 40 | ) 41 | 42 | 43 | class MultiPartUpload(object): 44 | 45 | def __init__(self, bucket, key_name, upload_id): 46 | """ 47 | @param bucket - The name of the bucket 48 | @param key_name - The name of the object 49 | @param upload_id - ID for the initiated multipart upload 50 | """ 51 | self.bucket = bucket 52 | self.key_name = key_name 53 | self.upload_id = upload_id 54 | 55 | def upload_part_from_file(self, fp, part_number): 56 | """ Upload multipart from a file 57 | 58 | Keyword arguments: 59 | fp - a file-like object 60 | part_number - The number of the multipart 61 | """ 62 | params = { 63 | "upload_id": self.upload_id, 64 | "part_number": str(part_number), 65 | } 66 | response = self.bucket.connection.make_request( 67 | "PUT", self.bucket.name, self.key_name, data=fp, params=params) 68 | if response.status == 201: 69 | part = Part(self.bucket.name, self.key_name, part_number) 70 | return part 71 | else: 72 | err = get_response_error(response) 73 | raise err 74 | 75 | def get_all_parts(self): 76 | """ Retrieve all multiparts of an object that uploaded. 77 | """ 78 | params = { 79 | "upload_id": self.upload_id, 80 | } 81 | response = self.bucket.connection.make_request( 82 | "GET", self.bucket.name, self.key_name, params=params) 83 | if response.status == 200: 84 | parts = [] 85 | resp = load_data(response.read()) 86 | for item in resp["object_parts"]: 87 | part = Part(self.bucket.name, self.key_name, 88 | item["part_number"]) 89 | part.size = item["size"] 90 | part.created = item["created"] 91 | parts.append(part) 92 | return parts 93 | else: 94 | err = get_response_error(response) 95 | raise err 96 | 97 | def cancel_upload(self): 98 | """Abort the multipart upload. 99 | """ 100 | return self.bucket.cancel_multipart_upload( 101 | self.key_name, self.upload_id 102 | ) 103 | 104 | def complete_upload(self, parts): 105 | """Complete the multipart upload. 106 | """ 107 | return self.bucket.complete_multipart_upload( 108 | self.key_name, self.upload_id, parts 109 | ) 110 | -------------------------------------------------------------------------------- /qingcloud/qingstor/util.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2016 Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import json 18 | 19 | def load_data(data): 20 | """ Wrapper to load json data, to be compatible with Python3. 21 | Returns: JSON data 22 | 23 | Keyword arguments: 24 | data: might be bytes or str 25 | """ 26 | if type(data) == bytes: 27 | return json.loads(data.decode("utf-8")) 28 | else: 29 | return json.loads(data) 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | import sys 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | if sys.version_info < (2, 6): 10 | error = 'ERROR: qingcloud-sdk requires Python Version 2.6 or above.' 11 | print >> sys.stderr, error 12 | sys.exit(1) 13 | 14 | 15 | setup( 16 | name='qingcloud-sdk', 17 | version='1.2.15', 18 | description='Software Development Kit for QingCloud.', 19 | long_description=open('README.rst', 'rb').read().decode('utf-8'), 20 | keywords='qingcloud iaas qingstor sdk', 21 | author='Yunify Team', 22 | author_email='simon@yunify.com', 23 | url='https://docs.qingcloud.com/sdk/', 24 | packages=['qingcloud', 'qingcloud.conn', 'qingcloud.iaas', 'qingcloud.iaas.actions', 25 | 'qingcloud.misc', 'qingcloud.qingstor', 'qingcloud.qai'], 26 | package_dir={'qingcloud-sdk': 'qingcloud'}, 27 | namespace_packages=['qingcloud'], 28 | include_package_data=True, 29 | install_requires=['future', 'requests'] 30 | ) 31 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import mock 2 | import unittest 3 | try: 4 | import httplib 5 | except: 6 | import http.client as httplib 7 | 8 | 9 | class MockTestCase(unittest.TestCase): 10 | """Base class for mocking http connection.""" 11 | connection_class = None 12 | 13 | def setUp(self): 14 | 15 | self.https_connection = mock.Mock( 16 | spec=httplib.HTTPSConnection("host", "port")) 17 | 18 | if self.connection_class is None: 19 | raise ValueError( 20 | "The connection_class attribute must be set firstly") 21 | 22 | self.connection = self.connection_class(qy_access_key_id="access_key_id", 23 | qy_secret_access_key="secret_access_key") 24 | 25 | self.connection._new_conn = mock.Mock( 26 | return_value=self.https_connection) 27 | 28 | def create_http_response(self, status_code, header=None, body=None): 29 | 30 | if header is None: 31 | header = {} 32 | if body is None: 33 | body = "" 34 | 35 | response = mock.Mock(spec=httplib.HTTPResponse) 36 | response.status = status_code 37 | response.read.return_value = body 38 | response.length = len(body) 39 | response.getheaders.return_value = header 40 | 41 | def overwrite_header(arg, default=None): 42 | header_dict = dict(header) 43 | if arg in header_dict: 44 | return header_dict[arg] 45 | else: 46 | return default 47 | response.getheader.side_effect = overwrite_header 48 | 49 | return response 50 | 51 | def assert_request_parameters(self, params, ignore_params_values=None): 52 | """Verify the actual parameters sent to the service API.""" 53 | request_params = self.actual_request.params.copy() 54 | if ignore_params_values is not None: 55 | for param in ignore_params_values: 56 | try: 57 | del request_params[param] 58 | except KeyError: 59 | pass 60 | self.assertDictEqual(request_params, params) 61 | 62 | def mock_http_response(self, status_code, header=None, body=None): 63 | http_response = self.create_http_response(status_code, header, body) 64 | self.https_connection.getresponse.return_value = http_response 65 | -------------------------------------------------------------------------------- /tests/actions/0test_instance_groups_action.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import os 18 | import time 19 | import random 20 | import unittest 21 | from qingcloud.iaas.connection import APIConnection 22 | 23 | 24 | class TestInstanceGroupsAction(unittest.TestCase): 25 | 26 | max_retry_times = 2 27 | 28 | @classmethod 29 | def setUpClass(cls): 30 | """ Initialization of test. """ 31 | 32 | cls.access_key_id = os.getenv('QY_ACCESS_KEY_ID') 33 | cls.secret_access_key = os.getenv('QY_SECRET_ACCESS_KEY') 34 | cls.zone = 'pek3' 35 | 36 | cls.conn = APIConnection( 37 | qy_access_key_id=cls.access_key_id, 38 | qy_secret_access_key=cls.secret_access_key, 39 | zone=cls.zone 40 | ) 41 | 42 | # Create two test instance. 43 | resp = cls.conn.run_instances( 44 | image_id='xenial4x64a', 45 | cpu=1, 46 | memory=1024, 47 | instance_name='Test_add_InstanceGroupsAction', 48 | count=2, 49 | login_mode="passwd", 50 | login_passwd='Test_add_InstanceGroupsAction99' 51 | ) 52 | cls.existed_instances = resp['instances'] 53 | cls.group_dict = {'repel_group': None, 'attract_group': None} 54 | 55 | if resp.get('ret_code') == 0: 56 | # Ensure that instances is available for test. 57 | while True: 58 | status_resp = cls.conn.describe_instances( 59 | instances=cls.existed_instances 60 | ) 61 | if status_resp['instance_set'][0].get('status') == 'running' and \ 62 | status_resp['instance_set'][1].get('status') == 'running': 63 | break 64 | else: 65 | raise Exception 66 | 67 | def test01_create_instance_groups(self): 68 | 69 | # Create a repel-group. 70 | resp_repel_group = self.conn.create_instance_groups(relation='repel') 71 | self.group_dict.update(repel_group=resp_repel_group['instance_groups'].pop()) 72 | self.assertEqual(resp_repel_group['ret_code'], 0) 73 | 74 | # Create a attract-group. 75 | resp_attract_group = self.conn.create_instance_groups(relation='attract') 76 | self.group_dict.update(attract_group=resp_attract_group['instance_groups'].pop()) 77 | self.assertEqual(resp_attract_group['ret_code'], 0) 78 | 79 | def test02_join_instance_group(self): 80 | 81 | existed_instances = [ 82 | self.existed_instances[0], 83 | self.existed_instances[1] 84 | ] 85 | tmp = {'instances': [random.choice(existed_instances)], 86 | 'group_id': self.group_dict.get('repel_group')} 87 | existed_instances.remove(tmp['instances'][0]) 88 | 89 | # Add an instance into repel-group. 90 | resp_repel = self.conn.join_instance_group( 91 | instances=tmp['instances'], 92 | instance_group=tmp['group_id'] 93 | ) 94 | self.group_dict.update(repel_group=tmp) 95 | self.assertEqual(resp_repel['ret_code'], 0) 96 | 97 | tmp = {'instances': [random.choice(existed_instances)], 98 | 'group_id': self.group_dict.get('attract_group')} 99 | 100 | # Add an instance into attract-group. 101 | resp_attract = self.conn.join_instance_group( 102 | instances=tmp['instances'], 103 | instance_group=tmp['group_id'] 104 | ) 105 | self.group_dict.update(attract_group=tmp) 106 | self.assertEqual(resp_attract['ret_code'], 0) 107 | 108 | def test03_describe_instance_groups(self): 109 | 110 | repel_id = self.group_dict['repel_group'].get('group_id') 111 | resp_repel = self.conn.describe_instance_groups( 112 | instance_groups=[repel_id] 113 | ) 114 | self.assertEqual(resp_repel['instance_group_set'][0]['instance_group_id'], repel_id) 115 | 116 | attract_id = self.group_dict['attract_group'].get('group_id') 117 | resp_attract = self.conn.describe_instance_groups( 118 | instance_groups=[attract_id] 119 | ) 120 | self.assertEqual(resp_attract['instance_group_set'][0]['instance_group_id'], attract_id) 121 | 122 | def test04_leave_instance_group(self): 123 | 124 | try_count = 0 125 | while try_count < self.max_retry_times: 126 | try: 127 | resp_repel = self.conn.leave_instance_group( 128 | instances=self.group_dict['repel_group'].get('instances'), 129 | instance_group=self.group_dict['repel_group'].get('group_id') 130 | ) 131 | self.assertEqual(resp_repel['ret_code'], 0) 132 | except Exception: 133 | try_count += 1 134 | time.sleep(2**try_count) 135 | pass 136 | 137 | try_count = 0 138 | while try_count < self.max_retry_times: 139 | try: 140 | resp_attract = self.conn.leave_instance_group( 141 | instances=self.group_dict['attract_group'].get('instances'), 142 | instance_group=self.group_dict['attract_group'].get('group_id') 143 | ) 144 | self.assertEqual(resp_attract['ret_code'], 0) 145 | except Exception: 146 | try_count += 1 147 | time.sleep(2**try_count) 148 | pass 149 | 150 | def test05_delete_instance_groups(self): 151 | 152 | try_count = 0 153 | while try_count < self.max_retry_times: 154 | check_empty = self.conn.describe_instance_groups( 155 | instance_groups=[ 156 | self.group_dict['repel_group'].get('group_id'), 157 | self.group_dict['attract_group'].get('group_id') 158 | ] 159 | ) 160 | if not check_empty['instance_group_set'][0].get('instances') and \ 161 | not check_empty['instance_group_set'][1].get('instances'): 162 | break 163 | 164 | resp_del_groups = self.conn.delete_instance_groups( 165 | instance_groups=[ 166 | self.group_dict['repel_group'].get('group_id'), 167 | self.group_dict['attract_group'].get('group_id') 168 | ] 169 | ) 170 | self.assertEqual(resp_del_groups['ret_code'], 0) 171 | 172 | @classmethod 173 | def tearDownClass(cls): 174 | """ Terminate the test instances.""" 175 | 176 | try_count = 0 177 | while try_count < cls.max_retry_times+3: 178 | 179 | resp = cls.conn.terminate_instances( 180 | instances=cls.existed_instances, 181 | direct_cease=1 182 | ) 183 | if resp['ret_code'] == 1400: 184 | try_count += 1 185 | time.sleep(2**try_count) 186 | continue 187 | elif resp['ret_code'] == 0: 188 | cls.conn._get_conn(cls.conn.host, cls.conn.port).close() 189 | break 190 | 191 | 192 | if __name__ == '__main__': 193 | 194 | unittest.main() 195 | -------------------------------------------------------------------------------- /tests/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunify/qingcloud-sdk-python/50e61f57bcb70e88e8847099958dd5f7c5520199/tests/actions/__init__.py -------------------------------------------------------------------------------- /tests/actions/test_instance_groups_action_mock.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import mock 18 | import random 19 | import unittest 20 | from qingcloud.iaas.actions.instance_groups import InstanceGroupsAction 21 | 22 | 23 | class TestInstanceGroupsAction(unittest.TestCase): 24 | 25 | max_retry_times = 2 26 | 27 | @classmethod 28 | def setUpClass(cls): 29 | """ Initialization of mock test. """ 30 | 31 | cls.ig_action_object = InstanceGroupsAction(mock.Mock()) 32 | cls.group_dict = {} 33 | cls.existed_instances = ["i-s5sdo5of", "i-a1wy8cvt"] 34 | 35 | def test01_create_instance_groups(self): 36 | 37 | # Create a repel-group. 38 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 39 | "action": "CreateInstanceGroupsResponse", 40 | "instance_groups": ["ig-9edrghud"], 41 | "ret_code": 0 42 | }) 43 | resp_repel_group = self.ig_action_object.create_instance_groups(relation='repel') 44 | self.group_dict.update(repel_group=resp_repel_group['instance_groups'].pop()) 45 | self.assertEqual(resp_repel_group['ret_code'], 0) 46 | 47 | # Create a attract-group. 48 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 49 | "action": "CreateInstanceGroupsResponse", 50 | "instance_groups": ["ig-ejtdp9su"], 51 | "ret_code": 0 52 | }) 53 | resp_attract_group = self.ig_action_object.create_instance_groups(relation='attract') 54 | self.group_dict.update(attract_group=resp_attract_group['instance_groups'].pop()) 55 | self.assertEqual(resp_attract_group['ret_code'], 0) 56 | 57 | def test02_join_instance_group(self): 58 | 59 | existed_instances = [ 60 | self.existed_instances[0], 61 | self.existed_instances[1] 62 | ] 63 | tmp = {'instances': [random.choice(existed_instances)], 64 | 'group_id': self.group_dict.get('repel_group')} 65 | existed_instances.remove(tmp['instances'][0]) 66 | 67 | # Add an instance into repel-group. 68 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 69 | "action": "JoinInstanceGroupResponse", 70 | "job_id": "j-ggxi98503wy", 71 | "ret_code": 0 72 | }) 73 | resp_repel = self.ig_action_object.join_instance_group( 74 | instances=tmp['instances'], 75 | instance_group=tmp['group_id'] 76 | ) 77 | self.group_dict.update(repel_group=tmp) 78 | self.assertEqual(resp_repel['ret_code'], 0) 79 | 80 | tmp = {'instances': [random.choice(existed_instances)], 81 | 'group_id': self.group_dict.get('attract_group')} 82 | 83 | # Add an instance into attract-group. 84 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 85 | "action": "JoinInstanceGroupResponse", 86 | "job_id": "j-j7e7lt0nk4c", 87 | "ret_code": 0 88 | }) 89 | resp_attract = self.ig_action_object.join_instance_group( 90 | instances=tmp['instances'], 91 | instance_group=tmp['group_id'] 92 | ) 93 | self.group_dict.update(attract_group=tmp) 94 | self.assertEqual(resp_attract['ret_code'], 0) 95 | 96 | def test03_describe_instance_groups(self): 97 | 98 | repel_id = self.group_dict['repel_group'].get('group_id') 99 | 100 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 101 | "action": "DescribeInstanceGroupsResponse", 102 | "total_count": 1, 103 | "instance_group_set":[{ 104 | "instance_group_name": "\u5206\u6563", 105 | "description": "", 106 | "tags": [], 107 | "controller": "self", 108 | "console_id": "alphacloud", 109 | "instances": [{ 110 | "instance_id": "i-s5sdo5of", 111 | "instance_name": "test4", 112 | "status": "running", 113 | "instance_group_id": "ig-9edrghud" 114 | }], 115 | "root_user_id": "usr-jGys5Ecd", 116 | "create_time": "2021-02-08T09:00:38Z", 117 | "relation": "repel", 118 | "owner": "usr-jGys5Ecd", 119 | "resource_project_info": [], 120 | "instance_group_id": "ig-9edrghud", 121 | "zone_id": "test" 122 | }], 123 | "ret_code": 0 124 | }) 125 | 126 | resp_repel = self.ig_action_object.describe_instance_groups( 127 | instance_groups=[repel_id] 128 | ) 129 | self.assertEqual(resp_repel['instance_group_set'][0]['instance_group_id'], repel_id) 130 | 131 | attract_id = self.group_dict['attract_group'].get('group_id') 132 | 133 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 134 | "action": "DescribeInstanceGroupsResponse", 135 | "total_count": 1, 136 | "instance_group_set": [{ 137 | "instance_group_name": "\u96c6\u4e2d", 138 | "description": "", 139 | "tags": [], 140 | "controller": "self", 141 | "console_id": "alphacloud", 142 | "instances":[{ 143 | "instance_id": "i-a1wy8cvt", 144 | "instance_name": "test3", 145 | "status": "running", 146 | "instance_group_id": "ig-ejtdp9su" 147 | }], 148 | "root_user_id": "usr-jGys5Ecd", 149 | "create_time": "2021-02-08T09:07:35Z", 150 | "relation": "attract", 151 | "owner": "usr-jGys5Ecd", 152 | "resource_project_info": [], 153 | "instance_group_id": "ig-ejtdp9su", 154 | "zone_id": "test" 155 | }], 156 | "ret_code": 0 157 | }) 158 | 159 | resp_attract = self.ig_action_object.describe_instance_groups( 160 | instance_groups=[attract_id] 161 | ) 162 | self.assertEqual(resp_attract['instance_group_set'][0]['instance_group_id'], attract_id) 163 | 164 | def test04_leave_instance_group(self): 165 | 166 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 167 | "action": "LeaveInstanceGroupResponse", 168 | "job_id": "j-lxuwydwb0mk", 169 | "ret_code": 0 170 | }) 171 | 172 | resp_repel = self.ig_action_object.leave_instance_group( 173 | instances=self.group_dict['repel_group'].get('instances'), 174 | instance_group=self.group_dict['repel_group'].get('group_id') 175 | ) 176 | self.assertEqual(resp_repel['ret_code'], 0) 177 | 178 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 179 | "action": "LeaveInstanceGroupResponse", 180 | "job_id": "j-oobmayrygpy", 181 | "ret_code": 0 182 | }) 183 | 184 | resp_attract = self.ig_action_object.leave_instance_group( 185 | instances=self.group_dict['attract_group'].get('instances'), 186 | instance_group=self.group_dict['attract_group'].get('group_id') 187 | ) 188 | self.assertEqual(resp_attract['ret_code'], 0) 189 | 190 | def test05_delete_instance_groups(self): 191 | 192 | self.ig_action_object.conn.send_request = mock.Mock(return_value={ 193 | "action": "DeleteInstanceGroupsResponse", 194 | "instance_groups": ["ig-9edrghud", "ig-ejtdp9su"], 195 | "ret_code": 0 196 | }) 197 | 198 | resp_del_groups = self.ig_action_object.delete_instance_groups( 199 | instance_groups=[ 200 | self.group_dict['repel_group'].get('group_id'), 201 | self.group_dict['attract_group'].get('group_id') 202 | ] 203 | ) 204 | self.assertEqual(resp_del_groups['ret_code'], 0) 205 | 206 | 207 | if __name__ == '__main__': 208 | 209 | unittest.main() 210 | -------------------------------------------------------------------------------- /tests/qingstor/test_bucket.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from tests import MockTestCase 5 | from qingcloud.qingstor.connection import QSConnection 6 | from qingcloud.qingstor.multipart import MultiPartUpload, Part 7 | 8 | 9 | class TestQingStorBucket(MockTestCase): 10 | 11 | connection_class = QSConnection 12 | 13 | def setUp(self): 14 | super(TestQingStorBucket, self).setUp() 15 | self.mock_http_response(status_code=201) 16 | self.bucket = self.connection.create_bucket( 17 | bucket="mybucket", zone="pek3") 18 | self.assertEqual(self.bucket.name, "mybucket") 19 | 20 | def test_bucket_delete(self): 21 | self.mock_http_response(status_code=204) 22 | self.bucket.delete() 23 | 24 | def test_bucket_new_key(self): 25 | key = self.bucket.new_key("myobject") 26 | self.assertEqual(key.name, "myobject") 27 | 28 | def test_bucket_get_key(self): 29 | self.mock_http_response(status_code=200) 30 | key = self.bucket.get_key("myobject") 31 | self.assertEqual(key.name, "myobject") 32 | 33 | def test_bucket_delete_key(self): 34 | self.mock_http_response(status_code=204) 35 | self.bucket.delete_key("myobject") 36 | 37 | def test_bucket_list(self): 38 | 39 | keys = [{ 40 | "modified": 1445508821, 41 | "created": "2015-10-22T10:13:41.000Z", 42 | "mime_type": "application/x-www-form-urlencoded", 43 | "key": "bbcfvevjkcoorkvo", 44 | "size": 409600 45 | }, { 46 | "modified": 1445508789, 47 | "created": "2015-10-22T10:13:09.000Z", 48 | "mime_type": "application/x-www-form-urlencoded", 49 | "key": "bbcnjwqyqpodqmqf", 50 | "size": 409600 51 | }, { 52 | "modified": 1445508833, 53 | "created": "2015-10-22T10:13:53.000Z", 54 | "mime_type": "application/x-www-form-urlencoded", 55 | "key": "bbdwopvqfbpjqwjo", 56 | "size": 409600 57 | }] 58 | 59 | body = { 60 | "keys": keys, 61 | "prefix": "", 62 | "limit": 200, 63 | "name": "tsung-get-1445508780", 64 | "owner": "f9c74ff873c311e5948c5254862d85f1", 65 | "delimiter": "", 66 | "marker": "bnlepimlrwmlosvq", 67 | "common_prefixes": [] 68 | } 69 | self.mock_http_response(status_code=200, body=json.dumps(body)) 70 | resp = self.bucket.list() 71 | for index, key in enumerate(resp): 72 | self.assertEqual(key.name, keys[index]["key"]) 73 | self.assertEqual(key.content_type, keys[index]["mime_type"]) 74 | self.assertEqual(key.bucket.name, "mybucket") 75 | 76 | def test_bucket_stats(self): 77 | body = { 78 | "count": 10000, 79 | "status": "active", 80 | "name": "tsung-get-1445508780", 81 | "created": "2015-10-22T10:12:59.000Z", 82 | "url": "http://tsung-get-1445508780.pek3.qingstor.com", 83 | "location": "pek3", 84 | "status_time": "2015-10-22T10:12:59.000Z", 85 | "size": 4096000000 86 | } 87 | self.mock_http_response(status_code=200, body=json.dumps(body)) 88 | stats = self.bucket.stats() 89 | self.assertDictEqual(stats, body) 90 | 91 | def test_bucket_get_acl(self): 92 | 93 | grants = [ 94 | { 95 | "grantee": {"type": "user", "id": "usr-1mvNCzZu", "name": "William"}, 96 | "permission": "FULL_CONTROL" 97 | }, 98 | { 99 | "grantee": {"type": "group", "name": "QS_ALL_USERS"}, 100 | "permission": "READ" 101 | }, 102 | ] 103 | 104 | owner = { 105 | "id": "usr-1mvNCzZu", 106 | "name": "William" 107 | } 108 | 109 | body = { 110 | "owner": owner, 111 | "acl": grants 112 | } 113 | 114 | self.mock_http_response(status_code=200, body=json.dumps(body)) 115 | acl = self.bucket.get_acl() 116 | self.assertDictContainsSubset(acl.grants[0].to_dict(), grants[0]) 117 | self.assertDictContainsSubset(acl.grants[1].to_dict(), grants[1]) 118 | 119 | def test_bucket_set_acl(self): 120 | self.mock_http_response(status_code=200) 121 | acl = [ 122 | { 123 | "grantee": {"type": "user", "id": "usr-B5WmNqAI"}, 124 | "permission": "FULL_CONTROL" 125 | }, 126 | { 127 | "grantee": {"type": "group", "name": "QS_ALL_USERS"}, 128 | "permission": "READ" 129 | }, 130 | ] 131 | ret = self.bucket.set_acl(acl) 132 | self.assertTrue(ret) 133 | 134 | def test_bucket_get_cors(self): 135 | body = { 136 | "cors_rules": [ 137 | { 138 | "allowed_origin": "http://*.qingcloud.com", 139 | "allowed_methods": [ 140 | "PUT", 141 | "GET", 142 | "DELETE", 143 | "POST" 144 | ], 145 | "allowed_headers": [ 146 | "X-QS-Date", 147 | "Content-Type", 148 | "Content-MD5", 149 | "Authorization" 150 | ], 151 | "max_age_seconds": 200, 152 | "expose_headers": [ 153 | "X-QS-Date" 154 | ] 155 | } 156 | ] 157 | } 158 | self.mock_http_response(status_code=200, body=json.dumps(body)) 159 | acls = self.bucket.get_cors() 160 | self.assertDictEqual(acls, body) 161 | 162 | def test_bucket_set_cors(self): 163 | self.mock_http_response(status_code=200) 164 | ret = self.bucket.set_cors({ 165 | "cors_rules": [ 166 | { 167 | "allowed_origin": "http://*.example.com", 168 | "allowed_methods": [ 169 | "PUT", 170 | "GET", 171 | "DELETE", 172 | "POST" 173 | ], 174 | "allowed_headers": [ 175 | "*" 176 | ], 177 | "max_age_seconds": 400 178 | } 179 | ] 180 | }) 181 | self.assertTrue(ret) 182 | 183 | def test_bucket_delete_cors(self): 184 | self.mock_http_response(status_code=204) 185 | ret = self.bucket.delete_cors() 186 | self.assertTrue(ret) 187 | 188 | def test_bucket_initiate_multipart_upload(self): 189 | body = { 190 | "bucket": "mybucket", 191 | "key": "myobject", 192 | "upload_id": "95e28428651f33ea8162b3209bf3b867" 193 | } 194 | self.mock_http_response(status_code=200, body=json.dumps(body)) 195 | handler = self.bucket.initiate_multipart_upload(key_name="myobject") 196 | self.assertIsInstance(handler, MultiPartUpload) 197 | 198 | def test_bucket_cancel_multipart_upload(self): 199 | self.mock_http_response(status_code=204) 200 | ret = self.bucket.cancel_multipart_upload(key_name="myobject", 201 | upload_id="95e28428651f33ea8162b3209bf3b867") 202 | self.assertTrue(ret) 203 | 204 | def test_bucket_complete_multipart_upload(self): 205 | parts = [] 206 | for part_number in range(0, 3): 207 | part = Part(bucket="mybucket", key_name="myobject", 208 | part_number=part_number) 209 | parts.append(part) 210 | self.mock_http_response(status_code=201) 211 | ret = self.bucket.complete_multipart_upload(key_name="myobject", 212 | upload_id="95e28428651f33ea8162b3209bf3b867", parts=parts) 213 | self.assertTrue(ret) 214 | 215 | 216 | if __name__ == "__main__": 217 | unittest.main() 218 | -------------------------------------------------------------------------------- /tests/qingstor/test_connection.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from tests import MockTestCase 5 | from qingcloud.qingstor.connection import QSConnection 6 | 7 | 8 | class TestQingStorConnection(MockTestCase): 9 | 10 | connection_class = QSConnection 11 | 12 | def setUp(self): 13 | super(TestQingStorConnection, self).setUp() 14 | self.conn = self.connection 15 | 16 | def test_connection_create_bucket(self): 17 | self.mock_http_response(status_code=201) 18 | bucket = self.conn.create_bucket(bucket="test-bucket", zone="pek3") 19 | self.assertEqual(bucket.name, "test-bucket") 20 | 21 | def test_connection_get_bucket(self): 22 | self.mock_http_response(status_code=200) 23 | bucket = self.conn.get_bucket(bucket="test-bucket") 24 | self.assertEqual(bucket.name, "test-bucket") 25 | 26 | def test_connection_get_all_buckets(self): 27 | body = { 28 | "count": 22, 29 | "buckets": [ 30 | { 31 | "location": "pek3", 32 | "name": "test1", 33 | "urls": [ 34 | "http://test1.qingstor.com", 35 | "http://test1.pek3.qingstor.com", 36 | "http://pek3.qingstor.com/test1" 37 | ], 38 | "created": "2015-10-15T15:08:19Z" 39 | }, { 40 | "location": "pek3", 41 | "name": "test0", 42 | "urls": [ 43 | "http://test0.qingstor.com", 44 | "http://test0.pek3.qingstor.com", 45 | "http://pek3.qingstor.com/test0" 46 | ], 47 | "created": "2015-10-15T15:27:27Z" 48 | }] 49 | } 50 | 51 | self.mock_http_response(status_code=200, body=json.dumps(body)) 52 | buckets = self.conn.get_all_buckets() 53 | self.assertDictEqual(buckets, body) 54 | 55 | if __name__ == "__main__": 56 | unittest.main() 57 | -------------------------------------------------------------------------------- /tests/qingstor/test_key.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from tests import MockTestCase 5 | from qingcloud.qingstor.connection import QSConnection 6 | 7 | 8 | class TestQingStorKey(MockTestCase): 9 | 10 | connection_class = QSConnection 11 | 12 | def setUp(self): 13 | super(TestQingStorKey, self).setUp() 14 | self.mock_http_response(status_code=201) 15 | conn = self.connection 16 | bucket = conn.create_bucket(bucket="mybucket", zone="pek3") 17 | self.mock_http_response(status_code=200) 18 | self.key = bucket.get_key("myobject") 19 | self.assertEqual(self.key.name, "myobject") 20 | 21 | def test_key_open(self): 22 | self.mock_http_response(status_code=200) 23 | self.key.open(mode="r") 24 | 25 | def test_key_read(self): 26 | self.mock_http_response(status_code=200, body="hello world") 27 | data = self.key.read() 28 | self.assertEqual(data, "hello world") 29 | 30 | def test_key_send_file(self): 31 | with open(".test_key_send_file", "w+") as f: 32 | f.write("hello world") 33 | self.mock_http_response(status_code=201) 34 | with open(".test_key_send_file", "r") as f: 35 | ret = self.key.send_file(f, content_type="text/plain") 36 | self.assertTrue(ret) 37 | os.remove(".test_key_send_file") 38 | 39 | def test_key_exists(self): 40 | self.mock_http_response(status_code=200) 41 | ret = self.key.exists() 42 | self.assertTrue(ret) 43 | self.mock_http_response(status_code=404) 44 | ret = self.key.exists() 45 | self.assertFalse(ret) 46 | 47 | 48 | if __name__ == "__main__": 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/qingstor/test_multipart.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import unittest 4 | 5 | from tests import MockTestCase 6 | from qingcloud.qingstor.connection import QSConnection 7 | 8 | 9 | class TestQingStorMultiPart(MockTestCase): 10 | 11 | connection_class = QSConnection 12 | 13 | def setUp(self): 14 | super(TestQingStorMultiPart, self).setUp() 15 | self.mock_http_response(status_code=201) 16 | bucket = self.connection.create_bucket(bucket="mybucket", zone="pek3") 17 | body = { 18 | "bucket": "mybucket", 19 | "key": "myobject", 20 | "upload_id": "95e28428651f33ea8162b3209bf3b867" 21 | } 22 | self.mock_http_response(status_code=200, body=json.dumps(body)) 23 | self.handler = bucket.initiate_multipart_upload(key_name="myobject") 24 | 25 | def test_multipart_upload_part_from_file(self): 26 | with open(".test_multipart_upload_part_from_file", "w+") as f: 27 | f.write("hello world") 28 | self.mock_http_response(status_code=201) 29 | for part_number in range(0, 3): 30 | with open(".test_multipart_upload_part_from_file", "r") as f: 31 | part = self.handler.upload_part_from_file(f, part_number) 32 | self.assertEqual(part.bucket, "mybucket") 33 | self.assertEqual(part.key_name, "myobject") 34 | self.assertEqual(part.part_number, part_number) 35 | os.remove(".test_multipart_upload_part_from_file") 36 | 37 | def test_get_all_parts(self): 38 | body = { 39 | "count": 3, 40 | "object_parts": [ 41 | {"part_number": 1, "size": 12, "created": "2015-10-26T03:19:32.000Z"}, 42 | {"part_number": 2, "size": 12, "created": "2015-10-26T03:19:32.000Z"}, 43 | {"part_number": 3, "size": 12, "created": "2015-10-26T03:19:32.000Z"} 44 | ] 45 | } 46 | self.mock_http_response(status_code=200, body=json.dumps(body)) 47 | parts = self.handler.get_all_parts() 48 | for index, part in enumerate(parts): 49 | self.assertEqual(part.part_number, body[ 50 | "object_parts"][index]["part_number"]) 51 | self.assertEqual(part.size, body["object_parts"][index]["size"]) 52 | self.assertEqual(part.created, body[ 53 | "object_parts"][index]["created"]) 54 | 55 | def test_cancel_upload(self): 56 | self.mock_http_response(status_code=204) 57 | ret = self.handler.cancel_upload() 58 | self.assertTrue(ret) 59 | 60 | def test_complete_upload(self): 61 | body = { 62 | "count": 3, 63 | "object_parts": [ 64 | {"part_number": 1, "size": 12, "created": "2015-10-26T03:19:32.000Z"}, 65 | {"part_number": 2, "size": 12, "created": "2015-10-26T03:19:32.000Z"}, 66 | {"part_number": 3, "size": 12, "created": "2015-10-26T03:19:32.000Z"} 67 | ] 68 | } 69 | self.mock_http_response(status_code=200, body=json.dumps(body)) 70 | parts = self.handler.get_all_parts() 71 | self.mock_http_response(status_code=201) 72 | ret = self.handler.complete_upload(parts) 73 | self.assertTrue(ret) 74 | 75 | if __name__ == "__main__": 76 | unittest.main() 77 | -------------------------------------------------------------------------------- /tests/test_app_auth.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import unittest 18 | from qingcloud.conn.auth import AppSignatureAuthHandler 19 | 20 | APP_ID = "app-zjd5o6ae" 21 | APP_KEY = "CXikZWDoRkttCO8Y7XXhRTtMxiUFSr7ZePO1tGQP" 22 | PAYLOAD = 'eyJhY2Nlc3NfdG9rZW4iOiIxMjM0NTY3IiwiYWN0aW9uIjoid\ 23 | mlld19hcHAiLCJleHBpcmVzIjoiMjAxNC0wMi0wOFQxMjowMDowMC4wMDBaIi\ 24 | wibGFuZyI6InpoX0NOIiwidXNlcl9pZCI6InVzZXItaWQxIiwiem9uZSI6ImJldGEifQ' 25 | SIGNATURE = 'bwt9NTDknRpa3vu-gh_2u2qpKnlMBkrar8TeJwp6KG0' 26 | 27 | ACCESS_INFO = {"user_id": "user-id1", 28 | "access_token": "1234567", 29 | "action": "view_app", 30 | "zone": "beta", 31 | "expires": "2014-02-08T12:00:00.000Z", 32 | "lang": "zh_CN"} 33 | 34 | 35 | class AppAuthTestCase(unittest.TestCase): 36 | 37 | def setUp(self): 38 | super(AppAuthTestCase, self).setUp() 39 | self.app = AppSignatureAuthHandler(APP_ID, APP_KEY) 40 | 41 | def test_sign_string(self): 42 | self.assertEqual("-xmepZZrlRW9dOgO3KA9MY8rgOjLvfSedNEPXsxrpcQ", 43 | self.app.sign_string("some string to sign")) 44 | 45 | def test_extract_payload(self): 46 | extracted_payload = self.app.extract_payload( 47 | PAYLOAD, 48 | SIGNATURE) 49 | self.assertEqual(ACCESS_INFO, extracted_payload) 50 | 51 | self.assertIsNone(self.app.extract_payload( 52 | PAYLOAD, 53 | "-xmepZZrlRW9dOgO3KA9MY8rgOjLvfSedNEPXsxrpcx")) 54 | 55 | def test_create_auth(self): 56 | 57 | auth_info = self.app.create_auth(ACCESS_INFO) 58 | self.assertEqual(PAYLOAD, auth_info['payload']) 59 | 60 | self.assertEqual(SIGNATURE, 61 | auth_info['signature']) 62 | 63 | def test_payload(self): 64 | app = AppSignatureAuthHandler( 65 | "app-b9p6qxqj", "jimLjavU3uQAGxrv6FkeJKd58ieRiuWeVtilFl7V") 66 | extracted_payload = app.extract_payload("eyJsYW5nIjoiemgtY24iLCJ1c2VyX2lkIjoidXNyLWluZWE5dzdaIiwiem9uZSI6ImFsbGlub25lIiwiYWNjZXNzX3Rva2VuIjoiYWhacEhCOFZ2aUJ1bXB0YTBHeTZGMUFDUFNtbkZNeGsiLCJleHBpcmVzIjoiMjAxNS0wMS0yOFQwOTozNTozNFoiLCJhY3Rpb24iOiJ2aWV3X2FwcCJ9", 67 | "3k-0pqvAjf5BvFtssV3_2t_KBkesEpYEmesv3O5sJeI") 68 | 69 | self.assertIsNotNone(extracted_payload) 70 | -------------------------------------------------------------------------------- /tests/test_json_tool.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import unittest 18 | from qingcloud.misc.json_tool import json_dump, json_load 19 | 20 | 21 | class JsonToolTestCase(unittest.TestCase): 22 | 23 | def test_json_dump_dict(self): 24 | obj = {'1': 1, 'str': 'string', 'none': None} 25 | expected = '{"1":1,"none":null,"str":"string"}' 26 | self.assertEqual(json_dump(obj), expected) 27 | 28 | def test_json_dump_invalid_obj(self): 29 | obj = {unittest: 'invalid key'} 30 | expected = None 31 | self.assertEqual(json_dump(obj), expected) 32 | 33 | def test_json_dump_list(self): 34 | obj = [1, 4, '3'] 35 | expected = '[1,4,"3"]' 36 | self.assertEqual(json_dump(obj), expected) 37 | 38 | def test_json_load_list(self): 39 | string = '{"int":1,"none":null,"str":"string"}' 40 | expected = {'int': 1, 'str': 'string', 'none': None} 41 | self.assertEqual(json_load(string), expected) 42 | 43 | def test_json_load_string(self): 44 | string = '{"int":1,"none":null,"str":"string"}' 45 | expected = {'int': 1, 'str': 'string', 'none': None} 46 | self.assertEqual(json_load(string), expected) 47 | 48 | def test_json_load_invalid_string(self): 49 | string = '{"int":1,:null,"str":"string"}' 50 | expected = None 51 | self.assertEqual(json_load(string), expected) 52 | -------------------------------------------------------------------------------- /tests/test_lb_backend.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import unittest 18 | 19 | from qingcloud.iaas.lb_backend import LoadBalancerBackend 20 | 21 | 22 | class LoadBalancerBackendTestCase(unittest.TestCase): 23 | 24 | def test_init_backend(self): 25 | port = 443 26 | weight = 12 27 | backend = LoadBalancerBackend('i-test1234', port, weight) 28 | self.assertEqual(backend.to_json()['port'], port) 29 | self.assertEqual(backend.to_json()['weight'], weight) 30 | 31 | def test_create_multiple_backends_from_string(self): 32 | string = ''' 33 | [{"status":"down","loadbalancer_backend_id":"lbb-rruzir3s","weight":1, 34 | "resource_id":"i-1234abcd","loadbalancer_backend_name":"", 35 | "port":23,"controller":"self", "create_time":"2014-02-03T17:12:03Z", 36 | "owner":"usr-1234abcd", "loadbalancer_listener_id":"lbl-1234abcd", 37 | "loadbalancer_id":"lb-1234abcd"}, 38 | {"status":"down","loadbalancer_backend_id":"lbb-vz51avzj","weight":1, 39 | "resource_id":"i-1234abcd","loadbalancer_backend_name":"", 40 | "port":3,"controller":"self","create_time":"2014-02-03T17:12:07Z", 41 | "owner":"usr-1234abcd","loadbalancer_listener_id":"lbl-1234abcd", 42 | "loadbalancer_id":"lb-1234abcd"}] 43 | ''' 44 | backends = LoadBalancerBackend.create_from_string(string) 45 | self.assertEqual(len(backends), 2) 46 | 47 | def test_create_single_backend_from_string(self): 48 | string = ''' 49 | {"status":"down","loadbalancer_backend_id":"lbb-rruzir3s","weight":1, 50 | "resource_id":"i-1234abcd","loadbalancer_backend_name":"", 51 | "port":23,"controller":"self", "create_time":"2014-02-03T17:12:03Z", 52 | "owner":"usr-1234abcd", "loadbalancer_listener_id":"lbl-1234abcd", 53 | "loadbalancer_id":"lb-1234abcd"} 54 | ''' 55 | backend = LoadBalancerBackend.create_from_string(string) 56 | self.assertTrue(isinstance(backend, LoadBalancerBackend)) 57 | -------------------------------------------------------------------------------- /tests/test_lb_listener.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import unittest 18 | 19 | from qingcloud.iaas.lb_listener import LoadBalancerListener 20 | 21 | 22 | class LoadBalancerListenerTestCase(unittest.TestCase): 23 | 24 | def test_init_instance(self): 25 | port = 80 26 | protocol = 'http' 27 | listener = LoadBalancerListener(port, listener_protocol=protocol, 28 | backend_protocol=protocol) 29 | json = listener.to_json() 30 | self.assertEqual(json['listener_port'], port) 31 | self.assertEqual(json['listener_protocol'], protocol) 32 | 33 | def test_init_forwardfor(self): 34 | port = 80 35 | protocol = 'http' 36 | listener = LoadBalancerListener(port, listener_protocol=protocol, 37 | backend_protocol=protocol, forwardfor=1) 38 | json = listener.to_json() 39 | self.assertEqual(json['forwardfor'], 1) 40 | 41 | listener = LoadBalancerListener(port, listener_protocol=protocol, 42 | backend_protocol=protocol, headers=['QC-LBIP']) 43 | json = listener.to_json() 44 | self.assertEqual(json['forwardfor'], 4) 45 | 46 | listener = LoadBalancerListener(port, listener_protocol=protocol, 47 | backend_protocol=protocol, forwardfor=1, headers=['QC-LBIP']) 48 | json = listener.to_json() 49 | self.assertEqual(json['forwardfor'], 1) 50 | 51 | def test_get_forwardfor(self): 52 | headers = [] 53 | self.assertEqual(LoadBalancerListener.get_forwardfor(headers), 0) 54 | headers = ['wrong_header'] 55 | self.assertEqual(LoadBalancerListener.get_forwardfor(headers), 0) 56 | headers = ['X-FORWARD-FOR'] 57 | self.assertEqual(LoadBalancerListener.get_forwardfor(headers), 1) 58 | headers = ['QC-LBID'] 59 | self.assertEqual(LoadBalancerListener.get_forwardfor(headers), 2) 60 | headers = ['QC-LBIP'] 61 | self.assertEqual(LoadBalancerListener.get_forwardfor(headers), 4) 62 | headers = ['X-FORWARD-FOR', 'QC-LBID'] 63 | self.assertEqual(LoadBalancerListener.get_forwardfor(headers), 3) 64 | headers = ['X-FORWARD-FOR', 'QC-LBIP', 'QC-LBID'] 65 | self.assertEqual(LoadBalancerListener.get_forwardfor(headers), 7) 66 | 67 | def test_create_multiple_listeners_from_string(self): 68 | string = ''' 69 | [{"forwardfor":0,"loadbalancer_listener_id":"lbl-1234abcd", 70 | "balance_mode":"roundrobin","listener_protocol":"tcp", 71 | "backend_protocol":"tcp","healthy_check_method":"tcp", 72 | "session_sticky":"","loadbalancer_listener_name":"demo", 73 | "controller":"self","backends":[],"create_time":"2014-02-02T16:51:25Z", 74 | "healthy_check_option":"10|5|2|5","owner":"usr-1234abcd", 75 | "console_id":"qingcloud","loadbalancer_id":"lb-1234abcd", 76 | "listener_port":443}, 77 | {"forwardfor":0, 78 | "loadbalancer_listener_id":"lbl-1234abcd","balance_mode":"roundrobin", 79 | "listener_protocol":"http","backend_protocol":"http", 80 | "healthy_check_method":"tcp","session_sticky":"", 81 | "loadbalancer_listener_name":"demo","controller":"self", 82 | "backends":[],"create_time":"2014-02-02T16:51:19Z", 83 | "healthy_check_option":"10|5|2|5","owner":"usr-1234abcd", 84 | "console_id":"qingcloud","loadbalancer_id":"lb-1234abcd", 85 | "listener_port":80}] 86 | ''' 87 | listeners = LoadBalancerListener.create_from_string(string) 88 | self.assertEqual(len(listeners), 2) 89 | 90 | def test_create_single_listener_from_string(self): 91 | string = ''' 92 | {"forwardfor":0,"loadbalancer_listener_id":"lbl-1234abcd", 93 | "balance_mode":"roundrobin","listener_protocol":"tcp", 94 | "backend_protocol":"tcp","healthy_check_method":"tcp", 95 | "session_sticky":"","loadbalancer_listener_name":"demo", 96 | "controller":"self","backends":[],"create_time":"2014-02-02T16:51:25Z", 97 | "healthy_check_option":"10|5|2|5","owner":"usr-1234abcd", 98 | "console_id":"qingcloud","loadbalancer_id":"lb-1234abcd", 99 | "listener_port":443} 100 | ''' 101 | listener = LoadBalancerListener.create_from_string(string) 102 | self.assertTrue(isinstance(listener, LoadBalancerListener)) 103 | -------------------------------------------------------------------------------- /tests/test_monitor.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import unittest 18 | from qingcloud.iaas.monitor import MonitorProcessor, NA 19 | 20 | 21 | class MonitorProcessorTestCase(unittest.TestCase): 22 | 23 | def _get_processor(self, meter_set=None, start_time=None, end_time=None, step=None): 24 | start_time = start_time or '2014-02-08T12:00:00Z' 25 | end_time = end_time or '2014-02-08T13:00:00Z' 26 | step = step or '15m' 27 | return MonitorProcessor(meter_set, start_time, end_time, step) 28 | 29 | def test_init_processor(self): 30 | p = self._get_processor() 31 | self.assertTrue(p.start_time) 32 | self.assertTrue(p.end_time) 33 | self.assertTrue(p.step) 34 | 35 | def test_init_processor_with_invalid_step(self): 36 | p = self._get_processor(step='1m') 37 | self.assertFalse(p.step) 38 | 39 | def test_init_processor_with_invalid_time_format(self): 40 | p = self._get_processor(start_time='2011-10-16 10:58:00') 41 | self.assertFalse(p.start_time) 42 | 43 | def test_is_invalid(self): 44 | p = self._get_processor() 45 | self.assertTrue(p._is_invalid(NA)) 46 | self.assertTrue(p._is_invalid([1, 2, NA])) 47 | self.assertFalse(p._is_invalid(1)) 48 | self.assertFalse(p._is_invalid(range(4))) 49 | 50 | def test_get_empty_item(self): 51 | p = self._get_processor() 52 | sample_item = 2 53 | expected = None 54 | self.assertEquals(p._get_empty_item(sample_item), expected) 55 | 56 | sample_item = [7, 11] 57 | expected = [None, None] 58 | self.assertEquals(p._get_empty_item(sample_item), expected) 59 | 60 | def test_fill_vacancies(self): 61 | p = self._get_processor(step='5m') 62 | value = 7 63 | from_ts, to_ts = 1000, 2000 64 | expected = [[1000, 7], [1300, 7], [1600, 7], [1900, 7]] 65 | self.assertEquals(p._fill_vacancies(value, from_ts, to_ts), expected) 66 | 67 | def test_decompress_meter_data(self): 68 | p = self._get_processor() 69 | data = [] 70 | self.assertEquals(p._decompress_meter_data(data), []) 71 | 72 | first_time = p.start_time + 1000 73 | data = [ 74 | [first_time, 3], 75 | 4, 76 | NA, 77 | 5, 78 | [1000, 3], 79 | 10, 80 | [100, 3], 81 | ] 82 | expected = [ 83 | [p.start_time, None], 84 | [p.start_time + p.step, None], 85 | [first_time, 3], 86 | [first_time + p.step * 1, 4], 87 | [first_time + p.step * 2, None], 88 | [first_time + p.step * 3, 5], 89 | [first_time + p.step * 4, None], 90 | [first_time + p.step * 3 + 1000, 3], 91 | [first_time + p.step * 4 + 1000, 10], 92 | [first_time + p.step * 4 + 1000 + 100, 3], 93 | ] 94 | self.assertEquals(p._decompress_meter_data(data), expected) 95 | 96 | def test_decompress_lb_monitoring_data(self): 97 | start_time = '2014-02-09T11:22:49.888Z' 98 | end_time = '2014-02-09T17:22:49.888Z' 99 | step = '5m' 100 | meter_set = [{ 101 | "data_set": [{"eip_id": "eip-kqpkxm8f", "data": [[1391945100, [23, 29]], 102 | [17, 30], [19, 29], [20, 31], [22, 29], [ 103 | 20, 29], [23, 30], [19, 27], 104 | [23, 30], [16, 29], [30, 33], [15, 29], [ 105 | 19, 30], [23, 30], [27, 29], 106 | [29, 29], [23, 29], [25, 31], [34, 30], [ 107 | 29, 29], [25, 33], [16, 29], 108 | [17, 31], [17, 31], [16, 29], [25, 28], [ 109 | 17, 31], [21, 33], [16, 29], 110 | [18, 30], [18, 31], [43, 28], [43, 29], [ 111 | 24, 29], [17, 31], [15, 29], 112 | [19, 30], [19, 29], [21, 28], [19, 31], [ 113 | 15, 29], [15, 29], [20, 29], 114 | [20, 29], [19, 31], [18, 31], [18, 29], [ 115 | 18, 29], [26, 31], [23, 29], 116 | [52, 29], [21, 29], [17, 30], [16, 29], [ 117 | 23, 30], [22, 31], [17, 29], 118 | [25, 29], [16, 29], [17, 31], [16, 29], [ 119 | 24, 31], [20, 29], [23, 31], 120 | [16, 29], [17, 30], [16, 29], [19, 31], [ 121 | 17, 31], [17, 29], [18, 29], 122 | [17, 30]]}], 123 | "meter_id":"traffic" 124 | }] 125 | p = self._get_processor(meter_set, start_time, end_time, step) 126 | meter_set = p.decompress_lb_monitoring_data() 127 | for meter in meter_set: 128 | for item in meter['data_set'][0]['data']: 129 | self.assertTrue(isinstance(item, list)) 130 | self.assertEquals(len(item), 2) 131 | 132 | def test_decompress_monitoring_data(self): 133 | start_time = '2014-02-09T12:01:49.032Z' 134 | end_time = '2014-02-09T18:01:49.032Z' 135 | step = '5m' 136 | meter_set = [ 137 | {"data": [[1391947500, [12, 12]], [14, 13], [16, 13], [20, 12], [13, 13], 138 | [12, 12], [12, 12], [13, 13], [13, 13], [ 139 | 12, 12], [12, 12], [16, 13], 140 | [14, 13], [13, 13], [15, 13], [15, 13], [ 141 | 12, 12], [13, 13], [12, 12], 142 | [13, 13], [12, 12], [13, 13], [14, 12], [ 143 | 14, 12], [14, 13], [18, 12], 144 | [15, 12], [32, 13], [12, 12], [13, 13], [ 145 | 15, 12], [13, 13], [12, 12], 146 | [12, 12], [12, 12], [14, 13], [12, 12], [ 147 | 19, 13], [40, 12], [39, 12], 148 | [13, 12], [13, 13], [13, 13], [17, 13], [ 149 | 12, 12], [12, 12], [13, 13], 150 | [14, 13], [12, 12], [15, 12], [12, 12], [ 151 | 12, 12], [12, 12], [12, 12], 152 | [14, 12], [3000, [11, 10]], [16, 12], [ 153 | 15, 13], [12, 12], [18, 15], 154 | [15, 12], [12, 12], [13, 13]], "vxnet_id":"vxnet-0", 155 | "meter_id":"if-52:54:b3:0c:30:89", "sequence":0 156 | }, 157 | {"data": [[1391947500, [0, 2799]], [0, 2441], [0, 2473], [0, 2320], 158 | [0, 2475], [0, 2216], [0, 2405], [ 159 | 0, 2372], [0, 2490], [0, 2387], 160 | [0, 2492], [0, 2315], [0, 2834], [ 161 | 0, 2523], [0, 2287], [0, 2573], 162 | [0, 2336], [0, 2439], [0, 2232], [ 163 | 0, 2422], [0, 2184], [0, 2457], 164 | [0, 2507], [0, 2368], [0, 2606], [ 165 | 0, 2459], [0, 2424], [0, 2456], 166 | [0, 2286], [0, 2575], [0, 2338], [ 167 | 0, 2490], [0, 2524], [0, 2200], 168 | [0, 2406], [0, 2540], [0, 2406], [ 169 | 0, 2304], [0, 2424], [0, 2216], 170 | [0, 2403], [0, 2404], [0, 2525], [ 171 | 0, 2525], [0, 2303], [0, 2473], 172 | [0, 2422], [0, 2341], [0, 2781], [ 173 | 0, 2406], [0, 2252], [0, 2320], 174 | [0, 2457], [0, 2202], [0, 2543], [ 175 | 3000, [921302, 11145]], [0, 2717], 176 | [0, 2943], [0, 2519], [0, 2911], [0, 2673], [0, 2648], [0, 2671]], 177 | "meter_id":"disk-os" 178 | }, 179 | {"data": [[1391947500, 3], 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 180 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 181 | 3, 3, 3, 3, 3, 3, [3000, 532], 3, 3, 3, 3, 3, 3, 3], "meter_id":"cpu" 182 | }, 183 | {"data": [[1391947500, 93], 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 184 | 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 185 | 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 93, 186 | 93, 93, 93, 93, [3000, 57], 56, 56, 56, 56, 56, 56, 56], "meter_id":"memory" 187 | } 188 | ] 189 | p = self._get_processor(meter_set, start_time, end_time, step) 190 | meter_set = p.decompress_monitoring_data() 191 | for meter in meter_set: 192 | for item in meter['data']: 193 | self.assertTrue(isinstance(item, list)) 194 | self.assertEquals(len(item), 2) 195 | -------------------------------------------------------------------------------- /tests/test_router_static.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import unittest 18 | 19 | from qingcloud.iaas.errors import InvalidRouterStatic 20 | from qingcloud.iaas.router_static import (RouterStaticFactory, _StaticForTunnel, 21 | _StaticForFiltering, _StaticForVPN, _StaticForPortForwarding) 22 | 23 | 24 | class RouterStaticFactoryTestCase(unittest.TestCase): 25 | 26 | def test_port_forwarding_static(self): 27 | name = 'unittest' 28 | src_port = 10 29 | dst_ip = '192.168.1.1' 30 | dst_port = 80 31 | protocol = 'udp' 32 | static = RouterStaticFactory.create(RouterStaticFactory.TYPE_PORT_FORWARDING, 33 | router_static_name=name, protocol=protocol, 34 | src_port=10, dst_ip='192.168.1.1', dst_port=80) 35 | 36 | json_data = static.to_json() 37 | self.assertEqual(json_data['router_static_name'], name) 38 | self.assertEqual(json_data['val1'], src_port) 39 | self.assertEqual(json_data['val2'], dst_ip) 40 | self.assertEqual(json_data['val3'], dst_port) 41 | self.assertEqual(json_data['val4'], protocol) 42 | 43 | def test_vpn_static(self): 44 | ip = '192.168.1.1' 45 | vpn_type = 'openvpn' 46 | static = RouterStaticFactory.create(RouterStaticFactory.TYPE_VPN, 47 | vpn_type=vpn_type, ip_network=ip) 48 | json_data = static.to_json() 49 | self.assertEqual(json_data['val1'], 'openvpn') 50 | self.assertEqual(json_data['val2'], '1194') 51 | self.assertEqual(json_data['val3'], 'udp') 52 | self.assertEqual(json_data['val4'], ip) 53 | 54 | vpn_type = 'pptp' 55 | usr = 'tester' 56 | pwd = 'passwd' 57 | static = RouterStaticFactory.create(RouterStaticFactory.TYPE_VPN, 58 | vpn_type=vpn_type, usr=usr, pwd=pwd, ip_network=ip) 59 | json_data = static.to_json() 60 | self.assertEqual(json_data['val1'], 'pptp') 61 | self.assertEqual(json_data['val2'], '%s:%s' % (usr, pwd)) 62 | self.assertEqual(json_data['val3'], 63 | RouterStaticFactory.PPTP_DEFAULT_CONNS) 64 | self.assertEqual(json_data['val4'], ip) 65 | 66 | def test_invalid_vpn_static(self): 67 | vpn_type = 'invalid' 68 | usr = 'tester' 69 | pwd = 'passwd' 70 | ip = '192.168.1.1' 71 | self.assertRaises(InvalidRouterStatic, RouterStaticFactory.create, 72 | RouterStaticFactory.TYPE_VPN, vpn_type=vpn_type, 73 | usr=usr, pwd=pwd, ip_network=ip) 74 | 75 | def test_tunnel_static(self): 76 | vxnet = 'vxnet-1234abcd' 77 | tunnel_entries = [ 78 | ('gre', '112.144.3.54', '123'), 79 | ('gre', '112.144.5.54', 'abc'), 80 | ] 81 | static = RouterStaticFactory.create(RouterStaticFactory.TYPE_TUNNEL, 82 | vxnet_id=vxnet, tunnel_entries=tunnel_entries) 83 | 84 | json_data = static.to_json() 85 | self.assertEqual(json_data['val1'], 86 | 'gre|112.144.3.54|123;gre|112.144.5.54|abc') 87 | 88 | def test_filtering_static(self): 89 | name = 'unittest' 90 | src_ip = '192.168.1.1' 91 | src_port = 10 92 | dst_ip = '192.168.2.1' 93 | dst_port = 80 94 | priority = 5 95 | action = 'drop' 96 | static = RouterStaticFactory.create(RouterStaticFactory.TYPE_FILTERING, 97 | router_static_name=name, src_ip=src_ip, src_port=src_port, 98 | dst_ip=dst_ip, dst_port=dst_port, priority=priority, action=action) 99 | 100 | json_data = static.to_json() 101 | self.assertEqual(json_data['val1'], src_ip) 102 | self.assertEqual(json_data['val2'], src_port) 103 | self.assertEqual(json_data['val3'], dst_ip) 104 | self.assertEqual(json_data['val4'], dst_port) 105 | self.assertEqual(json_data['val5'], priority) 106 | self.assertEqual(json_data['val6'], action) 107 | 108 | def test_static_with_existing_id(self): 109 | static = RouterStaticFactory.create(RouterStaticFactory.TYPE_VPN, 110 | vpn_type='openvpn', ip_network='', router_static_id='fakeid') 111 | 112 | json_data = static.to_json() 113 | self.assertEqual(json_data['router_static_id'], 'fakeid') 114 | 115 | def test_unsupported_static_type(self): 116 | self.assertRaises(InvalidRouterStatic, 117 | RouterStaticFactory.create, 'unsupported') 118 | 119 | def test_create_multiple_statics_from_string(self): 120 | string = ''' 121 | [{ 122 | "router_id": "rtr-1234abcd", "vxnet_id": "", 123 | "router_static_name": "filter", "static_type": 5, 124 | "router_static_id": "rtrs-1234abcd", "console_id": "qingcloud", 125 | "val3": "192.168.100.3", "controller": "self", 126 | "create_time": "2013-11-11T07:02:14Z", "val2": "80", 127 | "val1": "192.168.1.2", "val6": "drop", "val5": "4", "val4": "800" 128 | }, 129 | 130 | { 131 | "router_id":"rtr-1234abcd","vxnet_id":"","router_static_name":null, 132 | "static_type":2,"router_static_id":"rtrs-1234abcd", 133 | "console_id":"qingcloud","val3":"tcp","controller":"self", 134 | "create_time":"2014-01-27T11:22:30Z", 135 | "val2":"1194","val1":"openvpn","val6":"","val5":"","val4":"10.255.1.0/24" 136 | }, 137 | 138 | { 139 | "router_id":"rtr-ji5ais2q", 140 | "entry_set": [{"router_static_entry_id":"rse-gbgwguzq","val1":"test"}], 141 | "vxnet_id":"","router_static_id":"rtrs-9fh7wxrf","static_type":2, 142 | "router_static_name":null,"console_id":"qingcloud","val3":"253", 143 | "controller":"self","create_time":"2014-01-27T11:22:42Z", 144 | "owner":"usr-qkMLt5Oo","val2":"","val1":"pptp","val6":"","val5":"", 145 | "val4":"10.255.2.0/24" 146 | }, 147 | 148 | { 149 | "router_id": "rtr-1234abcd", "vxnet_id": "", 150 | "router_static_name": "fp1", "static_type": 1, 151 | "router_static_id": "rtrs-1234abcd", "console_id": "qingcloud", 152 | "val3": "80", "controller": "self", "create_time": "2014-01-26T16:58:51Z", 153 | "val2": "192.168.100.2", "val1": "80", "val6": "", "val5": "", "val4": "tcp" 154 | }, 155 | 156 | { 157 | "router_id": "rtr-1234abcd", "vxnet_id": "vxnet-1234abcd", 158 | "router_static_name": null, "static_type": 4, 159 | "router_static_id": "rtrs-1234abcd", "console_id": "qingcloud", 160 | "val3": "", "controller": "self", "create_time": "2013-11-11T03:02:37Z", 161 | "val2": "", "val1": "gre|182.32.32.1|1234;gre|12.1.12.2|123123", 162 | "val6": "", "val5": "", "val4": "" 163 | }] 164 | ''' 165 | rtrs = RouterStaticFactory.create_from_string(string) 166 | self.assertEqual(len(rtrs), 5) 167 | self.assertTrue(isinstance(rtrs[0], _StaticForFiltering)) 168 | self.assertTrue(isinstance(rtrs[1], _StaticForVPN)) 169 | self.assertTrue(isinstance(rtrs[2], _StaticForVPN)) 170 | self.assertTrue(isinstance(rtrs[3], _StaticForPortForwarding)) 171 | self.assertTrue(isinstance(rtrs[4], _StaticForTunnel)) 172 | 173 | def test_create_single_static_from_string(self): 174 | string = ''' 175 | { 176 | "router_id": "rtr-1234abcd", "vxnet_id": "", 177 | "router_static_name": "filter", "static_type": 5, 178 | "router_static_id": "rtrs-1234abcd", "console_id": "qingcloud", 179 | "val3": "192.168.100.3", "controller": "self", 180 | "create_time": "2013-11-11T07:02:14Z", "val2": "80", 181 | "val1": "192.168.1.2", "val6": "drop", "val5": "4", "val4": "800" 182 | } 183 | ''' 184 | rtr = RouterStaticFactory.create_from_string(string) 185 | self.assertTrue(isinstance(rtr, _StaticForFiltering)) 186 | -------------------------------------------------------------------------------- /tests/test_sg_rule.py: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # Copyright 2012-present Yunify, Inc. 3 | # ------------------------------------------------------------------------- 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this work except in compliance with the License. 6 | # You may obtain a copy of the License in the LICENSE file, or at: 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ========================================================================= 16 | 17 | import unittest 18 | 19 | from qingcloud.iaas.errors import InvalidSecurityGroupRule 20 | from qingcloud.iaas.sg_rule import SecurityGroupRuleFactory, _RuleForTCP, _RuleForGRE 21 | 22 | 23 | class SecurityGroupRuleFactoryTestCase(unittest.TestCase): 24 | 25 | start_port = 10 26 | end_port = 200 27 | ip_network = '192.168.2.0/24' 28 | icmp_type = 8 29 | icmp_code = 0 30 | 31 | def test_tcp_rule_structure(self): 32 | rule = SecurityGroupRuleFactory.create( 33 | SecurityGroupRuleFactory.PROTOCOL_TCP, 0, 34 | security_group_rule_name='unittest', 35 | start_port=self.start_port, end_port=self.end_port, 36 | ip_network=self.ip_network) 37 | 38 | json_data = rule.to_json() 39 | self.assertEqual(json_data['val1'], self.start_port) 40 | self.assertEqual(json_data['val2'], self.end_port) 41 | self.assertEqual(json_data['val3'], self.ip_network) 42 | 43 | def test_udp_rule_structure(self): 44 | rule = SecurityGroupRuleFactory.create( 45 | SecurityGroupRuleFactory.PROTOCOL_UDP, 0, 46 | start_port=self.start_port, end_port=self.end_port, 47 | ip_network=self.ip_network) 48 | 49 | json_data = rule.to_json() 50 | self.assertEqual(json_data['val1'], self.start_port) 51 | self.assertEqual(json_data['val2'], self.end_port) 52 | self.assertEqual(json_data['val3'], self.ip_network) 53 | 54 | def test_icmp_rule_structure(self): 55 | rule = SecurityGroupRuleFactory.create( 56 | SecurityGroupRuleFactory.PROTOCOL_ICMP, 0, 57 | icmp_type=self.icmp_type, icmp_code=self.icmp_code, 58 | ip_network=self.ip_network) 59 | 60 | json_data = rule.to_json() 61 | self.assertEqual(json_data['val1'], self.icmp_type) 62 | self.assertEqual(json_data['val2'], self.icmp_code) 63 | self.assertEqual(json_data['val3'], self.ip_network) 64 | 65 | def test_gre_rule_structure(self): 66 | rule = SecurityGroupRuleFactory.create( 67 | SecurityGroupRuleFactory.PROTOCOL_GRE, 0, direction=1, 68 | action='drop', security_group_rule_name='unittest', 69 | ip_network=self.ip_network) 70 | 71 | json_data = rule.to_json() 72 | self.assertEqual(json_data['val3'], self.ip_network) 73 | 74 | def test_rule_with_existing_id(self): 75 | rule = SecurityGroupRuleFactory.create('gre', 0, 76 | security_group_rule_id='fakeid') 77 | 78 | json_data = rule.to_json() 79 | self.assertEqual(json_data['security_group_rule_id'], 'fakeid') 80 | 81 | def test_unsupported_protocol(self): 82 | self.assertRaises(InvalidSecurityGroupRule, 83 | SecurityGroupRuleFactory.create, 'unsupported', 0) 84 | 85 | def test_invalid_priority(self): 86 | self.assertRaises(InvalidSecurityGroupRule, SecurityGroupRuleFactory.create, 87 | SecurityGroupRuleFactory.PROTOCOL_UDP, -1) 88 | self.assertRaises(InvalidSecurityGroupRule, SecurityGroupRuleFactory.create, 89 | SecurityGroupRuleFactory.PROTOCOL_UDP, 101) 90 | self.assertRaises(InvalidSecurityGroupRule, SecurityGroupRuleFactory.create, 91 | SecurityGroupRuleFactory.PROTOCOL_UDP, '10') 92 | 93 | rule = SecurityGroupRuleFactory.create( 94 | SecurityGroupRuleFactory.PROTOCOL_UDP, 0) 95 | self.assertTrue(rule) 96 | rule = SecurityGroupRuleFactory.create( 97 | SecurityGroupRuleFactory.PROTOCOL_UDP, 100) 98 | self.assertTrue(rule) 99 | 100 | def test_create_multiple_rules_from_string(self): 101 | string = ''' 102 | [{"direction": 0, "protocol": "tcp", 103 | "priority": 1, "action": "accept", "controller": "self", 104 | "security_group_rule_id": "sgr-sx5xrr5h", "val1": "1", 105 | "owner": "usr-F5iqdERj", "val2": "100", "val3": "", 106 | "security_group_rule_name": "", "security_group_id": "sg-0xegewrh" 107 | }, 108 | 109 | {"direction": 0, "protocol": "gre", 110 | "priority": 1, "action": "accept", "controller": "self", 111 | "security_group_rule_id": "sgr-0cv8wkew", "val1": "", 112 | "owner": "usr-F5iqdERj", "val2": "", "val3": "", 113 | "security_group_rule_name": "", "security_group_id": "sg-0xegewrh" 114 | }] 115 | ''' 116 | sgrs = SecurityGroupRuleFactory.create_from_string(string) 117 | self.assertEqual(len(sgrs), 2) 118 | self.assertTrue(isinstance(sgrs[0], _RuleForTCP)) 119 | self.assertTrue(isinstance(sgrs[1], _RuleForGRE)) 120 | 121 | def test_create_single_rule_from_string(self): 122 | string = ''' 123 | {"direction": 0, "protocol": "tcp", 124 | "priority": 1, "action": "accept", "controller": "self", 125 | "security_group_rule_id": "sgr-sx5xrr5h", "val1": "1", 126 | "owner": "usr-F5iqdERj", "val2": "100", "val3": "", 127 | "security_group_rule_name": "", "security_group_id": "sg-0xegewrh" 128 | } 129 | ''' 130 | sgr = SecurityGroupRuleFactory.create_from_string(string) 131 | self.assertTrue(isinstance(sgr, _RuleForTCP)) 132 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # ========================================================================= 3 | # Copyright 2012-present Yunify, Inc. 4 | # ------------------------------------------------------------------------- 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this work except in compliance with the License. 7 | # You may obtain a copy of the License in the LICENSE file, or at: 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # ========================================================================= 17 | 18 | import time 19 | import unittest 20 | 21 | import sys 22 | from mock import Mock 23 | from qingcloud.misc.utils import (get_utf8_value, filter_out_none, get_ts, 24 | parse_ts, local_ts, base64_url_encode, base64_url_decode, 25 | wait_job) 26 | 27 | 28 | class UtilsTestCase(unittest.TestCase): 29 | 30 | def test_get_utf8_value(self): 31 | self.assertEqual(get_utf8_value('utf-8'), 'utf-8') 32 | self.assertEqual(get_utf8_value(u'unicode'), 'unicode') 33 | self.assertEqual(get_utf8_value([1, 2]), '[1, 2]') 34 | if sys.version < "3": 35 | self.assertEqual(get_utf8_value(u'你好'), '\xe4\xbd\xa0\xe5\xa5\xbd') 36 | else: 37 | self.assertEqual(get_utf8_value(u'你好'), u'你好') 38 | 39 | def test_filter_out_none(self): 40 | data = {'a': 1, 'b': 2, 'c': None} 41 | self.assertEqual(filter_out_none(data), {}) 42 | 43 | data = {'a': 1, 'b': 2, 'c': None} 44 | self.assertEqual(filter_out_none(data, keys=['a', 'c']), {'a': 1}) 45 | 46 | def test_get_ts(self): 47 | ts = 1391832000 48 | ts = time.localtime(ts + time.timezone + 60 * 60 * 8) 49 | expected = '2014-02-08T12:00:00Z' 50 | self.assertEqual(get_ts(ts), expected) 51 | self.assertTrue(isinstance(get_ts(), str)) 52 | 53 | def test_parse_ts(self): 54 | ts = '2014-02-08T12:00:00Z' 55 | expected = 1391832000.0 + time.timezone + 60 * 60 * 8 56 | self.assertEqual(parse_ts(ts), expected) 57 | 58 | ts = '2014-02-08T12:00:00.000Z' 59 | expected = 1391832000.0 + time.timezone + 60 * 60 * 8 60 | self.assertEqual(parse_ts(ts), expected) 61 | 62 | def test_local_ts(self): 63 | ts = '2014-02-08T12:00:00Z' 64 | expected = 1391860800.0 65 | self.assertEqual(local_ts(ts), expected) 66 | 67 | ts = '2014-02-08T12:00:00.000Z' 68 | expected = 1391860800.0 69 | self.assertEqual(local_ts(ts), expected) 70 | 71 | def test_base64_url_encode(self): 72 | self.assertEqual("c29tZSBzdHJpbmcgdG8gZW5jb2RlIA", 73 | base64_url_encode("some string to encode ")) 74 | 75 | def test_base64_url_decode(self): 76 | self.assertEqual("some string to encode ", base64_url_decode( 77 | "c29tZSBzdHJpbmcgdG8gZW5jb2RlIA")) 78 | 79 | def test_wait_job(self): 80 | job_id = 'job-id' 81 | conn = Mock() 82 | # timeout 83 | conn.describe_jobs.return_value = {'job_set': [{'status': 'working'}]} 84 | self.assertFalse(wait_job(conn, job_id, 4)) 85 | self.assertEqual(conn.describe_jobs.call_count, 2) 86 | # call api failed 87 | conn.describe_jobs.return_value = None 88 | self.assertFalse(wait_job(conn, job_id, 2)) 89 | # job complete 90 | conn.describe_jobs.return_value = {'job_set': [{'status': 'failed'}]} 91 | self.assertTrue(wait_job(conn, job_id)) 92 | conn.describe_jobs.return_value = { 93 | 'job_set': [{'status': 'successful'}]} 94 | self.assertTrue(wait_job(conn, job_id)) 95 | conn.describe_jobs.return_value = { 96 | 'job_set': [{'status': 'done with failure'}]} 97 | self.assertTrue(wait_job(conn, job_id)) 98 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py27, py33, py34, py35, pypy 8 | 9 | [testenv] 10 | commands = py.test 11 | deps = 12 | pytest 13 | mock 14 | future 15 | --------------------------------------------------------------------------------