Implement log viewing/following with API

This commit is contained in:
Joshua Boniface 2019-12-25 19:52:15 -05:00
parent b9fe918d7d
commit a9aeb2e836
2 changed files with 148 additions and 65 deletions

View File

@ -20,26 +20,64 @@
# #
############################################################################### ###############################################################################
import difflib import time
import colorama import subprocess
import click import click
import requests
from collections import deque from collections import deque
import client_lib.ansiprint as ansiprint import cli_lib.ansiprint as ansiprint
import client_lib.zkhandler as zkhandler import cli_lib.ceph as ceph
import client_lib.common as common
import client_lib.ceph as ceph def get_request_uri(config, endpoint):
"""
Return the fully-formed URI for {endpoint}
"""
uri = '{}://{}{}{}'.format(
config['api_scheme'],
config['api_host'],
config['api_prefix'],
endpoint
)
return uri
def get_console_log(zk_conn, domain, lines=1000): #
# Validate that VM exists in cluster # Primary functions
dom_uuid = getDomainUUID(zk_conn, domain) #
if not dom_uuid: def view_console_log(config, vm, lines=100):
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain) """
Return console log lines from the API and display them in a pager
# Get the data from ZK API endpoint: GET /vm/{vm}/console
console_log = zkhandler.readdata(zk_conn, '/domains/{}/consolelog'.format(dom_uuid)) API arguments: lines={lines}
API schema: {"name":"{vmname}","data":"{console_log}"}
"""
request_uri = get_request_uri(config, '/vm/{vm}/console'.format(vm=vm))
if config['debug']:
print(
'API endpoint: GET {}'.format(request_uri)
)
# Get the data from the API
response = requests.get(
request_uri,
params={'lines': lines}
)
if config['debug']:
print(
'Response code: {}'.format(
response.status_code
)
)
print(
'Response headers: {}'.format(
response.headers
)
)
console_log = response.json()['data']
# Shrink the log buffer to length lines # Shrink the log buffer to length lines
shrunk_log = console_log.split('\n')[-lines:] shrunk_log = console_log.split('\n')[-lines:]
@ -50,18 +88,44 @@ def get_console_log(zk_conn, domain, lines=1000):
pager = subprocess.Popen(['less', '-R'], stdin=subprocess.PIPE) pager = subprocess.Popen(['less', '-R'], stdin=subprocess.PIPE)
pager.communicate(input=loglines.encode('utf8')) pager.communicate(input=loglines.encode('utf8'))
except FileNotFoundError: except FileNotFoundError:
return False, 'ERROR: The "less" pager is required to view console logs.' click.echo("Error: `less` pager not found, dumping log ({} lines) to stdout".format(lines))
return True, loglines
return True, '' return True, ''
def follow_console_log(zk_conn, domain, lines=10): def follow_console_log(config, vm, lines=10):
# Validate that VM exists in cluster """
dom_uuid = getDomainUUID(zk_conn, domain) Return and follow console log lines from the API
if not dom_uuid:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Get the initial data from ZK API endpoint: GET /vm/{vm}/console
console_log = zkhandler.readdata(zk_conn, '/domains/{}/consolelog'.format(dom_uuid)) API arguments: lines={lines}
API schema: {"name":"{vmname}","data":"{console_log}"}
"""
request_uri = get_request_uri(config, '/vm/{vm}/console'.format(vm=vm))
if config['debug']:
print(
'API endpoint: GET {}'.format(request_uri)
)
# Get the (initial) data from the API
response = requests.get(
request_uri,
params={'lines': lines}
)
if config['debug']:
print(
'Response code: {}'.format(
response.status_code
)
)
print(
'Response headers: {}'.format(
response.headers
)
)
console_log = response.json()['data']
# Shrink the log buffer to length lines # Shrink the log buffer to length lines
shrunk_log = console_log.split('\n')[-lines:] shrunk_log = console_log.split('\n')[-lines:]
@ -70,10 +134,32 @@ def follow_console_log(zk_conn, domain, lines=10):
# Print the initial data and begin following # Print the initial data and begin following
print(loglines, end='') print(loglines, end='')
try:
while True: while True:
# Grab the next line set # Grab the next line set
new_console_log = zkhandler.readdata(zk_conn, '/domains/{}/consolelog'.format(dom_uuid)) # Get the (initial) data from the API
response = requests.get(
'{}://{}{}{}'.format(
config['api_scheme'],
config['api_host'],
config['api_prefix'],
'/vm/{}/console'.format(vm)
),
params={'lines': lines}
)
if config['debug']:
print(
'Response code: {}'.format(
response.status_code
)
)
print(
'Response headers: {}'.format(
response.headers
)
)
new_console_log = response.json()['data']
# Split the new and old log strings into constitutent lines # Split the new and old log strings into constitutent lines
old_console_loglines = console_log.split('\n') old_console_loglines = console_log.split('\n')
new_console_loglines = new_console_log.split('\n') new_console_loglines = new_console_log.split('\n')
@ -95,14 +181,13 @@ def follow_console_log(zk_conn, domain, lines=10):
print(diff_console_log, end='') print(diff_console_log, end='')
# Wait a second # Wait a second
time.sleep(1) time.sleep(1)
except kazoo.exceptions.NoNodeError:
return False, 'ERROR: VM has gone away.'
except:
return False, 'ERROR: Lost connection to Zookeeper node.'
return True, '' return True, ''
def format_info(zk_conn, domain_information, long_output): #
# Output display functions
#
def format_info(config, domain_information, long_output):
# Format a nice output; do this line-by-line then concat the elements at the end # Format a nice output; do this line-by-line then concat the elements at the end
ainformation = [] ainformation = []
ainformation.append('{}Virtual machine information:{}'.format(ansiprint.bold(), ansiprint.end())) ainformation.append('{}Virtual machine information:{}'.format(ansiprint.bold(), ansiprint.end()))
@ -211,7 +296,7 @@ def format_info(zk_conn, domain_information, long_output):
click.echo('') click.echo('')
def format_list(zk_conn, vm_list, raw): def format_list(config, vm_list, raw):
# Function to strip the "br" off of nets and return a nicer list # Function to strip the "br" off of nets and return a nicer list
def getNiceNetID(domain_information): def getNiceNetID(domain_information):
# Network list # Network list

View File

@ -32,7 +32,6 @@ import yaml
import requests import requests
import cli_lib.ansiprint as ansiprint import cli_lib.ansiprint as ansiprint
import cli_lib.common as pvc_common
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
import cli_lib.vm as pvc_vm import cli_lib.vm as pvc_vm
@ -42,11 +41,15 @@ import cli_lib.ceph as pvc_ceph
myhostname = socket.gethostname().split('.')[0] myhostname = socket.gethostname().split('.')[0]
zk_host = '' zk_host = ''
config = dict()
config['debug'] = True
config['api_scheme'] = 'http'
config['api_host'] = 'localhost:7370'
config['api_prefix'] = '/api/v1'
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=120) CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=120)
def cleanup(retcode, retmsg, zk_conn=None): def cleanup(retcode, retmsg):
if zk_conn:
pvc_common.stopZKConnection(zk_conn)
if retcode == True: if retcode == True:
if retmsg != '': if retmsg != '':
click.echo(retmsg) click.echo(retmsg)
@ -647,7 +650,7 @@ def vm_info(domain, long_output):
'domain' 'domain'
) )
@click.option( @click.option(
'-l', '--lines', 'lines', default=1000, show_default=True, '-l', '--lines', 'lines', default=100, show_default=True,
help='Display this many log lines from the end of the log buffer.' help='Display this many log lines from the end of the log buffer.'
) )
@click.option( @click.option(
@ -659,16 +662,11 @@ def vm_log(domain, lines, follow):
Show console logs of virtual machine DOMAIN on its current node in the 'less' pager or continuously. DOMAIN may be a UUID or name. Note that migrating a VM to a different node will cause the log buffer to be overwritten by entries from the new node. Show console logs of virtual machine DOMAIN on its current node in the 'less' pager or continuously. DOMAIN may be a UUID or name. Note that migrating a VM to a different node will cause the log buffer to be overwritten by entries from the new node.
""" """
# Open a Zookeeper connection
zk_conn = pvc_common.startZKConnection(zk_host)
if follow: if follow:
# Handle the "new" default of the follow retcode, retmsg = pvc_vm.follow_console_log(config, domain, lines)
if lines == 1000:
lines = 10
retcode, retmsg = pvc_vm.follow_console_log(zk_conn, domain, lines)
else: else:
retcode, retmsg = pvc_vm.get_console_log(zk_conn, domain, lines) retcode, retmsg = pvc_vm.view_console_log(config, domain, lines)
cleanup(retcode, retmsg, zk_conn) cleanup(retcode, retmsg)
############################################################################### ###############################################################################
# pvc vm list # pvc vm list