| # 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. |
| |
| import json |
| import re |
| |
| from libcloud.compute.base import Node, NodeDriver, NodeLocation |
| from libcloud.compute.base import NodeSize, NodeImage |
| from libcloud.compute.base import KeyPair |
| from libcloud.common.maxihost import MaxihostConnection |
| from libcloud.compute.types import Provider, NodeState |
| from libcloud.common.exceptions import BaseHTTPError |
| from libcloud.utils.py3 import httplib |
| |
| |
| __all__ = [ |
| "MaxihostNodeDriver" |
| ] |
| |
| |
| class MaxihostNodeDriver(NodeDriver): |
| """ |
| Base Maxihost node driver. |
| """ |
| |
| connectionCls = MaxihostConnection |
| type = Provider.MAXIHOST |
| name = 'Maxihost' |
| website = 'https://www.maxihost.com/' |
| |
| def create_node(self, name, size, image, location, |
| ex_ssh_key_ids=None): |
| """ |
| Create a node. |
| |
| :return: The newly created node. |
| :rtype: :class:`Node` |
| """ |
| attr = {'hostname': name, 'plan': size.id, |
| 'operating_system': image.id, |
| 'facility': location.id.lower(), 'billing_cycle': 'monthly'} |
| |
| if ex_ssh_key_ids: |
| attr['ssh_keys'] = ex_ssh_key_ids |
| |
| try: |
| res = self.connection.request('/devices', |
| params=attr, method='POST') |
| except BaseHTTPError as exc: |
| error_message = exc.message.get('error_messages', '') |
| raise ValueError('Failed to create node: %s' % (error_message)) |
| |
| return self._to_node(res.object['devices'][0]) |
| |
| def start_node(self, node): |
| """ |
| Start a node. |
| """ |
| params = {"type": "power_on"} |
| res = self.connection.request('/devices/%s/actions' % node.id, |
| params=params, method='PUT') |
| |
| return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] |
| |
| def stop_node(self, node): |
| """ |
| Stop a node. |
| """ |
| params = {"type": "power_off"} |
| res = self.connection.request('/devices/%s/actions' % node.id, |
| params=params, method='PUT') |
| |
| return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] |
| |
| def destroy_node(self, node): |
| """ |
| Destroy a node. |
| """ |
| res = self.connection.request('/devices/%s' % node.id, |
| method='DELETE') |
| |
| return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] |
| |
| def reboot_node(self, node): |
| """ |
| Reboot a node. |
| """ |
| params = {"type": "power_cycle"} |
| res = self.connection.request('/devices/%s/actions' % node.id, |
| params=params, method='PUT') |
| |
| return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] |
| |
| def list_nodes(self): |
| """ |
| List nodes |
| |
| :rtype: ``list`` of :class:`MaxihostNode` |
| """ |
| response = self.connection.request('/devices') |
| nodes = [self._to_node(host) |
| for host in response.object['devices']] |
| return nodes |
| |
| def _to_node(self, data): |
| extra = {} |
| private_ips = [] |
| public_ips = [] |
| for ip in data['ips']: |
| if 'Private' in ip['ip_description']: |
| private_ips.append(ip['ip_address']) |
| else: |
| public_ips.append(ip['ip_address']) |
| |
| if data['power_status']: |
| state = NodeState.RUNNING |
| else: |
| state = NodeState.STOPPED |
| |
| for key in data: |
| extra[key] = data[key] |
| |
| node = Node(id=data['id'], name=data['description'], state=state, |
| private_ips=private_ips, public_ips=public_ips, |
| driver=self, extra=extra) |
| return node |
| |
| def list_locations(self, ex_available=True): |
| """ |
| List locations |
| |
| If ex_available is True, show only locations which are available |
| """ |
| locations = [] |
| data = self.connection.request('/regions') |
| for location in data.object['regions']: |
| if ex_available: |
| if location.get('available'): |
| locations.append(self._to_location(location)) |
| else: |
| locations.append(self._to_location(location)) |
| return locations |
| |
| def _to_location(self, data): |
| name = data.get('location').get('city', '') |
| country = data.get('location').get('country', '') |
| return NodeLocation(id=data['slug'], name=name, country=country, |
| driver=self) |
| |
| def list_sizes(self): |
| """ |
| List sizes |
| """ |
| sizes = [] |
| data = self.connection.request('/plans') |
| for size in data.object['servers']: |
| sizes.append(self._to_size(size)) |
| return sizes |
| |
| def _to_size(self, data): |
| extra = {'specs': data['specs'], |
| 'regions': data['regions'], |
| 'pricing': data['pricing']} |
| ram = data['specs']['memory']['total'] |
| ram = re.sub("[^0-9]", "", ram) |
| return NodeSize(id=data['slug'], name=data['name'], ram=int(ram), |
| disk=None, bandwidth=None, |
| price=data['pricing']['usd_month'], |
| driver=self, extra=extra) |
| |
| def list_images(self): |
| """ |
| List images |
| """ |
| images = [] |
| data = self.connection.request('/plans/operating-systems') |
| for image in data.object['operating-systems']: |
| images.append(self._to_image(image)) |
| return images |
| |
| def _to_image(self, data): |
| extra = {'operating_system': data['operating_system'], |
| 'distro': data['distro'], |
| 'version': data['version'], |
| 'pricing': data['pricing']} |
| return NodeImage(id=data['slug'], name=data['name'], driver=self, |
| extra=extra) |
| |
| def list_key_pairs(self): |
| """ |
| List all the available SSH keys. |
| |
| :return: Available SSH keys. |
| :rtype: ``list`` of :class:`KeyPair` |
| """ |
| data = self.connection.request('/account/keys') |
| return list(map(self._to_key_pair, data.object['ssh_keys'])) |
| |
| def create_key_pair(self, name, public_key): |
| """ |
| Create a new SSH key. |
| |
| :param name: Key name (required) |
| :type name: ``str`` |
| |
| :param public_key: base64 encoded public key string (required) |
| :type public_key: ``str`` |
| """ |
| attr = {'name': name, 'public_key': public_key} |
| res = self.connection.request('/account/keys', method='POST', |
| data=json.dumps(attr)) |
| |
| data = res.object['ssh_key'] |
| |
| return self._to_key_pair(data=data) |
| |
| def _to_key_pair(self, data): |
| extra = {'id': data['id']} |
| return KeyPair(name=data['name'], |
| fingerprint=data['fingerprint'], |
| public_key=data['public_key'], |
| private_key=None, |
| driver=self, |
| extra=extra) |
| |
| def ex_start_node(self, node): |
| # NOTE: This method is here for backward compatibility reasons after |
| # this method was promoted to be part of the standard compute API in |
| # Libcloud v2.7.0 |
| return self.start_node(node=node) |
| |
| def ex_stop_node(self, node): |
| # NOTE: This method is here for backward compatibility reasons after |
| # this method was promoted to be part of the standard compute API in |
| # Libcloud v2.7.0 |
| return self.stop_node(node=node) |