initial
This commit is contained in:
214
lib/proxmox.py
Normal file
214
lib/proxmox.py
Normal file
@@ -0,0 +1,214 @@
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
|
||||
import proxmoxer
|
||||
|
||||
from django_proxmox_mikrotik.settings import ProxmoxConfig
|
||||
|
||||
|
||||
def get_comma_separated_values(value):
|
||||
_vlist = [v.strip().split('=', 1) for v in value.split(',') if '=' in v] if value else []
|
||||
return {k: v for k, v in _vlist}
|
||||
|
||||
|
||||
class PMDict(dict):
|
||||
def __getattr__(self, name):
|
||||
if name in self:
|
||||
return self[name]
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
|
||||
|
||||
class PMCollection(list):
|
||||
def append(self, item):
|
||||
assert isinstance(item, dict)
|
||||
if not isinstance(item, PMDict):
|
||||
item = PMDict(**item)
|
||||
super().append(item)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if self and hasattr(self[0], item):
|
||||
for i in self:
|
||||
yield getattr(i, item)
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object (or its content[0]) has no attribute '{item}'")
|
||||
|
||||
|
||||
class Proxmox:
|
||||
|
||||
def __init__(self, node=ProxmoxConfig.NODE):
|
||||
self.initialize(node)
|
||||
|
||||
def initialize(self, node=ProxmoxConfig.NODE, ):
|
||||
self.api = proxmoxer.ProxmoxAPI(
|
||||
ProxmoxConfig.HOST,
|
||||
user=ProxmoxConfig.USER,
|
||||
password=ProxmoxConfig.PASS,
|
||||
verify_ssl=False
|
||||
)
|
||||
self.nodepath = f'nodes/{node}'
|
||||
return self
|
||||
|
||||
def __enter__(self):
|
||||
return self.initialize()
|
||||
|
||||
def __exit__(self, *args):
|
||||
"""Actually, this is a no-op, just for the with statement :)"""
|
||||
return
|
||||
|
||||
def nodes(self, route=''):
|
||||
return self.api(f'{self.nodepath}/{route.lstrip("/")}')
|
||||
|
||||
def lxc(self, route=''):
|
||||
return self.nodes(f'lxc/{route.lstrip("/")}')
|
||||
|
||||
def storage(self, route=''):
|
||||
return self.nodes(f'storage/{route.lstrip("/")}')
|
||||
|
||||
def qemu(self, route=''):
|
||||
return self.nodes(f'qemu/{route.lstrip("/")}')
|
||||
|
||||
def cluster(self, route=''):
|
||||
return self.api(f'cluster/{route.lstrip("/")}')
|
||||
|
||||
@property
|
||||
def next_vmid(self):
|
||||
return int(self.cluster_get('nextid'))
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""This makes a 'magic' trick
|
||||
We can call the proxmox api like this:
|
||||
* proxmox.lxc_115_config_get()
|
||||
* proxmox.lxc_115_get('config')
|
||||
* proxmox.lxc_get('115/config')
|
||||
* proxmox.lxc('115/config').get()
|
||||
* ...
|
||||
seems handy at the moment ...
|
||||
The first in *args will always be taken as route!
|
||||
"""
|
||||
if "_" in name:
|
||||
nameparts = name.split("_")
|
||||
action = nameparts.pop()
|
||||
if action not in ["get", "post", "put", "delete"]:
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}' - 1")
|
||||
|
||||
if hasattr(self, nameparts[0]):
|
||||
base_method = getattr(self, nameparts.pop(0))
|
||||
else:
|
||||
base_method = self.nodes
|
||||
|
||||
route_part = "/".join(nameparts)
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
if args:
|
||||
route = str(args[0]).rstrip('/')
|
||||
args = args[1:]
|
||||
else:
|
||||
route = ''
|
||||
if route_part:
|
||||
route = f'{route_part}/{route}'
|
||||
if ProxmoxConfig.READONLY and action != 'get':
|
||||
logging.warning(f'PROXMOX_READONLY is set - not calling {route} '
|
||||
f'with method {base_method.__name__}.{action}'
|
||||
f'({args}, {kwargs})')
|
||||
# Return appropriate mock response based on action
|
||||
if action == 'post':
|
||||
return 'UPID:READONLY:00000000:00000000:00000000:vzcreate:readonly:root@pam:'
|
||||
elif action == 'put':
|
||||
return None
|
||||
elif action == 'delete':
|
||||
return None
|
||||
return {}
|
||||
logging.debug(f'Calling {base_method.__name__}.{action}({route}, {args}, {kwargs})')
|
||||
return getattr(base_method(route), action)(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
|
||||
@classmethod
|
||||
def get_task_status(cls, taskhash, sleeptime=10, pm=None):
|
||||
cls = pm or cls()
|
||||
logging.debug(f"Getting task status for {taskhash}")
|
||||
maxtime = ProxmoxConfig.CREATE_LXC_TIMEOUT
|
||||
response = cls.nodes_get(f'tasks/{taskhash}/status')
|
||||
logging.debug(f"Status response for {taskhash}: {response}")
|
||||
while True:
|
||||
response = cls.nodes_get(f'tasks/{taskhash}/status')
|
||||
logging.debug(f"Status response for {taskhash}: {response}")
|
||||
yield response
|
||||
logging.debug(f"Status response for {taskhash}: {response}")
|
||||
status = response['status']
|
||||
if status == 'stopped':
|
||||
if not response.get('exitstatus') == 'OK':
|
||||
raise ValueError(f"Exitstatus is {response.get('exitstatus')}")
|
||||
break
|
||||
time.sleep(sleeptime)
|
||||
maxtime -= sleeptime
|
||||
if maxtime <= 0:
|
||||
raise TimeoutError("Took to long")
|
||||
|
||||
def get_all_lxc(self, *filterargs, as_dict=True, **filterkwargs):
|
||||
logging.debug(f"Getting all LXC with filter {filterargs} and {filterkwargs}")
|
||||
from proxmox.models import Lxc
|
||||
lxc_filter = {}
|
||||
_raw = self.lxc_get()
|
||||
logging.debug(f"All LXC: {_raw}")
|
||||
if not _raw:
|
||||
return []
|
||||
all = _raw
|
||||
comps = {}
|
||||
for key in list(_raw[0].keys()):
|
||||
if key in filterkwargs:
|
||||
comps[key] = filterkwargs.pop(key)
|
||||
for key, comp in comps.items():
|
||||
all = filter(lambda x: (
|
||||
logging.debug(f'{key} of lxc is {x[key]}, must be {comp}' if key in x else f"{key} not in {x}")
|
||||
or key not in x
|
||||
or x[key] == comp
|
||||
), all)
|
||||
if not all:
|
||||
logging.debug(f"No LXC found with filter {filterargs} and {filterkwargs}")
|
||||
logging.debug(f"All LXC: {json.dumps(all, indent=2, default=str)}")
|
||||
return []
|
||||
if filterargs:
|
||||
for prop, c, v in filterargs:
|
||||
invert = False
|
||||
if c.startswith('!'):
|
||||
invert = True
|
||||
if c.endswith('='):
|
||||
comparer = lambda x: x != v if invert else x == v
|
||||
elif c.endswith('in'):
|
||||
comparer = lambda x: x not in v if invert else x in v
|
||||
elif c.endswith('startswith'):
|
||||
comparer = lambda x: v.startswith(v) if invert else v.startswith(v)
|
||||
elif c.endswith('endswith'):
|
||||
comparer = lambda x: v.endswith(v) if invert else v.endswith(v)
|
||||
lxc_filter[prop] = comparer
|
||||
if filterkwargs:
|
||||
for k, v in filterkwargs.items():
|
||||
lxc_filter[k] = lambda x: x == v
|
||||
|
||||
def filter_out(lxc_):
|
||||
if not lxc_filter:
|
||||
return True
|
||||
for prop, comparer in lxc_filter.items():
|
||||
if not prop in lxc_:
|
||||
continue
|
||||
if not comparer(lxc_.get(prop)):
|
||||
logging.debug(f"Filter out {lxc_} because {prop} is {lxc_.get(prop)}")
|
||||
return False
|
||||
return True
|
||||
|
||||
ret = []
|
||||
for lxc in filter(filter_out, all):
|
||||
lxc_config = self.lxc_get(f'{lxc["vmid"]}/config')
|
||||
_lx_data = lxc | lxc_config
|
||||
if as_dict:
|
||||
ret.append(PMDict(**_lx_data))
|
||||
# yield PMDict(**_lx_data)
|
||||
else:
|
||||
ret.append(Lxc().from_proxmox(**_lx_data))
|
||||
# yield Lxc().from_proxmox(**_lx_data)
|
||||
if not ret:
|
||||
logging.warning(f"Found no LXC with filter {filterargs} and {filterkwargs}")
|
||||
return ret
|
||||
Reference in New Issue
Block a user