Implement log viewing/following with API
This commit is contained in:
parent
b9fe918d7d
commit
a9aeb2e836
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue