2019-12-25 14:10:23 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
# cluster.py - PVC CLI client function library, cluster management
|
|
|
|
# Part of the Parallel Virtual Cluster (PVC) system
|
|
|
|
#
|
2021-03-25 17:01:55 -04:00
|
|
|
# Copyright (C) 2018-2021 Joshua M. Boniface <joshua@boniface.me>
|
2019-12-25 14:10:23 -05:00
|
|
|
#
|
|
|
|
# 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
|
2021-03-25 16:57:17 -04:00
|
|
|
# the Free Software Foundation, version 3.
|
2019-12-25 14:10:23 -05:00
|
|
|
#
|
|
|
|
# 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 <https://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
import cli_lib.ansiprint as ansiprint
|
2020-01-08 19:34:24 -05:00
|
|
|
from cli_lib.common import call_api
|
2019-12-29 20:33:51 -05:00
|
|
|
|
2020-11-07 14:45:24 -05:00
|
|
|
|
2021-05-30 23:59:17 -04:00
|
|
|
def initialize(config, overwrite=False):
|
2019-12-29 20:33:51 -05:00
|
|
|
"""
|
|
|
|
Initialize the PVC cluster
|
|
|
|
|
|
|
|
API endpoint: GET /api/v1/initialize
|
2021-05-30 23:59:17 -04:00
|
|
|
API arguments: overwrite, yes-i-really-mean-it
|
2020-11-24 02:39:06 -05:00
|
|
|
API schema: {json_data_object}
|
|
|
|
"""
|
|
|
|
params = {
|
2021-05-30 23:59:17 -04:00
|
|
|
'yes-i-really-mean-it': 'yes',
|
|
|
|
'overwrite': overwrite
|
2020-11-24 02:39:06 -05:00
|
|
|
}
|
|
|
|
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
|
2019-12-29 20:33:51 -05:00
|
|
|
API arguments:
|
|
|
|
API schema: {json_data_object}
|
|
|
|
"""
|
2020-11-24 02:39:06 -05:00
|
|
|
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)
|
2019-12-29 20:33:51 -05:00
|
|
|
|
|
|
|
if response.status_code == 200:
|
|
|
|
retstatus = True
|
|
|
|
else:
|
|
|
|
retstatus = False
|
|
|
|
|
2020-07-20 12:30:53 -04:00
|
|
|
return retstatus, response.json().get('message', '')
|
2019-12-29 20:33:51 -05:00
|
|
|
|
2020-11-07 14:45:24 -05:00
|
|
|
|
2020-01-09 10:53:27 -05:00
|
|
|
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
|
|
|
|
|
2020-07-20 12:30:53 -04:00
|
|
|
return retstatus, response.json().get('message', '')
|
2020-01-09 10:53:27 -05:00
|
|
|
|
2020-11-07 14:45:24 -05:00
|
|
|
|
2019-12-29 20:33:51 -05:00
|
|
|
def get_info(config):
|
|
|
|
"""
|
|
|
|
Get status of the PVC cluster
|
|
|
|
|
|
|
|
API endpoint: GET /api/v1/status
|
|
|
|
API arguments:
|
|
|
|
API schema: {json_data_object}
|
|
|
|
"""
|
2020-01-08 19:34:24 -05:00
|
|
|
response = call_api(config, 'get', '/status')
|
2019-12-29 20:33:51 -05:00
|
|
|
|
|
|
|
if response.status_code == 200:
|
|
|
|
return True, response.json()
|
|
|
|
else:
|
2020-07-20 12:30:53 -04:00
|
|
|
return False, response.json().get('message', '')
|
2019-12-29 20:33:51 -05:00
|
|
|
|
2020-11-07 14:45:24 -05:00
|
|
|
|
2019-12-25 14:10:23 -05:00
|
|
|
def format_info(cluster_information, oformat):
|
|
|
|
if oformat == 'json':
|
2020-01-05 13:19:21 -05:00
|
|
|
return json.dumps(cluster_information)
|
2019-12-25 14:10:23 -05:00
|
|
|
|
|
|
|
if oformat == 'json-pretty':
|
2020-01-05 13:19:21 -05:00
|
|
|
return json.dumps(cluster_information, indent=4)
|
2019-12-25 14:10:23 -05:00
|
|
|
|
|
|
|
# Plain formatting, i.e. human-readable
|
|
|
|
if cluster_information['health'] == 'Optimal':
|
|
|
|
health_colour = ansiprint.green()
|
2020-01-09 10:53:27 -05:00
|
|
|
elif cluster_information['health'] == 'Maintenance':
|
|
|
|
health_colour = ansiprint.blue()
|
2019-12-25 14:10:23 -05:00
|
|
|
else:
|
|
|
|
health_colour = ansiprint.yellow()
|
|
|
|
|
2020-08-13 15:06:19 -04:00
|
|
|
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()
|
|
|
|
|
2019-12-25 14:10:23 -05:00
|
|
|
ainformation = []
|
2020-11-17 12:32:16 -05:00
|
|
|
|
|
|
|
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))
|
2020-11-17 12:37:33 -05:00
|
|
|
|
2020-11-17 12:32:16 -05:00
|
|
|
return '\n'.join(ainformation)
|
|
|
|
|
2019-12-25 14:10:23 -05:00
|
|
|
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()))
|
2020-08-14 12:27:13 -04:00
|
|
|
if cluster_information['health_msg']:
|
|
|
|
for line in cluster_information['health_msg']:
|
2020-11-07 14:58:13 -05:00
|
|
|
ainformation.append(' > {}'.format(line))
|
2020-08-13 15:06:19 -04:00
|
|
|
ainformation.append('{}Storage health:{} {}{}{}'.format(ansiprint.purple(), ansiprint.end(), storage_health_colour, cluster_information['storage_health'], ansiprint.end()))
|
2020-08-14 12:27:13 -04:00
|
|
|
if cluster_information['storage_health_msg']:
|
|
|
|
for line in cluster_information['storage_health_msg']:
|
2020-11-07 14:58:13 -05:00
|
|
|
ainformation.append(' > {}'.format(line))
|
2020-11-17 12:32:16 -05:00
|
|
|
|
2020-08-13 15:06:19 -04:00
|
|
|
ainformation.append('')
|
2019-12-25 14:10:23 -05:00
|
|
|
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']))
|
|
|
|
|
2020-01-06 11:56:34 -05:00
|
|
|
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())
|
2019-12-25 14:10:23 -05:00
|
|
|
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)
|
|
|
|
|
2020-01-06 11:56:34 -05:00
|
|
|
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())
|
2019-12-25 14:10:23 -05:00
|
|
|
for state, count in cluster_information['vms'].items():
|
|
|
|
if state == 'total' or state == 'start':
|
|
|
|
continue
|
|
|
|
|
2020-01-08 17:40:02 -05:00
|
|
|
if state in ['disable', 'migrate', 'unmigrate', 'provision']:
|
2019-12-25 14:10:23 -05:00
|
|
|
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:
|
2020-01-06 11:56:34 -05:00
|
|
|
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())
|
2019-12-25 14:10:23 -05:00
|
|
|
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)
|
|
|
|
|
2020-01-05 12:35:00 -05:00
|
|
|
ainformation.append('')
|
|
|
|
return '\n'.join(ainformation)
|