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,39 +134,60 @@ 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 # Get the (initial) data from the API
new_console_log = zkhandler.readdata(zk_conn, '/domains/{}/consolelog'.format(dom_uuid)) response = requests.get(
# Split the new and old log strings into constitutent lines '{}://{}{}{}'.format(
old_console_loglines = console_log.split('\n') config['api_scheme'],
new_console_loglines = new_console_log.split('\n') config['api_host'],
# Set the console log to the new log value for the next iteration config['api_prefix'],
console_log = new_console_log '/vm/{}/console'.format(vm)
# Remove the lines from the old log until we hit the first line of the new log; this ),
# ensures that the old log is a string that we can remove from the new log entirely params={'lines': lines}
for index, line in enumerate(old_console_loglines, start=0): )
if line == new_console_loglines[0]:
del old_console_loglines[0:index] if config['debug']:
break print(
# Rejoin the log lines into strings 'Response code: {}'.format(
old_console_log = '\n'.join(old_console_loglines) response.status_code
new_console_log = '\n'.join(new_console_loglines) )
# Remove the old lines from the new log )
diff_console_log = new_console_log.replace(old_console_log, "") print(
# If there's a difference, print it out 'Response headers: {}'.format(
if diff_console_log: response.headers
print(diff_console_log, end='') )
# Wait a second )
time.sleep(1)
except kazoo.exceptions.NoNodeError: new_console_log = response.json()['data']
return False, 'ERROR: VM has gone away.' # Split the new and old log strings into constitutent lines
except: old_console_loglines = console_log.split('\n')
return False, 'ERROR: Lost connection to Zookeeper node.' new_console_loglines = new_console_log.split('\n')
# Set the console log to the new log value for the next iteration
console_log = new_console_log
# Remove the lines from the old log until we hit the first line of the new log; this
# ensures that the old log is a string that we can remove from the new log entirely
for index, line in enumerate(old_console_loglines, start=0):
if line == new_console_loglines[0]:
del old_console_loglines[0:index]
break
# Rejoin the log lines into strings
old_console_log = '\n'.join(old_console_loglines)
new_console_log = '\n'.join(new_console_loglines)
# Remove the old lines from the new log
diff_console_log = new_console_log.replace(old_console_log, "")
# If there's a difference, print it out
if diff_console_log:
print(diff_console_log, end='')
# Wait a second
time.sleep(1)
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