mirror of
https://github.com/xcat2/confluent.git
synced 2025-09-09 11:48:23 +00:00
267 lines
9.9 KiB
Python
267 lines
9.9 KiB
Python
|
|
import codecs
|
|
import confluent.util as util
|
|
import confluent.messages as msg
|
|
import eventlet
|
|
import json
|
|
import struct
|
|
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
|
import eventlet.green.socket as socket
|
|
import eventlet
|
|
import confluent.interface.console as conapi
|
|
import io
|
|
import urllib.parse as urlparse
|
|
|
|
class RetainedIO(io.BytesIO):
|
|
# Need to retain buffer after close
|
|
def __init__(self):
|
|
self.resultbuffer = None
|
|
def close(self):
|
|
self.resultbuffer = self.getbuffer()
|
|
super().close()
|
|
|
|
class PmxConsole(conapi.Console):
|
|
pass
|
|
# this more closely resembles OpenBMC.., websocket based and all
|
|
|
|
class PmxApiClient:
|
|
def __init__(self, server, user, password, configmanager):
|
|
self.user = user
|
|
self.password = password
|
|
if configmanager:
|
|
cv = util.TLSCertVerifier(
|
|
configmanager, server, 'pubkeys.tls'
|
|
).verify_cert
|
|
else:
|
|
cv = lambda x: True
|
|
|
|
try:
|
|
self.user = self.user.decode()
|
|
self.password = self.password.decode()
|
|
except Exception:
|
|
pass
|
|
self.wc = webclient.SecureHTTPConnection(server, port=8006, verifycallback=cv)
|
|
self.vmmap = {}
|
|
self.login()
|
|
self.vmlist = {}
|
|
self.vmbyid = {}
|
|
|
|
def login(self):
|
|
loginform = {
|
|
'username': self.user,
|
|
'password': self.password,
|
|
}
|
|
loginbody = urlparse.urlencode(loginform)
|
|
rsp = self.wc.grab_json_response_with_status('/api2/json/access/ticket', loginbody)
|
|
self.wc.cookies['PVEAuthCookie'] = rsp[0]['data']['ticket']
|
|
self.wc.set_header('CSRFPreventionToken', rsp[0]['data']['CSRFPreventionToken'])
|
|
|
|
|
|
def get_screenshot(self, vm, outfile):
|
|
raise Exception("Not implemented")
|
|
|
|
def map_vms(self):
|
|
rsp = self.wc.grab_json_response('/api2/json/cluster/resources')
|
|
for datum in rsp.get('data', []):
|
|
if datum['type'] == 'qemu':
|
|
self.vmmap[datum['name']] = (datum['node'], datum['id'])
|
|
return self.vmmap
|
|
|
|
|
|
def get_vm(self, vm):
|
|
if vm not in self.vmmap:
|
|
self.map_vms()
|
|
return self.vmmap[vm]
|
|
|
|
|
|
def get_vm_inventory(self, vm):
|
|
host, guest = self.get_vm(vm)
|
|
cfg = self.wc.grab_json_response(f'/api2/json/nodes/{host}/{guest}/pending')
|
|
myuuid = None
|
|
sysinfo = {'name': 'System', 'present': True, 'information': {
|
|
'Product name': 'Proxmox qemu virtual machine',
|
|
'Manufacturer': 'qemu'
|
|
}}
|
|
invitems = [sysinfo]
|
|
for datum in cfg['data']:
|
|
if datum['key'] == 'smbios1':
|
|
smbios = datum['value']
|
|
for smbio in smbios.split(','):
|
|
if '=' in smbio:
|
|
k, v = smbio.split('=')
|
|
if k == 'uuid':
|
|
sysinfo['information']['UUID'] = v
|
|
elif datum['key'].startswith('net'):
|
|
label = 'Network adapter {}'.format(datum['key'])
|
|
niccfg = datum['value']
|
|
cfgparts = niccfg.split(',')
|
|
nicmodel, mac = cfgparts[0].split('=')
|
|
invitems.append({
|
|
'present': True,
|
|
'name': label,
|
|
'information': {
|
|
'Type': 'Ethernet',
|
|
'Model': nicmodel,
|
|
'MAC Address 1': mac,
|
|
}
|
|
})
|
|
yield msg.KeyValueData({'inventory': invitems}, vm)
|
|
|
|
|
|
def get_vm_serial(self, vm):
|
|
# This would be termproxy
|
|
# Example url
|
|
#wss:///api2/json/nodes/{host}/{guest}/vncwebsocket?port=5900&vncticket=URLENCODEDTICKET
|
|
raise Exception('TODO')
|
|
|
|
def get_vm_bootdev(self, vm):
|
|
host, guest = self.get_vm(vm)
|
|
cfg = self.wc.grab_json_response(f'/api2/json/nodes/{host}/{guest}/pending')
|
|
for datum in cfg['data']:
|
|
if datum['key'] == 'boot':
|
|
bootseq = datum.get('pending', datum['value'])
|
|
for kv in bootseq.split(','):
|
|
k, v = kv.split('=')
|
|
if k == 'order':
|
|
bootdev = v.split(';')[0]
|
|
if bootdev.startswith('net'):
|
|
return 'network'
|
|
return 'default'
|
|
|
|
|
|
def get_vm_power(self, vm):
|
|
host, guest = self.get_vm(vm)
|
|
rsp = self.wc.grab_json_response(f'/api2/json/nodes/{host}/{guest}/status/current')
|
|
rsp = rsp['data']
|
|
currstatus = rsp["qmpstatus"] # stopped, "running"
|
|
if currstatus == 'running':
|
|
return 'on'
|
|
elif currstatus == 'stopped':
|
|
return off
|
|
raise Exception("Unknnown response to status query")
|
|
|
|
def set_vm_power(self, vm, state):
|
|
host, guest = self.get_vm(vm)
|
|
if state == 'boot':
|
|
current = self.get_vm_power(vm)
|
|
if current == 'on':
|
|
state = 'reset'
|
|
else:
|
|
state = 'start'
|
|
elif state == 'on':
|
|
state = 'start'
|
|
elif state == 'off':
|
|
state = 'stop'
|
|
rsp = self.wc.grab_json_response_with_status(f'/api2/json/nodes/{host}/{guest}/status/{state}', method='POST')
|
|
print(repr(rsp))
|
|
|
|
def set_vm_bootdev(self, vm, bootdev):
|
|
host, guest = self.get_vm(vm)
|
|
if bootdev not in ('net', 'network', 'default'):
|
|
raise Exception('Requested boot device not supported')
|
|
cfg = self.wc.grab_json_response(f'/api2/json/nodes/{host}/{guest}/pending')
|
|
nonnetdevs = []
|
|
netdevs = []
|
|
for datum in cfg['data']:
|
|
if datum['key'] == 'boot':
|
|
bootseq = datum.get('pending', datum['value'])
|
|
for item in bootseq.split(','):
|
|
if item.startswith('order='):
|
|
bootdevs = item.replace('order=', '').split(';')
|
|
for cbootdev in bootdevs:
|
|
if cbootdev.startswith('net'):
|
|
netdevs.append(cbootdev)
|
|
else:
|
|
nonnetdevs.append(cbootdev)
|
|
if bootdev in ('net', 'network'):
|
|
newbootdevs = netdevs + nonnetdevs
|
|
else:
|
|
newbootdevs = nonnetdevs + netdevs
|
|
neworder = 'order=' + ';'.join(newbootdevs)
|
|
self.wc.set_header('Content-Type', 'application/json')
|
|
try:
|
|
self.wc.grab_json_response_with_status(f'/api2/json/nodes/{host}/{guest}/config', {'boot': neworder}, method='PUT')
|
|
finally:
|
|
del self.wc.stdheaders['Content-Type']
|
|
|
|
|
|
def prep_proxmox_clients(nodes, configmanager):
|
|
cfginfo = configmanager.get_node_attributes(nodes, ['hardwaremanagement.manager', 'secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
|
|
clientsbypmx = {}
|
|
clientsbynode = {}
|
|
for node in nodes:
|
|
cfg = cfginfo[node]
|
|
currpmx = cfg['hardwaremanagement.manager']['value']
|
|
if currpmx not in clientsbypmx:
|
|
user = cfg.get('secret.hardwaremanagementuser', {}).get('value', None)
|
|
passwd = cfg.get('secret.hardwaremanagementpassword', {}).get('value', None)
|
|
clientsbypmx[currpmx] = PmxApiClient(currpmx, user, passwd, configmanager)
|
|
clientsbynode[node] = clientsbypmx[currpmx]
|
|
return clientsbynode
|
|
|
|
def retrieve(nodes, element, configmanager, inputdata):
|
|
clientsbynode = prep_proxmox_clients(nodes, configmanager)
|
|
for node in nodes:
|
|
currclient = clientsbynode[node]
|
|
if element == ['power', 'state']:
|
|
yield msg.PowerState(node, currclient.get_vm_power(node))
|
|
elif element == ['boot', 'nextdevice']:
|
|
yield msg.BootDevice(node, currclient.get_vm_bootdev(node))
|
|
elif element[:2] == ['inventory', 'hardware'] and len(element) == 4:
|
|
for rsp in currclient.get_vm_inventory(node):
|
|
yield rsp
|
|
elif element == ['console', 'ikvm_methods']:
|
|
dsc = {'ikvm_methods': ['screenshot']}
|
|
yield msg.KeyValueData(dsc, node)
|
|
elif element == ['console', 'ikvm_screenshot']:
|
|
# good background for the webui, and kitty
|
|
imgdata = RetainedIO()
|
|
imgformat = currclient.get_screenshot(node, imgdata)
|
|
imgdata = imgdata.getvalue()
|
|
if imgdata:
|
|
yield msg.ScreenShot(imgdata, node, imgformat=imgformat)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update(nodes, element, configmanager, inputdata):
|
|
clientsbynode = prep_proxmox_clients(nodes, configmanager)
|
|
for node in nodes:
|
|
currclient = clientsbynode[node]
|
|
if element == ['power', 'state']:
|
|
currclient.set_vm_power(node, inputdata.powerstate(node))
|
|
yield msg.PowerState(node, currclient.get_vm_power(node))
|
|
elif element == ['boot', 'nextdevice']:
|
|
currclient.set_vm_bootdev(node, inputdata.bootdevice(node))
|
|
yield msg.BootDevice(node, currclient.get_vm_bootdev(node))
|
|
|
|
# assume this is only console for now
|
|
def create(nodes, element, configmanager, inputdata):
|
|
clientsbynode = prep_vcsa_clients(nodes, configmanager)
|
|
for node in nodes:
|
|
serialdata = clientsbynode[node].get_vm_serial(node)
|
|
return VmConsole(serialdata['server'], serialdata['port'], serialdata['tls'])
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
import os
|
|
from pprint import pprint
|
|
myuser = os.environ['PMXUSER']
|
|
mypass = os.environ['PMXPASS']
|
|
vc = PmxApiClient(sys.argv[1], myuser, mypass, None)
|
|
vm = sys.argv[2]
|
|
if sys.argv[3] == 'setboot':
|
|
vc.set_vm_bootdev(vm, sys.argv[4])
|
|
vc.get_vm_bootdev(vm)
|
|
elif sys.argv[3] == 'power':
|
|
vc.set_vm_power(vm, sys.argv[4])
|
|
elif sys.argv[3] == 'getinfo':
|
|
print(repr(list(vc.get_vm_inventory(vm))))
|
|
print("Bootdev: " + vc.get_vm_bootdev(vm))
|
|
print("Power: " + vc.get_vm_power(vm))
|
|
#print("Serial: " + repr(vc.get_vm_serial(vm)))
|