#!/usr/bin/python -tt # # Copyright(c) FUJITSU Limited 2007. # # Cloning a virtual machine module. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import os import sys import stat import re import libxml2 import urlgrabber.progress as progress import util import commands import libvirt # # This class is the design paper for cloning a virtual machine. # class CloneDesign(object): def __init__(self): self._guest_name = None self._guest_conn = None self._guest_dom = None self._guest_devices = [] self._guest_devices_size = [] self._src_xml = None self._clone_name = None self._clone_devices = [] self._clone_rate = 1024 self._clone_mac = [] def get_guest_name(self): return self._guest_name def set_guest_name(self, guest_name): if len(guest_name) == 0: raise ValueError, "Source domain name must be needed" try: connect = None self._guest_conn = libvirt.open(connect) self._guest_dom = self._guest_conn.lookupByName(guest_name) self._src_xml = self._guest_dom.XMLDesc(0) self._guest_devices = _get_src_devices(self._src_xml) self._guest_devices_size = _get_src_devices_size(self._guest_devices) # # check status. Firt, shutoff domain is available. # status = self._guest_dom.info()[0] if status != libvirt.VIR_DOMAIN_SHUTOFF: raise RuntimeError, "Domain status must be shutoff" except libvirt.libvirtError, e: raise RuntimeError, "Domain %s is not found" % guest_name guest_name = property(get_guest_name, set_guest_name) def get_clone_name(self): return self._clone_name def set_clone_name(self, name): if name is not None : if re.match("^[0-9]+$", name): raise ValueError, "Domain name must not be numeric only" if re.match("^[a-zA-Z0-9_-]+$", name) == None: raise ValueError, "Domain name must be alphanumeric or _ or -" if len(name) > 50: raise ValueError, "Domain name must be less than or equal to 50 characters" if type(name) != type("string"): raise ValueError, "Domain name must be a string" try: self._guest_conn.lookupByName(name) raise RuntimeError, "Domain %s already exists" % name except libvirt.libvirtError, e: pass # # When Name is None, string _CLONE is automatically added # self._clone_name = name clone_name = property(get_clone_name, set_clone_name) def set_clone_devices(self, devices): if len(devices) == 0: raise ValueError, "Destinaton devices must be needed" self._clone_devices.append(devices) def get_clone_devices(self): return self._clone_devices clone_devices = property(get_clone_devices) def set_clone_mac(self, mac): form = re.match("^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$", str(mac)) if form is None: raise ValueError, "Invalid value for MAC address" self._clone_mac.append(mac) def get_clone_mac(self): return self._clone_mac clone_mac = property(get_clone_mac) def get_clone_rate(self): return self._clone_rate def set_clone_rate(self, rate): self._clone_rate = rate clone_rate = property(get_clone_rate, set_clone_rate) def get_guest_devices_size(self): return self._guest_devices_size guest_devices_size = property(get_guest_devices_size) def get_guest_devices(self): return self._guest_devices guest_devices = property(get_guest_devices) def get_guest_conn(self): return self._guest_conn guest_conn = property(get_guest_conn) def get_guest_dom(self): return self._guest_dom guest_dom = property(get_guest_dom) def get_src_xml(self): return self._src_xml src_xml = property(get_src_xml) # # start dumplicate # this function clones the virtual machine according to the ClonDesign object # def start_dumplicate(design): # # changing XML # doc = libxml2.parseDoc(design.src_xml) ctx = doc.xpathNewContext() # changing name node = ctx.xpathEval("/domain/name") if design.clone_name is None: name = node[0].getContent() + "_CLONE" node[0].setContent(name) else: node[0].setContent(design.clone_name) # changing devices count = ctx.xpathEval("count(/domain/devices/disk)") devices = design.get_clone_devices() for i in range(1, int(count+1)): node = ctx.xpathEval("/domain/devices/disk[%d]/source/@dev" % i) try: node[0].setContent(devices[i-1]) except Exception, e: raise ValueError, "Missing destination device for %s" % node[0].getContent() # changing uuid node = ctx.xpathEval("/domain/uuid") node[0].setContent(util.uuidToString(util.randomUUID())) # changing mac count = ctx.xpathEval("count(/domain/devices/interface/mac)") for i in range(1, int(count+1)): node = ctx.xpathEval("/domain/devices/interface[%d]/mac/@address" % i) try: node[0].setContent(design.clone_mac[i-1]) except Exception, e: node[0].setContent(util.randomMAC()) # do dupulicate # at this point, handling the cloning way. _do_dumplicate(design) # define xml design.guest_conn.defineXML(str(doc)) ctx.xpathFreeContext() doc.freeDoc() # # Get the List of source devices name from the XML # def _get_src_devices(xml): src_dev = [] doc = libxml2.parseDoc(xml) ctx = doc.xpathNewContext() count = ctx.xpathEval("count(/domain/devices/disk)") for i in range(1, int(count+1)): node = ctx.xpathEval("/domain/devices/disk[%d]/source/@dev" % i) src_dev.append(node[0].getContent()) ctx.xpathFreeContext() doc.freeDoc() return src_dev # # Get Sizes of source devices for display progress # def _get_src_devices_size(src_dev): size = [] for i in src_dev: mode = os.stat(i)[stat.ST_MODE] if stat.S_ISBLK(mode): ret,str = commands.getstatusoutput('fdisk -s %s' % i) size.append(int(str) * 1024) elif stat.S_ISREG(mode): size.append(os.path.getsize(i)) return size # # Now this Cloning way is reading and writting devices. # For future, there are many cloning way (e.g. forck snapshot cmd). # def _do_dumplicate(design): src_fd = None dst_fd = None dst_dev_iter = iter(design.clone_devices) dst_siz_iter = iter(design.guest_devices_size) try: for src_dev in design.guest_devices: dst_dev = dst_dev_iter.next() dst_siz = dst_siz_iter.next() src_fd = os.open(src_dev, os.O_RDONLY) dst_fd = os.open(dst_dev, os.O_WRONLY) size = dst_siz meter = progress.TextMeter() print "Cloning from %s to %s" % (src_dev, dst_dev) meter.start(size=size, text="Cloning domain...") i=0 while 1: l = os.read(src_fd, design.clone_rate) s = len(l) if s == 0: meter.end(size) break b = os.write(dst_fd, l) if s != b: meter.end(size) break i += s if i < size: meter.update(i) os.close(src_fd) src_fd = None os.close(dst_fd) dst_fd = None finally: if src_fd is not None: os.close(src_fd) if dst_fd is not None: os.close(dst_fd)