Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 1 | import re |
| 2 | import json |
| 3 | import time |
koder aka kdanilov | cee4334 | 2015-04-14 22:52:53 +0300 | [diff] [blame] | 4 | import logging |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 5 | import urllib2 |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 6 | import urlparse |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 7 | from functools import partial, wraps |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 8 | |
| 9 | import netaddr |
| 10 | |
| 11 | from keystoneclient.v2_0 import Client as keystoneclient |
| 12 | from keystoneclient import exceptions |
| 13 | |
| 14 | |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 15 | logger = logging.getLogger("wally.fuel_api") |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 16 | |
| 17 | |
| 18 | class Urllib2HTTP(object): |
| 19 | """ |
| 20 | class for making HTTP requests |
| 21 | """ |
| 22 | |
| 23 | allowed_methods = ('get', 'put', 'post', 'delete', 'patch', 'head') |
| 24 | |
koder aka kdanilov | cee4334 | 2015-04-14 22:52:53 +0300 | [diff] [blame] | 25 | def __init__(self, root_url, headers=None): |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 26 | """ |
| 27 | """ |
| 28 | if root_url.endswith('/'): |
| 29 | self.root_url = root_url[:-1] |
| 30 | else: |
| 31 | self.root_url = root_url |
| 32 | |
| 33 | self.headers = headers if headers is not None else {} |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 34 | |
| 35 | def host(self): |
| 36 | return self.root_url.split('/')[2] |
| 37 | |
| 38 | def do(self, method, path, params=None): |
| 39 | if path.startswith('/'): |
| 40 | url = self.root_url + path |
| 41 | else: |
| 42 | url = self.root_url + '/' + path |
| 43 | |
| 44 | if method == 'get': |
| 45 | assert params == {} or params is None |
| 46 | data_json = None |
| 47 | else: |
| 48 | data_json = json.dumps(params) |
| 49 | |
koder aka kdanilov | 6b1341a | 2015-04-21 22:44:21 +0300 | [diff] [blame] | 50 | logger.debug("HTTP: {0} {1}".format(method.upper(), url)) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 51 | |
| 52 | request = urllib2.Request(url, |
| 53 | data=data_json, |
| 54 | headers=self.headers) |
| 55 | if data_json is not None: |
| 56 | request.add_header('Content-Type', 'application/json') |
| 57 | |
| 58 | request.get_method = lambda: method.upper() |
| 59 | response = urllib2.urlopen(request) |
| 60 | |
koder aka kdanilov | 6b1341a | 2015-04-21 22:44:21 +0300 | [diff] [blame] | 61 | logger.debug("HTTP Responce: {0}".format(response.code)) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 62 | |
| 63 | if response.code < 200 or response.code > 209: |
| 64 | raise IndexError(url) |
| 65 | |
| 66 | content = response.read() |
| 67 | |
| 68 | if '' == content: |
| 69 | return None |
| 70 | |
| 71 | return json.loads(content) |
| 72 | |
| 73 | def __getattr__(self, name): |
| 74 | if name in self.allowed_methods: |
| 75 | return partial(self.do, name) |
| 76 | raise AttributeError(name) |
| 77 | |
| 78 | |
| 79 | class KeystoneAuth(Urllib2HTTP): |
koder aka kdanilov | cee4334 | 2015-04-14 22:52:53 +0300 | [diff] [blame] | 80 | def __init__(self, root_url, creds, headers=None): |
| 81 | super(KeystoneAuth, self).__init__(root_url, headers) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 82 | admin_node_ip = urlparse.urlparse(root_url).hostname |
| 83 | self.keystone_url = "http://{0}:5000/v2.0".format(admin_node_ip) |
| 84 | self.keystone = keystoneclient( |
| 85 | auth_url=self.keystone_url, **creds) |
| 86 | self.refresh_token() |
| 87 | |
| 88 | def refresh_token(self): |
| 89 | """Get new token from keystone and update headers""" |
| 90 | try: |
| 91 | self.keystone.authenticate() |
| 92 | self.headers['X-Auth-Token'] = self.keystone.auth_token |
| 93 | except exceptions.AuthorizationFailure: |
koder aka kdanilov | cee4334 | 2015-04-14 22:52:53 +0300 | [diff] [blame] | 94 | logger.warning( |
| 95 | 'Cant establish connection to keystone with url %s', |
| 96 | self.keystone_url) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 97 | |
| 98 | def do(self, method, path, params=None): |
| 99 | """Do request. If gets 401 refresh token""" |
| 100 | try: |
| 101 | return super(KeystoneAuth, self).do(method, path, params) |
| 102 | except urllib2.HTTPError as e: |
| 103 | if e.code == 401: |
koder aka kdanilov | cee4334 | 2015-04-14 22:52:53 +0300 | [diff] [blame] | 104 | logger.warning( |
| 105 | 'Authorization failure: {0}'.format(e.read())) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 106 | self.refresh_token() |
| 107 | return super(KeystoneAuth, self).do(method, path, params) |
| 108 | else: |
| 109 | raise |
| 110 | |
| 111 | |
| 112 | def get_inline_param_list(url): |
| 113 | format_param_rr = re.compile(r"\{([a-zA-Z_]+)\}") |
| 114 | for match in format_param_rr.finditer(url): |
| 115 | yield match.group(1) |
| 116 | |
| 117 | |
| 118 | class RestObj(object): |
| 119 | name = None |
| 120 | id = None |
| 121 | |
| 122 | def __init__(self, conn, **kwargs): |
| 123 | self.__dict__.update(kwargs) |
| 124 | self.__connection__ = conn |
| 125 | |
| 126 | def __str__(self): |
koder aka kdanilov | 6b1341a | 2015-04-21 22:44:21 +0300 | [diff] [blame] | 127 | res = ["{0}({1}):".format(self.__class__.__name__, self.name)] |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 128 | for k, v in sorted(self.__dict__.items()): |
| 129 | if k.startswith('__') or k.endswith('__'): |
| 130 | continue |
| 131 | if k != 'name': |
koder aka kdanilov | 6b1341a | 2015-04-21 22:44:21 +0300 | [diff] [blame] | 132 | res.append(" {0}={1!r}".format(k, v)) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 133 | return "\n".join(res) |
| 134 | |
| 135 | def __getitem__(self, item): |
| 136 | return getattr(self, item) |
| 137 | |
| 138 | |
| 139 | def make_call(method, url): |
| 140 | def closure(obj, entire_obj=None, **data): |
| 141 | inline_params_vals = {} |
| 142 | for name in get_inline_param_list(url): |
| 143 | if name in data: |
| 144 | inline_params_vals[name] = data[name] |
| 145 | del data[name] |
| 146 | else: |
| 147 | inline_params_vals[name] = getattr(obj, name) |
| 148 | result_url = url.format(**inline_params_vals) |
| 149 | |
| 150 | if entire_obj is not None: |
| 151 | if data != {}: |
| 152 | raise ValueError("Both entire_obj and data provided") |
| 153 | data = entire_obj |
| 154 | return obj.__connection__.do(method, result_url, params=data) |
| 155 | return closure |
| 156 | |
| 157 | |
| 158 | PUT = partial(make_call, 'put') |
| 159 | GET = partial(make_call, 'get') |
| 160 | DELETE = partial(make_call, 'delete') |
| 161 | |
| 162 | |
| 163 | def with_timeout(tout, message): |
| 164 | def closure(func): |
| 165 | @wraps(func) |
| 166 | def closure2(*dt, **mp): |
| 167 | ctime = time.time() |
| 168 | etime = ctime + tout |
| 169 | |
| 170 | while ctime < etime: |
| 171 | if func(*dt, **mp): |
| 172 | return |
| 173 | sleep_time = ctime + 1 - time.time() |
| 174 | if sleep_time > 0: |
| 175 | time.sleep(sleep_time) |
| 176 | ctime = time.time() |
| 177 | raise RuntimeError("Timeout during " + message) |
| 178 | return closure2 |
| 179 | return closure |
| 180 | |
| 181 | |
| 182 | # ------------------------------- ORM ---------------------------------------- |
| 183 | |
| 184 | |
| 185 | def get_fuel_info(url): |
| 186 | conn = Urllib2HTTP(url) |
| 187 | return FuelInfo(conn) |
| 188 | |
| 189 | |
| 190 | class FuelInfo(RestObj): |
| 191 | |
| 192 | """Class represents Fuel installation info""" |
| 193 | |
| 194 | get_nodes = GET('api/nodes') |
| 195 | get_clusters = GET('api/clusters') |
| 196 | get_cluster = GET('api/clusters/{id}') |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 197 | get_info = GET('api/releases') |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 198 | |
| 199 | @property |
| 200 | def nodes(self): |
| 201 | """Get all fuel nodes""" |
| 202 | return NodeList([Node(self.__connection__, **node) for node |
| 203 | in self.get_nodes()]) |
| 204 | |
| 205 | @property |
| 206 | def free_nodes(self): |
| 207 | """Get unallocated nodes""" |
| 208 | return NodeList([Node(self.__connection__, **node) for node in |
| 209 | self.get_nodes() if not node['cluster']]) |
| 210 | |
| 211 | @property |
| 212 | def clusters(self): |
| 213 | """List clusters in fuel""" |
| 214 | return [Cluster(self.__connection__, **cluster) for cluster |
| 215 | in self.get_clusters()] |
| 216 | |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 217 | def get_version(self): |
| 218 | for info in self.get_info(): |
| 219 | vers = info['version'].split("-")[1].split('.') |
| 220 | return map(int, vers) |
| 221 | raise ValueError("No version found") |
| 222 | |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 223 | |
| 224 | class Node(RestObj): |
| 225 | """Represents node in Fuel""" |
| 226 | |
| 227 | get_info = GET('/api/nodes/{id}') |
| 228 | get_interfaces = GET('/api/nodes/{id}/interfaces') |
| 229 | update_interfaces = PUT('/api/nodes/{id}/interfaces') |
| 230 | |
| 231 | def set_network_assigment(self, mapping): |
| 232 | """Assings networks to interfaces |
| 233 | :param mapping: list (dict) interfaces info |
| 234 | """ |
| 235 | |
| 236 | curr_interfaces = self.get_interfaces() |
| 237 | |
| 238 | network_ids = {} |
| 239 | for interface in curr_interfaces: |
| 240 | for net in interface['assigned_networks']: |
| 241 | network_ids[net['name']] = net['id'] |
| 242 | |
| 243 | # transform mappings |
| 244 | new_assigned_networks = {} |
| 245 | |
| 246 | for dev_name, networks in mapping.items(): |
| 247 | new_assigned_networks[dev_name] = [] |
| 248 | for net_name in networks: |
| 249 | nnet = {'name': net_name, 'id': network_ids[net_name]} |
| 250 | new_assigned_networks[dev_name].append(nnet) |
| 251 | |
| 252 | # update by ref |
| 253 | for dev_descr in curr_interfaces: |
| 254 | if dev_descr['name'] in new_assigned_networks: |
| 255 | nass = new_assigned_networks[dev_descr['name']] |
| 256 | dev_descr['assigned_networks'] = nass |
| 257 | |
| 258 | self.update_interfaces(curr_interfaces, id=self.id) |
| 259 | |
| 260 | def set_node_name(self, name): |
| 261 | """Update node name""" |
| 262 | self.__connection__.put('api/nodes', [{'id': self.id, 'name': name}]) |
| 263 | |
| 264 | def get_network_data(self): |
| 265 | """Returns node network data""" |
| 266 | node_info = self.get_info() |
| 267 | return node_info.get('network_data') |
| 268 | |
Yulia Portnova | 0e64ea2 | 2015-03-20 17:27:22 +0200 | [diff] [blame] | 269 | def get_roles(self, pending=False): |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 270 | """Get node roles |
| 271 | |
| 272 | Returns: (roles, pending_roles) |
| 273 | """ |
| 274 | node_info = self.get_info() |
Yulia Portnova | 0e64ea2 | 2015-03-20 17:27:22 +0200 | [diff] [blame] | 275 | if pending: |
| 276 | return node_info.get('roles'), node_info.get('pending_roles') |
| 277 | else: |
| 278 | return node_info.get('roles') |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 279 | |
| 280 | def get_ip(self, network='public'): |
| 281 | """Get node ip |
| 282 | |
| 283 | :param network: network to pick |
| 284 | """ |
| 285 | nets = self.get_network_data() |
| 286 | for net in nets: |
| 287 | if net['name'] == network: |
| 288 | iface_name = net['dev'] |
| 289 | for iface in self.get_info()['meta']['interfaces']: |
| 290 | if iface['name'] == iface_name: |
| 291 | try: |
| 292 | return iface['ip'] |
| 293 | except KeyError: |
| 294 | return netaddr.IPNetwork(net['ip']).ip |
| 295 | raise Exception('Network %s not found' % network) |
| 296 | |
| 297 | |
| 298 | class NodeList(list): |
| 299 | """Class for filtering nodes through attributes""" |
| 300 | allowed_roles = ['controller', 'compute', 'cinder', 'ceph-osd', 'mongo', |
| 301 | 'zabbix-server'] |
| 302 | |
| 303 | def __getattr__(self, name): |
| 304 | if name in self.allowed_roles: |
| 305 | return [node for node in self if name in node.roles] |
| 306 | |
| 307 | |
| 308 | class Cluster(RestObj): |
| 309 | """Class represents Cluster in Fuel""" |
| 310 | |
| 311 | add_node_call = PUT('api/nodes') |
| 312 | start_deploy = PUT('api/clusters/{id}/changes') |
| 313 | get_status = GET('api/clusters/{id}') |
| 314 | delete = DELETE('api/clusters/{id}') |
| 315 | get_tasks_status = GET("api/tasks?cluster_id={id}") |
| 316 | get_networks = GET( |
Yulia Portnova | 415447a | 2015-05-12 15:09:21 +0300 | [diff] [blame] | 317 | 'api/clusters/{id}/network_configuration/neutron') |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 318 | |
| 319 | get_attributes = GET( |
| 320 | 'api/clusters/{id}/attributes') |
| 321 | |
| 322 | set_attributes = PUT( |
| 323 | 'api/clusters/{id}/attributes') |
| 324 | |
| 325 | configure_networks = PUT( |
| 326 | 'api/clusters/{id}/network_configuration/{net_provider}') |
| 327 | |
| 328 | _get_nodes = GET('api/nodes?cluster_id={id}') |
| 329 | |
| 330 | def __init__(self, *dt, **mp): |
| 331 | super(Cluster, self).__init__(*dt, **mp) |
Yulia Portnova | d976704 | 2015-04-10 17:32:06 +0300 | [diff] [blame] | 332 | self.nodes = NodeList([Node(self.__connection__, **node) for node in |
| 333 | self._get_nodes()]) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 334 | self.network_roles = {} |
| 335 | |
| 336 | def check_exists(self): |
| 337 | """Check if cluster exists""" |
| 338 | try: |
| 339 | self.get_status() |
| 340 | return True |
| 341 | except urllib2.HTTPError as err: |
| 342 | if err.code == 404: |
| 343 | return False |
| 344 | raise |
| 345 | |
Yulia Portnova | d976704 | 2015-04-10 17:32:06 +0300 | [diff] [blame] | 346 | def get_openrc(self): |
Yulia Portnova | 00025a5 | 2015-04-07 12:17:32 +0300 | [diff] [blame] | 347 | access = self.get_attributes()['editable']['access'] |
| 348 | creds = {} |
| 349 | creds['username'] = access['user']['value'] |
| 350 | creds['password'] = access['password']['value'] |
| 351 | creds['tenant_name'] = access['tenant']['value'] |
koder aka kdanilov | fd2cfa5 | 2015-05-20 03:17:42 +0300 | [diff] [blame^] | 352 | creds['os_auth_url'] = "http://{0}:5000/v2.0".format( |
| 353 | self.get_networks()['public_vip']) |
Yulia Portnova | 00025a5 | 2015-04-07 12:17:32 +0300 | [diff] [blame] | 354 | return creds |
| 355 | |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 356 | def get_nodes(self): |
| 357 | for node_descr in self._get_nodes(): |
| 358 | yield Node(self.__connection__, **node_descr) |
| 359 | |
| 360 | def add_node(self, node, roles, interfaces=None): |
| 361 | """Add node to cluster |
| 362 | |
| 363 | :param node: Node object |
| 364 | :param roles: roles to assign |
| 365 | :param interfaces: mapping iface name to networks |
| 366 | """ |
| 367 | data = {} |
| 368 | data['pending_roles'] = roles |
| 369 | data['cluster_id'] = self.id |
| 370 | data['id'] = node.id |
| 371 | data['pending_addition'] = True |
| 372 | |
koder aka kdanilov | cee4334 | 2015-04-14 22:52:53 +0300 | [diff] [blame] | 373 | logger.debug("Adding node %s to cluster..." % node.id) |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 374 | |
| 375 | self.add_node_call([data]) |
| 376 | self.nodes.append(node) |
| 377 | |
| 378 | if interfaces is not None: |
| 379 | networks = {} |
| 380 | for iface_name, params in interfaces.items(): |
| 381 | networks[iface_name] = params['networks'] |
| 382 | |
| 383 | node.set_network_assigment(networks) |
| 384 | |
| 385 | def wait_operational(self, timeout): |
| 386 | """Wait until cluster status operational""" |
| 387 | def wo(): |
| 388 | status = self.get_status()['status'] |
| 389 | if status == "error": |
| 390 | raise Exception("Cluster deploy failed") |
| 391 | return self.get_status()['status'] == 'operational' |
| 392 | with_timeout(timeout, "deploy cluster")(wo)() |
| 393 | |
| 394 | def deploy(self, timeout): |
| 395 | """Start deploy and wait until all tasks finished""" |
| 396 | logger.debug("Starting deploy...") |
| 397 | self.start_deploy() |
| 398 | |
| 399 | self.wait_operational(timeout) |
| 400 | |
| 401 | def all_tasks_finished_ok(obj): |
| 402 | ok = True |
| 403 | for task in obj.get_tasks_status(): |
| 404 | if task['status'] == 'error': |
| 405 | raise Exception('Task execution error') |
| 406 | elif task['status'] != 'ready': |
| 407 | ok = False |
| 408 | return ok |
| 409 | |
| 410 | wto = with_timeout(timeout, "wait deployment finished") |
| 411 | wto(all_tasks_finished_ok)(self) |
| 412 | |
| 413 | def set_networks(self, net_descriptions): |
| 414 | """Update cluster networking parameters""" |
| 415 | configuration = self.get_networks() |
| 416 | current_networks = configuration['networks'] |
| 417 | parameters = configuration['networking_parameters'] |
| 418 | |
| 419 | if net_descriptions.get('networks'): |
| 420 | net_mapping = net_descriptions['networks'] |
| 421 | |
| 422 | for net in current_networks: |
| 423 | net_desc = net_mapping.get(net['name']) |
| 424 | if net_desc: |
| 425 | net.update(net_desc) |
| 426 | |
| 427 | if net_descriptions.get('networking_parameters'): |
| 428 | parameters.update(net_descriptions['networking_parameters']) |
| 429 | |
| 430 | self.configure_networks(**configuration) |
| 431 | |
| 432 | |
| 433 | def reflect_cluster(conn, cluster_id): |
| 434 | """Returns cluster object by id""" |
| 435 | c = Cluster(conn, id=cluster_id) |
| 436 | c.nodes = NodeList(list(c.get_nodes())) |
| 437 | return c |
| 438 | |
| 439 | |
| 440 | def get_all_nodes(conn): |
| 441 | """Get all nodes from Fuel""" |
| 442 | for node_desc in conn.get('api/nodes'): |
| 443 | yield Node(conn, **node_desc) |
| 444 | |
| 445 | |
| 446 | def get_all_clusters(conn): |
| 447 | """Get all clusters""" |
| 448 | for cluster_desc in conn.get('api/clusters'): |
| 449 | yield Cluster(conn, **cluster_desc) |
| 450 | |
| 451 | |
koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 452 | def get_cluster_id(conn, name): |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 453 | """Get cluster id by name""" |
| 454 | for cluster in get_all_clusters(conn): |
| 455 | if cluster.name == name: |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 456 | return cluster.id |
| 457 | |
koder aka kdanilov | da45e88 | 2015-04-06 02:24:42 +0300 | [diff] [blame] | 458 | raise ValueError("Cluster {0} not found".format(name)) |
| 459 | |
Yulia Portnova | 3556a06 | 2015-03-17 16:30:11 +0200 | [diff] [blame] | 460 | |
| 461 | sections = { |
| 462 | 'sahara': 'additional_components', |
| 463 | 'murano': 'additional_components', |
| 464 | 'ceilometer': 'additional_components', |
| 465 | 'volumes_ceph': 'storage', |
| 466 | 'images_ceph': 'storage', |
| 467 | 'ephemeral_ceph': 'storage', |
| 468 | 'objects_ceph': 'storage', |
| 469 | 'osd_pool_size': 'storage', |
| 470 | 'volumes_lvm': 'storage', |
| 471 | 'volumes_vmdk': 'storage', |
| 472 | 'tenant': 'access', |
| 473 | 'password': 'access', |
| 474 | 'user': 'access', |
| 475 | 'vc_password': 'vcenter', |
| 476 | 'cluster': 'vcenter', |
| 477 | 'host_ip': 'vcenter', |
| 478 | 'vc_user': 'vcenter', |
| 479 | 'use_vcenter': 'vcenter', |
| 480 | } |
| 481 | |
| 482 | |
| 483 | def create_empty_cluster(conn, cluster_desc, debug_mode=False): |
| 484 | """Create new cluster with configuration provided""" |
| 485 | |
| 486 | data = {} |
| 487 | data['nodes'] = [] |
| 488 | data['tasks'] = [] |
| 489 | data['name'] = cluster_desc['name'] |
| 490 | data['release'] = cluster_desc['release'] |
| 491 | data['mode'] = cluster_desc.get('deployment_mode') |
| 492 | data['net_provider'] = cluster_desc.get('net_provider') |
| 493 | |
| 494 | params = conn.post(path='/api/clusters', params=data) |
| 495 | cluster = Cluster(conn, **params) |
| 496 | |
| 497 | attributes = cluster.get_attributes() |
| 498 | |
| 499 | ed_attrs = attributes['editable'] |
| 500 | |
| 501 | ed_attrs['common']['libvirt_type']['value'] = \ |
| 502 | cluster_desc.get('libvirt_type', 'kvm') |
| 503 | |
| 504 | if 'nodes' in cluster_desc: |
| 505 | use_ceph = cluster_desc['nodes'].get('ceph_osd', None) is not None |
| 506 | else: |
| 507 | use_ceph = False |
| 508 | |
| 509 | if 'storage_type' in cluster_desc: |
| 510 | st = cluster_desc['storage_type'] |
| 511 | if st == 'ceph': |
| 512 | use_ceph = True |
| 513 | else: |
| 514 | use_ceph = False |
| 515 | |
| 516 | if use_ceph: |
| 517 | opts = ['ephemeral_ceph', 'images_ceph', 'images_vcenter'] |
| 518 | opts += ['iser', 'objects_ceph', 'volumes_ceph'] |
| 519 | opts += ['volumes_lvm', 'volumes_vmdk'] |
| 520 | |
| 521 | for name in opts: |
| 522 | val = ed_attrs['storage'][name] |
| 523 | if val['type'] == 'checkbox': |
| 524 | is_ceph = ('images_ceph' == name) |
| 525 | is_ceph = is_ceph or ('volumes_ceph' == name) |
| 526 | |
| 527 | if is_ceph: |
| 528 | val['value'] = True |
| 529 | else: |
| 530 | val['value'] = False |
| 531 | # else: |
| 532 | # raise NotImplementedError("Non-ceph storages are not implemented") |
| 533 | |
| 534 | cluster.set_attributes(attributes) |
| 535 | |
| 536 | return cluster |