Implement log viewing/following with API
This commit is contained in:
		| @@ -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 | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user