1# Licensed to the Apache Software Foundation (ASF) under one or more 2# contributor license agreements. See the NOTICE file distributed with 3# this work for additional information regarding copyright ownership. 4# The ASF licenses this file to You under the Apache License, Version 2.0 5# (the "License"); you may not use this file except in compliance with 6# the License. You may obtain a copy of the License 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 16import json 17import re 18 19from libcloud.compute.base import Node, NodeDriver, NodeLocation 20from libcloud.compute.base import NodeSize, NodeImage 21from libcloud.compute.base import KeyPair 22from libcloud.common.maxihost import MaxihostConnection 23from libcloud.compute.types import Provider, NodeState 24from libcloud.common.exceptions import BaseHTTPError 25from libcloud.utils.py3 import httplib 26 27 28__all__ = [ 29 "MaxihostNodeDriver" 30] 31 32 33class MaxihostNodeDriver(NodeDriver): 34 """ 35 Base Maxihost node driver. 36 """ 37 38 connectionCls = MaxihostConnection 39 type = Provider.MAXIHOST 40 name = 'Maxihost' 41 website = 'https://www.maxihost.com/' 42 43 def create_node(self, name, size, image, location, 44 ex_ssh_key_ids=None): 45 """ 46 Create a node. 47 48 :return: The newly created node. 49 :rtype: :class:`Node` 50 """ 51 attr = {'hostname': name, 'plan': size.id, 52 'operating_system': image.id, 53 'facility': location.id.lower(), 'billing_cycle': 'monthly'} 54 55 if ex_ssh_key_ids: 56 attr['ssh_keys'] = ex_ssh_key_ids 57 58 try: 59 res = self.connection.request('/devices', 60 params=attr, method='POST') 61 except BaseHTTPError as exc: 62 error_message = exc.message.get('error_messages', '') 63 raise ValueError('Failed to create node: %s' % (error_message)) 64 65 return self._to_node(res.object['devices'][0]) 66 67 def start_node(self, node): 68 """ 69 Start a node. 70 """ 71 params = {"type": "power_on"} 72 res = self.connection.request('/devices/%s/actions' % node.id, 73 params=params, method='PUT') 74 75 return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] 76 77 def stop_node(self, node): 78 """ 79 Stop a node. 80 """ 81 params = {"type": "power_off"} 82 res = self.connection.request('/devices/%s/actions' % node.id, 83 params=params, method='PUT') 84 85 return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] 86 87 def destroy_node(self, node): 88 """ 89 Destroy a node. 90 """ 91 res = self.connection.request('/devices/%s' % node.id, 92 method='DELETE') 93 94 return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] 95 96 def reboot_node(self, node): 97 """ 98 Reboot a node. 99 """ 100 params = {"type": "power_cycle"} 101 res = self.connection.request('/devices/%s/actions' % node.id, 102 params=params, method='PUT') 103 104 return res.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] 105 106 def list_nodes(self): 107 """ 108 List nodes 109 110 :rtype: ``list`` of :class:`MaxihostNode` 111 """ 112 response = self.connection.request('/devices') 113 nodes = [self._to_node(host) 114 for host in response.object['devices']] 115 return nodes 116 117 def _to_node(self, data): 118 extra = {} 119 private_ips = [] 120 public_ips = [] 121 for ip in data['ips']: 122 if 'Private' in ip['ip_description']: 123 private_ips.append(ip['ip_address']) 124 else: 125 public_ips.append(ip['ip_address']) 126 127 if data['power_status']: 128 state = NodeState.RUNNING 129 else: 130 state = NodeState.STOPPED 131 132 for key in data: 133 extra[key] = data[key] 134 135 node = Node(id=data['id'], name=data['description'], state=state, 136 private_ips=private_ips, public_ips=public_ips, 137 driver=self, extra=extra) 138 return node 139 140 def list_locations(self, ex_available=True): 141 """ 142 List locations 143 144 If ex_available is True, show only locations which are available 145 """ 146 locations = [] 147 data = self.connection.request('/regions') 148 for location in data.object['regions']: 149 if ex_available: 150 if location.get('available'): 151 locations.append(self._to_location(location)) 152 else: 153 locations.append(self._to_location(location)) 154 return locations 155 156 def _to_location(self, data): 157 name = data.get('location').get('city', '') 158 country = data.get('location').get('country', '') 159 return NodeLocation(id=data['slug'], name=name, country=country, 160 driver=self) 161 162 def list_sizes(self): 163 """ 164 List sizes 165 """ 166 sizes = [] 167 data = self.connection.request('/plans') 168 for size in data.object['servers']: 169 sizes.append(self._to_size(size)) 170 return sizes 171 172 def _to_size(self, data): 173 extra = {'specs': data['specs'], 174 'regions': data['regions'], 175 'pricing': data['pricing']} 176 ram = data['specs']['memory']['total'] 177 ram = re.sub("[^0-9]", "", ram) 178 return NodeSize(id=data['slug'], name=data['name'], ram=int(ram), 179 disk=None, bandwidth=None, 180 price=data['pricing']['usd_month'], 181 driver=self, extra=extra) 182 183 def list_images(self): 184 """ 185 List images 186 """ 187 images = [] 188 data = self.connection.request('/plans/operating-systems') 189 for image in data.object['operating-systems']: 190 images.append(self._to_image(image)) 191 return images 192 193 def _to_image(self, data): 194 extra = {'operating_system': data['operating_system'], 195 'distro': data['distro'], 196 'version': data['version'], 197 'pricing': data['pricing']} 198 return NodeImage(id=data['slug'], name=data['name'], driver=self, 199 extra=extra) 200 201 def list_key_pairs(self): 202 """ 203 List all the available SSH keys. 204 205 :return: Available SSH keys. 206 :rtype: ``list`` of :class:`KeyPair` 207 """ 208 data = self.connection.request('/account/keys') 209 return list(map(self._to_key_pair, data.object['ssh_keys'])) 210 211 def create_key_pair(self, name, public_key): 212 """ 213 Create a new SSH key. 214 215 :param name: Key name (required) 216 :type name: ``str`` 217 218 :param public_key: base64 encoded public key string (required) 219 :type public_key: ``str`` 220 """ 221 attr = {'name': name, 'public_key': public_key} 222 res = self.connection.request('/account/keys', method='POST', 223 data=json.dumps(attr)) 224 225 data = res.object['ssh_key'] 226 227 return self._to_key_pair(data=data) 228 229 def _to_key_pair(self, data): 230 extra = {'id': data['id']} 231 return KeyPair(name=data['name'], 232 fingerprint=data['fingerprint'], 233 public_key=data['public_key'], 234 private_key=None, 235 driver=self, 236 extra=extra) 237 238 def ex_start_node(self, node): 239 # NOTE: This method is here for backward compatibility reasons after 240 # this method was promoted to be part of the standard compute API in 241 # Libcloud v2.7.0 242 return self.start_node(node=node) 243 244 def ex_stop_node(self, node): 245 # NOTE: This method is here for backward compatibility reasons after 246 # this method was promoted to be part of the standard compute API in 247 # Libcloud v2.7.0 248 return self.stop_node(node=node) 249