diff --git a/NodeInstance.py b/NodeInstance.py index 43998660..bae2b79d 100644 --- a/NodeInstance.py +++ b/NodeInstance.py @@ -20,20 +20,10 @@ # ############################################################################### -import os, sys, socket, time, libvirt, kazoo.client, threading, fencenode - -# ANSII colours for output -class bcolours: - PURPLE = '\033[95m' - BLUE = '\033[94m' - GREEN = '\033[92m' - YELLOW = '\033[93m' - RED = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' +import os, sys, socket, time, libvirt, kazoo.client, threading, fencenode, ansiiprint class NodeInstance(): + # Initialization function def __init__(self, name, t_node, s_domain, zk): # Passed-in variables on creation self.zk = zk @@ -98,7 +88,7 @@ class NodeInstance(): # Flush all VMs on the host def flush(self): - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + 'Flushing node {} of running VMs.'.format(self.name)) + ansiiprint.echo('Flushing node "{}" of running VMs'.format(self.name), '', 'i') for dom_uuid in self.domain_list: most_memfree = 0 target_hypervisor = None @@ -115,12 +105,12 @@ class NodeInstance(): target_hypervisor = hypervisor if target_hypervisor == None: - print(bcolours.RED + '>>> ' + bcolours.ENDC + 'Failed to find migration target for VM "{}"; shutting down.'.format(dom_uuid)) + ansiiprint.echo('Failed to find migration target for VM "{}"; shutting down'.format(dom_uuid), '', 'e') transaction = self.zk.transaction() transaction.set_data('/domains/{}/state'.format(dom_uuid), 'shutdown'.encode('ascii')) transaction.commit() else: - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + 'Migrating VM "{}" to hypervisor "{}".'.format(dom_uuid, target_hypervisor)) + ansiiprint.echo('Migrating VM "{}" to hypervisor "{}"'.format(dom_uuid, '', target_hypervisor), 'i') transaction = self.zk.transaction() transaction.set_data('/domains/{}/state'.format(dom_uuid), 'migrate'.encode('ascii')) transaction.set_data('/domains/{}/hypervisor'.format(dom_uuid), target_hypervisor.encode('ascii')) @@ -131,14 +121,14 @@ class NodeInstance(): time.sleep(1) def unflush(self): - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + 'Restoring node {} to active service.'.format(self.name)) + ansiiprint.echo('Restoring node {} to active service.'.format(self.name), '', 'i') self.zk.set('/nodes/{}/state'.format(self.name), 'start'.encode('ascii')) for dom_uuid in self.s_domain: last_hypervisor = self.zk.get('/domains/{}/lasthypervisor'.format(dom_uuid))[0].decode('ascii') if last_hypervisor != self.name: continue - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + 'Setting unmigration for VM "{}".'.format(dom_uuid)) + ansiiprint.echo('Setting unmigration for VM "{}"'.format(dom_uuid), '', 'i') transaction = self.zk.transaction() transaction.set_data('/domains/{}/state'.format(dom_uuid), 'migrate'.encode('ascii')) transaction.set_data('/domains/{}/hypervisor'.format(dom_uuid), self.name.encode('ascii')) @@ -153,7 +143,7 @@ class NodeInstance(): libvirt_name = "qemu:///system" conn = libvirt.open(libvirt_name) if conn == None: - print(bcolours.RED + '>>> ' + bcolours.ENDC + 'Failed to open connection to {}'.format(libvirt_name)) + ansiiprint.echo('Failed to open connection to "{}"'.format(libvirt_name), '', 'e') return # Get past state and update if needed @@ -204,9 +194,9 @@ class NodeInstance(): conn.close() # Display node information to the terminal - print(bcolours.PURPLE + '>>> ' + bcolours.ENDC + '{} - {} keepalive'.format(time.strftime('%d/%m/%Y %H:%M:%S'), self.name)) - print(' CPUs: {} | Free memory: {} | Load: {}'.format(self.cpucount, self.memfree, self.cpuload)) - print(' Active domains: {}'.format(' '.join(self.domain_list))) + ansiiprint.echo('{} keepalive'.format(self.name), '', 't') + ansiiprint.echo('CPUs: {} | Free memory: {} | Load: {}'.format(self.cpucount, self.memfree, self.cpuload), '', 'c') + ansiiprint.echo('Active domains: {}'.format(' '.join(self.domain_list)), '', 'c') # Update our local node lists for node_name in self.t_node: @@ -221,7 +211,7 @@ class NodeInstance(): # (A node is considered dead when its keepalive timer is >30s out-of-date while in 'start' state) node_deadtime = int(time.time()) - 30 if node_keepalive < node_deadtime and node_state == 'start': - print(bcolours.RED + '>>> ' + bcolours.ENDC + 'Node {} is dead! Performing fence operation in 3 seconds.'.format(node_name)) + ansiiprint.echo('Node {} is dead - performing fence operation in 3 seconds'.format(node_name), '', 'w') self.zk.set('/nodes/{}/state'.format(node_name), 'dead'.encode('ascii')) fence_thread = threading.Thread(target=fencenode.fence, args=(node_name, self.zk), kwargs={}) fence_thread.start() @@ -259,7 +249,7 @@ class NodeInstance(): pass # Display cluster information to the terminal - print(bcolours.PURPLE + '>>> ' + bcolours.ENDC + '{} - Cluster status'.format(time.strftime('%d/%m/%Y %H:%M:%S'))) - print(' Active nodes: {}'.format(' '.join(self.active_node_list))) - print(' Flushed nodes: {}'.format(' '.join(self.flushed_node_list))) - print(' Inactive nodes: {}'.format(' '.join(self.inactive_node_list))) + ansiiprint.echo('Cluster status', '', 't') + ansiiprint.echo('Active nodes: {}'.format(' '.join(self.active_node_list)), '', 'c') + ansiiprint.echo('Flushed nodes: {}'.format(' '.join(self.flushed_node_list)), '', 'c') + ansiiprint.echo('Inactive nodes: {}'.format(' '.join(self.inactive_node_list)), '', 'c') diff --git a/VMInstance.py b/VMInstance.py index a03b228d..83e28fe6 100644 --- a/VMInstance.py +++ b/VMInstance.py @@ -20,20 +20,10 @@ # ############################################################################### -import os, sys, uuid, socket, time, threading, libvirt, kazoo.client - -# ANSII colours for output -class bcolours: - PURPLE = '\033[95m' - BLUE = '\033[94m' - GREEN = '\033[92m' - YELLOW = '\033[93m' - RED = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' - UNDERLINE = '\033[4m' +import os, sys, uuid, socket, time, threading, libvirt, kazoo.client, ansiiprint class VMInstance: + # Initialization function def __init__(self, domuuid, zk, thishypervisor): # Passed-in variables on creation self.domuuid = domuuid @@ -49,7 +39,7 @@ class VMInstance: self.inmigrate = False self.inreceive = False - self.dom = lookupByUUID(self.domuuid) + self.dom = self.lookupByUUID(self.domuuid) # Watch for changes to the hypervisor field in Zookeeper @zk.DataWatch('/domains/{}/hypervisor'.format(self.domuuid)) @@ -78,54 +68,54 @@ class VMInstance: # Start up the VM def start_vm(self, xmlconfig): - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + '{} - Starting VM.'.format(self.domuuid)) + ansiiprint.echo('Starting VM', '{}:'.format(self.domuuid), 'i') self.instart = True # Start up a new Libvirt connection libvirt_name = "qemu:///system" conn = libvirt.open(libvirt_name) if conn == None: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Failed to open local libvirt connection.'.format(self.domuuid)) + ansiiprint.echo('Failed to open local libvirt connection', '{}:'.format(self.domuuid), 'e') self.instart = False return try: dom = conn.createXML(xmlconfig, 0) except libvirt.libvirtError as e: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Failed to create VM.'.format(self.domuuid)) + ansiiprint.echo('Failed to create VM', '{}:'.format(self.domuuid), 'e') self.zk.set('/domains/{}/state'.format(self.domuuid), 'stop'.encode('ascii')) if not self.domuuid in self.thishypervisor.domain_list: self.thishypervisor.domain_list.append(self.domuuid) conn.close() - print(bcolours.GREEN + '>>> ' + bcolours.ENDC + '{} - Successfully started VM.'.format(self.domuuid)) + ansiiprint.echo('Successfully started VM', '{}:'.format(self.domuuid), 'o') self.dom = dom self.instart = False # Stop the VM forcibly without updating state def terminate_vm(self): - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + '{} - Terminating VM.'.format(self.domuuid)) + ansiiprint.echo('Terminating VM', '{}:'.format(self.domuuid), 'i') self.instop = True try: self.dom.destroy() except AttributeError: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Failed to terminate VM.'.format(self.domuuid)) + ansiiprint.echo('Failed to terminate VM', '{}:'.format(self.domuuid), 'e') if self.domuuid in self.thishypervisor.domain_list: try: self.thishypervisor.domain_list.remove(self.domuuid) except ValueError: pass - print(bcolours.GREEN + '>>> ' + bcolours.ENDC + '{} - Successfully terminated VM.'.format(self.domuuid)) + ansiiprint.echo('Successfully terminated VM', '{}:'.format(self.domuuid), 'o') # Stop the VM forcibly def stop_vm(self): - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + '{} - Forcibly stopping VM.'.format(self.domuuid)) + ansiiprint.echo('Forcibly stopping VM', '{}:'.format(self.domuuid), 'i') self.instop = True try: self.dom.destroy() except AttributeError: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Failed to stop VM.'.format(self.domuuid)) + ansiiprint.echo('Failed to stop VM', '{}:'.format(self.domuuid), 'e') if self.domuuid in self.thishypervisor.domain_list: try: self.thishypervisor.domain_list.remove(self.domuuid) @@ -133,13 +123,13 @@ class VMInstance: pass self.zk.set('/domains/{}/state'.format(self.domuuid), 'stop'.encode('ascii')) - print(bcolours.GREEN + '>>> ' + bcolours.ENDC + '{} - Successfully stopped VM.'.format(self.domuuid)) + ansiiprint.echo('Successfully stopped VM', '{}:'.format(self.domuuid), 'o') self.dom = None self.instop = False # Shutdown the VM gracefully def shutdown_vm(self): - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + '{} - Gracefully stopping VM.'.format(self.domuuid)) + ansiiprint.echo('Gracefully stopping VM', '{}:'.format(self.domuuid), 'i') self.inshutdown = True self.dom.shutdown() try: @@ -149,7 +139,7 @@ class VMInstance: time.sleep(0.5) if tick >= 60: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Shutdown timeout expired.'.format(self.domuuid)) + ansiiprint.echo('Shutdown timeout expired', '{}:'.format(self.domuuid), 'e') self.stop_vm() self.inshutdown = False return @@ -163,7 +153,7 @@ class VMInstance: pass self.zk.set('/domains/{}/state'.format(self.domuuid), 'stop'.encode('ascii')) - print(bcolours.GREEN + '>>> ' + bcolours.ENDC + '{} - Successfully shutdown VM.'.format(self.domuuid)) + ansiiprint.echo('Successfully shutdown VM', '{}:'.format(self.domuuid), 'o') self.dom = None self.inshutdown = False @@ -173,14 +163,15 @@ class VMInstance: if dest_conn == None: raise except: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Failed to open connection to qemu+tcp://{}/system; aborting migration.'.format(self.dom_uuid, self.hypervisor)) + ansiiprint.echo('Failed to open connection to qemu+tcp://{}/system; aborting migration.'.format(self.hypervisor), '{}:'.format(self.domuuid), 'e') return 1 try: target_dom = self.dom.migrate(dest_conn, libvirt.VIR_MIGRATE_LIVE, None, None, 0) if target_dom == None: raise - print(bcolours.GREEN + '>>> ' + bcolours.ENDC + '{} - Migrated successfully.'.format(self.domuuid)) + ansiiprint.echo('Successfully migrated VM', '{}:'.format(self.domuuid), 'o') + except: dest_conn.close() return 1 @@ -192,10 +183,10 @@ class VMInstance: def migrate_vm(self): self.inmigrate = True - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + '{} - Migrating VM to hypervisor "{}".'.format(self.domuuid, self.hypervisor)) + ansiiprint.echo('Migrating VM to hypervisor "{}"'.format(self.hypervisor), '{}:'.format(self.domuuid), 'i') migrate_ret = self.live_migrate_vm(self.hypervisor) if migrate_ret != 0: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Could not live migrate VM; shutting down to migrate instead.'.format(self.domuuid)) + ansiiprint.echo('Could not live migrate VM; shutting down to migrate instead', '{}:'.format(self.domuuid), 'e') self.shutdown_vm() time.sleep(1) self.zk.set('/domains/{}/state'.format(self.domuuid), 'start'.encode('ascii')) @@ -209,7 +200,7 @@ class VMInstance: # Receive the migration from another host (wait until VM is running) def receive_migrate(self): - print(bcolours.BLUE + '>>> ' + bcolours.ENDC + '{} - Receiving migration.'.format(self.domuuid)) + ansiiprint.echo('Receiving migration', '{}:'.format(self.domuuid), 'i') self.inreceive = True while True: self.dom = lookupByUUID(self.domuuid) @@ -224,7 +215,7 @@ class VMInstance: if not self.domuuid in self.thishypervisor.domain_list: self.thishypervisor.domain_list.append(self.domuuid) - print(bcolours.GREEN + '>>> ' + bcolours.ENDC + '{} - Migrated successfully.'.format(self.domuuid)) + ansiiprint.echo('Successfully migrated VM', '{}:'.format(self.domuuid), 'o') self.inreceive = False # @@ -273,37 +264,37 @@ class VMInstance: self.start_vm(domxml) -# This function is a wrapper for libvirt.lookupByUUID which fixes some problems -# 1. Takes a text UUID and handles converting it to bytes -# 2. Try's it and returns a sensible value if not -def lookupByUUID(tuuid): - conn = None - dom = None - libvirt_name = "qemu:///system" - - # Convert the text UUID to bytes - buuid = uuid.UUID(tuuid).bytes - - # Try - try: - # Open a libvirt connection - conn = libvirt.open(libvirt_name) - if conn == None: - print(bcolours.RED + '>>> ' + bcolours.ENDC + '{} - Failed to open local libvirt connection.'.format(self.domuuid)) - return dom + # This function is a wrapper for libvirt.lookupByUUID which fixes some problems + # 1. Takes a text UUID and handles converting it to bytes + # 2. Try's it and returns a sensible value if not + def lookupByUUID(self, tuuid): + conn = None + dom = None + libvirt_name = "qemu:///system" - # Lookup the UUID - dom = conn.lookupByUUID(buuid) - - # Fail - except: - pass - - # After everything - finally: - # Close the libvirt connection - if conn != None: - conn.close() - - # Return the dom object (or None) - return dom + # Convert the text UUID to bytes + buuid = uuid.UUID(tuuid).bytes + + # Try + try: + # Open a libvirt connection + conn = libvirt.open(libvirt_name) + if conn == None: + ansiiprint.echo('Failed to open local libvirt connection', '{}:'.format(self.domuuid), 'e') + return dom + + # Lookup the UUID + dom = conn.lookupByUUID(buuid) + + # Fail + except: + pass + + # After everything + finally: + # Close the libvirt connection + if conn != None: + conn.close() + + # Return the dom object (or None) + return dom diff --git a/ansiiprint.py b/ansiiprint.py new file mode 100644 index 00000000..34b28b77 --- /dev/null +++ b/ansiiprint.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +# ansiprint.py - Printing function for formatted daemon messages +# 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, sys, socket, time, libvirt, kazoo.client, threading, fencenode, ansiiprint + +# Print function +def echo(message, prefix, state): + date = '{} - '.format(time.strftime('%Y/%m/%d %H:%M:%S')) + # Continuation + if state == 'c': + date = '' + colour = '' + prompt = ' ' + # OK + elif state == 'o': + colour = '\033[92m' # Green + prompt = '>>> ' + # Error + elif state == 'e': + colour = '\033[91m' # Red + prompt = '>>> ' + # Warning + elif state == 'w': + colour = '\033[93m' # Yellow + prompt = '>>> ' + # Tick + elif state == 't': + colour = '\033[95m' # Purple + prompt = '>>> ' + # Information + elif state == 'i': + colour = '\033[94m' # Blue + prompt = '>>> ' + else: + colour = '\033[1m' # Bold + prompt = '>>> ' + end = '\033[0m' + print(colour + prompt + end + date + prefix + '{}'.format(message)) diff --git a/pvcd.py b/pvcd.py index f77282d0..a97d9659 100755 --- a/pvcd.py +++ b/pvcd.py @@ -30,6 +30,7 @@ import NodeInstance import time import atexit import apscheduler.schedulers.background +import ansiiprint # ANSII colours for output class bcolours: