Add working --last-used-mac
{'create_vm': False, 'last_used_mac': True, 'get_new_mac': False, 'debug': False, 'conf_dir': '/home/nico/uncloud', 'etcd_host': 'etcd1.ungleich.ch', 'etcd_port': None, 'etcd_ca_cert': '/home/nico/vcs/ungleich-dot-cdist/files/etcd/ca.pem', 'etcd_cert_cert': '/home/nico/vcs/ungleich-dot-cdist/files/etcd/nico.pem', 'etcd_cert_key': '/home/nico/vcs/ungleich-dot-cdist/files/etcd/nico-key.pem'}
00:20:00:00:00:00
(venv) [19:02] diamond:uncloud% ./bin/uncloud-run-reinstall hack  --etcd-host etcd1.ungleich.ch --etcd-ca-cert /home/nico/vcs/ungleich-dot-cdist/files/etcd/ca.pem --etcd-cert-cert /home/nico/vcs/ungleich-dot-cdist/files/etcd/nico.pem --etcd-cert-key /home/nico/vcs/ungleich-dot-cdist/files/etcd/nico-key.pem --last-used-mac
	
	
This commit is contained in:
		
					parent
					
						
							
								1b36c2f96f
							
						
					
				
			
			
				commit
				
					
						8078ffae5a
					
				
			
		
					 4 changed files with 132 additions and 102 deletions
				
			
		|  | @ -22,30 +22,75 @@ | ||||||
| 
 | 
 | ||||||
| import etcd3 | import etcd3 | ||||||
| import json | import json | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
|  | from functools import wraps | ||||||
|  | from uncloud import UncloudException | ||||||
|  | 
 | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def readable_errors(func): | ||||||
|  |     @wraps(func) | ||||||
|  |     def wrapper(*args, **kwargs): | ||||||
|  |         try: | ||||||
|  |             return func(*args, **kwargs) | ||||||
|  |         except etcd3.exceptions.ConnectionFailedError as e: | ||||||
|  |             raise UncloudException('Cannot connect to etcd: is etcd running and reachable? {}'.format(e)) | ||||||
|  |         except etcd3.exceptions.ConnectionTimeoutError as e: | ||||||
|  |             raise UncloudException('etcd connection timeout. {}'.format(e)) | ||||||
|  | 
 | ||||||
|  |     return wrapper | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class DB(object): | class DB(object): | ||||||
|     def __init__(self, config): |     def __init__(self, config, prefix="/"): | ||||||
|         self.config = config |         self.config = config | ||||||
|         self.prefix= '/nicohack/' | 
 | ||||||
|  |         # Root for everything | ||||||
|  |         self.base_prefix= '/nicohack' | ||||||
|  | 
 | ||||||
|  |         # Can be set from outside | ||||||
|  |         self.prefix = prefix | ||||||
|  | 
 | ||||||
|         self.connect() |         self.connect() | ||||||
| 
 | 
 | ||||||
|  |     @readable_errors | ||||||
|     def connect(self): |     def connect(self): | ||||||
|         self._db_clients = [] |         self._db_clients = [] | ||||||
|         for endpoint in self.config.etcd_hosts: |         for endpoint in self.config.etcd_hosts: | ||||||
|             client = etcd3.client(host=endpoint, **self.config.etcd_args) |             client = etcd3.client(host=endpoint, **self.config.etcd_args) | ||||||
|             self._db_clients.append(client) |             self._db_clients.append(client) | ||||||
| 
 | 
 | ||||||
|     def get_value(self, key): |     def realkey(self, key): | ||||||
|         pass |         return "{}{}/{}".format(self.base_prefix, | ||||||
|  |                                 self.prefix, | ||||||
|  |                                 key) | ||||||
| 
 | 
 | ||||||
|     def set(self, key, value, store_as_json=False, **kwargs): |     @readable_errors | ||||||
|         if store_as_json: |     def get(self, key, as_json=False, **kwargs): | ||||||
|  |         value, _ = self._db_clients[0].get(self.realkey(key), **kwargs) | ||||||
|  | 
 | ||||||
|  |         if as_json: | ||||||
|  |             value = json.loads(value) | ||||||
|  | 
 | ||||||
