#!/usr/bin/env python3 # cluster.py - PVC CLI client function library, cluster management # Part of the Parallel Virtual Cluster (PVC) system # # Copyright (C) 2018-2021 Joshua M. Boniface # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ############################################################################### import json import cli_lib.ansiprint as ansiprint from cli_lib.common import call_api def initialize(config): """ Initialize the PVC cluster API endpoint: GET /api/v1/initialize API arguments: yes-i-really-mean-it API schema: {json_data_object} """ params = { 'yes-i-really-mean-it': 'yes' } response = call_api(config, 'post', '/initialize', params=params) if response.status_code == 200: retstatus = True else: retstatus = False return retstatus, response.json().get('message', '') def backup(config): """ Get a JSON backup of the cluster API endpoint: GET /api/v1/backup API arguments: API schema: {json_data_object} """ response = call_api(config, 'get', '/backup') if response.status_code == 200: return True, response.json() else: return False, response.json().get('message', '') def restore(config, cluster_data): """ Restore a JSON backup to the cluster API endpoint: POST /api/v1/restore API arguments: yes-i-really-mean-it API schema: {json_data_object} """ cluster_data_json = json.dumps(cluster_data) params = { 'yes-i-really-mean-it': 'yes' } data = { 'cluster_data': cluster_data_json } response = call_api(config, 'post', '/restore', params=params, data=data) if response.status_code == 200: retstatus = True else: retstatus = False return retstatus, response.json().get('message', '') def maintenance_mode(config, state): """ Enable or disable PVC cluster maintenance mode API endpoint: POST /api/v1/status API arguments: {state}={state} API schema: {json_data_object} """ params = { 'state': state } response = call_api(config, 'post', '/status', params=params) if response.status_code == 200: retstatus = True else: retstatus = False return retstatus, response.json().get('message', '') def get_info(config): """ Get status of the PVC cluster API endpoint: GET /api/v1/status API arguments: API schema: {json_data_object} """ response = call_api(config, 'get', '/status') if response.status_code == 200: return True, response.json() else: return False, response.json().get('message', '') def format_info(cluster_information, oformat): if oformat == 'json': return json.dumps(cluster_information) if oformat == 'json-pretty': return json.dumps(cluster_information, indent=4) # Plain formatting, i.e. human-readable if cluster_information['health'] == 'Optimal': health_colour = ansiprint.green() elif cluster_information['health'] == 'Maintenance': health_colour = ansiprint.blue() else: health_colour = ansiprint.yellow() if cluster_information['storage_health'] == 'Optimal': storage_health_colour = ansiprint.green() elif cluster_information['storage_health'] == 'Maintenance': storage_health_colour = ansiprint.blue() else: storage_health_colour = ansiprint.yellow() ainformation = [] if oformat == 'short': ainformation.append('{}PVC cluster status:{}'.format(ansiprint.bold(), ansiprint.end())) ainformation.append('{}Cluster health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), health_colour, cluster_information['health'], ansiprint.end())) if cluster_information['health_msg']: for line in cluster_information['health_msg']: ainformation.append(' > {}'.format(line)) ainformation.append('{}Storage health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), storage_health_colour, cluster_information['storage_health'], ansiprint.end())) if cluster_information['storage_health_msg']: for line in cluster_information['storage_health_msg']: ainformation.append(' > {}'.format(line)) return '\n'.join(ainformation) ainformation.append('{}PVC cluster status:{}'.format(ansiprint.bold(), ansiprint.end())) ainformation.append('') ainformation.append('{}Cluster health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), health_colour, cluster_information['health'], ansiprint.end())) if cluster_information['health_msg']: for line in cluster_information['health_msg']: ainformation.append(' > {}'.format(line)) ainformation.append('{}Storage health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), storage_health_colour, cluster_information['storage_health'], ansiprint.end())) if cluster_information['storage_health_msg']: for line in cluster_information['storage_health_msg']: ainformation.append(' > {}'.format(line)) ainformation.append('') ainformation.append('{}Primary node:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['primary_node'])) ainformation.append('{}Cluster upstream IP:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['upstream_ip'])) ainformation.append('') ainformation.append('{}Total nodes:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['nodes']['total'])) ainformation.append('{}Total VMs:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['vms']['total'])) ainformation.append('{}Total networks:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['networks'])) ainformation.append('{}Total OSDs:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['osds']['total'])) ainformation.append('{}Total pools:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['pools'])) ainformation.append('{}Total volumes:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['volumes'])) ainformation.append('{}Total snapshots:{} {}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['snapshots'])) nodes_string = '{}Nodes:{} {}/{} {}ready,run{}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['nodes'].get('run,ready', 0), cluster_information['nodes'].get('total', 0), ansiprint.green(), ansiprint.end()) for state, count in cluster_information['nodes'].items(): if state == 'total' or state == 'run,ready': continue nodes_string += ' {}/{} {}{}{}'.format(count, cluster_information['nodes']['total'], ansiprint.yellow(), state, ansiprint.end()) ainformation.append('') ainformation.append(nodes_string) vms_string = '{}VMs:{} {}/{} {}start{}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['vms'].get('start', 0), cluster_information['vms'].get('total', 0), ansiprint.green(), ansiprint.end()) for state, count in cluster_information['vms'].items(): if state == 'total' or state == 'start': continue if state in ['disable', 'migrate', 'unmigrate', 'provision']: colour = ansiprint.blue() else: colour = ansiprint.yellow() vms_string += ' {}/{} {}{}{}'.format(count, cluster_information['vms']['total'], colour, state, ansiprint.end()) ainformation.append('') ainformation.append(vms_string) if cluster_information['osds']['total'] > 0: osds_string = '{}Ceph OSDs:{} {}/{} {}up,in{}'.format(ansiprint.purple(), ansiprint.end(), cluster_information['osds'].get('up,in', 0), cluster_information['osds'].get('total', 0), ansiprint.green(), ansiprint.end()) for state, count in cluster_information['osds'].items(): if state == 'total' or state == 'up,in': continue osds_string += ' {}/{} {}{}{}'.format(count, cluster_information['osds']['total'], ansiprint.yellow(), state, ansiprint.end()) ainformation.append('') ainformation.append(osds_string) ainformation.append('') return '\n'.join(ainformation)