From 1ffc6f57681db8cc1eae76038be324afcceca97d Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 28 Jan 2020 15:34:09 +0500 Subject: [PATCH] Added scripts related to getting info of OpenNebula VM into etcd and related queries --- .gitignore | 6 + opennebula-vm-etcd/config-and-secrets.conf | 18 +++ opennebula-vm-etcd/config.py | 12 ++ opennebula-vm-etcd/etcd_wrapper.py | 75 ++++++++++++ opennebula-vm-etcd/put-vm-info-into-etcd.py | 126 ++++++++++++++++++++ opennebula-vm-etcd/vm-queries.py | 55 +++++++++ 6 files changed, 292 insertions(+) create mode 100644 .gitignore create mode 100644 opennebula-vm-etcd/config-and-secrets.conf create mode 100644 opennebula-vm-etcd/config.py create mode 100644 opennebula-vm-etcd/etcd_wrapper.py create mode 100644 opennebula-vm-etcd/put-vm-info-into-etcd.py create mode 100644 opennebula-vm-etcd/vm-queries.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c093faa --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +opennebula-snapshot/config-and-secrets.conf + +*.pyc + +.idea/ +.vscode/ \ No newline at end of file diff --git a/opennebula-vm-etcd/config-and-secrets.conf b/opennebula-vm-etcd/config-and-secrets.conf new file mode 100644 index 0000000..b7faa08 --- /dev/null +++ b/opennebula-vm-etcd/config-and-secrets.conf @@ -0,0 +1,18 @@ +# Do not put single/double quotation mark for string as they are +# aslo considered as normal character. + +[oca] +client_secrets = ahmedbilal96@gmail.com:d00359fa33a74fcb5ea40bb088e299fd2ab85126 + +[etcd] +# url = localhost +# port = 2379 +# ca_cert +# cert_cert +# cert_key + +url = etcd1.ungleich.ch +port = 2379 +ca_cert = /home/meow/.cdist/files/etcd/ca.pem +cert_cert = /home/meow/.cdist/files/etcd/developer.pem +cert_key = /home/meow/.cdist/files/etcd/developer-key.pem \ No newline at end of file diff --git a/opennebula-vm-etcd/config.py b/opennebula-vm-etcd/config.py new file mode 100644 index 0000000..b329f4f --- /dev/null +++ b/opennebula-vm-etcd/config.py @@ -0,0 +1,12 @@ +import configparser + +from etcd_wrapper import EtcdWrapper + +config = configparser.ConfigParser(allow_no_value=True) +config.read('config-and-secrets.conf') + +etcd_client = EtcdWrapper( + host=config['etcd']['url'], port=config['etcd']['port'], + ca_cert=config['etcd']['ca_cert'], cert_key=config['etcd']['cert_key'], + cert_cert=config['etcd']['cert_cert'] +) diff --git a/opennebula-vm-etcd/etcd_wrapper.py b/opennebula-vm-etcd/etcd_wrapper.py new file mode 100644 index 0000000..9624677 --- /dev/null +++ b/opennebula-vm-etcd/etcd_wrapper.py @@ -0,0 +1,75 @@ +import etcd3 +import json + +from functools import wraps + +from uncloud import UncloudException +from uncloud.common import logger + + +class EtcdEntry: + def __init__(self, meta_or_key, value, value_in_json=True): + if hasattr(meta_or_key, 'key'): + # if meta has attr 'key' then get it + self.key = meta_or_key.key.decode('utf-8') + else: + # otherwise meta is the 'key' + self.key = meta_or_key + self.value = value.decode('utf-8') + + if value_in_json: + self.value = json.loads(self.value) + + +def readable_errors(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except etcd3.exceptions.ConnectionFailedError: + raise UncloudException('Cannot connect to etcd: is etcd running as configured in uncloud.conf?') + except etcd3.exceptions.ConnectionTimeoutError as err: + raise etcd3.exceptions.ConnectionTimeoutError('etcd connection timeout.') from err + except Exception as err: + logger.exception('Some etcd error occured. See syslog for details.', err) + + return wrapper + + +class EtcdWrapper: + @readable_errors + def __init__(self, *args, **kwargs): + self.client = etcd3.client(*args, **kwargs) + + @readable_errors + def get(self, *args, value_in_json=True, **kwargs): + _value, _key = self.client.get(*args, **kwargs) + if _key is None or _value is None: + return None + return EtcdEntry(_key, _value, value_in_json=value_in_json) + + @readable_errors + def put(self, *args, value_in_json=True, **kwargs): + _key, _value = args + if value_in_json: + _value = json.dumps(_value) + + if not isinstance(_key, str): + _key = _key.decode('utf-8') + + return self.client.put(_key, _value, **kwargs) + + @readable_errors + def get_prefix(self, *args, value_in_json=True, **kwargs): + event_iterator = self.client.get_prefix(*args, **kwargs) + for e in event_iterator: + yield EtcdEntry(*e[::-1], value_in_json=value_in_json) + + @readable_errors + def watch_prefix(self, key, value_in_json=True): + event_iterator, cancel = self.client.watch_prefix(key) + for e in event_iterator: + if hasattr(e, '_event'): + e = getattr('e', '_event') + if e.type == e.PUT: + yield EtcdEntry(e.kv.key, e.kv.value, value_in_json=value_in_json) diff --git a/opennebula-vm-etcd/put-vm-info-into-etcd.py b/opennebula-vm-etcd/put-vm-info-into-etcd.py new file mode 100644 index 0000000..70a1b09 --- /dev/null +++ b/opennebula-vm-etcd/put-vm-info-into-etcd.py @@ -0,0 +1,126 @@ +import pyone + +from enum import IntEnum +from config import config, etcd_client + +# How to get client secrets? +# 1. Login to OpenNebula +# 2. Go to Settings then Auth +# 3. Click on "Manage login tokens" button +# 4. Click on "Get a new token" button + +one_client = pyone.OneServer( + uri='https://opennebula.ungleich.ch:2634/RPC2', + session=config['oca']['client_secrets'] +) + + +def get_hostname_of_vm(vm_id): + host_pool = { + host.NAME: { + 'name': host.NAME, + 'id': host.ID, + 'cluster': { + 'name': host.CLUSTER, + 'id': host.CLUSTER_ID + }, + 'vms': host.VMS.ID + } + for host in one_client.hostpool.info().HOST + } + for hostname, host in host_pool.items(): + if vm_id in host['vms']: + return host + + return None + + +def put_under_list(obj): + if not isinstance(obj, list): + return [obj] + return obj + + +class Snapshot: + def __init__(self, disk_id, snapshot): + self.active = bool(snapshot.ACTIVE) + self.date = snapshot.DATE + self.id = snapshot.ID + self.name = snapshot.NAME + self.size = snapshot.SIZE + self.disk_id = disk_id + + def get_data(self): + return { + attr: getattr(self, attr) + for attr in dir(self) + if not attr.startswith('__') and not callable(getattr(self, attr)) + } + + +class VM: + def __init__(self, vm): + self.name = vm.get_NAME() + self.id = vm.get_ID() + self.owner = { + 'name': vm.get_UNAME(), + 'id': vm.get_UID(), + } + + template = vm.get_TEMPLATE() + host = get_hostname_of_vm(self.id) + + self.vcpu = template.get('VCPU', None) + self.memory = template.get('MEMORY', None) + self.disks = [dict(disk) for disk in put_under_list(template.get('DISK', []))] + self.graphics = [dict(graphics) for graphics in put_under_list(template.get('GRAPHICS', []))] + self.nics = [dict(nic) for nic in put_under_list(template.get('NIC', []))] + self.status = pyone.VM_STATE(vm.get_STATE()).name.lower() + self.snapshots = [] + + for disk in one_client.vm.info(self.id).SNAPSHOTS: + disk_id = disk.DISK_ID + for snapshot in disk.SNAPSHOT: + self.snapshots.append(Snapshot(disk_id, snapshot).get_data()) + + if host: + self.host = { + 'name': host['name'], + 'id': host['id'] + } + else: + self.host = host + + def get_data(self): + return { + attr: getattr(self, attr) + for attr in dir(self) + if not attr.startswith('__') and not callable(getattr(self, attr)) + } + + def __repr__(self): + return str(self.get_data()) + + +class VmFilterFlag(IntEnum): + UIDUserResources = 0 # UID User’s Resources + UserAndItsGroupsResources = -1 # Resources belonging to the user and any of his groups + AllResources = -2 # All resources + UserResources = -3 # Resources belonging to the user + UserPrimaryGroupResources = -4 # Resources belonging to the user’s primary group + + +def main(): + VM_STATES = list(pyone.VM_STATE) + START_ID = -1 # First id whatever it is + END_ID = -1 # Last id whatever it is + + for VM_STATE in VM_STATES: + vm_pool = one_client.vmpool.infoextended(VmFilterFlag.AllResources.value, START_ID, END_ID, VM_STATE) + for i, vm in enumerate(vm_pool.VM): + vm = VM(vm) + etcd_client.put('/opennebula/vm/{}'.format(vm.id), vm.get_data()) + + +if __name__ == "__main__": + main() diff --git a/opennebula-vm-etcd/vm-queries.py b/opennebula-vm-etcd/vm-queries.py new file mode 100644 index 0000000..e92ef14 --- /dev/null +++ b/opennebula-vm-etcd/vm-queries.py @@ -0,0 +1,55 @@ +from pprint import pprint + +from config import config, etcd_client + + +def get_vm_by_ip(vms, ip, status='active'): + vms_by_status = { + vm_id: vm + for vm_id, vm in vms.items() + if vm['status'] == status + } + for vm_id, vm in vms_by_status.items(): + vm_ips = [] + for nic in vm.get('nics', []): + global_ipv6 = nic.get('IP6_GLOBAL', None) + local_ipv6 = nic.get('IP6_LINK', None) + ipv4 = nic.get('IP', None) + vm_ips += [global_ipv6, local_ipv6, ipv4] + + if ip in vm_ips: + return {vm_id: vm} + return None + + +def main(): + vm_prefix = '/opennebula/vm/' + + vms = { + int(vm.key.split('/')[-1]): vm.value + for vm in etcd_client.get_prefix(vm_prefix) + } + + VM_ID = 10761 # One of nico's VM + + # Get all data related to a VM + pprint(vms.get(VM_ID)) + + # Get host of a VM + print(vms.get(VM_ID).get('host').get('name')) + + # Get VNC Port of a VM + print(vms.get(VM_ID).get('graphics')[0].get('PORT')) + + # Get all disks attached with VM + pprint(vms.get(VM_ID).get('disks')) + + # Who is owner of a VM? + print(vms.get(VM_ID).get('owner').get('name')) + + # Get VM who has 2a0a:e5c0:0:5:0:78ff:fe11:d75f + search_ungleich_ch = get_vm_by_ip(vms, '2a0a:e5c0:0:5:0:78ff:fe11:d75f') + pprint(search_ungleich_ch) + +if __name__ == '__main__': + main()