|  |         return value | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     @readable_errors | ||||||
|  |     def set(self, key, value, as_json=False, **kwargs): | ||||||
|  |         if as_json: | ||||||
|             value = json.dumps(value) |             value = json.dumps(value) | ||||||
| 
 | 
 | ||||||
|         key = "{}/{}".format(self.prefix, key) |  | ||||||
| 
 |  | ||||||
|         # FIXME: iterate over clients in case of failure ? |         # FIXME: iterate over clients in case of failure ? | ||||||
|         return self._db_clients[0].put(key, value, **kwargs) |         return self._db_clients[0].put(self.realkey(key), value, **kwargs) | ||||||
|  | 
 | ||||||
|  |     @readable_errors | ||||||
|  |     def increment(key, **kwargs): | ||||||
|  |         with self._db_clients[0].lock(key) as lock: | ||||||
|  |             value = int(self.get(self.realkey(key), **kwargs)) | ||||||
|  |             self.set(self.realkey(key), str(value + 1), **kwargs) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     endpoints = [ "https://etcd1.ungleich.ch:2379", |     endpoints = [ "https://etcd1.ungleich.ch:2379", | ||||||
|  |  | ||||||
|  | @ -25,93 +25,65 @@ import logging | ||||||
| import os.path | import os.path | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
|  | import json | ||||||
|  | 
 | ||||||
|  | from uncloud import UncloudException | ||||||
|  | from uncloud.hack.db import DB | ||||||
| 
 | 
 | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
| class Error(Exception): |  | ||||||
|     pass |  | ||||||
| 
 | 
 | ||||||
| 
 | class MAC(object): | ||||||
| class Mac(object): |     def __init__(self, config): | ||||||
|     def __init__(self): |         self.config = config | ||||||
|         self.base_dir = "." |         self.db = DB(config, prefix="/mac") | ||||||
| 
 | 
 | ||||||
|         self.prefix = 0x002000000000 |         self.prefix = 0x002000000000 | ||||||
|         #self.prefix = "{:012x}".format(self._prefix) |  | ||||||
| 
 |  | ||||||
|         self.free    = self.read_file("mac-free") |  | ||||||
|         self.last    = self.read_file("mac-last") |  | ||||||
| 
 |  | ||||||
|     def read_file(self, filename): |  | ||||||
|         fname = os.path.join(self.base_dir, filename) |  | ||||||
|         ret = [] |  | ||||||
| 
 |  | ||||||
|         try: |  | ||||||
|             with open(fname, "r") as fd: |  | ||||||
|                 ret = fd.readlines() |  | ||||||
|         except Exception as e: |  | ||||||
|             pass |  | ||||||
| 
 |  | ||||||
|         return ret |  | ||||||
| 
 |  | ||||||
|     def append_to_file(self, text, filename): |  | ||||||
|         fname = os.path.join(self.base_dir, filename) |  | ||||||
|         with open(fname, "a+") as fd: |  | ||||||
|             fd.write("{}\n".format(text)) |  | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def validate_mac(mac): |     def validate_mac(mac): | ||||||
|         if not re.match(r'([0-9A-F]{2}[-:]){5}[0-9A-F]{2}$', mac, re.I): |         if not re.match(r'([0-9A-F]{2}[-:]){5}[0-9A-F]{2}$', mac, re.I): | ||||||
|             raise Error("Not a valid mac address: %s" % mac) |             raise Error("Not a valid mac address: %s" % mac) | ||||||
| 
 | 
 | ||||||
|     def free_append(self, mac): |     def last_used_index(self): | ||||||
|         if mac in self.free: |         value = self.db.get("last_used_index") | ||||||
|             raise Error("Mac already in free database: %s" % mac) |         if not value: | ||||||
| 
 |             return 0 | ||||||
|         self.append_to_file(mac, "mac-free") |         return int(value) | ||||||
|         self.free    = self.read_file("mac-free") |  | ||||||
| 
 | 
 | ||||||
|  |     def last_used_mac(self): | ||||||
|  |         return self.int_to_mac(self.prefix + self.last_used_index()) | ||||||
| 
 | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def int_to_mac(number): |     def int_to_mac(number): | ||||||
|         b = number.to_bytes(6, byteorder="big") |         b = number.to_bytes(6, byteorder="big") | ||||||
|         return ':'.join(format(s, '02x') for s in b) |         return ':'.join(format(s, '02x') for s in b) | ||||||
| 
 | 
 | ||||||
|     def getnext(self): |     def get_next(self, vmuuid=None, as_int=False): | ||||||
| #        if self.free: | #        if self.free: | ||||||
| #            return self.free.pop() | #            return self.free.pop() | ||||||
| 
 | 
 | ||||||
| #        if not self.prefix: |         last_number = self.last_used_index() | ||||||
| #            raise Error("Cannot generate address without prefix - use prefix-set") |  | ||||||
| 
 |  | ||||||
|         if self.last: |  | ||||||
|             last_number = int(self.last[0], 16) |  | ||||||
| 
 | 
 | ||||||
|  |         # FIXME: compare to 48bit minus prefix length | ||||||
|         if last_number == int('0xffffff', 16): |         if last_number == int('0xffffff', 16): | ||||||
|                 raise Error("Exhausted all possible mac addresses - try to free some") |             raise UncloudException("Exhausted all possible mac addresses - try to free some") | ||||||
| 
 | 
 | ||||||
|         next_number = last_number + 1 |         next_number = last_number + 1 | ||||||
|         else: |  | ||||||
|             next_number = 0 |  | ||||||
| 
 |  | ||||||
|         next_number_string = "{:012x}".format(next_number) |         next_number_string = "{:012x}".format(next_number) | ||||||
| 
 | 
 | ||||||
|         next_mac_number = self.prefix + next_number |         next_mac_number = self.prefix + next_number | ||||||
|         next_mac = self.int_to_mac(next_mac_number) |         next_mac = self.int_to_mac(next_mac_number) | ||||||
| 
 | 
 | ||||||
|         with open(os.path.join(self.base_dir, "mac-last"), "w+") as fd: |         db_entry = {} | ||||||
|             fd.write("{}\n".format(next_number_string)) |         db_entry['vm_uuid'] = vmuuid | ||||||
|  |         db_entry['index'] = next_number | ||||||
|  |         db_entry['mac_address'] = next_mac | ||||||
| 
 | 
 | ||||||
|  |         self.db.set("used/{}".format(next_mac), | ||||||
|  |                     db_entry) | ||||||
|  | 
 | ||||||
|  |         if as_int: | ||||||
|  |             return next_mac_number | ||||||
|  |         else: | ||||||
|             return next_mac |             return next_mac | ||||||
| 
 |  | ||||||
|     @classmethod |  | ||||||
|     def commandline(cls): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     m = Mac() |  | ||||||
|     m.commandline() |  | ||||||
|     # print(m.free) |  | ||||||
|     #print(m.last) |  | ||||||
|     print(m.getnext()) |  | ||||||
|  |  | ||||||
|  | @ -2,10 +2,13 @@ import argparse | ||||||
| 
 | 
 | ||||||
| from uncloud.hack.vm import VM | from uncloud.hack.vm import VM | ||||||
| from uncloud.hack.config import Config | from uncloud.hack.config import Config | ||||||
|  | from uncloud.hack.mac import MAC | ||||||
| 
 | 
 | ||||||
| arg_parser = argparse.ArgumentParser('hack', add_help=False) | arg_parser = argparse.ArgumentParser('hack', add_help=False) | ||||||
|                                      #description="Commands that are unfinished - use at own risk") |                                      #description="Commands that are unfinished - use at own risk") | ||||||
| arg_parser.add_argument('--create-vm', action='store_true') | arg_parser.add_argument('--create-vm', action='store_true') | ||||||
|  | arg_parser.add_argument('--last-used-mac', action='store_true') | ||||||
|  | arg_parser.add_argument('--get-new-mac', action='store_true') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def main(arguments): | def main(arguments): | ||||||
|  | @ -16,3 +19,11 @@ def main(arguments): | ||||||
|         print("Creating VM") |         print("Creating VM") | ||||||
|         vm = VM(config) |         vm = VM(config) | ||||||
|         vm.create() |         vm.create() | ||||||
|  | 
 | ||||||
|  |     if arguments['last_used_mac']: | ||||||
|  |         m = MAC(config) | ||||||
|  |         print(m.last_used_mac()) | ||||||
|  | 
 | ||||||
|  |     if arguments['get_new_mac']: | ||||||
|  |         m = MAC(config).get_next() | ||||||
|  |         print(m.last_used()) | ||||||
|  |  | ||||||
|  | @ -25,33 +25,35 @@ import uuid | ||||||
| import os | import os | ||||||
| 
 | 
 | ||||||
| from uncloud.hack.db import DB | from uncloud.hack.db import DB | ||||||
|  | from uncloud.hack.mac import MAC | ||||||
| 
 | 
 | ||||||
| class VM(object): | class VM(object): | ||||||
|     def __init__(self, config): |     def __init__(self, config): | ||||||
|        self.config = config |        self.config = config | ||||||
|       self.db = DB(config) |        self.db = DB(config, prefix="/vm") | ||||||
| 
 | 
 | ||||||
|        self.hackprefix="/home/nico/vcs/uncloud/uncloud/hack/hackcloud" |        self.hackprefix="/home/nico/vcs/uncloud/uncloud/hack/hackcloud" | ||||||
|        self.qemu="/usr/bin/qemu-system-x86_64" |        self.qemu="/usr/bin/qemu-system-x86_64" | ||||||
|  |        self.accel="kvm" | ||||||
| 
 | 
 | ||||||
|        self.vm = {} |        self.vm = {} | ||||||
| 
 | 
 | ||||||
|       self.accel="kvm" |  | ||||||
| 
 | 
 | ||||||
| #      self.mac=$(./mac-gen.py) |  | ||||||
|       self.mac="42:00:00:00:00:42" |  | ||||||
|        self.owner="nico" |        self.owner="nico" | ||||||
|        self.bridge="br100" |        self.bridge="br100" | ||||||
| 
 | 
 | ||||||
|        self.ifup = os.path.join(self.hackprefix, "ifup.sh") |        self.ifup = os.path.join(self.hackprefix, "ifup.sh") | ||||||
|        self.ifdown = os.path.join(self.hackprefix, "ifdown.sh") |        self.ifdown = os.path.join(self.hackprefix, "ifdown.sh") | ||||||
| 
 | 
 | ||||||
|  |     def create(self): | ||||||
|        self.uuid = uuid.uuid4() |        self.uuid = uuid.uuid4() | ||||||
|        self.vm['uuid'] = str(self.uuid) |        self.vm['uuid'] = str(self.uuid) | ||||||
|        self.vm['memory'] = 1024 |        self.vm['memory'] = 1024 | ||||||
|        self.vm['cores'] = 2 |        self.vm['cores'] = 2 | ||||||
|        self.vm['os_image'] = os.path.join(self.hackprefix, "alpine-virt-3.11.2-x86_64.iso") |        self.vm['os_image'] = os.path.join(self.hackprefix, "alpine-virt-3.11.2-x86_64.iso") | ||||||
| 
 | 
 | ||||||
|  |        self.mac=MAC().next() | ||||||
|  | 
 | ||||||
|        self.vm['commandline' ] = [ "sudo", |        self.vm['commandline' ] = [ "sudo", | ||||||
|                                    "{}".format(self.qemu), |                                    "{}".format(self.qemu), | ||||||
|                                    "-name", "uncloud-{}".format(self.vm['uuid']), |                                    "-name", "uncloud-{}".format(self.vm['uuid']), | ||||||
|  | @ -64,9 +66,9 @@ class VM(object): | ||||||
|                                    "-device", "virtio-net-pci,netdev=netmain,id=net0,mac={}".format(self.mac) |                                    "-device", "virtio-net-pci,netdev=netmain,id=net0,mac={}".format(self.mac) | ||||||
|        ] |        ] | ||||||
| 
 | 
 | ||||||
|    def create(self): |        self.db.set(str(self.vm['uuid']), | ||||||
|       self.db.set("vm/{}".format(str(self.vm['uuid'])), |                    self.vm, | ||||||
|                   self.vm, store_as_json=True) |                    as_json=True) | ||||||
| 
 | 
 | ||||||
|        print(" ".join(self.vm['commandline'])) |        print(" ".join(self.vm['commandline'])) | ||||||
|        subprocess.run(self.vm['commandline']) |        subprocess.run(self.vm['commandline']) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue