mirror of
https://github.com/xcat2/confluent.git
synced 2025-09-08 19:28:38 +00:00
Add preliminary proxmox node support
This commit is contained in:
@@ -637,6 +637,9 @@ node = {
|
||||
'description': ('Fingerprint of the TLS certificate recognized as'
|
||||
'belonging to the hardware manager of the server'),
|
||||
},
|
||||
'pubkeys.tls': {
|
||||
'description': ('Fingerprint of the TLS certificate for service running on host.'),
|
||||
},
|
||||
'pubkeys.ssh': {
|
||||
'description': ('Fingerprint of the SSH key of the OS running on the '
|
||||
'system.'),
|
||||
|
266
confluent_server/confluent/plugins/hardwaremanagement/proxmox.py
Normal file
266
confluent_server/confluent/plugins/hardwaremanagement/proxmox.py
Normal file
@@ -0,0 +1,266 @@
|
||||
|
||||
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)))
|
Reference in New Issue
Block a user