Add support for multiple clusters in CLI
This commit is contained in:
parent
18c14cbf77
commit
18e6192178
|
@ -29,9 +29,12 @@ import difflib
|
||||||
import re
|
import re
|
||||||
import colorama
|
import colorama
|
||||||
import yaml
|
import yaml
|
||||||
|
import json
|
||||||
import lxml.etree as etree
|
import lxml.etree as etree
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from distutils.util import strtobool
|
||||||
|
|
||||||
import cli_lib.ansiprint as ansiprint
|
import cli_lib.ansiprint as ansiprint
|
||||||
import cli_lib.cluster as pvc_cluster
|
import cli_lib.cluster as pvc_cluster
|
||||||
import cli_lib.node as pvc_node
|
import cli_lib.node as pvc_node
|
||||||
|
@ -42,11 +45,81 @@ import cli_lib.ceph as pvc_ceph
|
||||||
myhostname = socket.gethostname().split('.')[0]
|
myhostname = socket.gethostname().split('.')[0]
|
||||||
zk_host = ''
|
zk_host = ''
|
||||||
|
|
||||||
config = dict()
|
default_store_data = {
|
||||||
config['debug'] = False
|
'cfgfile': '/etc/pvc/pvc-api.yaml' # pvc/api/listen_address, pvc/api/listen_port
|
||||||
config['api_scheme'] = 'http'
|
}
|
||||||
config['api_host'] = 'localhost:7370'
|
|
||||||
config['api_prefix'] = '/api/v1'
|
#
|
||||||
|
# Data store handling functions
|
||||||
|
#
|
||||||
|
def read_from_yaml(cfgfile):
|
||||||
|
with open(cfgfile, 'r') as fh:
|
||||||
|
api_config = yaml.load(fh, Loader=yaml.BaseLoader)
|
||||||
|
host = api_config['pvc']['api']['listen_address']
|
||||||
|
port = api_config['pvc']['api']['listen_port']
|
||||||
|
if strtobool(api_config['pvc']['api']['ssl']['enabled']):
|
||||||
|
scheme = 'https'
|
||||||
|
else:
|
||||||
|
scheme = 'http'
|
||||||
|
return host, port, scheme
|
||||||
|
|
||||||
|
def get_config(store_data, cluster=None):
|
||||||
|
# This is generally static
|
||||||
|
prefix = '/api/v1'
|
||||||
|
|
||||||
|
cluster_details = store_data.get(cluster)
|
||||||
|
|
||||||
|
if not cluster_details:
|
||||||
|
cluster_details = default_store_data
|
||||||
|
cluster = 'local'
|
||||||
|
|
||||||
|
if cluster_details.get('cfgfile', None):
|
||||||
|
# This is a reference to an API configuration; grab the details from its listen address
|
||||||
|
cfgfile = cluster_details.get('cfgfile')
|
||||||
|
if os.path.isfile(cfgfile):
|
||||||
|
host, port, scheme = read_from_yaml(cfgfile)
|
||||||
|
else:
|
||||||
|
print('Attempted to load API configuration from a nonexistent file; using defaults')
|
||||||
|
host = 'localhost'
|
||||||
|
port = '7370'
|
||||||
|
scheme = 'http'
|
||||||
|
else:
|
||||||
|
# This is a static configuration, get the raw details
|
||||||
|
host = cluster_details['host']
|
||||||
|
port = cluster_details['port']
|
||||||
|
scheme = cluster_details['scheme']
|
||||||
|
|
||||||
|
config = dict()
|
||||||
|
config['debug'] = False
|
||||||
|
config['cluster'] = cluster
|
||||||
|
config['api_host'] = '{}:{}'.format(host, port)
|
||||||
|
config['api_scheme'] = scheme
|
||||||
|
config['api_prefix'] = prefix
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def get_store(store_path):
|
||||||
|
store_file = '{}/pvc-cli.json'.format(store_path)
|
||||||
|
with open(store_file, 'r') as fh:
|
||||||
|
store_data = json.loads(fh.read())
|
||||||
|
return store_data
|
||||||
|
|
||||||
|
def update_store(store_path, store_data):
|
||||||
|
store_file = '{}/pvc-cli.json'.format(store_path)
|
||||||
|
with open(store_file, 'w') as fh:
|
||||||
|
fh.write(json.dumps(store_data, sort_keys=True, indent=4))
|
||||||
|
|
||||||
|
home_dir = os.environ.get('HOME', None)
|
||||||
|
if home_dir:
|
||||||
|
store_path = '{}/.config/pvc'.format(home_dir)
|
||||||
|
else:
|
||||||
|
print('No home dir found - not permanently saving any configurations as this user')
|
||||||
|
store_path = '/tmp/pvc'
|
||||||
|
|
||||||
|
if not os.path.isdir(store_path):
|
||||||
|
os.makedirs(store_path)
|
||||||
|
if not os.path.isfile(store_path + '/pvc-cli.json'):
|
||||||
|
update_store(store_path, {"local": default_store_data})
|
||||||
|
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=120)
|
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=120)
|
||||||
|
|
||||||
|
@ -60,6 +133,163 @@ def cleanup(retcode, retmsg):
|
||||||
click.echo(retmsg)
|
click.echo(retmsg)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc cluster
|
||||||
|
###############################################################################
|
||||||
|
@click.group(name='cluster', short_help='Manage PVC cluster connections.', context_settings=CONTEXT_SETTINGS)
|
||||||
|
def cli_cluster():
|
||||||
|
"""
|
||||||
|
Manage the PVC clusters this CLI can connect to.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc cluster add
|
||||||
|
###############################################################################
|
||||||
|
@click.command(name='add', short_help='Add a new cluster to the client.')
|
||||||
|
@click.option(
|
||||||
|
'-a', '--address', 'address', required=True,
|
||||||
|
help='The IP address or hostname of the cluster API client.'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-p', '--port', 'port', required=False, default=7370, show_default=True,
|
||||||
|
help='The cluster API client port.'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-s/-S', '--ssl/--no-ssl', 'ssl', is_flag=True, default=False, show_default=True,
|
||||||
|
help='Whether to use SSL or not.'
|
||||||
|
)
|
||||||
|
@click.argument(
|
||||||
|
'name'
|
||||||
|
)
|
||||||
|
def cluster_add(address, port, ssl, name):
|
||||||
|
"""
|
||||||
|
Add a new PVC cluster NAME, via its API connection details, to the configuration of the local CLI client. Replaces any existing cluster with this name.
|
||||||
|
"""
|
||||||
|
if ssl:
|
||||||
|
scheme = 'https'
|
||||||
|
else:
|
||||||
|
scheme = 'http'
|
||||||
|
|
||||||
|
# Get the existing data
|
||||||
|
existing_config = get_store(store_path)
|
||||||
|
# Append our new entry to the end
|
||||||
|
existing_config[name] = {
|
||||||
|
'host': address,
|
||||||
|
'port': port,
|
||||||
|
'scheme': scheme
|
||||||
|
}
|
||||||
|
# Update the store
|
||||||
|
update_store(store_path, existing_config)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc cluster remove
|
||||||
|
###############################################################################
|
||||||
|
@click.command(name='remove', short_help='Remove a cluster from the client.')
|
||||||
|
@click.argument(
|
||||||
|
'name'
|
||||||
|
)
|
||||||
|
def cluster_remove(name):
|
||||||
|
"""
|
||||||
|
Remove a PVC cluster from the configuration of the local CLI client.
|
||||||
|
"""
|
||||||
|
# Get the existing data
|
||||||
|
existing_config = get_store(store_path)
|
||||||
|
# Remove the entry matching the name
|
||||||
|
try:
|
||||||
|
existing_config.pop(name)
|
||||||
|
except KeyError:
|
||||||
|
print('No cluster with name "{}" found'.format(name))
|
||||||
|
# Update the store
|
||||||
|
update_store(store_path, existing_config)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc cluster list
|
||||||
|
###############################################################################
|
||||||
|
@click.command(name='list', short_help='List all available clusters.')
|
||||||
|
def cluster_list():
|
||||||
|
"""
|
||||||
|
List all the available PVC clusters configured in this CLI instance.
|
||||||
|
"""
|
||||||
|
# Get the existing data
|
||||||
|
clusters = get_store(store_path)
|
||||||
|
# Find the lengths of each column
|
||||||
|
name_length = 5
|
||||||
|
address_length = 10
|
||||||
|
port_length = 5
|
||||||
|
scheme_length = 5
|
||||||
|
|
||||||
|
for cluster in clusters:
|
||||||
|
cluster_details = clusters[cluster]
|
||||||
|
if cluster_details.get('cfgfile', None):
|
||||||
|
# This is a reference to an API configuration; grab the details from its listen address
|
||||||
|
cfgfile = cluster_details.get('cfgfile')
|
||||||
|
if os.path.isfile(cfgfile):
|
||||||
|
address, port, scheme = read_from_yaml(cfgfile)
|
||||||
|
else:
|
||||||
|
address = cluster_details.get('host', None)
|
||||||
|
port = cluster_details.get('port', None)
|
||||||
|
scheme = cluster_details.get('scheme', None)
|
||||||
|
|
||||||
|
_name_length = len(cluster) + 1
|
||||||
|
if _name_length > name_length:
|
||||||
|
name_length = _name_length
|
||||||
|
_address_length = len(address) + 1
|
||||||
|
if _address_length > address_length:
|
||||||
|
address_length = _address_length
|
||||||
|
_port_length = len(str(port)) + 1
|
||||||
|
if _port_length > port_length:
|
||||||
|
port_length = _port_length
|
||||||
|
_scheme_length = len(scheme) + 1
|
||||||
|
if _scheme_length > scheme_length:
|
||||||
|
scheme_length = _scheme_length
|
||||||
|
|
||||||
|
# Display the data nicely
|
||||||
|
click.echo("Available clusters:")
|
||||||
|
click.echo()
|
||||||
|
click.echo(
|
||||||
|
'{bold}{name: <{name_length}} {address: <{address_length}} {port: <{port_length}} {scheme: <{scheme_length}}{end_bold}'.format(
|
||||||
|
bold=ansiprint.bold(),
|
||||||
|
end_bold=ansiprint.end(),
|
||||||
|
name="Name",
|
||||||
|
name_length=name_length,
|
||||||
|
address="Address",
|
||||||
|
address_length=address_length,
|
||||||
|
port="Port",
|
||||||
|
port_length=port_length,
|
||||||
|
scheme="Scheme",
|
||||||
|
scheme_length=scheme_length
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for cluster in clusters:
|
||||||
|
cluster_details = clusters[cluster]
|
||||||
|
if cluster_details.get('cfgfile', None):
|
||||||
|
# This is a reference to an API configuration; grab the details from its listen address
|
||||||
|
cfgfile = cluster_details.get('cfgfile')
|
||||||
|
if os.path.isfile(cfgfile):
|
||||||
|
address, port, scheme = read_from_yaml(cfgfile)
|
||||||
|
else:
|
||||||
|
address = cluster_details.get('host', None)
|
||||||
|
port = cluster_details.get('port', None)
|
||||||
|
scheme = cluster_details.get('scheme', None)
|
||||||
|
|
||||||
|
click.echo(
|
||||||
|
'{bold}{name: <{name_length}} {address: <{address_length}} {port: <{port_length}} {scheme: <{scheme_length}}{end_bold}'.format(
|
||||||
|
bold=ansiprint.bold(),
|
||||||
|
end_bold=ansiprint.end(),
|
||||||
|
name=cluster,
|
||||||
|
name_length=name_length,
|
||||||
|
address=address,
|
||||||
|
address_length=address_length,
|
||||||
|
port=port,
|
||||||
|
port_length=port_length,
|
||||||
|
scheme=scheme,
|
||||||
|
scheme_length=scheme_length
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# pvc node
|
# pvc node
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -1669,43 +1899,34 @@ def init_cluster(yes):
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@click.group(context_settings=CONTEXT_SETTINGS)
|
@click.group(context_settings=CONTEXT_SETTINGS)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-z', '--zookeeper', '_zk_host', envvar='PVC_ZOOKEEPER', default=None,
|
'-c', '--cluster', '_cluster', envvar='PVC_CLUSTER', default=None,
|
||||||
help='Zookeeper connection string.'
|
help='Zookeeper connection string.'
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
'-v', '--debug', '_debug', envvar='PVC_DEBUG', is_flag=True, default=False,
|
'-v', '--debug', '_debug', envvar='PVC_DEBUG', is_flag=True, default=False,
|
||||||
help='Additional debug details.'
|
help='Additional debug details.'
|
||||||
)
|
)
|
||||||
def cli(_zk_host, _debug):
|
def cli(_cluster, _debug):
|
||||||
"""
|
"""
|
||||||
Parallel Virtual Cluster CLI management tool
|
Parallel Virtual Cluster CLI management tool
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
|
|
||||||
"PVC_ZOOKEEPER": Set the cluster Zookeeper address instead of using "--zookeeper".
|
"PVC_CLUSTER": Set the cluster to access instead of using --cluster/-c
|
||||||
|
|
||||||
If no PVC_ZOOKEEPER/--zookeeper is specified, attempts to load coordinators list from /etc/pvc/pvcd.yaml.
|
If no PVC_CLUSTER/--cluster is specified, attempts first to load the "local" cluster, checking
|
||||||
|
for an API configuration in "/etc/pvc/pvc-api.yaml". If this is also not found, connection defaults
|
||||||
|
to "http://localhost:7370" as a last restort.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If no zk_host was passed, try to read from /etc/pvc/pvcd.yaml; otherwise fail
|
|
||||||
if _zk_host is None:
|
|
||||||
try:
|
|
||||||
cfgfile = '/etc/pvc/pvcd.yaml'
|
|
||||||
with open(cfgfile) as cfgf:
|
|
||||||
o_config = yaml.load(cfgf)
|
|
||||||
_zk_host = o_config['pvc']['cluster']['coordinators']
|
|
||||||
except:
|
|
||||||
_zk_host = None
|
|
||||||
|
|
||||||
if _zk_host is None:
|
|
||||||
print('ERROR: Must specify a PVC_ZOOKEEPER value or have a coordinator set configured in /etc/pvc/pvcd.yaml.')
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
global zk_host
|
|
||||||
zk_host = _zk_host
|
|
||||||
global config
|
global config
|
||||||
|
store_data = get_store(store_path)
|
||||||
|
config = get_config(store_data, _cluster)
|
||||||
config['debug'] = _debug
|
config['debug'] = _debug
|
||||||
|
click.echo('Using cluster "{}" - Host: "{}" Scheme: "{}" Prefix: "{}"'.format(config['cluster'], config['api_host'], config['api_scheme'], config['api_prefix']))
|
||||||
|
click.echo()
|
||||||
|
|
||||||
|
config = dict()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Click command tree
|
# Click command tree
|
||||||
|
@ -1786,13 +2007,19 @@ cli_ceph.add_command(ceph_volume)
|
||||||
|
|
||||||
cli_storage.add_command(cli_ceph)
|
cli_storage.add_command(cli_ceph)
|
||||||
|
|
||||||
|
cli_cluster.add_command(cluster_add)
|
||||||
|
cli_cluster.add_command(cluster_remove)
|
||||||
|
cli_cluster.add_command(cluster_list)
|
||||||
|
|
||||||
cli.add_command(cli_node)
|
cli.add_command(cli_node)
|
||||||
cli.add_command(cli_vm)
|
cli.add_command(cli_vm)
|
||||||
cli.add_command(cli_network)
|
cli.add_command(cli_network)
|
||||||
cli.add_command(cli_storage)
|
cli.add_command(cli_storage)
|
||||||
|
cli.add_command(cli_cluster)
|
||||||
cli.add_command(status_cluster)
|
cli.add_command(status_cluster)
|
||||||
cli.add_command(init_cluster)
|
cli.add_command(init_cluster)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Main entry point
|
# Main entry point
|
||||||
#
|
#
|
||||||
|
|
Loading…
Reference in New Issue