diff --git a/src/database.py b/src/database.py index 4758c9c7fac8b31a337c4de965c667557aede0fc..148482750ae9f18024fbd2254651e8e8172cd28e 100644 --- a/src/database.py +++ b/src/database.py @@ -20,7 +20,8 @@ class DataBase: vms = util.get_vms(db) pass vm_dict = dict(map(lambda vm_elem: (vm_elem.name, vm_elem), vms)) - return collections.OrderedDict([ (n,vm_dict[n]) for n in sorted(vm_dict)]) + return collections.OrderedDict([ (n,vm_dict[n]) for n in + sorted(vm_dict)]) self.vms = util.FutureCache(vms, self) self.hypervisors = vm.get_hypervisors(self) self.mount = util.FutureCache(mount.get_active_mounts) diff --git a/src/images.py b/src/images.py index cee4a4b236b96edf06ad2026f2ef23a5c0a4a1b4..be1985e225ff2e3d1d43f14cc0fbcf24cfa76ebd 100644 --- a/src/images.py +++ b/src/images.py @@ -31,4 +31,3 @@ def mount_all(db): vm.mount(db) pass pass - diff --git a/src/mount.py b/src/mount.py index fb3702fc3829f4b8d949f321fcb07706e0561426..214e9477436450ace104e7933283f1412ffa57c3 100644 --- a/src/mount.py +++ b/src/mount.py @@ -1,6 +1,6 @@ class Mount: - def __init__(self, parent, root, mount, mount_opt, tags, fstype, + def __init__(self, parent, root, mount, mount_opt, tags, fstype, super_opt, active): self.parent = parent self.root = root @@ -16,7 +16,7 @@ class Mount: pass pass self.is_mounted = True - + def add_child(self, child): self.child.append(child) pass @@ -25,14 +25,6 @@ class Mount: self.active = False for c in self.child: c.set_inactive() - - def unmount(self): - if self.is_mounted: - for c in self.child: - c.unmount() - pass - self.is_mounted = False - subprocess.check_call(['umount', self.mount]) pass pass diff --git a/src/parser.py b/src/parser.py index daeb5dbd89f5341d9dcdfe63fe79c479dc5cda4b..b9e5ea54d8f01b9fe0a195a2a6fea4e57c0c67ef 100755 --- a/src/parser.py +++ b/src/parser.py @@ -64,14 +64,14 @@ SPECIAL ATTRIBUTES _tag the XML tag the top level node of %(module)s.parse also contains - + _mtime modification time of XML source (0 if not file) _url the URL that was parsed [ see also %(module)s.Node ] -EXAMPLE - +EXAMPLE + Assume an XML document with the following content: <doc attr='doc_attr'> @@ -133,19 +133,19 @@ def xml_escape(s): result += '&' else: result += c - + return result class ChildAccessor: """Helper class for iterating over xml trees - + [see Node._child(...)] """ def __init__(self, root, path): self._root = root self._path = path self._elem = None - + def __getattr__(self, attr): if attr.startswith("__") and attr != "__": raise AttributeError(attr) @@ -172,7 +172,7 @@ class ChildAccessor: if self._elem == None: self._elem = self._root._child_path(self._path) return self._elem.__len__() - + def __getitem__(self, index): if self._elem == None: self._elem = self._root._child_path(self._path) @@ -209,7 +209,7 @@ class AttributeAccessor: node = node._parent i += 1 return None - + def __getitem__(self, index): """declared value of attr @@ -226,7 +226,7 @@ class AttributeAccessor: return node._attribute[self._name] except: return None - + elif index.__class__ == slice: if index.step == None: # Return first declaration within range @@ -261,7 +261,7 @@ class AttributeAccessor: return result else: raise TypeError("Illegal index kind %s" % index.__class__) - + class Iterator: """Fix for python 2.2""" @@ -277,7 +277,7 @@ class Iterator: raise StopIteration() self.current += 1 return self.elem[self.current - 1] - + class Node: def __init__(self, tag, line=None, attr={}): self._tag = tag @@ -298,15 +298,15 @@ class Node: """Copy node, but not children""" result = Node(self._tag, self._line, self._attribute) return result - + def __deepcopy__(self, memo): """Copy node and all children""" result = self.__copy__() for c in self._children: result._add(c.__deepcopy__(memo)) return result - - def __getattr__(self, attr): + + def __getattr__(self, attr): if attr.startswith("__") and attr != "__": raise AttributeError(attr) elif attr.startswith("_") and attr.endswith("_"): @@ -321,7 +321,7 @@ class Node: raise AttributeError(attr) else: return AttributeAccessor(self, attr) - + def _xml(self, indent=0, attr_key=None, node_key=None, width=80): """Generate a prettyprinted xml of the tree rooted in this node""" result = "" @@ -352,20 +352,20 @@ class Node: def _child(self, name=''): """Return all children matching 'name', '' matches all children""" return ChildAccessor(self, [ name ]) - + def _attr(self, name): """Return a accessor for attribute matching 'name' [ see AttributeAccessor ] """ return AttributeAccessor(self, name) - + def _child_path(self, path=None): if path == None or len(path) == 0: # Return all immediate children return self._children else: - # Return children matching the path given + # Return children matching the path given result = [] for c in self._children: if c._tag == path[0] or path[0] == '': @@ -404,11 +404,11 @@ class Comment: """Copy self""" result = Comment(self._content, self._line) return result - + def __deepcopy__(self, memo): """Copy node and all children""" return self.__copy__() - + def _xml(self, indent=0, attr_key=None, node_key=None, width=80): """Generate a prettyprinted xml of the tree rooted in this node""" result = "%s<!--" % (" " * indent) @@ -424,13 +424,13 @@ class Parser: """Parse the given url into .tree augment the tree with - ._mtime == url modification time if url is a file, + ._mtime == url modification time if url is a file, current time otherwise ._url == the url that was parsed """ self.include_comments = include_comments self.tree = None - + # Create a xml parser self.parser = xml.parsers.expat.ParserCreate() self.parser.StartElementHandler = self.startElement @@ -451,8 +451,8 @@ class Parser: pass self.tree._mtime = mtime self.tree._url = url - - + + # # This will be called by the parser for the top hostinfo node # @@ -467,12 +467,12 @@ class Parser: for (name, value) in attrs.items(): child._set(name, value) self.current.append(child) - + def endElement(self, tag): child = self.current.pop() parent = self.current[-1] parent._add(child) - + def characters(self, char): if not char.isspace(): self.current[-1]._append(char) @@ -487,7 +487,7 @@ def parse(source, include_comments=False, url=None): (scheme, host, path, param, query, frag) = urlparse(source) if not scheme: data = open(source).read() - mtime = os.stat(source)[stat.ST_MTIME] + mtime = os.stat(source)[stat.ST_MTIME] pass else: u = urllib2.urlopen(source) @@ -501,9 +501,9 @@ def parse(source, include_comments=False, url=None): pass pass pass - return Parser(data, url=url or source, mtime=mtime, + return Parser(data, url=url or source, mtime=mtime, include_comments=include_comments).tree def parse_string(source, include_comments=False): - return Parser(source, url=None, mtime=None, + return Parser(source, url=None, mtime=None, include_comments=include_comments).tree diff --git a/src/util.py b/src/util.py index 5d19c66cef5be1f684dd7647c698235a19139b60..584251cdf681c2472c28385aace1c0c7a34a2133 100644 --- a/src/util.py +++ b/src/util.py @@ -1,5 +1,4 @@ import os -import calendar import ipaddress import subprocess import re @@ -47,26 +46,25 @@ def qemu_img_info(img): result = AttributeDict() for l in run('qemu-img', 'info', '--force-share', img): m = re.match('^file format:\\s*(\\S+)\\s*$', l) - if m: + if m: result.format = m.group(1) continue m = re.match('^virtual size:.*[(]([0-9]+) bytes[)]', l) - if m: + if m: result.size = int(m.group(1)) continue pass return result def is_mounted(path): - return (os.path.exists(path) and + return (os.path.exists(path) and os.stat(path).st_dev != os.stat(os.path.dirname(path)).st_dev) -class AttributeDict(dict): +class AttributeDict(dict): __getattr__ = dict.__getitem__ __setattr__ = dict.__setitem__ class FutureCache: - def __init__(self, func, *args): self._func = func self._args = args @@ -106,7 +104,7 @@ class FutureCache: def clear_cache(self): self._cache = None pass - + class InvalidHostException(Exception): def __init__(self, allowed=[]): @@ -114,11 +112,14 @@ class InvalidHostException(Exception): super(Exception, self).__init__('Invalid host (allowed=%s)' % allowed) pass +class MountException(Exception): + pass + class NeedCommandException(Exception): def __init__(self, command): self.command = command - super(Exception, self).__init__("Run '%s'" % + super(Exception, self).__init__("Run '%s'" % (' '.join(map(str, command)))) pass @@ -129,18 +130,18 @@ class NeedOptionException(Exception): def __init__(self, option): self.option = option super(Exception, self).__init__("You need to specify '%s'" % option) - + class Interface: def __init__(self, db): - self.db = db + self.db = db def host(db): net = db.prefix[1][0] result = {} - for h,i,b in [ (i.name[2], IPAddress(i.address[0]), + for h,i,b in [ (i.name[2], IPAddress(i.address[0]), i.bridge[1]) for i in db.tree._host_._interface_._ipv6_ - if (i.address[0] and + if (i.address[0] and IPAddress(i.address[0]) in net)]: result[h] = (i, b) pass @@ -155,9 +156,9 @@ class Interface: except KeyError: raise Exception('%s is not reachable from this machine' % host) pass - + def bridge(self, host): - try: + try: return self.host[host][1] except KeyError: raise Exception('%s is not reachable from this machine' % host) @@ -172,16 +173,18 @@ class VirtualMachine: self.qemu = qemu self.netgroups = netgroups self.correct = True + self.physical = False if db.option.virtualize_physical: self.name = qemu.name[0] self.physical = True encoded_name = self.name.encode('utf-8') name_hash = sha512(encoded_name).digest() - hypervisor_index = (int.from_bytes(name_hash) % - len(db.option.check_hypervisor)) - self.host = list(sorted(db.option.check_hypervisor))[hypervisor_index] + if not db.option.vm_xml: + hypervisor_index = (int.from_bytes(name_hash) % + len(db.option.check_hypervisor)) + self.host = list(sorted(db.option.check_hypervisor))[hypervisor_index] - if not db.option.create: + if not db.option.create and not db.option.vm_xml: if (self.host not in db.hypervisors or self.name not in db.hypervisors[self.host].domain): self.correct = False @@ -192,19 +195,21 @@ class VirtualMachine: pass pass - if db.option.virtualize_physical and db.option.create: + if db.option.virtualize_physical and (db.option.create or + db.option.vm_xml): self.memory = db.option.physical_ram self.arch = db.option.physical_arch self.machine_type = db.option.physical_machine_type self.cpu_model = db.option.physical_model self.cpus = db.option.physical_cores + h = "" if db.option.vm_xml else self.host uuid_seed=(int.from_bytes(encoded_name) + int.from_bytes(self.memory.encode('utf-8')) + int.from_bytes(self.arch.encode('utf-8')) + int.from_bytes(self.machine_type.encode('utf-8')) + int.from_bytes(self.cpu_model.encode('utf-8')) + int.from_bytes(self.cpus.encode('utf-8')) - + int.from_bytes(str(self.host).encode('utf-8'))) + + int.from_bytes(str(h).encode('utf-8'))) while uuid_seed < (1<<100): uuid_seed=uuid_seed*uuid_seed pass @@ -231,7 +236,6 @@ class VirtualMachine: self.machine_type = qemu_tag.machine[0] self.cpu_model = qemu_tag.cpu_model[0] self.cpus = qemu_tag.cpus[0] - self.physical = False self.uuid = qemu_tag.uuid[0] pass def regular_disks(disk_tree): @@ -253,18 +257,19 @@ class VirtualMachine: def physical_disks(): disk = [] pd = qemu._qemu_[0] - if db.option.create: + if db.option.create or db.option.vm_xml: for d_name in db.option.physical_disks: size = db.option.physical_disks[d_name] path = db.option.physical_disk_paths[d_name] or '/tw/general' + h = "" if db.option.vm_xml else self.host disk.append(Disk(db=db, vm=self, - host=self.host, + host=h, root=path, id=d_name, raid=False, format='qcow2', size=size)) attr = {"root": path, - "host": self.host, + "host": h, "id": d_name, "size": size, "format": 'qcow2' @@ -276,7 +281,8 @@ class VirtualMachine: return disk def disk(): disk = [] - if db.option.virtualize_physical and db.option.create: + if db.option.virtualize_physical and (db.option.create or + db.option.vm_xml): disk = physical_disks() pass else: @@ -317,7 +323,7 @@ class VirtualMachine: if not db.option.virtualize_physical: b = i.qemu_bridge[0] or 'main' pass - elif db.option.create: + elif db.option.create or db.option.vm_xml: bridge_len = len(db.option.physical_interface_bridge) if index < bridge_len: b = db.option.physical_interface_bridge[index] @@ -336,7 +342,7 @@ class VirtualMachine: if not self.db.option.virtualize_physical: l = self.qemu._parent._interface_ pass - elif self.db.option.create: + elif self.db.option.create or self.db.option.vm_xml: l = self.qemu._interface_ pass else: @@ -511,29 +517,29 @@ class Disk: def check_mounted_image(self, db): if not os.path.exists(self.mount_at): - raise Exception('%s does not exists' % self.mount_at) + raise MountException('%s does not exists' % self.mount_at) if not is_mounted(self.mount_at): - raise Exception('%s is not mounted' % self.mount_at) + raise MountException('%s is not mounted' % self.mount_at) if not os.path.exists(self.link_target): - raise Exception('%s does not exist' % self.link_target) + raise MountException('%s does not exist' % self.link_target) if not is_mounted(self.link_target): - raise Exception('%s is not mounted' % self.link_target) + raise MountException('%s is not mounted' % self.link_target) current = qemu_img_info(self.link_target) if current.size < self.size: - raise Exception('%s invalid size, fix on %s' % + raise MountException('%s invalid size, fix on %s' % (self.mount_at, self.host)) if current.format != self.format: - raise Exception('%s invalid format, fix on %s' % + raise MountException('%s invalid format, fix on %s' % (self.mount_at, self.host)) def check_local_image(self, db): result = True if not os.path.exists(self.image_path): cmd = [ str(a) for a in [ 'qemu-img', 'create', - '-f', self.format, + '-f', self.format, self.image_path, self.size ] ] if db.option.create: - print("Running '%s'" % ' '.join(cmd), file=sys.stderr) + print("Running '%s'" % ' '.join(cmd), file=sys.stderr) run(*cmd) pass else: @@ -541,7 +547,7 @@ class Disk: pass current = qemu_img_info(self.image_path) if current.size < self.size: - print('qemu-img resize %s %s' % (self.image_path, + print('qemu-img resize %s %s' % (self.image_path, self.size), file=sys.stderr) result = False if current.format != self.format: @@ -549,7 +555,7 @@ class Disk: print('qemu-img convert -O %s %s' % (self.format, self.image_path), file=sys.stderr) result = False - return result + return result def check(self, db): #self.vm.check(options) @@ -563,8 +569,8 @@ class Disk: def __repr__(self): return os.path.normpath('%s:/%s/%s (%s, %s)' % ( - self.host, self.root, - self.file, self.format, + self.host, self.root, + self.file, self.format, self.size)) pass @@ -652,15 +658,20 @@ def get_vms(db): return get_from_netgroup(db, lambda h: not h._qemu_) def get_physical(db): - filter_hosts = lambda h: h._qemu_ or h.name[0] in db.option.check_hypervisor + filter_hosts = lambda h: h._qemu_ or (db.option.check_hypervisor and + h.name[0] in db.option.check_hypervisor) return get_from_netgroup(db, filter_hosts) def get_from_netgroup(db, filter_host): result = set() + done = set() ngs = netgroups(db.tree, filter_host=filter_host) for g in ngs: if (not db.option.check_netgroup or ngs[g].name in db.option.check_netgroup): for m in ngs[g].hosts(): + if m in done: + continue + done.add(m) groups = [gr for gr in ngs if m in ngs[g].hosts()] result.add(VirtualMachine(db, m, groups)) pass @@ -682,7 +693,7 @@ def get_dev_with_prefix(prefix): addr[dev] += l pass for dev,s in addr.items(): - match = [ IPNetwork(n) for n,a in + match = [ IPNetwork(n) for n,a in re.findall('\\s(([0-9a-f.:]+)/[0-9]+)\\s', s) if IPAddress(a) in net ] if match: diff --git a/src/vm.py b/src/vm.py index 496621585fbc45d4aeea0906c7ea472a495c19b5..65c01aa12d9165d50e6ba3051e4fc7fd4342855b 100644 --- a/src/vm.py +++ b/src/vm.py @@ -1,5 +1,4 @@ import util -import subprocess import parser import libvirt import sys @@ -120,7 +119,7 @@ class Hypervisor: def get_storage_pool(name): return self.libvirt.storagePoolLookupByName(name) pass - def get_storage_pool_list(): + def get_storage_pool_list(): return dict([ (p.name(), p) for p in self.libvirt.listAllStoragePools() ]) self.storage = util.FutureCache(get_storage_pool_list) @@ -148,12 +147,14 @@ class Hypervisor: if not attr.startswith('__'): return getattr(self.libvirt, attr) raise AttributeError() - - + + def get_hypervisors(db): hosts = set() if db.option.virtualize_physical: if not db.option.check_hypervisor: + if db.option.vm_xml: + return hosts raise util.NeedOptionException("--check-hypervisor") for h in db.option.check_hypervisor: hosts.add(h) @@ -261,7 +262,7 @@ def check_interfaces(virtual_machine, hypervisor): present.add((i._mac_[0].address[0], i._source_[0].bridge[0])) pass return wanted == present - + def check_RNG(host, hypervisor, attach): def has_rng(tree): for rng in tree._devices_._rng_: @@ -441,11 +442,10 @@ def print_xml(db): print('\n'.join([ 'Wrong hypervisor host (%s not in %s),' % (db.option.host, e.allowed), - 'XML might not reflect reality']), file=sys.stderr) + 'XML might not reflect reality']), file=sys.stderr) pass for c in command: print(' '.join(map(str, c)), file=sys.stderr) pass print(domain_xml(vm)) pass -