diff --git a/cli-client/pvc.py b/cli-client/pvc.py index 4de48bd0..08699195 100755 --- a/cli-client/pvc.py +++ b/cli-client/pvc.py @@ -25,6 +25,7 @@ import click import client_lib.common as pvc_common import client_lib.node as pvc_node +import client_lib.router as pvc_router import client_lib.vm as pvc_vm import client_lib.network as pvc_network @@ -140,6 +141,85 @@ def node_list(limit): retcode, retmsg = pvc_node.get_list(zk_conn, limit) cleanup(retcode, retmsg, zk_conn) +############################################################################### +# pvc router +############################################################################### +@click.group(name='router', short_help='Manage a PVC router.', context_settings=CONTEXT_SETTINGS) +def cli_router(): + """ + Manage the state of a router in the PVC cluster. + """ + pass + + +############################################################################### +# pvc router secondary +############################################################################### +@click.command(name='secondary', short_help='Set a router in secondary status.') +@click.argument( + 'router' +) +def router_secondary(router): + """ + Take ROUTER out of primary mode handling gateways and into secondary mode. + """ + + zk_conn = pvc_common.startZKConnection(zk_host) + retcode, retmsg = pvc_router.secondary_router(zk_conn, router, wait) + cleanup(retcode, retmsg, zk_conn) + +############################################################################### +# pvc router primary +############################################################################### +@click.command(name='primary', short_help='Set a router in primary status.') +@click.argument( + 'router' +) +def router_primary(router): + """ + Put ROUTER into primary mode handling gateways. + """ + + zk_conn = pvc_common.startZKConnection(zk_host) + retcode, retmsg = pvc_router.primary_router(zk_conn, router) + cleanup(retcode, retmsg, zk_conn) + +############################################################################### +# pvc router info +############################################################################### +@click.command(name='info', short_help='Show details of a router object.') +@click.argument( + 'router' +) +@click.option( + '-l', '--long', 'long_output', is_flag=True, default=False, + help='Display more detailed information.' +) +def router_info(router, long_output): + """ + Show information about router ROUTER. + """ + + zk_conn = pvc_common.startZKConnection(zk_host) + retcode, retmsg = pvc_router.get_info(router, long_output) + cleanup(retcode, retmsg, zk_conn) + +############################################################################### +# pvc router list +############################################################################### +@click.command(name='list', short_help='List all router objects.') +@click.argument( + 'limit', default=None, required=False +) +def router_list(limit): + """ + List all routers in the cluster; optionally only match names matching regex LIMIT. + """ + + zk_conn = pvc_common.startZKConnection(zk_host) + retcode, retmsg = pvc_router.get_list(zk_conn, limit) + cleanup(retcode, retmsg, zk_conn) + ############################################################################### # pvc vm ############################################################################### @@ -703,6 +783,11 @@ cli_node.add_command(node_unflush) cli_node.add_command(node_info) cli_node.add_command(node_list) +cli_router.add_command(router_secondary) +cli_router.add_command(router_primary) +cli_router.add_command(router_info) +cli_router.add_command(router_list) + cli_vm.add_command(vm_define) cli_vm.add_command(vm_modify) cli_vm.add_command(vm_undefine) @@ -723,6 +808,7 @@ cli_network.add_command(net_info) cli_network.add_command(net_list) cli.add_command(cli_node) +cli.add_command(cli_router) cli.add_command(cli_vm) cli.add_command(cli_network) cli.add_command(init_cluster) diff --git a/client-common/client_lib/common.py b/client-common/client_lib/common.py index 94404019..1fd14077 100644 --- a/client-common/client_lib/common.py +++ b/client-common/client_lib/common.py @@ -173,6 +173,16 @@ def verifyNode(zk_conn, node): return True except: return False +# +# Verify router is valid in cluster +# +def verifyRouter(zk_conn, router): + try: + zk_conn.get('/routers/{}'.format(router)) + return True + except: + return False + # # Get the list of valid target hypervisors diff --git a/client-common/client_lib/router.py b/client-common/client_lib/router.py new file mode 100644 index 00000000..ad559d6e --- /dev/null +++ b/client-common/client_lib/router.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 + +# router.py - PVC client function library, router management +# Part of the Parallel Virtual Cluster (PVC) system +# +# Copyright (C) 2018 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, either version 3 of the License, or +# (at your option) any later version. +# +# 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 os +import socket +import time +import uuid +import re +import tempfile +import subprocess +import difflib +import colorama +import click +import lxml.objectify +import configparser +import kazoo.client + +import client_lib.ansiiprint as ansiiprint +import client_lib.zkhandler as zkhandler +import client_lib.common as common + +def getInformationFromRouter(zk_conn, router_name, long_output): + router_daemon_state = zk_conn.get('/routers/{}/daemonstate'.format(router_name))[0].decode('ascii') + router_network_state = zk_conn.get('/routers/{}/networkstate'.format(router_name))[0].decode('ascii') + router_cpu_count = zk_conn.get('/routers/{}/staticdata'.format(router_name))[0].decode('ascii').split()[0] + router_cpu_load = zk_conn.get('/routers/{}/cpuload'.format(router_name))[0].decode('ascii').split()[0] + router_kernel = zk_conn.get('/routers/{}/staticdata'.format(router_name))[0].decode('ascii').split()[1] + router_os = zk_conn.get('/routers/{}/staticdata'.format(router_name))[0].decode('ascii').split()[2] + router_arch = zk_conn.get('/routers/{}/staticdata'.format(router_name))[0].decode('ascii').split()[3] + + if router_daemon_state == 'run': + daemon_state_colour = ansiiprint.green() + elif router_daemon_state == 'stop': + daemon_state_colour = ansiiprint.red() + elif router_daemon_state == 'init': + daemon_state_colour = ansiiprint.yellow() + elif router_daemon_state == 'dead': + daemon_state_colour = ansiiprint.red() + ansiiprint.bold() + else: + daemon_state_colour = ansiiprint.blue() + + if router_network_state == 'primary': + network_state_colour = ansiiprint.green() + else: + network_state_colour = ansiiprint.blue() + + # Format a nice output; do this line-by-line then concat the elements at the end + ainformation = [] + ainformation.append('{}Hypervisor Router information:{}'.format(ansiiprint.bold(), ansiiprint.end())) + ainformation.append('') + # Basic information + ainformation.append('{}Name:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), router_name)) + ainformation.append('{}Daemon State:{} {}{}{}'.format(ansiiprint.purple(), ansiiprint.end(), daemon_state_colour, router_daemon_state, ansiiprint.end())) + ainformation.append('{}Domain State:{} {}{}{}'.format(ansiiprint.purple(), ansiiprint.end(), network_state_colour, router_network_state, ansiiprint.end())) + ainformation.append('{}CPUs:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), router_cpu_count)) + ainformation.append('{}Load:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), router_cpu_load)) + if long_output == True: + ainformation.append('') + ainformation.append('{}Architecture:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), router_arch)) + ainformation.append('{}Operating System:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), router_os)) + ainformation.append('{}Kernel Version:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), router_kernel)) + + # Join it all together + information = '\n'.join(ainformation) + return information + +# +# Direct Functions +# +def secondary_router(zk_conn, router): + # Verify router is valid + if not common.verifyRouter(zk_conn, router): + return False, 'ERROR: No router named "{}" is present in the cluster.'.format(router) + + click.echo('Setting router {} in secondary mode.'.format(router)) + zkhandler.writedata(zk_conn, { '/routers/{}/networkstate'.format(router): 'secondary' }) + return True, '' + +def primary_router(zk_conn, router): + # Verify router is valid + if not common.verifyRouter(zk_conn, router): + return False, 'ERROR: No router named "{}" is present in the cluster.'.format(router) + + click.echo('Setting router {} in primary mode.'.format(router)) + zkhandler.writedata(zk_conn, { '/routers/{}/networkstate'.format(router): 'primary' }) + return True, '' + +def get_info(zk_conn, router, long_output): + # Verify router is valid + if not common.verifyRouter(zk_conn, router): + return False, 'ERROR: No router named "{}" is present in the cluster.'.format(router) + + # Get information about router in a pretty format + information = getInformationFromRouter(zk_conn, router, long_output) + click.echo(information) + return True, '' + +def get_list(zk_conn, limit): + # Match our limit + router_list = [] + full_router_list = zk_conn.get_children('/routers') + for router in full_router_list: + if limit != None: + try: + if re.match(limit, router) == None: + continue + except Exception as e: + common.stopZKConnection(zk_conn) + return False, 'Regex Error: {}'.format(e) + router_list.append(router) + + router_list_output = [] + router_daemon_state = {} + router_network_state = {} + router_cpu_count = {} + router_cpu_load = {} + + # Gather information for printing + for router_name in router_list: + router_daemon_state[router_name] = zk_conn.get('/routers/{}/daemonstate'.format(router_name))[0].decode('ascii') + router_network_state[router_name] = zk_conn.get('/routers/{}/networkstate'.format(router_name))[0].decode('ascii') + router_cpu_count[router_name] = zk_conn.get('/routers/{}/staticdata'.format(router_name))[0].decode('ascii').split()[0] + router_cpu_load[router_name] = zk_conn.get('/routers/{}/cpuload'.format(router_name))[0].decode('ascii').split()[0] + + # Determine optimal column widths + # Dynamic columns: router_name + router_name_length = 0 + for router_name in router_list: + # router_name column + _router_name_length = len(router_name) + 1 + if _router_name_length > router_name_length: + router_name_length = _router_name_length + + # Format the string (header) + router_list_output.append( + '{bold}{router_name: <{router_name_length}} \ +State: {daemon_state_colour}{router_daemon_state: <7}{end_colour} {network_state_colour}{router_network_state: <10}{end_colour} \ +Resources: {router_cpu_count: <5} {router_cpu_load: <6}{end_bold}'.format( + router_name_length=router_name_length, + bold=ansiiprint.bold(), + end_bold=ansiiprint.end(), + daemon_state_colour='', + network_state_colour='', + end_colour='', + router_name='Name', + router_daemon_state='Daemon', + router_network_state='Network', + router_cpu_count='CPUs', + router_cpu_load='Load' + ) + ) + + # Format the string (elements) + for router_name in router_list: + if router_daemon_state[router_name] == 'run': + daemon_state_colour = ansiiprint.green() + elif router_daemon_state[router_name] == 'stop': + daemon_state_colour = ansiiprint.red() + elif router_daemon_state[router_name] == 'init': + daemon_state_colour = ansiiprint.yellow() + elif router_daemon_state[router_name] == 'dead': + daemon_state_colour = ansiiprint.red() + ansiiprint.bold() + else: + daemon_state_colour = ansiiprint.blue() + + if router_network_state[router_name] == 'primary': + network_state_colour = ansiiprint.green() + else: + network_state_colour = ansiiprint.blue() + + router_list_output.append( + '{bold}{router_name: <{router_name_length}} \ + {daemon_state_colour}{router_daemon_state: <7}{end_colour} {network_state_colour}{router_network_state: <10}{end_colour} \ + {router_cpu_count: <5} {router_cpu_load: <6}{end_bold}'.format( + router_name_length=router_name_length, + bold='', + end_bold='', + daemon_state_colour=daemon_state_colour, + network_state_colour=network_state_colour, + end_colour=ansiiprint.end(), + router_name=router_name, + router_daemon_state=router_daemon_state[router_name], + router_network_state=router_network_state[router_name], + router_cpu_count=router_cpu_count[router_name], + router_cpu_load=router_cpu_load[router_name] + ) + ) + + click.echo('\n'.join(sorted(router_list_output))) + + return True, ''