http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/elasticstack.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/elasticstack.py b/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/elasticstack.py deleted file mode 100644 index 18b581b..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/elasticstack.py +++ /dev/null @@ -1,495 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Base driver for the providers based on the ElasticStack platform - -http://www.elasticstack.com. -""" - -import re -import time -import base64 - -from libcloud.utils.py3 import httplib -from libcloud.utils.py3 import b - -try: - import simplejson as json -except ImportError: - import json - -from libcloud.common.base import ConnectionUserAndKey, JsonResponse -from libcloud.common.types import InvalidCredsError -from libcloud.compute.types import NodeState -from libcloud.compute.base import NodeDriver, NodeSize, Node -from libcloud.compute.base import NodeImage -from libcloud.compute.deployment import ScriptDeployment, SSHKeyDeployment -from libcloud.compute.deployment import MultiStepDeployment - - -NODE_STATE_MAP = { - 'active': NodeState.RUNNING, - 'dead': NodeState.TERMINATED, - 'dumped': NodeState.TERMINATED, -} - -# Default timeout (in seconds) for the drive imaging process -IMAGING_TIMEOUT = 10 * 60 - -# ElasticStack doesn't specify special instance types, so I just specified -# some plans based on the other provider offerings. -# -# Basically for CPU any value between 500Mhz and 20000Mhz should work, -# 256MB to 8192MB for ram and 1GB to 2TB for disk. -INSTANCE_TYPES = { - 'small': { - 'id': 'small', - 'name': 'Small instance', - 'cpu': 2000, - 'memory': 1700, - 'disk': 160, - 'bandwidth': None, - }, - 'medium': { - 'id': 'medium', - 'name': 'Medium instance', - 'cpu': 3000, - 'memory': 4096, - 'disk': 500, - 'bandwidth': None, - }, - 'large': { - 'id': 'large', - 'name': 'Large instance', - 'cpu': 4000, - 'memory': 7680, - 'disk': 850, - 'bandwidth': None, - }, - 'extra-large': { - 'id': 'extra-large', - 'name': 'Extra Large instance', - 'cpu': 8000, - 'memory': 8192, - 'disk': 1690, - 'bandwidth': None, - }, - 'high-cpu-medium': { - 'id': 'high-cpu-medium', - 'name': 'High-CPU Medium instance', - 'cpu': 5000, - 'memory': 1700, - 'disk': 350, - 'bandwidth': None, - }, - 'high-cpu-extra-large': { - 'id': 'high-cpu-extra-large', - 'name': 'High-CPU Extra Large instance', - 'cpu': 20000, - 'memory': 7168, - 'disk': 1690, - 'bandwidth': None, - }, -} - - -class ElasticStackException(Exception): - def __str__(self): - return self.args[0] - - def __repr__(self): - return "<ElasticStackException '%s'>" % (self.args[0]) - - -class ElasticStackResponse(JsonResponse): - def success(self): - if self.status == 401: - raise InvalidCredsError() - - return self.status >= 200 and self.status <= 299 - - def parse_error(self): - error_header = self.headers.get('x-elastic-error', '') - return 'X-Elastic-Error: %s (%s)' % (error_header, self.body.strip()) - - -class ElasticStackNodeSize(NodeSize): - def __init__(self, id, name, cpu, ram, disk, bandwidth, price, driver): - self.id = id - self.name = name - self.cpu = cpu - self.ram = ram - self.disk = disk - self.bandwidth = bandwidth - self.price = price - self.driver = driver - - def __repr__(self): - return (('<NodeSize: id=%s, name=%s, cpu=%s, ram=%s ' - 'disk=%s bandwidth=%s price=%s driver=%s ...>') - % (self.id, self.name, self.cpu, self.ram, - self.disk, self.bandwidth, self.price, self.driver.name)) - - -class ElasticStackBaseConnection(ConnectionUserAndKey): - """ - Base connection class for the ElasticStack driver - """ - - host = None - responseCls = ElasticStackResponse - - def add_default_headers(self, headers): - headers['Accept'] = 'application/json' - headers['Content-Type'] = 'application/json' - headers['Authorization'] = \ - ('Basic %s' % (base64.b64encode(b('%s:%s' % (self.user_id, - self.key)))) - .decode('utf-8')) - return headers - - -class ElasticStackBaseNodeDriver(NodeDriver): - website = 'http://www.elasticstack.com' - connectionCls = ElasticStackBaseConnection - features = {"create_node": ["generates_password"]} - - def reboot_node(self, node): - # Reboots the node - response = self.connection.request( - action='/servers/%s/reset' % (node.id), - method='POST' - ) - return response.status == 204 - - def destroy_node(self, node): - # Kills the server immediately - response = self.connection.request( - action='/servers/%s/destroy' % (node.id), - method='POST' - ) - return response.status == 204 - - def list_images(self, location=None): - # Returns a list of available pre-installed system drive images - images = [] - for key, value in self._standard_drives.items(): - image = NodeImage( - id=value['uuid'], - name=value['description'], - driver=self.connection.driver, - extra={ - 'size_gunzipped': value['size_gunzipped'] - } - ) - images.append(image) - - return images - - def list_sizes(self, location=None): - sizes = [] - for key, value in INSTANCE_TYPES.items(): - size = ElasticStackNodeSize( - id=value['id'], - name=value['name'], cpu=value['cpu'], ram=value['memory'], - disk=value['disk'], bandwidth=value['bandwidth'], - price=self._get_size_price(size_id=value['id']), - driver=self.connection.driver - ) - sizes.append(size) - - return sizes - - def list_nodes(self): - # Returns a list of active (running) nodes - response = self.connection.request(action='/servers/info').object - - nodes = [] - for data in response: - node = self._to_node(data) - nodes.append(node) - - return nodes - - def create_node(self, **kwargs): - """Creates an ElasticStack instance - - @inherits: :class:`NodeDriver.create_node` - - :keyword name: String with a name for this new node (required) - :type name: ``str`` - - :keyword smp: Number of virtual processors or None to calculate - based on the cpu speed - :type smp: ``int`` - - :keyword nic_model: e1000, rtl8139 or virtio - (if not specified, e1000 is used) - :type nic_model: ``str`` - - :keyword vnc_password: If set, the same password is also used for - SSH access with user toor, - otherwise VNC access is disabled and - no SSH login is possible. - :type vnc_password: ``str`` - """ - size = kwargs['size'] - image = kwargs['image'] - smp = kwargs.get('smp', 'auto') - nic_model = kwargs.get('nic_model', 'e1000') - vnc_password = ssh_password = kwargs.get('vnc_password', None) - - if nic_model not in ('e1000', 'rtl8139', 'virtio'): - raise ElasticStackException('Invalid NIC model specified') - - # check that drive size is not smaller than pre installed image size - - # First we create a drive with the specified size - drive_data = {} - drive_data.update({'name': kwargs['name'], - 'size': '%sG' % (kwargs['size'].disk)}) - - response = self.connection.request(action='/drives/create', - data=json.dumps(drive_data), - method='POST').object - - if not response: - raise ElasticStackException('Drive creation failed') - - drive_uuid = response['drive'] - - # Then we image the selected pre-installed system drive onto it - response = self.connection.request( - action='/drives/%s/image/%s/gunzip' % (drive_uuid, image.id), - method='POST' - ) - - if response.status not in (200, 204): - raise ElasticStackException('Drive imaging failed') - - # We wait until the drive is imaged and then boot up the node - # (in most cases, the imaging process shouldn't take longer - # than a few minutes) - response = self.connection.request( - action='/drives/%s/info' % (drive_uuid) - ).object - - imaging_start = time.time() - while 'imaging' in response: - response = self.connection.request( - action='/drives/%s/info' % (drive_uuid) - ).object - - elapsed_time = time.time() - imaging_start - if ('imaging' in response and elapsed_time >= IMAGING_TIMEOUT): - raise ElasticStackException('Drive imaging timed out') - - time.sleep(1) - - node_data = {} - node_data.update({'name': kwargs['name'], - 'cpu': size.cpu, - 'mem': size.ram, - 'ide:0:0': drive_uuid, - 'boot': 'ide:0:0', - 'smp': smp}) - node_data.update({'nic:0:model': nic_model, 'nic:0:dhcp': 'auto'}) - - if vnc_password: - node_data.update({'vnc': 'auto', 'vnc:password': vnc_password}) - - response = self.connection.request( - action='/servers/create', data=json.dumps(node_data), - method='POST' - ).object - - if isinstance(response, list): - nodes = [self._to_node(node, ssh_password) for node in response] - else: - nodes = self._to_node(response, ssh_password) - - return nodes - - # Extension methods - def ex_set_node_configuration(self, node, **kwargs): - """ - Changes the configuration of the running server - - :param node: Node which should be used - :type node: :class:`Node` - - :param kwargs: keyword arguments - :type kwargs: ``dict`` - - :rtype: ``bool`` - """ - valid_keys = ('^name$', '^parent$', '^cpu$', '^smp$', '^mem$', - '^boot$', '^nic:0:model$', '^nic:0:dhcp', - '^nic:1:model$', '^nic:1:vlan$', '^nic:1:mac$', - '^vnc:ip$', '^vnc:password$', '^vnc:tls', - '^ide:[0-1]:[0-1](:media)?$', - '^scsi:0:[0-7](:media)?$', '^block:[0-7](:media)?$') - - invalid_keys = [] - keys = list(kwargs.keys()) - for key in keys: - matches = False - for regex in valid_keys: - if re.match(regex, key): - matches = True - break - if not matches: - invalid_keys.append(key) - - if invalid_keys: - raise ElasticStackException( - 'Invalid configuration key specified: %s' - % (',' .join(invalid_keys)) - ) - - response = self.connection.request( - action='/servers/%s/set' % (node.id), data=json.dumps(kwargs), - method='POST' - ) - - return (response.status == httplib.OK and response.body != '') - - def deploy_node(self, **kwargs): - """ - Create a new node, and start deployment. - - @inherits: :class:`NodeDriver.deploy_node` - - :keyword enable_root: If true, root password will be set to - vnc_password (this will enable SSH access) - and default 'toor' account will be deleted. - :type enable_root: ``bool`` - """ - image = kwargs['image'] - vnc_password = kwargs.get('vnc_password', None) - enable_root = kwargs.get('enable_root', False) - - if not vnc_password: - raise ValueError('You need to provide vnc_password argument ' - 'if you want to use deployment') - - if (image in self._standard_drives and - not self._standard_drives[image]['supports_deployment']): - raise ValueError('Image %s does not support deployment' - % (image.id)) - - if enable_root: - script = ("unset HISTFILE;" - "echo root:%s | chpasswd;" - "sed -i '/^toor.*$/d' /etc/passwd /etc/shadow;" - "history -c") % vnc_password - root_enable_script = ScriptDeployment(script=script, - delete=True) - deploy = kwargs.get('deploy', None) - if deploy: - if (isinstance(deploy, ScriptDeployment) or - isinstance(deploy, SSHKeyDeployment)): - deployment = MultiStepDeployment([deploy, - root_enable_script]) - elif isinstance(deploy, MultiStepDeployment): - deployment = deploy - deployment.add(root_enable_script) - else: - deployment = root_enable_script - - kwargs['deploy'] = deployment - - if not kwargs.get('ssh_username', None): - kwargs['ssh_username'] = 'toor' - - return super(ElasticStackBaseNodeDriver, self).deploy_node(**kwargs) - - def ex_shutdown_node(self, node): - """ - Sends the ACPI power-down event - - :param node: Node which should be used - :type node: :class:`Node` - - :rtype: ``bool`` - """ - response = self.connection.request( - action='/servers/%s/shutdown' % (node.id), - method='POST' - ) - return response.status == 204 - - def ex_destroy_drive(self, drive_uuid): - """ - Deletes a drive - - :param drive_uuid: Drive uuid which should be used - :type drive_uuid: ``str`` - - :rtype: ``bool`` - """ - response = self.connection.request( - action='/drives/%s/destroy' % (drive_uuid), - method='POST' - ) - return response.status == 204 - - # Helper methods - def _to_node(self, data, ssh_password=None): - try: - state = NODE_STATE_MAP[data['status']] - except KeyError: - state = NodeState.UNKNOWN - - if 'nic:0:dhcp:ip' in data: - if isinstance(data['nic:0:dhcp:ip'], list): - public_ip = data['nic:0:dhcp:ip'] - else: - public_ip = [data['nic:0:dhcp:ip']] - else: - public_ip = [] - - extra = {'cpu': data['cpu'], - 'mem': data['mem']} - - if 'started' in data: - extra['started'] = data['started'] - - if 'smp' in data: - extra['smp'] = data['smp'] - - if 'vnc:ip' in data: - extra['vnc:ip'] = data['vnc:ip'] - - if 'vnc:password' in data: - extra['vnc:password'] = data['vnc:password'] - - boot_device = data['boot'] - - if isinstance(boot_device, list): - for device in boot_device: - extra[device] = data[device] - else: - extra[boot_device] = data[boot_device] - - if ssh_password: - extra.update({'password': ssh_password}) - - node = Node(id=data['server'], name=data['name'], state=state, - public_ips=public_ip, private_ips=None, - driver=self.connection.driver, - extra=extra) - - return node
http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/exoscale.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/exoscale.py b/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/exoscale.py deleted file mode 100644 index 9f883e0..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/exoscale.py +++ /dev/null @@ -1,31 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from libcloud.compute.providers import Provider -from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver - -__all__ = [ - 'ExoscaleNodeDriver' -] - - -class ExoscaleNodeDriver(CloudStackNodeDriver): - type = Provider.EXOSCALE - name = 'Exoscale' - website = 'https://www.exoscale.ch/' - - # API endpoint info - host = 'api.exoscale.ch' - path = '/compute' http://git-wip-us.apache.org/repos/asf/libcloud/blob/8afcda91/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gandi.py ---------------------------------------------------------------------- diff --git a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gandi.py b/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gandi.py deleted file mode 100644 index 844850a..0000000 --- a/apache-libcloud-1.0.0rc2/libcloud/compute/drivers/gandi.py +++ /dev/null @@ -1,825 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Gandi driver for compute -""" -import sys -from datetime import datetime - -from libcloud.common.gandi import BaseGandiDriver, GandiException,\ - NetworkInterface, IPAddress, Disk -from libcloud.compute.base import KeyPair -from libcloud.compute.base import StorageVolume -from libcloud.compute.types import NodeState, Provider -from libcloud.compute.base import Node, NodeDriver -from libcloud.compute.base import NodeSize, NodeImage, NodeLocation - - -NODE_STATE_MAP = { - 'running': NodeState.RUNNING, - 'halted': NodeState.TERMINATED, - 'paused': NodeState.TERMINATED, - 'locked': NodeState.TERMINATED, - 'being_created': NodeState.PENDING, - 'invalid': NodeState.UNKNOWN, - 'legally_locked': NodeState.PENDING, - 'deleted': NodeState.TERMINATED -} - -NODE_PRICE_HOURLY_USD = 0.02 - -INSTANCE_TYPES = { - 'small': { - 'id': 'small', - 'name': 'Small instance', - 'cpu': 1, - 'memory': 256, - 'disk': 3, - 'bandwidth': 10240, - }, - 'medium': { - 'id': 'medium', - 'name': 'Medium instance', - 'cpu': 1, - 'memory': 1024, - 'disk': 20, - 'bandwidth': 10240, - }, - 'large': { - 'id': 'large', - 'name': 'Large instance', - 'cpu': 2, - 'memory': 2048, - 'disk': 50, - 'bandwidth': 10240, - }, - 'x-large': { - 'id': 'x-large', - 'name': 'Extra Large instance', - 'cpu': 4, - 'memory': 4096, - 'disk': 100, - 'bandwidth': 10240, - }, -} - - -class GandiNodeDriver(BaseGandiDriver, NodeDriver): - """ - Gandi node driver - - """ - api_name = 'gandi' - friendly_name = 'Gandi.net' - website = 'http://www.gandi.net/' - country = 'FR' - type = Provider.GANDI - # TODO : which features to enable ? - features = {} - - def __init__(self, *args, **kwargs): - """ - @inherits: :class:`NodeDriver.__init__` - """ - super(BaseGandiDriver, self).__init__(*args, **kwargs) - - def _resource_info(self, type, id): - try: - obj = self.connection.request('hosting.%s.info' % type, int(id)) - return obj.object - except Exception: - e = sys.exc_info()[1] - raise GandiException(1003, e) - return None - - def _node_info(self, id): - return self._resource_info('vm', id) - - def _volume_info(self, id): - return self._resource_info('disk', id) - - # Generic methods for driver - def _to_node(self, vm): - return Node( - id=vm['id'], - name=vm['hostname'], - state=NODE_STATE_MAP.get( - vm['state'], - NodeState.UNKNOWN - ), - public_ips=vm.get('ips', []), - private_ips=[], - driver=self, - extra={ - 'ai_active': vm.get('ai_active'), - 'datacenter_id': vm.get('datacenter_id'), - 'description': vm.get('description') - } - ) - - def _to_nodes(self, vms): - return [self._to_node(v) for v in vms] - - def _to_volume(self, disk): - extra = {'can_snapshot': disk['can_snapshot']} - return StorageVolume( - id=disk['id'], - name=disk['name'], - size=int(disk['size']), - driver=self, - extra=extra) - - def _to_volumes(self, disks): - return [self._to_volume(d) for d in disks] - - def list_nodes(self): - """ - Return a list of nodes in the current zone or all zones. - - :return: List of Node objects - :rtype: ``list`` of :class:`Node` - """ - vms = self.connection.request('hosting.vm.list').object - ips = self.connection.request('hosting.ip.list').object - for vm in vms: - vm['ips'] = [] - for ip in ips: - if vm['ifaces_id'][0] == ip['iface_id']: - ip = ip.get('ip', None) - if ip: - vm['ips'].append(ip) - - nodes = self._to_nodes(vms) - return nodes - - def ex_get_node(self, node_id): - """ - Return a Node object based on a node id. - - :param name: The ID of the node - :type name: ``int`` - - :return: A Node object for the node - :rtype: :class:`Node` - """ - vm = self.connection.request('hosting.vm.info', int(node_id)).object - ips = self.connection.request('hosting.ip.list').object - vm['ips'] = [] - for ip in ips: - if vm['ifaces_id'][0] == ip['iface_id']: - ip = ip.get('ip', None) - if ip: - vm['ips'].append(ip) - node = self._to_node(vm) - return node - - def reboot_node(self, node): - """ - Reboot a node. - - :param node: Node to be rebooted - :type node: :class:`Node` - - :return: True if successful, False if not - :rtype: ``bool`` - """ - op = self.connection.request('hosting.vm.reboot', int(node.id)) - self._wait_operation(op.object['id']) - vm = self._node_info(int(node.id)) - if vm['state'] == 'running': - return True - return False - - def destroy_node(self, node): - """ - Destroy a node. - - :param node: Node object to destroy - :type node: :class:`Node` - - :return: True if successful - :rtype: ``bool`` - """ - vm = self._node_info(node.id) - if vm['state'] == 'running': - # Send vm_stop and wait for accomplish - op_stop = self.connection.request('hosting.vm.stop', int(node.id)) - if not self._wait_operation(op_stop.object['id']): - raise GandiException(1010, 'vm.stop failed') - # Delete - op = self.connection.request('hosting.vm.delete', int(node.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def deploy_node(self, **kwargs): - """ - deploy_node is not implemented for gandi driver - - :rtype: ``bool`` - """ - raise NotImplementedError( - 'deploy_node not implemented for gandi driver') - - def create_node(self, **kwargs): - """ - Create a new Gandi node - - :keyword name: String with a name for this new node (required) - :type name: ``str`` - - :keyword image: OS Image to boot on node. (required) - :type image: :class:`NodeImage` - - :keyword location: Which data center to create a node in. If empty, - undefined behavior will be selected. (optional) - :type location: :class:`NodeLocation` - - :keyword size: The size of resources allocated to this node. - (required) - :type size: :class:`NodeSize` - - :keyword login: user name to create for login on machine (required) - :type login: ``str`` - - :keyword password: password for user that'll be created (required) - :type password: ``str`` - - :keyword inet_family: version of ip to use, default 4 (optional) - :type inet_family: ``int`` - - :keyword keypairs: IDs of keypairs or Keypairs object - :type keypairs: list of ``int`` or :class:`.KeyPair` - - :rtype: :class:`Node` - """ - - if not kwargs.get('login') and not kwargs.get('keypairs'): - raise GandiException(1020, "Login and password or ssh keypair " - "must be defined for node creation") - - location = kwargs.get('location') - if location and isinstance(location, NodeLocation): - dc_id = int(location.id) - else: - raise GandiException( - 1021, 'location must be a subclass of NodeLocation') - - size = kwargs.get('size') - if not size and not isinstance(size, NodeSize): - raise GandiException( - 1022, 'size must be a subclass of NodeSize') - - keypairs = kwargs.get('keypairs', []) - keypair_ids = [ - k if isinstance(k, int) else k.extra['id'] - for k in keypairs - ] - - # If size name is in INSTANCE_TYPE we use new rating model - instance = INSTANCE_TYPES.get(size.id) - cores = instance['cpu'] if instance else int(size.id) - - src_disk_id = int(kwargs['image'].id) - - disk_spec = { - 'datacenter_id': dc_id, - 'name': 'disk_%s' % kwargs['name'] - } - - vm_spec = { - 'datacenter_id': dc_id, - 'hostname': kwargs['name'], - 'memory': int(size.ram), - 'cores': cores, - 'bandwidth': int(size.bandwidth), - 'ip_version': kwargs.get('inet_family', 4), - } - - if kwargs.get('login') and kwargs.get('password'): - vm_spec.update({ - 'login': kwargs['login'], - 'password': kwargs['password'], # TODO : use NodeAuthPassword - }) - if keypair_ids: - vm_spec['keys'] = keypair_ids - - # Call create_from helper api. Return 3 operations : disk_create, - # iface_create,vm_create - (op_disk, op_iface, op_vm) = self.connection.request( - 'hosting.vm.create_from', - vm_spec, disk_spec, src_disk_id - ).object - - # We wait for vm_create to finish - if self._wait_operation(op_vm['id']): - # after successful operation, get ip information - # thru first interface - node = self._node_info(op_vm['vm_id']) - ifaces = node.get('ifaces') - if len(ifaces) > 0: - ips = ifaces[0].get('ips') - if len(ips) > 0: - node['ip'] = ips[0]['ip'] - return self._to_node(node) - - return None - - def _to_image(self, img): - return NodeImage( - id=img['disk_id'], - name=img['label'], - driver=self.connection.driver - ) - - def list_images(self, location=None): - """ - Return a list of image objects. - - :keyword location: Which data center to filter a images in. - :type location: :class:`NodeLocation` - - :return: List of GCENodeImage objects - :rtype: ``list`` of :class:`GCENodeImage` - """ - try: - if location: - filtering = {'datacenter_id': int(location.id)} - else: - filtering = {} - images = self.connection.request('hosting.image.list', filtering) - return [self._to_image(i) for i in images.object] - except Exception: - e = sys.exc_info()[1] - raise GandiException(1011, e) - - def _to_size(self, id, size): - return NodeSize( - id=id, - name='%s cores' % id, - ram=size['memory'], - disk=size['disk'], - bandwidth=size['bandwidth'], - price=(self._get_size_price(size_id='1') * id), - driver=self.connection.driver, - ) - - def _instance_type_to_size(self, instance): - return NodeSize( - id=instance['id'], - name=instance['name'], - ram=instance['memory'], - disk=instance['disk'], - bandwidth=instance['bandwidth'], - price=self._get_size_price(size_id=instance['id']), - driver=self.connection.driver, - ) - - def list_instance_type(self, location=None): - return [self._instance_type_to_size(instance) - for name, instance in INSTANCE_TYPES.items()] - - def list_sizes(self, location=None): - """ - Return a list of sizes (machineTypes) in a zone. - - :keyword location: Which data center to filter a sizes in. - :type location: :class:`NodeLocation` or ``None`` - - :return: List of NodeSize objects - :rtype: ``list`` of :class:`NodeSize` - """ - account = self.connection.request('hosting.account.info').object - if account.get('rating_enabled'): - # This account use new rating model - return self.list_instance_type(location) - # Look for available shares, and return a list of share_definition - available_res = account['resources']['available'] - - if available_res['shares'] == 0: - return None - else: - share_def = account['share_definition'] - available_cores = available_res['cores'] - # 0.75 core given when creating a server - max_core = int(available_cores + 0.75) - shares = [] - if available_res['servers'] < 1: - # No server quota, no way - return shares - for i in range(1, max_core + 1): - share = {id: i} - share_is_available = True - for k in ['memory', 'disk', 'bandwidth']: - if share_def[k] * i > available_res[k]: - # We run out for at least one resource inside - share_is_available = False - else: - share[k] = share_def[k] * i - if share_is_available: - nb_core = i - shares.append(self._to_size(nb_core, share)) - return shares - - def _to_loc(self, loc): - return NodeLocation( - id=loc['id'], - name=loc['name'], - country=loc['country'], - driver=self - ) - - def list_locations(self): - """ - Return a list of locations (datacenters). - - :return: List of NodeLocation objects - :rtype: ``list`` of :class:`NodeLocation` - """ - res = self.connection.request('hosting.datacenter.list') - return [self._to_loc(l) for l in res.object] - - def list_volumes(self): - """ - Return a list of volumes. - - :return: A list of volume objects. - :rtype: ``list`` of :class:`StorageVolume` - """ - res = self.connection.request('hosting.disk.list', {}) - return self._to_volumes(res.object) - - def ex_get_volume(self, volume_id): - """ - Return a Volume object based on a volume ID. - - :param volume_id: The ID of the volume - :type volume_id: ``int`` - - :return: A StorageVolume object for the volume - :rtype: :class:`StorageVolume` - """ - res = self.connection.request('hosting.disk.info', volume_id) - return self._to_volume(res.object) - - def create_volume(self, size, name, location=None, snapshot=None): - """ - Create a volume (disk). - - :param size: Size of volume to create (in GB). - :type size: ``int`` - - :param name: Name of volume to create - :type name: ``str`` - - :keyword location: Location (zone) to create the volume in - :type location: :class:`NodeLocation` or ``None`` - - :keyword snapshot: Snapshot to create image from - :type snapshot: :class:`Snapshot` - - :return: Storage Volume object - :rtype: :class:`StorageVolume` - """ - disk_param = { - 'name': name, - 'size': int(size), - 'datacenter_id': int(location.id) - } - if snapshot: - op = self.connection.request('hosting.disk.create_from', - disk_param, int(snapshot.id)) - else: - op = self.connection.request('hosting.disk.create', disk_param) - if self._wait_operation(op.object['id']): - disk = self._volume_info(op.object['disk_id']) - return self._to_volume(disk) - return None - - def attach_volume(self, node, volume, device=None): - """ - Attach a volume to a node. - - :param node: The node to attach the volume to - :type node: :class:`Node` - - :param volume: The volume to attach. - :type volume: :class:`StorageVolume` - - :keyword device: Not used in this cloud. - :type device: ``None`` - - :return: True if successful - :rtype: ``bool`` - """ - op = self.connection.request('hosting.vm.disk_attach', - int(node.id), int(volume.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def detach_volume(self, node, volume): - """ - Detaches a volume from a node. - - :param node: Node which should be used - :type node: :class:`Node` - - :param volume: Volume to be detached - :type volume: :class:`StorageVolume` - - :rtype: ``bool`` - """ - op = self.connection.request('hosting.vm.disk_detach', - int(node.id), int(volume.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def destroy_volume(self, volume): - """ - Destroy a volume. - - :param volume: Volume object to destroy - :type volume: :class:`StorageVolume` - - :return: True if successful - :rtype: ``bool`` - """ - op = self.connection.request('hosting.disk.delete', int(volume.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def _to_iface(self, iface): - ips = [] - for ip in iface.get('ips', []): - new_ip = IPAddress( - ip['id'], - NODE_STATE_MAP.get( - ip['state'], - NodeState.UNKNOWN - ), - ip['ip'], - self.connection.driver, - version=ip.get('version'), - extra={'reverse': ip['reverse']} - ) - ips.append(new_ip) - return NetworkInterface( - iface['id'], - NODE_STATE_MAP.get( - iface['state'], - NodeState.UNKNOWN - ), - mac_address=None, - driver=self.connection.driver, - ips=ips, - node_id=iface.get('vm_id'), - extra={'bandwidth': iface['bandwidth']}, - ) - - def _to_ifaces(self, ifaces): - return [self._to_iface(i) for i in ifaces] - - def ex_list_interfaces(self): - """ - Specific method to list network interfaces - - :rtype: ``list`` of :class:`GandiNetworkInterface` - """ - ifaces = self.connection.request('hosting.iface.list').object - ips = self.connection.request('hosting.ip.list').object - for iface in ifaces: - iface['ips'] = list( - filter(lambda i: i['iface_id'] == iface['id'], ips)) - return self._to_ifaces(ifaces) - - def _to_disk(self, element): - disk = Disk( - id=element['id'], - state=NODE_STATE_MAP.get( - element['state'], - NodeState.UNKNOWN - ), - name=element['name'], - driver=self.connection.driver, - size=element['size'], - extra={'can_snapshot': element['can_snapshot']} - ) - return disk - - def _to_disks(self, elements): - return [self._to_disk(el) for el in elements] - - def ex_list_disks(self): - """ - Specific method to list all disk - - :rtype: ``list`` of :class:`GandiDisk` - """ - res = self.connection.request('hosting.disk.list', {}) - return self._to_disks(res.object) - - def ex_node_attach_disk(self, node, disk): - """ - Specific method to attach a disk to a node - - :param node: Node which should be used - :type node: :class:`Node` - - :param disk: Disk which should be used - :type disk: :class:`GandiDisk` - - :rtype: ``bool`` - """ - op = self.connection.request('hosting.vm.disk_attach', - int(node.id), int(disk.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def ex_node_detach_disk(self, node, disk): - """ - Specific method to detach a disk from a node - - :param node: Node which should be used - :type node: :class:`Node` - - :param disk: Disk which should be used - :type disk: :class:`GandiDisk` - - :rtype: ``bool`` - """ - op = self.connection.request('hosting.vm.disk_detach', - int(node.id), int(disk.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def ex_node_attach_interface(self, node, iface): - """ - Specific method to attach an interface to a node - - :param node: Node which should be used - :type node: :class:`Node` - - :param iface: Network interface which should be used - :type iface: :class:`GandiNetworkInterface` - - :rtype: ``bool`` - """ - op = self.connection.request('hosting.vm.iface_attach', - int(node.id), int(iface.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def ex_node_detach_interface(self, node, iface): - """ - Specific method to detach an interface from a node - - :param node: Node which should be used - :type node: :class:`Node` - - :param iface: Network interface which should be used - :type iface: :class:`GandiNetworkInterface` - - :rtype: ``bool`` - """ - op = self.connection.request('hosting.vm.iface_detach', - int(node.id), int(iface.id)) - if self._wait_operation(op.object['id']): - return True - return False - - def ex_snapshot_disk(self, disk, name=None): - """ - Specific method to make a snapshot of a disk - - :param disk: Disk which should be used - :type disk: :class:`GandiDisk` - - :param name: Name which should be used - :type name: ``str`` - - :rtype: ``bool`` - """ - if not disk.extra.get('can_snapshot'): - raise GandiException(1021, 'Disk %s can\'t snapshot' % disk.id) - if not name: - suffix = datetime.today().strftime('%Y%m%d') - name = 'snap_%s' % (suffix) - op = self.connection.request( - 'hosting.disk.create_from', - {'name': name, 'type': 'snapshot', }, - int(disk.id), - ) - if self._wait_operation(op.object['id']): - return True - return False - - def ex_update_disk(self, disk, new_size=None, new_name=None): - """Specific method to update size or name of a disk - WARNING: if a server is attached it'll be rebooted - - :param disk: Disk which should be used - :type disk: :class:`GandiDisk` - - :param new_size: New size - :type new_size: ``int`` - - :param new_name: New name - :type new_name: ``str`` - - :rtype: ``bool`` - """ - params = {} - if new_size: - params.update({'size': new_size}) - if new_name: - params.update({'name': new_name}) - op = self.connection.request('hosting.disk.update', - int(disk.id), - params) - if self._wait_operation(op.object['id']): - return True - return False - - def _to_key_pair(self, data): - key_pair = KeyPair(name=data['name'], - fingerprint=data['fingerprint'], - public_key=data.get('value', None), - private_key=data.get('privatekey', None), - driver=self, extra={'id': data['id']}) - return key_pair - - def _to_key_pairs(self, data): - return [self._to_key_pair(k) for k in data] - - def list_key_pairs(self): - """ - List registered key pairs. - - :return: A list of key par objects. - :rtype: ``list`` of :class:`libcloud.compute.base.KeyPair` - """ - kps = self.connection.request('hosting.ssh.list').object - return self._to_key_pairs(kps) - - def get_key_pair(self, name): - """ - Retrieve a single key pair. - - :param name: Name of the key pair to retrieve. - :type name: ``str`` - - :rtype: :class:`.KeyPair` - """ - filter_params = {'name': name} - kps = self.connection.request('hosting.ssh.list', filter_params).object - return self._to_key_pair(kps[0]) - - def import_key_pair_from_string(self, name, key_material): - """ - Create a new key pair object. - - :param name: Key pair name. - :type name: ``str`` - - :param key_material: Public key material. - :type key_material: ``str`` - - :return: Imported key pair object. - :rtype: :class:`.KeyPair` - """ - params = {'name': name, 'value': key_material} - kp = self.connection.request('hosting.ssh.create', params).object - return self._to_key_pair(kp) - - def delete_key_pair(self, key_pair): - """ - Delete an existing key pair. - - :param key_pair: Key pair object or ID. - :type key_pair: :class.KeyPair` or ``int`` - - :return: True of False based on success of Keypair deletion - :rtype: ``bool`` - """ - key_id = key_pair if isinstance(key_pair, int) \ - else key_pair.extra['id'] - success = self.connection.request('hosting.ssh.delete', key_id).object - return success