Refactor CLI client to split out actual functions for reuse

This commit is contained in:
Joshua Boniface 2018-09-20 03:25:58 -04:00
parent 378fcccbe5
commit c9258edcad
8 changed files with 3142 additions and 1263 deletions

1
cli-client/lib Symbolic link
View File

@ -0,0 +1 @@
../client-common/lib

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
#!/usr/bin/env python3
# ansiprint.py - Printing function for formatted messages
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# 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 <https://www.gnu.org/licenses/>.
#
###############################################################################
import datetime
# ANSII colours for output
def red():
return '\033[91m'
def blue():
return '\033[94m'
def green():
return '\033[92m'
def yellow():
return '\033[93m'
def purple():
return '\033[95m'
def bold():
return '\033[1m'
def end():
return '\033[0m'
# Print function
def echo(message, prefix, state):
# Get the date
date = '{} - '.format(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S.%f'))
endc = end()
# Continuation
if state == 'c':
date = ''
colour = ''
prompt = ' '
# OK
elif state == 'o':
colour = green()
prompt = '>>> '
# Error
elif state == 'e':
colour = red()
prompt = '>>> '
# Warning
elif state == 'w':
colour = yellow()
prompt = '>>> '
# Tick
elif state == 't':
colour = purple()
prompt = '>>> '
# Information
elif state == 'i':
colour = blue()
prompt = '>>> '
else:
colour = bold()
prompt = '>>> '
# Append space to prefix
if prefix != '':
prefix = prefix + ' '
print(colour + prompt + endc + date + prefix + message)

254
client-common/lib/common.py Normal file
View File

@ -0,0 +1,254 @@
#!/usr/bin/env python3
# common.py - PVC client function library, common fuctions
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# 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 <https://www.gnu.org/licenses/>.
#
###############################################################################
import uuid
import lxml
import kazoo.client
###############################################################################
# Supplemental functions
###############################################################################
#
# Validate a UUID
#
def validateUUID(dom_uuid):
try:
uuid.UUID(dom_uuid)
return True
except:
return False
#
# Connect and disconnect from Zookeeper
#
def startZKConnection(zk_host):
zk_conn = kazoo.client.KazooClient(hosts=zk_host)
zk_conn.start()
return zk_conn
def stopZKConnection(zk_conn):
zk_conn.stop()
zk_conn.close()
return 0
#
# Parse a Domain XML object
#
def getDomainXML(zk_conn, dom_uuid):
try:
xml = zk_conn.get('/domains/%s/xml' % dom_uuid)[0].decode('ascii')
except:
return None
# Parse XML using lxml.objectify
parsed_xml = lxml.objectify.fromstring(xml)
return parsed_xml
#
# Get the main details for a VM object from XML
#
def getDomainMainDetails(parsed_xml):
# Get the information we want from it
duuid = str(parsed_xml.uuid)
try:
ddescription = str(parsed_xml.description)
except AttributeError:
ddescription = "N/A"
dname = str(parsed_xml.name)
dmemory = str(parsed_xml.memory)
dmemory_unit = str(parsed_xml.memory.attrib['unit'])
if dmemory_unit == 'KiB':
dmemory = str(int(dmemory) * 1024)
elif dmemory_unit == 'GiB':
dmemory = str(int(dmemory) / 1024)
dvcpu = str(parsed_xml.vcpu)
try:
dvcputopo = '{}/{}/{}'.format(parsed_xml.cpu.topology.attrib['sockets'], parsed_xml.cpu.topology.attrib['cores'], parsed_xml.cpu.topology.attrib['threads'])
except:
dvcputopo = 'N/A'
return duuid, dname, ddescription, dmemory, dvcpu, dvcputopo
#
# Get long-format details
#
def getDomainExtraDetails(parsed_xml):
dtype = parsed_xml.os.type
darch = parsed_xml.os.type.attrib['arch']
dmachine = parsed_xml.os.type.attrib['machine']
dconsole = parsed_xml.devices.console.attrib['type']
demulator = parsed_xml.devices.emulator
return dtype, darch, dmachine, dconsole, demulator
#
# Get CPU features
#
def getDomainCPUFeatures(parsed_xml):
dfeatures = []
for feature in parsed_xml.features.getchildren():
dfeatures.append(feature.tag)
return dfeatures
#
# Get disk devices
#
def getDomainDisks(parsed_xml):
ddisks = []
for device in parsed_xml.devices.getchildren():
if device.tag == 'disk':
disk_attrib = device.source.attrib
disk_target = device.target.attrib
disk_type = device.attrib['type']
if disk_type == 'network':
disk_obj = { 'type': disk_attrib.get('protocol'), 'name': disk_attrib.get('name'), 'dev': disk_target.get('dev'), 'bus': disk_target.get('bus') }
elif disk_type == 'file':
disk_obj = { 'type': 'file', 'name': disk_attrib.get('file'), 'dev': disk_target.get('dev'), 'bus': disk_target.get('bus') }
else:
disk_obj = {}
ddisks.append(disk_obj)
return ddisks
#
# Get network devices
#
def getDomainNetworks(parsed_xml):
dnets = []
for device in parsed_xml.devices.getchildren():
if device.tag == 'interface':
net_type = device.attrib['type']
net_mac = device.mac.attrib['address']
net_bridge = device.source.attrib[net_type]
net_model = device.model.attrib['type']
net_obj = { 'type': net_type, 'mac': net_mac, 'source': net_bridge, 'model': net_model }
dnets.append(net_obj)
return dnets
#
# Get controller devices
#
def getDomainControllers(parsed_xml):
dcontrollers = []
for device in parsed_xml.devices.getchildren():
if device.tag == 'controller':
controller_type = device.attrib['type']
try:
controller_model = device.attrib['model']
except KeyError:
controller_model = 'none'
controller_obj = { 'type': controller_type, 'model': controller_model }
dcontrollers.append(controller_obj)
return dcontrollers
#
# Verify node is valid in cluster
#
def verifyNode(zk_conn, node):
try:
zk_conn.get('/nodes/{}'.format(node))
except:
click.echo('ERROR: No node named "{}" is present in the cluster.'.format(node))
exit(1)
#
# Find a migration target
#
def findTargetHypervisor(zk_conn, search_field, dom_uuid):
if search_field == 'mem':
return findTargetHypervisorMem(zk_conn, dom_uuid)
if search_field == 'load':
return findTargetHypervisorLoad(zk_conn, dom_uuid)
if search_field == 'vcpus':
return findTargetHypervisorVCPUs(zk_conn, dom_uuid)
if search_field == 'vms':
return findTargetHypervisorVMs(zk_conn, dom_uuid)
return None
# via free memory (relative to allocated memory)
def findTargetHypervisorMem(zk_conn, dom_uuid):
most_allocfree = 0
target_hypervisor = None
hypervisor_list = getHypervisors(zk_conn, dom_uuid)
for hypervisor in hypervisor_list:
memalloc = int(zk_conn.get('/nodes/{}/memalloc'.format(hypervisor))[0].decode('ascii'))
memused = int(zk_conn.get('/nodes/{}/memused'.format(hypervisor))[0].decode('ascii'))
memfree = int(zk_conn.get('/nodes/{}/memfree'.format(hypervisor))[0].decode('ascii'))
memtotal = memused + memfree
allocfree = memtotal - memalloc
if allocfree > most_allocfree:
most_allocfree = allocfree
target_hypervisor = hypervisor
return target_hypervisor
# via load average
def findTargetHypervisorLoad(zk_conn, dom_uuid):
least_load = 9999
target_hypervisor = None
hypervisor_list = getHypervisors(zk_conn, dom_uuid)
for hypervisor in hypervisor_list:
load = float(zk_conn.get('/nodes/{}/cpuload'.format(hypervisor))[0].decode('ascii'))
if load < least_load:
least_load = load
target_hypervisor = hypervisor
return target_hypervisor
# via total vCPUs
def findTargetHypervisorVCPUs(zk_conn, dom_uuid):
least_vcpus = 9999
target_hypervisor = None
hypervisor_list = getHypervisors(zk_conn, dom_uuid)
for hypervisor in hypervisor_list:
vcpus = int(zk_conn.get('/nodes/{}/vcpualloc'.format(hypervisor))[0].decode('ascii'))
if vcpus < least_vcpus:
least_vcpus = vcpus
target_hypervisor = hypervisor
return target_hypervisor
# via total VMs
def findTargetHypervisorVMs(zk_conn, dom_uuid):
least_vms = 9999
target_hypervisor = None
hypervisor_list = getHypervisors(zk_conn, dom_uuid)
for hypervisor in hypervisor_list:
vms = int(zk_conn.get('/nodes/{}/domainscount'.format(hypervisor))[0].decode('ascii'))
if vms < least_vms:
least_vms = vms
target_hypervisor = hypervisor
return target_hypervisor

1717
client-common/lib/network.py Normal file

File diff suppressed because it is too large Load Diff

315
client-common/lib/node.py Normal file
View File

@ -0,0 +1,315 @@
#!/usr/bin/env python3
# node.py - PVC client function library, node management
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# 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 <https://www.gnu.org/licenses/>.
#
###############################################################################
import os
import socket
import time
import uuid
import re
import tempfile
import subprocess
import difflib
import colorama
import click
import lxml.objectify
import configparser
import kazoo.client
import lib.ansiiprint as ansiiprint
import lib.common as common
def getInformationFromNode(zk_conn, node_name, long_output):
node_daemon_state = zk_conn.get('/nodes/{}/daemonstate'.format(node_name))[0].decode('ascii')
node_domain_state = zk_conn.get('/nodes/{}/domainstate'.format(node_name))[0].decode('ascii')
node_cpu_count = zk_conn.get('/nodes/{}/staticdata'.format(node_name))[0].decode('ascii').split()[0]
node_kernel = zk_conn.get('/nodes/{}/staticdata'.format(node_name))[0].decode('ascii').split()[1]
node_os = zk_conn.get('/nodes/{}/staticdata'.format(node_name))[0].decode('ascii').split()[2]
node_arch = zk_conn.get('/nodes/{}/staticdata'.format(node_name))[0].decode('ascii').split()[3]
node_mem_used = zk_conn.get('/nodes/{}/memused'.format(node_name))[0].decode('ascii')
node_mem_free = zk_conn.get('/nodes/{}/memfree'.format(node_name))[0].decode('ascii')
node_mem_total = int(node_mem_used) + int(node_mem_free)
node_load = zk_conn.get('/nodes/{}/cpuload'.format(node_name))[0].decode('ascii')
node_domains_count = zk_conn.get('/nodes/{}/domainscount'.format(node_name))[0].decode('ascii')
node_running_domains = zk_conn.get('/nodes/{}/runningdomains'.format(node_name))[0].decode('ascii').split()
node_mem_allocated = 0
for domain in node_running_domains:
try:
parsed_xml = common.getDomainXML(zk_conn, domain)
duuid, dname, ddescription, dmemory, dvcpu, dvcputopo = common.getDomainMainDetails(parsed_xml)
node_mem_allocated += int(dmemory)
except AttributeError:
click.echo('Error: Domain {} does not exist.'.format(domain))
if node_daemon_state == 'run':
daemon_state_colour = ansiiprint.green()
elif node_daemon_state == 'stop':
daemon_state_colour = ansiiprint.red()
elif node_daemon_state == 'init':
daemon_state_colour = ansiiprint.yellow()
elif node_daemon_state == 'dead':
daemon_state_colour = ansiiprint.red() + ansiiprint.bold()
else:
daemon_state_colour = ansiiprint.blue()
if node_domain_state == 'ready':
domain_state_colour = ansiiprint.green()
else:
domain_state_colour = ansiiprint.blue()
# Format a nice output; do this line-by-line then concat the elements at the end
ainformation = []
ainformation.append('{}Hypervisor Node information:{}'.format(ansiiprint.bold(), ansiiprint.end()))
ainformation.append('')
# Basic information
ainformation.append('{}Name:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_name))
ainformation.append('{}Daemon State:{} {}{}{}'.format(ansiiprint.purple(), ansiiprint.end(), daemon_state_colour, node_daemon_state, ansiiprint.end()))
ainformation.append('{}Domain State:{} {}{}{}'.format(ansiiprint.purple(), ansiiprint.end(), domain_state_colour, node_domain_state, ansiiprint.end()))
ainformation.append('{}Active VM Count:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_domains_count))
if long_output == True:
ainformation.append('')
ainformation.append('{}Architecture:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_arch))
ainformation.append('{}Operating System:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_os))
ainformation.append('{}Kernel Version:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_kernel))
ainformation.append('')
ainformation.append('{}CPUs:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_cpu_count))
ainformation.append('{}Load:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_load))
ainformation.append('{}Total RAM (MiB):{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_mem_total))
ainformation.append('{}Used RAM (MiB):{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_mem_used))
ainformation.append('{}Free RAM (MiB):{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_mem_free))
ainformation.append('{}Allocated RAM (MiB):{} {}'.format(ansiiprint.purple(), ansiiprint.end(), node_mem_allocated))
# Join it all together
information = '\n'.join(ainformation)
return information
# Get the list of valid target hypervisors
def getHypervisors(zk_conn, dom_uuid):
valid_hypervisor_list = []
full_hypervisor_list = zk_conn.get_children('/nodes')
try:
current_hypervisor = zk_conn.get('/domains/{}/hypervisor'.format(dom_uuid))[0].decode('ascii')
except:
current_hypervisor = None
for hypervisor in full_hypervisor_list:
daemon_state = zk_conn.get('/nodes/{}/daemonstate'.format(hypervisor))[0].decode('ascii')
domain_state = zk_conn.get('/nodes/{}/domainstate'.format(hypervisor))[0].decode('ascii')
if hypervisor == current_hypervisor:
continue
if daemon_state != 'run' or domain_state != 'ready':
continue
valid_hypervisor_list.append(hypervisor)
return valid_hypervisor_list
#
# Direct Functions
#
def flush_node(zk_conn, node, wait):
# Verify node is valid
verifyNode(zk_conn, node)
click.echo('Flushing hypervisor {} of running VMs.'.format(node))
# Add the new domain to Zookeeper
transaction = zk_conn.transaction()
transaction.set_data('/nodes/{}/domainstate'.format(node), 'flush'.encode('ascii'))
results = transaction.commit()
if wait == True:
while True:
time.sleep(1)
node_state = zk_conn.get('/nodes/{}/domainstate'.format(node))[0].decode('ascii')
if node_state == "flushed":
break
return True, ''
def ready_node(zk_conn, node):
# Verify node is valid
verifyNode(zk_conn, node)
click.echo('Restoring hypervisor {} to active service.'.format(node))
# Add the new domain to Zookeeper
transaction = zk_conn.transaction()
transaction.set_data('/nodes/{}/domainstate'.format(node), 'unflush'.encode('ascii'))
results = transaction.commit()
return True, ''
def get_info(zk_conn, node, long_output):
# Verify node is valid
verifyNode(zk_conn, node)
# Get information about node in a pretty format
information = getInformationFromNode(zk_conn, node, long_output)
if information == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find a node matching that name.'
click.echo(information)
if long_output == True:
click.echo('')
click.echo('{}Virtual machines on node:{}'.format(ansiiprint.bold(), ansiiprint.end()))
# List all VMs on this node
common.get_list(zk_conn, node, None)
click.echo('')
return True, ''
def get_list(zk_conn, limit):
# Match our limit
node_list = []
full_node_list = zk_conn.get_children('/nodes')
for node in full_node_list:
if limit != None:
try:
if re.match(limit, node) == None:
continue
except Exception as e:
common.stopZKConnection(zk_conn)
return False, 'Regex Error: {}'.format(e)
node_list.append(node)
node_list_output = []
node_daemon_state = {}
node_daemon_state = {}
node_domain_state = {}
node_cpu_count = {}
node_mem_used = {}
node_mem_free = {}
node_mem_total = {}
node_domains_count = {}
node_running_domains = {}
node_mem_allocated = {}
node_load = {}
# Gather information for printing
for node_name in node_list:
node_daemon_state[node_name] = zk_conn.get('/nodes/{}/daemonstate'.format(node_name))[0].decode('ascii')
node_domain_state[node_name] = zk_conn.get('/nodes/{}/domainstate'.format(node_name))[0].decode('ascii')
node_cpu_count[node_name] = zk_conn.get('/nodes/{}/staticdata'.format(node_name))[0].decode('ascii').split()[0]
node_mem_used[node_name] = zk_conn.get('/nodes/{}/memused'.format(node_name))[0].decode('ascii')
node_mem_free[node_name] = zk_conn.get('/nodes/{}/memfree'.format(node_name))[0].decode('ascii')
node_mem_total[node_name] = int(node_mem_used[node_name]) + int(node_mem_free[node_name])
node_load[node_name] = zk_conn.get('/nodes/{}/cpuload'.format(node_name))[0].decode('ascii')
node_domains_count[node_name] = zk_conn.get('/nodes/{}/domainscount'.format(node_name))[0].decode('ascii')
node_running_domains[node_name] = zk_conn.get('/nodes/{}/runningdomains'.format(node_name))[0].decode('ascii').split()
node_mem_allocated[node_name] = 0
for domain in node_running_domains[node_name]:
try:
parsed_xml = common.getDomainXML(zk_conn, domain)
duuid, dname, ddescription, dmemory, dvcpu, dvcputopo = common.getDomainMainDetails(parsed_xml)
node_mem_allocated[node_name] += int(dmemory)
except AttributeError:
click.echo('Error: Domain {} does not exist.'.format(domain))
# Determine optimal column widths
# Dynamic columns: node_name, hypervisor, migrated
node_name_length = 0
for node_name in node_list:
# node_name column
_node_name_length = len(node_name) + 1
if _node_name_length > node_name_length:
node_name_length = _node_name_length
# Format the string (header)
node_list_output.append(
'{bold}{node_name: <{node_name_length}} \
State: {daemon_state_colour}{node_daemon_state: <7}{end_colour} {domain_state_colour}{node_domain_state: <8}{end_colour} \
Resources: {node_domains_count: <4} {node_cpu_count: <5} {node_load: <6} \
RAM (MiB): {node_mem_total: <6} {node_mem_used: <6} {node_mem_free: <6} {node_mem_allocated: <6}{end_bold}'.format(
node_name_length=node_name_length,
bold=ansiiprint.bold(),
end_bold=ansiiprint.end(),
daemon_state_colour='',
domain_state_colour='',
end_colour='',
node_name='Name',
node_daemon_state='Daemon',
node_domain_state='Domains',
node_domains_count='VMs',
node_cpu_count='CPUs',
node_load='Load',
node_mem_total='Total',
node_mem_used='Used',
node_mem_free='Free',
node_mem_allocated='VMs',
)
)
# Format the string (elements)
for node_name in node_list:
if node_daemon_state[node_name] == 'run':
daemon_state_colour = ansiiprint.green()
elif node_daemon_state[node_name] == 'stop':
daemon_state_colour = ansiiprint.red()
elif node_daemon_state[node_name] == 'init':
daemon_state_colour = ansiiprint.yellow()
elif node_daemon_state[node_name] == 'dead':
daemon_state_colour = ansiiprint.red() + ansiiprint.bold()
else:
daemon_state_colour = ansiiprint.blue()
if node_mem_allocated[node_name] >= node_mem_total[node_name]:
node_domain_state[node_name] = 'overprov'
domain_state_colour = ansiiprint.yellow()
elif node_domain_state[node_name] == 'ready':
domain_state_colour = ansiiprint.green()
else:
domain_state_colour = ansiiprint.blue()
node_list_output.append(
'{bold}{node_name: <{node_name_length}} \
{daemon_state_colour}{node_daemon_state: <7}{end_colour} {domain_state_colour}{node_domain_state: <8}{end_colour} \
{node_domains_count: <4} {node_cpu_count: <5} {node_load: <6} \
{node_mem_total: <6} {node_mem_used: <6} {node_mem_free: <6} {node_mem_allocated: <6}{end_bold}'.format(
node_name_length=node_name_length,
bold='',
end_bold='',
daemon_state_colour=daemon_state_colour,
domain_state_colour=domain_state_colour,
end_colour=ansiiprint.end(),
node_name=node_name,
node_daemon_state=node_daemon_state[node_name],
node_domain_state=node_domain_state[node_name],
node_domains_count=node_domains_count[node_name],
node_cpu_count=node_cpu_count[node_name],
node_load=node_load[node_name],
node_mem_total=node_mem_total[node_name],
node_mem_used=node_mem_used[node_name],
node_mem_free=node_mem_free[node_name],
node_mem_allocated=node_mem_allocated[node_name]
)
)
click.echo('\n'.join(sorted(node_list_output)))
return True, ''

607
client-common/lib/vm.py Normal file
View File

@ -0,0 +1,607 @@
#!/usr/bin/env python3
# vm.py - PVC client function library, VM fuctions
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# 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 <https://www.gnu.org/licenses/>.
#
###############################################################################
import os
import socket
import time
import uuid
import re
import tempfile
import subprocess
import difflib
import colorama
import click
import lxml.objectify
import configparser
import kazoo.client
import lib.ansiiprint as ansiiprint
import lib.common as common
#
# XML information parsing functions
#
def getInformationFromXML(zk_conn, uuid, long_output):
# Obtain the contents of the XML from Zookeeper
try:
dstate = zk_conn.get('/domains/{}/state'.format(uuid))[0].decode('ascii')
dhypervisor = zk_conn.get('/domains/{}/hypervisor'.format(uuid))[0].decode('ascii')
dlasthypervisor = zk_conn.get('/domains/{}/lasthypervisor'.format(uuid))[0].decode('ascii')
except:
return None
if dlasthypervisor == '':
dlasthypervisor = 'N/A'
try:
parsed_xml = common.getDomainXML(zk_conn, uuid)
duuid, dname, ddescription, dmemory, dvcpu, dvcputopo = common.getDomainMainDetails(parsed_xml)
except AttributeError:
click.echo('Error: Domain {} does not exist.'.format(domain))
if long_output == True:
dtype, darch, dmachine, dconsole, demulator = common.getDomainExtraDetails(parsed_xml)
dfeatures = common.getDomainCPUFeatures(parsed_xml)
ddisks = common.getDomainDisks(parsed_xml)
dnets = common.getDomainNetworks(parsed_xml)
dcontrollers = common.getDomainControllers(parsed_xml)
# Format a nice output; do this line-by-line then concat the elements at the end
ainformation = []
ainformation.append('{}Virtual machine information:{}'.format(ansiiprint.bold(), ansiiprint.end()))
ainformation.append('')
# Basic information
ainformation.append('{}UUID:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), duuid))
ainformation.append('{}Name:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dname))
ainformation.append('{}Description:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), ddescription))
ainformation.append('{}Memory (MiB):{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dmemory))
ainformation.append('{}vCPUs:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dvcpu))
ainformation.append('{}Topology (S/C/T):{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dvcputopo))
if long_output == True:
# Virtualization information
ainformation.append('')
ainformation.append('{}Emulator:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), demulator))
ainformation.append('{}Type:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dtype))
ainformation.append('{}Arch:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), darch))
ainformation.append('{}Machine:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dmachine))
ainformation.append('{}Features:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), ' '.join(dfeatures)))
# PVC cluster information
ainformation.append('')
dstate_colour = {
'start': ansiiprint.green(),
'restart': ansiiprint.yellow(),
'shutdown': ansiiprint.yellow(),
'stop': ansiiprint.red(),
'failed': ansiiprint.red(),
'migrate': ansiiprint.blue(),
'unmigrate': ansiiprint.blue()
}
ainformation.append('{}State:{} {}{}{}'.format(ansiiprint.purple(), ansiiprint.end(), dstate_colour[dstate], dstate, ansiiprint.end()))
ainformation.append('{}Active Hypervisor:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dhypervisor))
ainformation.append('{}Last Hypervisor:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), dlasthypervisor))
if long_output == True:
# Disk list
ainformation.append('')
name_length = 0
for disk in ddisks:
_name_length = len(disk['name']) + 1
if _name_length > name_length:
name_length = _name_length
ainformation.append('{0}Disks:{1} {2}ID Type {3: <{width}} Dev Bus{4}'.format(ansiiprint.purple(), ansiiprint.end(), ansiiprint.bold(), 'Name', ansiiprint.end(), width=name_length))
for disk in ddisks:
ainformation.append(' {0: <3} {1: <5} {2: <{width}} {3: <4} {4: <5}'.format(ddisks.index(disk), disk['type'], disk['name'], disk['dev'], disk['bus'], width=name_length))
# Network list
ainformation.append('')
ainformation.append('{}Interfaces:{} {}ID Type Source Model MAC{}'.format(ansiiprint.purple(), ansiiprint.end(), ansiiprint.bold(), ansiiprint.end()))
for net in dnets:
ainformation.append(' {0: <3} {1: <8} {2: <10} {3: <8} {4}'.format(dnets.index(net), net['type'], net['source'], net['model'], net['mac']))
# Controller list
ainformation.append('')
ainformation.append('{}Controllers:{} {}ID Type Model{}'.format(ansiiprint.purple(), ansiiprint.end(), ansiiprint.bold(), ansiiprint.end()))
for controller in dcontrollers:
ainformation.append(' {0: <3} {1: <14} {2: <8}'.format(dcontrollers.index(controller), controller['type'], controller['model']))
# Join it all together
information = '\n'.join(ainformation)
return information
#
# Cluster search functions
#
def getClusterDomainList(zk_conn):
# Get a list of UUIDs by listing the children of /domains
uuid_list = zk_conn.get_children('/domains')
name_list = []
# For each UUID, get the corresponding name from the data
for uuid in uuid_list:
name_list.append(zk_conn.get('/domains/%s' % uuid)[0].decode('ascii'))
return uuid_list, name_list
def searchClusterByUUID(zk_conn, uuid):
try:
# Get the lists
uuid_list, name_list = getClusterDomainList(zk_conn)
# We're looking for UUID, so find that element ID
index = uuid_list.index(uuid)
# Get the name_list element at that index
name = name_list[index]
except ValueError:
# We didn't find anything
return None
return name
def searchClusterByName(zk_conn, name):
try:
# Get the lists
uuid_list, name_list = getClusterDomainList(zk_conn)
# We're looking for name, so find that element ID
index = name_list.index(name)
# Get the uuid_list element at that index
uuid = uuid_list[index]
except ValueError:
# We didn't find anything
return None
return uuid
def getDomainUUID(zk_conn, domain):
# Validate and obtain alternate passed value
if common.validateUUID(domain):
dom_name = searchClusterByUUID(zk_conn, domain)
dom_uuid = searchClusterByName(zk_conn, dom_name)
else:
dom_uuid = searchClusterByName(zk_conn, domain)
dom_name = searchClusterByUUID(zk_conn, dom_uuid)
return dom_uuid
#
# Direct functions
#
def define_vm(zk_conn, config_data, target_hypervisor, selector):
# Parse the XML data
parsed_xml = lxml.objectify.fromstring(data)
dom_uuid = parsed_xml.uuid.text
dom_name = parsed_xml.name.text
click.echo('Adding new VM with Name "{}" and UUID "{}" to database.'.format(dom_name, dom_uuid))
if target_hypervisor == None:
target_hypervisor = common.findTargetHypervisor(zk_conn, selector, dom_uuid)
# Verify node is valid
common.verifyNode(zk_conn, target_hypervisor)
# Add the new domain to Zookeeper
transaction = zk_conn.transaction()
transaction.create('/domains/{}'.format(dom_uuid), dom_name.encode('ascii'))
transaction.create('/domains/{}/state'.format(dom_uuid), 'stop'.encode('ascii'))
transaction.create('/domains/{}/hypervisor'.format(dom_uuid), target_hypervisor.encode('ascii'))
transaction.create('/domains/{}/lasthypervisor'.format(dom_uuid), ''.encode('ascii'))
transaction.create('/domains/{}/failedreason'.format(dom_uuid), ''.encode('ascii'))
transaction.create('/domains/{}/xml'.format(dom_uuid), data.encode('ascii'))
results = transaction.commit()
return True, ''
def modify_vm(zk_conn, domain, restart):
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Add the modified config to Zookeeper
transaction = zk_conn.transaction()
transaction.set_data('/domains/{}'.format(dom_uuid), dom_name.encode('ascii'))
transaction.set_data('/domains/{}/xml'.format(dom_uuid), new_vm_config.encode('ascii'))
if restart == True:
transaction.set_data('/domains/{}/state'.format(dom_uuid), 'restart'.encode('ascii'))
results = transaction.commit()
return True, ''
def undefine_vm(zk_conn, domain):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Shut down the VM
try:
current_vm_state = zk_conn.get('/domains/{}/state'.format(dom_uuid))[0].decode('ascii')
if current_vm_state != 'stop':
click.echo('Forcibly stopping VM "{}".'.format(dom_uuid))
# Set the domain into stop mode
transaction = zk_conn.transaction()
transaction.set_data('/domains/{}/state'.format(dom_uuid), 'stop'.encode('ascii'))
transaction.commit()
# Wait for 3 seconds to allow state to flow to all hypervisors
click.echo('Waiting for cluster to update.')
time.sleep(1)
except:
pass
# Gracefully terminate the class instances
try:
click.echo('Deleting VM "{}" from nodes.'.format(dom_uuid))
zk_conn.set('/domains/{}/state'.format(dom_uuid), 'delete'.encode('ascii'))
time.sleep(5)
except:
pass
# Delete the configurations
try:
click.echo('Undefining VM "{}".'.format(dom_uuid))
transaction = zk_conn.transaction()
transaction.delete('/domains/{}/state'.format(dom_uuid))
transaction.delete('/domains/{}/hypervisor'.format(dom_uuid))
transaction.delete('/domains/{}/lasthypervisor'.format(dom_uuid))
transaction.delete('/domains/{}/failedreason'.format(dom_uuid))
transaction.delete('/domains/{}/xml'.format(dom_uuid))
transaction.delete('/domains/{}'.format(dom_uuid))
transaction.commit()
except:
pass
return True, ''
def start_vm(zk_conn, domain):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Set the VM to start
click.echo('Starting VM "{}".'.format(dom_uuid))
zk_conn.set('/domains/%s/state' % dom_uuid, 'start'.encode('ascii'))
return True, ''
def restart_vm(zk_conn, domain):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Get state and verify we're OK to proceed
current_state = zk_conn.get('/domains/{}/state'.format(dom_uuid))[0].decode('ascii')
if current_state != 'start':
common.stopZKConnection(zk_conn)
return False, 'ERROR: VM "{}" is not in "start" state!'.format(dom_uuid)
# Set the VM to start
click.echo('Restarting VM "{}".'.format(dom_uuid))
zk_conn.set('/domains/%s/state' % dom_uuid, 'restart'.encode('ascii'))
return True, ''
def shutdown_vm(zk_conn, domain):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Get state and verify we're OK to proceed
current_state = zk_conn.get('/domains/{}/state'.format(dom_uuid))[0].decode('ascii')
if current_state != 'start':
common.stopZKConnection(zk_conn)
return False, 'ERROR: VM "{}" is not in "start" state!'.format(dom_uuid)
# Set the VM to shutdown
click.echo('Shutting down VM "{}".'.format(dom_uuid))
zk_conn.set('/domains/%s/state' % dom_uuid, 'shutdown'.encode('ascii'))
return True, ''
def stop_vm(zk_conn, domain):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Get state and verify we're OK to proceed
current_state = zk_conn.get('/domains/{}/state'.format(dom_uuid))[0].decode('ascii')
if current_state != 'start':
common.stopZKConnection(zk_conn)
return False, 'ERROR: VM "{}" is not in "start" state!'.format(dom_uuid)
# Set the VM to start
click.echo('Forcibly stopping VM "{}".'.format(dom_uuid))
zk_conn.set('/domains/%s/state' % dom_uuid, 'stop'.encode('ascii'))
return True, ''
def move_vm(zk_conn, domain, target_hypervisor, selector):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
current_hypervisor = zk_conn.get('/domains/{}/hypervisor'.format(dom_uuid))[0].decode('ascii')
if target_hypervisor == None:
target_hypervisor = common.findTargetHypervisor(zk_conn, selector, dom_uuid)
else:
if target_hypervisor == current_hypervisor:
common.stopZKConnection(zk_conn)
return False, 'ERROR: VM "{}" is already running on hypervisor "{}".'.format(dom_uuid, current_hypervisor)
# Verify node is valid
common.verifyNode(zk_conn, target_hypervisor)
current_vm_state = zk_conn.get('/domains/{}/state'.format(dom_uuid))[0].decode('ascii')
if current_vm_state == 'start':
click.echo('Permanently migrating VM "{}" to hypervisor "{}".'.format(dom_uuid, target_hypervisor))
transaction = zk_conn.transaction()
transaction.set_data('/domains/{}/state'.format(dom_uuid), 'migrate'.encode('ascii'))
transaction.set_data('/domains/{}/hypervisor'.format(dom_uuid), target_hypervisor.encode('ascii'))
transaction.set_data('/domains/{}/lasthypervisor'.format(dom_uuid), ''.encode('ascii'))
transaction.commit()
else:
click.echo('Permanently moving VM "{}" to hypervisor "{}".'.format(dom_uuid, target_hypervisor))
transaction = zk_conn.transaction()
transaction.set_data('/domains/{}/hypervisor'.format(dom_uuid), target_hypervisor.encode('ascii'))
transaction.set_data('/domains/{}/lasthypervisor'.format(dom_uuid), ''.encode('ascii'))
transaction.commit()
return True, ''
def migrate_vm(zk_conn, domain, target_hypervisor, selector, force_migrate):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Get state and verify we're OK to proceed
current_state = zk_conn.get('/domains/{}/state'.format(dom_uuid))[0].decode('ascii')
if current_state != 'start':
target_state = 'start'
else:
target_state = 'migrate'
current_hypervisor = zk_conn.get('/domains/{}/hypervisor'.format(dom_uuid))[0].decode('ascii')
last_hypervisor = zk_conn.get('/domains/{}/lasthypervisor'.format(dom_uuid))[0].decode('ascii')
if last_hypervisor != '' and force_migrate != True:
click.echo('ERROR: VM "{}" has been previously migrated.'.format(dom_uuid))
click.echo('> Last hypervisor: {}'.format(last_hypervisor))
click.echo('> Current hypervisor: {}'.format(current_hypervisor))
click.echo('Run `vm unmigrate` to restore the VM to its previous hypervisor, or use `--force` to override this check.')
common.stopZKConnection(zk_conn)
return False, ''
if target_hypervisor == None:
target_hypervisor = findTargetHypervisor(zk_conn, selector, dom_uuid)
else:
if target_hypervisor == current_hypervisor:
common.stopZKConnection(zk_conn)
return False, 'ERROR: VM "{}" is already running on hypervisor "{}".'.format(dom_uuid, current_hypervisor)
# Verify node is valid
common.verifyNode(zk_conn, target_hypervisor)
click.echo('Migrating VM "{}" to hypervisor "{}".'.format(dom_uuid, target_hypervisor))
transaction = zk_conn.transaction()
transaction.set_data('/domains/{}/state'.format(dom_uuid), target_state.encode('ascii'))
transaction.set_data('/domains/{}/hypervisor'.format(dom_uuid), target_hypervisor.encode('ascii'))
transaction.set_data('/domains/{}/lasthypervisor'.format(dom_uuid), current_hypervisor.encode('ascii'))
transaction.commit()
return True, ''
def unmigrate_vm(zk_conn, domain):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Get state and verify we're OK to proceed
current_state = zk_conn.get('/domains/{}/state'.format(dom_uuid))[0].decode('ascii')
if current_state != 'start':
target_state = 'start'
else:
target_state = 'migrate'
target_hypervisor = zk_conn.get('/domains/{}/lasthypervisor'.format(dom_uuid))[0].decode('ascii')
if target_hypervisor == '':
common.stopZKConnection(zk_conn)
return False, 'ERROR: VM "{}" has not been previously migrated.'.format(dom_uuid)
click.echo('Unmigrating VM "{}" back to hypervisor "{}".'.format(dom_uuid, target_hypervisor))
transaction = zk_conn.transaction()
transaction.set_data('/domains/{}/state'.format(dom_uuid), target_state.encode('ascii'))
transaction.set_data('/domains/{}/hypervisor'.format(dom_uuid), target_hypervisor.encode('ascii'))
transaction.set_data('/domains/{}/lasthypervisor'.format(dom_uuid), ''.encode('ascii'))
transaction.commit()
return True, ''
def get_info(zk_conn, domain, long_output):
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID(zk_conn, domain)
if dom_uuid == None:
common.stopZKConnection(zk_conn)
return False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)
# Gather information from XML config and print it
information = getInformationFromXML(zk_conn, dom_uuid, long_output)
click.echo(information)
# Get a failure reason if applicable
failedreason = zk_conn.get('/domains/{}/failedreason'.format(dom_uuid))[0].decode('ascii')
if failedreason != '':
click.echo('')
click.echo('{}Failure reason:{} {}'.format(ansiiprint.purple(), ansiiprint.end(), failedreason))
click.echo('')
return True, ''
def get_list(zk_conn, hypervisor, limit):
if hypervisor != None:
# Verify node is valid
common.verifyNode(zk_conn, hypervisor)
vm_list_raw = zk_conn.get_children('/domains')
vm_list = []
vm_list_output = []
vm_hypervisor = {}
vm_state = {}
vm_migrated = {}
vm_uuid = {}
vm_name = {}
vm_description = {}
vm_memory = {}
vm_vcpu = {}
# If we're limited, remove other nodes' VMs
for vm in vm_list_raw:
# Check we don't match the limit
name = zk_conn.get('/domains/{}'.format(vm))[0].decode('ascii')
if limit != None:
try:
if re.match(limit, name) == None:
continue
except Exception as e:
click.echo('Regex Error: {}'.format(e))
exit(1)
# Check hypervisor to avoid unneeded ZK calls
vm_hypervisor[vm] = zk_conn.get('/domains/{}/hypervisor'.format(vm))[0].decode('ascii')
if hypervisor == None:
vm_list.append(vm)
else:
if vm_hypervisor[vm] == hypervisor:
vm_list.append(vm)
# Gather information for printing
for vm in vm_list:
vm_state[vm] = zk_conn.get('/domains/{}/state'.format(vm))[0].decode('ascii')
vm_lasthypervisor = zk_conn.get('/domains/{}/lasthypervisor'.format(vm))[0].decode('ascii')
if vm_lasthypervisor != '':
vm_migrated[vm] = 'from {}'.format(vm_lasthypervisor)
else:
vm_migrated[vm] = 'no'
try:
vm_xml = common.getDomainXML(zk_conn, vm)
vm_uuid[vm], vm_name[vm], vm_description[vm], vm_memory[vm], vm_vcpu[vm], vm_vcputopo = common.getDomainMainDetails(vm_xml)
except AttributeError:
click.echo('Error: Domain {} does not exist.'.format(domain))
# Determine optimal column widths
# Dynamic columns: node_name, hypervisor, migrated
vm_name_length = 0
vm_hypervisor_length = 0
vm_migrated_length = 0
for vm in vm_list:
# vm_name column
_vm_name_length = len(vm_name[vm]) + 1
if _vm_name_length > vm_name_length:
vm_name_length = _vm_name_length
# vm_hypervisor column
_vm_hypervisor_length = len(vm_hypervisor[vm]) + 1
if _vm_hypervisor_length > vm_hypervisor_length:
vm_hypervisor_length = _vm_hypervisor_length
# vm_migrated column
_vm_migrated_length = len(vm_migrated[vm]) + 1
if _vm_migrated_length > vm_migrated_length:
vm_migrated_length = _vm_migrated_length
# Format the string (header)
vm_list_header = ansiiprint.bold() + 'Name UUID State RAM [MiB] vCPUs Hypervisor Migrated?' + ansiiprint.end()
vm_list_output.append(
'{bold}{vm_name: <{vm_name_length}} {vm_uuid: <37} \
{vm_state_colour}{vm_state: <8}{end_colour} \
{vm_memory: <10} {vm_vcpu: <6} \
{vm_hypervisor: <{vm_hypervisor_length}} \
{vm_migrated: <{vm_migrated_length}}{end_bold}'.format(
vm_name_length=vm_name_length,
vm_hypervisor_length=vm_hypervisor_length,
vm_migrated_length=vm_migrated_length,
bold=ansiiprint.bold(),
end_bold=ansiiprint.end(),
vm_state_colour='',
end_colour='',
vm_name='Name',
vm_uuid='UUID',
vm_state='State',
vm_memory='RAM (MiB)',
vm_vcpu='vCPUs',
vm_hypervisor='Hypervisor',
vm_migrated='Migrated'
)
)
# Format the string (elements)
for vm in vm_list:
if vm_state[vm] == 'start':
vm_state_colour = ansiiprint.green()
elif vm_state[vm] == 'restart':
vm_state_colour = ansiiprint.yellow()
elif vm_state[vm] == 'shutdown':
vm_state_colour = ansiiprint.yellow()
elif vm_state[vm] == 'stop':
vm_state_colour = ansiiprint.red()
elif vm_state[vm] == 'failed':
vm_state_colour = ansiiprint.red()
else:
vm_state_colour = ansiiprint.blue()
vm_list_output.append(
'{bold}{vm_name: <{vm_name_length}} {vm_uuid: <37} \
{vm_state_colour}{vm_state: <8}{end_colour} \
{vm_memory: <10} {vm_vcpu: <6} \
{vm_hypervisor: <{vm_hypervisor_length}} \
{vm_migrated: <{vm_migrated_length}}{end_bold}'.format(
vm_name_length=vm_name_length,
vm_hypervisor_length=vm_hypervisor_length,
vm_migrated_length=vm_migrated_length,
bold='',
end_bold='',
vm_state_colour=vm_state_colour,
end_colour=ansiiprint.end(),
vm_name=vm_name[vm],
vm_uuid=vm_uuid[vm],
vm_state=vm_state[vm],
vm_memory=vm_memory[vm],
vm_vcpu=vm_vcpu[vm],
vm_hypervisor=vm_hypervisor[vm],
vm_migrated=vm_migrated[vm]
)
)
click.echo('\n'.join(sorted(vm_list_output)))
return True, ''

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
# zkhandler.py - Secure versioned ZooKeeper updates
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# 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 <https://www.gnu.org/licenses/>.
#
###############################################################################
import kazoo.client
import lib.ansiiprint
# Child list function
def listchildren(zk_conn, key):
children = zk_conn.get_children(key)
return children
# Data read function
def readdata(zk_conn, key):
data_raw = zk_conn.get(key)
data = data_raw[0].decode('ascii')
meta = data_raw[1]
return data
# Data write function
def writedata(zk_conn, kv):
# Get the current version; we base this off the first key (ordering in multi-key calls is irrelevant)
first_key = list(kv.keys())[0]
orig_data_raw = zk_conn.get(first_key)
meta = orig_data_raw[1]
if meta == None:
ansiiprint.echo('Zookeeper key "{}" does not exist'.format(first_key), '', 'e')
return 1
version = meta.version
new_version = version + 1
zk_transaction = zk_conn.transaction()
for key, data in kv.items():
zk_transaction.set_data(key, data.encode('ascii'))
try:
zk_transaction.check(first_key, new_version)
except TypeError:
ansiiprint.echo('Zookeeper key "{}" does not match expected version'.format(first_key), '', 'e')
return 1
zk_transaction.commit()
return 0