Also fixes up the Debian packaging such that this works how I would want, with proper module installation while leaving everything else untouched. Finally implements automatic installation and removal of the BASH completion for the PVC command.
227 lines
8.9 KiB
Python
227 lines
8.9 KiB
Python
#!/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 <joshua@boniface.me>
|
|
#
|
|
# 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 <https://www.gnu.org/licenses/>.
|
|
#
|
|
###############################################################################
|
|
|
|
import json
|
|
|
|
import pvc.cli_lib.ansiprint as ansiprint
|
|
from pvc.cli_lib.common import call_api
|
|
|
|
|
|
def initialize(config, overwrite=False):
|
|
"""
|
|
Initialize the PVC cluster
|
|
|
|
API endpoint: GET /api/v1/initialize
|
|
API arguments: overwrite, yes-i-really-mean-it
|
|
API schema: {json_data_object}
|
|
"""
|
|
params = {
|
|
'yes-i-really-mean-it': 'yes',
|
|
'overwrite': overwrite
|
|
}
|
|
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)
|