mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-01-24 02:04:13 +03:00
clone: Bunch of API cleanup
Simplify imports, drop back compat stuff, drop needless property handling.
This commit is contained in:
parent
351ff1ae57
commit
802ed1cba6
@ -20,8 +20,7 @@ import logging
|
||||
|
||||
from tests import utils
|
||||
|
||||
from virtinst import CloneManager
|
||||
CloneDesign = CloneManager.CloneDesign
|
||||
from virtinst import Cloner
|
||||
|
||||
ORIG_NAME = "clone-orig"
|
||||
CLONE_NAME = "clone-new"
|
||||
@ -70,7 +69,7 @@ class TestClone(unittest.TestCase):
|
||||
infile = os.path.join(clonexml_dir, filebase + "-in.xml")
|
||||
in_content = utils.read_file(infile)
|
||||
|
||||
cloneobj = CloneDesign(conn=useconn or conn)
|
||||
cloneobj = Cloner(useconn or conn)
|
||||
cloneobj.original_xml = in_content
|
||||
for force in force_list or []:
|
||||
cloneobj.force_target = force
|
||||
@ -90,20 +89,14 @@ class TestClone(unittest.TestCase):
|
||||
cloneobj.clone_name = "clone-new"
|
||||
cloneobj.clone_uuid = "12345678-1234-1234-1234-123456789012"
|
||||
|
||||
cloneobj.clone_mac = "22:23:45:67:89:00"
|
||||
cloneobj.clone_mac = "22:23:45:67:89:01"
|
||||
cloneobj.clone_macs = ["22:23:45:67:89:00", "22:23:45:67:89:01"]
|
||||
|
||||
if disks is not None:
|
||||
for disk in disks:
|
||||
cloneobj.clone_devices = disk
|
||||
else:
|
||||
cloneobj.clone_devices = "/dev/loop0"
|
||||
cloneobj.clone_devices = "/tmp/clone2.img"
|
||||
cloneobj.clone_devices = "/tmp/clone3.img"
|
||||
cloneobj.clone_devices = "/tmp/clone4.img"
|
||||
cloneobj.clone_devices = "/tmp/clone5.img"
|
||||
cloneobj.clone_devices = None
|
||||
if disks is None:
|
||||
disks = ["/dev/loop0", "/tmp/clone2.img",
|
||||
"/tmp/clone3.img", "/tmp/clone4.img",
|
||||
"/tmp/clone5.img", None]
|
||||
|
||||
cloneobj.clone_paths = disks
|
||||
return cloneobj
|
||||
|
||||
def _clone_compare(self, cloneobj, outbase):
|
||||
@ -133,7 +126,7 @@ class TestClone(unittest.TestCase):
|
||||
try:
|
||||
vm = conn.defineXML(utils.read_file(infile))
|
||||
|
||||
cloneobj = CloneDesign(conn=conn)
|
||||
cloneobj = Cloner(conn)
|
||||
cloneobj.original_guest = ORIG_NAME
|
||||
|
||||
cloneobj = self._default_clone_values(cloneobj)
|
||||
|
@ -210,9 +210,8 @@ args = {
|
||||
'valid' : ['some.valid-name_9']},
|
||||
'clone_uuid': {'invalid' : [0],
|
||||
'valid' : ['12345678123456781234567812345678']},
|
||||
'clone_mac' : {'invalid' : ['badformat'],
|
||||
'clone_macs' : {'invalid' : ['badformat'],
|
||||
'valid' : ['AA:BB:CC:DD:EE:FF']},
|
||||
'clone_bs' : {'invalid' : [], 'valid' : ['valid']},
|
||||
},
|
||||
|
||||
'inputdev' : {
|
||||
@ -492,11 +491,11 @@ class TestValidation(unittest.TestCase):
|
||||
#self._testArgs(inst_obj, virtinst.ImageInstaller, 'installer')
|
||||
self._testArgs(inst_obj, virtinst.ImageInstaller, label)
|
||||
|
||||
def testCloneManager(self):
|
||||
def testCloner(self):
|
||||
label = 'clonedesign'
|
||||
for conn in self._getInitConns(label):
|
||||
cman = virtinst.CloneManager.CloneDesign(conn)
|
||||
self._testArgs(cman, virtinst.CloneManager.CloneDesign, label)
|
||||
cman = virtinst.Cloner(conn)
|
||||
self._testArgs(cman, virtinst.Cloner, label)
|
||||
|
||||
def testInputDev(self):
|
||||
label = 'inputdev'
|
||||
|
49
virt-clone
49
virt-clone
@ -20,15 +20,14 @@
|
||||
# MA 02110-1301 USA.
|
||||
|
||||
|
||||
import optparse
|
||||
from optparse import OptionGroup
|
||||
import sys
|
||||
import logging
|
||||
import optparse
|
||||
import sys
|
||||
|
||||
import urlgrabber.progress as progress
|
||||
|
||||
import virtinst.CloneManager as clmgr
|
||||
import virtinst.cli as cli
|
||||
from virtinst import Cloner
|
||||
from virtinst.cli import fail, print_stdout, print_stderr
|
||||
from virtinst.User import User
|
||||
|
||||
@ -37,7 +36,7 @@ from virtinst.User import User
|
||||
def get_clone_name(new_name, auto_clone, design):
|
||||
if not new_name and auto_clone:
|
||||
# Generate a name to use
|
||||
new_name = clmgr.generate_clone_name(design)
|
||||
new_name = design.generate_clone_name()
|
||||
logging.debug("Auto-generated clone name '%s'", new_name)
|
||||
|
||||
prompt_txt = _("What is the name for the cloned virtual machine?")
|
||||
@ -66,13 +65,9 @@ def get_original_guest(guest_name, origfile, design):
|
||||
|
||||
|
||||
def get_clone_macaddr(new_mac, design):
|
||||
if new_mac is None:
|
||||
pass
|
||||
elif new_mac[0] == "RANDOM":
|
||||
new_mac = None
|
||||
else:
|
||||
for i in new_mac:
|
||||
design.clone_mac = i
|
||||
if new_mac is None or new_mac[0] == "RANDOM":
|
||||
return
|
||||
design.clone_macs = new_mac
|
||||
|
||||
|
||||
def get_clone_uuid(new_uuid, design):
|
||||
@ -80,15 +75,14 @@ def get_clone_uuid(new_uuid, design):
|
||||
design.clone_uuid = new_uuid
|
||||
|
||||
|
||||
def get_clone_diskfile(new_diskfiles, design, conn, preserve=False,
|
||||
def get_clone_diskfile(new_diskfiles, design, preserve=False,
|
||||
auto_clone=False):
|
||||
if new_diskfiles is None:
|
||||
new_diskfiles = [None]
|
||||
|
||||
conn = design.original_conn
|
||||
|
||||
newidx = 0
|
||||
for origdev in design.original_devices:
|
||||
clonepaths = []
|
||||
for origpath in [d.path for d in design.original_disks]:
|
||||
if len(new_diskfiles) <= newidx:
|
||||
# Extend the new/passed paths list with None if it's not
|
||||
# long enough
|
||||
@ -96,16 +90,17 @@ def get_clone_diskfile(new_diskfiles, design, conn, preserve=False,
|
||||
disk = new_diskfiles[newidx]
|
||||
|
||||
if disk is None and auto_clone:
|
||||
disk = clmgr.generate_clone_disk_path(origdev, design)
|
||||
disk = design.generate_clone_disk_path(origpath)
|
||||
|
||||
if origdev is None:
|
||||
if origpath is None:
|
||||
devpath = None
|
||||
else:
|
||||
dev = _check_disk(conn, disk, origdev, preserve)
|
||||
dev = _check_disk(design.conn, disk, origpath, preserve)
|
||||
devpath = dev.path
|
||||
|
||||
design.clone_devices = devpath
|
||||
clonepaths.append(devpath)
|
||||
newidx += 1
|
||||
design.clone_paths = clonepaths
|
||||
|
||||
|
||||
def _check_disk(conn, clone_path, orig_path, preserve):
|
||||
@ -140,7 +135,7 @@ def parse_args():
|
||||
"like MAC address, name, etc. The VM contents are not altered."))
|
||||
cli.add_connect_option(parser)
|
||||
|
||||
geng = OptionGroup(parser, _("General Options"))
|
||||
geng = optparse.OptionGroup(parser, _("General Options"))
|
||||
geng.add_option("-o", "--original", dest="original_guest",
|
||||
help=_("Name of the original guest; "
|
||||
"The status must be shut off or paused."))
|
||||
@ -156,7 +151,7 @@ def parse_args():
|
||||
"randomly generated UUID"))
|
||||
parser.add_option_group(geng)
|
||||
|
||||
stog = OptionGroup(parser, _("Storage Configuration"))
|
||||
stog = optparse.OptionGroup(parser, _("Storage Configuration"))
|
||||
stog.add_option("-f", "--file", dest="new_diskfile", action="append",
|
||||
help=_("New file to use as the disk image for the "
|
||||
"new guest"))
|
||||
@ -173,13 +168,13 @@ def parse_args():
|
||||
"via --file are preserved unchanged"))
|
||||
parser.add_option_group(stog)
|
||||
|
||||
netg = OptionGroup(parser, _("Networking Configuration"))
|
||||
netg = optparse.OptionGroup(parser, _("Networking Configuration"))
|
||||
netg.add_option("-m", "--mac", dest="new_mac", action="append",
|
||||
help=_("New fixed MAC address for the clone guest. "
|
||||
"Default is a randomly generated MAC"))
|
||||
parser.add_option_group(netg)
|
||||
|
||||
misc = OptionGroup(parser, _("Miscellaneous Options"))
|
||||
misc = optparse.OptionGroup(parser, _("Miscellaneous Options"))
|
||||
misc.add_option("", "--print-xml", action="store_true", dest="xmlonly",
|
||||
help=_("Print the generated domain XML rather than define "
|
||||
"and clone the guest."))
|
||||
@ -227,7 +222,7 @@ def main(conn=None):
|
||||
if not User.current().has_priv(User.PRIV_CLONE, conn.getURI()):
|
||||
fail(_("Must be privileged to clone Xen guests"))
|
||||
|
||||
design = clmgr.CloneDesign(conn=conn)
|
||||
design = Cloner(conn)
|
||||
|
||||
design.clone_running = options.clone_running
|
||||
design.replace = bool(options.replace)
|
||||
@ -245,7 +240,7 @@ def main(conn=None):
|
||||
# get_clone_diskfile knows how many new disk paths it needs
|
||||
design.setup_original()
|
||||
|
||||
get_clone_diskfile(options.new_diskfile, design, conn,
|
||||
get_clone_diskfile(options.new_diskfile, design,
|
||||
not options.preserve, options.auto_clone)
|
||||
|
||||
# setup design object
|
||||
@ -256,7 +251,7 @@ def main(conn=None):
|
||||
else:
|
||||
# start cloning
|
||||
meter = progress.TextMeter(fo=sys.stdout)
|
||||
clmgr.start_duplicate(design, meter)
|
||||
design.start_duplicate(meter)
|
||||
|
||||
print_stdout("")
|
||||
print_stdout(_("Clone '%s' created successfully.") % design.clone_name)
|
||||
|
@ -32,8 +32,7 @@ from virtManager.storagebrowse import vmmStorageBrowser
|
||||
from virtManager import util
|
||||
|
||||
import virtinst
|
||||
from virtinst import CloneManager
|
||||
from virtinst.CloneManager import CloneDesign
|
||||
from virtinst import Cloner
|
||||
from virtinst import VirtualNetworkInterface
|
||||
|
||||
STORAGE_COMBO_CLONE = 0
|
||||
@ -256,17 +255,16 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
self.clone_design = self.build_new_clone_design()
|
||||
|
||||
def build_new_clone_design(self, new_name=None):
|
||||
cd = CloneDesign(self.conn.vmm)
|
||||
cd.original_guest = self.orig_vm.get_name()
|
||||
design = Cloner(self.conn.vmm)
|
||||
design.original_guest = self.orig_vm.get_name()
|
||||
if not new_name:
|
||||
new_name = virtinst.CloneManager.generate_clone_name(cd)
|
||||
cd.clone_name = new_name
|
||||
new_name = design.generate_clone_name()
|
||||
design.clone_name = new_name
|
||||
|
||||
# Erase any clone_policy from the original design, so that we
|
||||
# get the entire device list.
|
||||
cd.clone_policy = []
|
||||
|
||||
return cd
|
||||
design.clone_policy = []
|
||||
return design
|
||||
|
||||
def populate_network_list(self):
|
||||
net_box = self.widget("clone-network-box")
|
||||
@ -420,8 +418,8 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
logging.debug("Original path: %s\nGenerated clone path: %s",
|
||||
path, clone_path)
|
||||
|
||||
cd.clone_devices = clone_path
|
||||
size = cd.original_virtual_disks[0].size
|
||||
cd.clone_paths = clone_path
|
||||
size = cd.original_disks[0].size
|
||||
except Exception, e:
|
||||
logging.exception("Error setting generated path '%s'",
|
||||
clone_path)
|
||||
@ -438,8 +436,8 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
cd = self.clone_design
|
||||
if not newname:
|
||||
newname = cd.clone_name
|
||||
clone_path = CloneManager.generate_clone_disk_path(origpath, cd,
|
||||
newname=newname)
|
||||
clone_path = cd.generate_clone_disk_path(origpath,
|
||||
newname=newname)
|
||||
return clone_path
|
||||
|
||||
def set_paths_from_clone_name(self):
|
||||
@ -582,7 +580,7 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
skip_targets.append(target)
|
||||
|
||||
self.clone_design.skip_target = skip_targets
|
||||
self.clone_design.clone_devices = new_disks
|
||||
self.clone_design.clone_paths = new_disks
|
||||
|
||||
# If any storage cannot be cloned or shared, don't allow cloning
|
||||
clone = True
|
||||
@ -707,7 +705,7 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
|
||||
new_path = self.widget("change-storage-new").get_text()
|
||||
|
||||
if virtinst.VirtualDisk.path_exists(self.clone_design.original_conn,
|
||||
if virtinst.VirtualDisk.path_exists(self.clone_design.conn,
|
||||
new_path):
|
||||
res = self.err.yes_no(_("Cloning will overwrite the existing "
|
||||
"file"),
|
||||
@ -718,7 +716,7 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
return
|
||||
|
||||
try:
|
||||
self.clone_design.clone_devices = new_path
|
||||
self.clone_design.clone_paths = new_path
|
||||
self.populate_storage_lists()
|
||||
row[STORAGE_INFO_NEW_PATH] = new_path
|
||||
row[STORAGE_INFO_MANUAL_PATH] = True
|
||||
@ -742,10 +740,11 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
cd = self.build_new_clone_design(name)
|
||||
|
||||
# Set MAC addresses
|
||||
clonemacs = []
|
||||
for mac in self.mac_list:
|
||||
row = self.net_list[mac]
|
||||
new_mac = row[NETWORK_INFO_NEW_MAC]
|
||||
cd.clone_mac = new_mac
|
||||
clonemacs.append(row[NETWORK_INFO_NEW_MAC])
|
||||
cd.clone_macs = clonemacs
|
||||
|
||||
skip_targets = []
|
||||
new_paths = []
|
||||
@ -770,7 +769,7 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
|
||||
cd.skip_target = skip_targets
|
||||
cd.setup_original()
|
||||
cd.clone_devices = new_paths
|
||||
cd.clone_paths = new_paths
|
||||
|
||||
if warn_str:
|
||||
res = self.err.ok_cancel(
|
||||
@ -802,7 +801,7 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
title = (_("Creating virtual machine clone '%s'") %
|
||||
self.clone_design.clone_name)
|
||||
text = title
|
||||
if self.clone_design.clone_devices:
|
||||
if self.clone_design.clone_disks:
|
||||
text = title + _(" and selected storage (this may take a while)")
|
||||
|
||||
progWin = vmmAsyncJob(self._async_clone, [], title, text, self.topwin)
|
||||
@ -831,11 +830,11 @@ class vmmCloneVM(vmmGObjectUI):
|
||||
meter = asyncjob.get_meter()
|
||||
|
||||
self.clone_design.orig_connection = newconn
|
||||
for d in self.clone_design.clone_virtual_disks:
|
||||
for d in self.clone_design.clone_disks:
|
||||
d.conn = newconn
|
||||
|
||||
self.clone_design.setup()
|
||||
CloneManager.start_duplicate(self.clone_design, meter)
|
||||
self.clone_design.start_duplicate(meter)
|
||||
finally:
|
||||
self.orig_vm.set_cloning(False)
|
||||
|
||||
|
@ -18,20 +18,6 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
# MA 02110-1301 USA.
|
||||
"""
|
||||
Module for cloning an existing virtual machine
|
||||
|
||||
General workflow for cloning:
|
||||
|
||||
- Instantiate CloneDesign. Requires at least a libvirt connection and
|
||||
either the name of an existing domain to clone (original_guest), or
|
||||
a string of libvirt xml representing the guest to clone
|
||||
(original_guest_xml)
|
||||
|
||||
- Run 'setup' from the CloneDesign instance to prep for cloning
|
||||
|
||||
- Run 'CloneManager.start_duplicate', passing the CloneDesign instance
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
@ -47,113 +33,44 @@ from virtinst import Storage
|
||||
from virtinst import util
|
||||
|
||||
|
||||
def _listify(val):
|
||||
"""
|
||||
Return (was_val_a_list, listified_val)
|
||||
"""
|
||||
if type(val) is list:
|
||||
return True, val
|
||||
else:
|
||||
return False, [val]
|
||||
|
||||
|
||||
def generate_clone_disk_path(origpath, design, newname=None):
|
||||
origname = design.original_guest
|
||||
newname = newname or design.clone_name
|
||||
path = origpath
|
||||
suffix = ""
|
||||
|
||||
# Try to split the suffix off the existing disk name. Ex.
|
||||
# foobar.img -> foobar-clone.img
|
||||
#
|
||||
# If the suffix is greater than 7 characters, assume it isn't
|
||||
# a file extension and is part of the disk name, at which point
|
||||
# just stick '-clone' on the end.
|
||||
if origpath.count(".") and len(origpath.rsplit(".", 1)[1]) <= 7:
|
||||
path, suffix = origpath.rsplit(".", 1)
|
||||
suffix = "." + suffix
|
||||
|
||||
dirname = os.path.dirname(path)
|
||||
basename = os.path.basename(path)
|
||||
|
||||
clonebase = basename + "-clone"
|
||||
if origname and basename == origname:
|
||||
clonebase = newname
|
||||
|
||||
clonebase = os.path.join(dirname, clonebase)
|
||||
return util.generate_name(
|
||||
clonebase,
|
||||
lambda p: VirtualDisk.path_exists(design.original_conn, p),
|
||||
suffix,
|
||||
lib_collision=False)
|
||||
|
||||
|
||||
def generate_clone_name(design):
|
||||
# If the orig name is "foo-clone", we don't want the clone to be
|
||||
# "foo-clone-clone", we want "foo-clone1"
|
||||
basename = design.original_guest
|
||||
|
||||
match = re.search("-clone[1-9]*$", basename)
|
||||
start_num = 0
|
||||
if match:
|
||||
num_match = re.search("[1-9]+$", match.group())
|
||||
if num_match:
|
||||
start_num = int(str(num_match.group()))
|
||||
basename = basename.replace(match.group(), "")
|
||||
|
||||
basename = basename + "-clone"
|
||||
return util.generate_name(basename,
|
||||
design.original_conn.lookupByName,
|
||||
sep="", start_num=start_num)
|
||||
|
||||
|
||||
#
|
||||
# This class is the design paper for a clone virtual machine.
|
||||
#
|
||||
class CloneDesign(object):
|
||||
class Cloner(object):
|
||||
|
||||
# Reasons why we don't default to cloning.
|
||||
CLONE_POLICY_NO_READONLY = 1
|
||||
CLONE_POLICY_NO_SHAREABLE = 2
|
||||
CLONE_POLICY_NO_EMPTYMEDIA = 3
|
||||
|
||||
def __init__(self, connection=None, conn=None):
|
||||
conn = conn or connection
|
||||
|
||||
if not isinstance(conn, libvirt.virConnect):
|
||||
raise ValueError(_("Connection must be a 'virConnect' instance."))
|
||||
self._hyper_conn = conn
|
||||
def __init__(self, conn):
|
||||
self.conn = conn
|
||||
|
||||
# original guest name or uuid
|
||||
self._original_guest = None
|
||||
self._original_dom = None
|
||||
self._original_virtual_disks = []
|
||||
self._original_xml = None
|
||||
self._guest = None
|
||||
self._original_guest = None
|
||||
self.original_dom = None
|
||||
self._original_disks = []
|
||||
self._original_xml = None
|
||||
self._guest = None
|
||||
|
||||
# clone guest
|
||||
self._clone_name = None
|
||||
self._clone_devices = []
|
||||
self._clone_virtual_disks = []
|
||||
self._clone_bs = 1024 * 1024 * 10
|
||||
self._clone_mac = []
|
||||
self._clone_uuid = None
|
||||
self._clone_sparse = True
|
||||
self._clone_xml = None
|
||||
self._clone_name = None
|
||||
self._clone_disks = []
|
||||
self._clone_macs = []
|
||||
self._clone_uuid = None
|
||||
self._clone_sparse = True
|
||||
self._clone_xml = None
|
||||
|
||||
self._force_target = []
|
||||
self._skip_target = []
|
||||
self._preserve = True
|
||||
self._clone_running = False
|
||||
self._force_target = []
|
||||
self._skip_target = []
|
||||
self._preserve = True
|
||||
self._clone_running = False
|
||||
|
||||
# Default clone policy for back compat: don't clone readonly,
|
||||
# shareable, or empty disks
|
||||
self._clone_policy = [self.CLONE_POLICY_NO_READONLY,
|
||||
self.CLONE_POLICY_NO_SHAREABLE,
|
||||
self.CLONE_POLICY_NO_EMPTYMEDIA]
|
||||
self._clone_policy = [self.CLONE_POLICY_NO_READONLY,
|
||||
self.CLONE_POLICY_NO_SHAREABLE,
|
||||
self.CLONE_POLICY_NO_EMPTYMEDIA]
|
||||
|
||||
# Throwaway guest to use for easy validation
|
||||
self._valid_guest = Guest(conn=conn)
|
||||
self._valid_guest = Guest(conn=conn)
|
||||
|
||||
# Generate a random UUID at the start
|
||||
self.clone_uuid = util.generate_uuid(conn)
|
||||
@ -198,7 +115,7 @@ class CloneDesign(object):
|
||||
except ValueError, e:
|
||||
raise ValueError(_("Invalid uuid for new guest: %s") % e)
|
||||
|
||||
if util.vm_uuid_collision(self._hyper_conn, uuid):
|
||||
if util.vm_uuid_collision(self.conn, uuid):
|
||||
raise ValueError(_("UUID '%s' is in use by another guest.") %
|
||||
uuid)
|
||||
self._clone_uuid = uuid
|
||||
@ -207,107 +124,50 @@ class CloneDesign(object):
|
||||
clone_uuid = property(get_clone_uuid, set_clone_uuid,
|
||||
doc="UUID to use for the new guest clone")
|
||||
|
||||
def set_clone_devices(self, devpath):
|
||||
# Devices here is a string path. Every call to set_clone_devices
|
||||
# Adds the path (if valid) to the internal _clone_devices list
|
||||
|
||||
def set_clone_paths(self, paths):
|
||||
disklist = []
|
||||
is_list, pathlist = _listify(devpath)
|
||||
|
||||
# Check path is valid
|
||||
for path in pathlist:
|
||||
for path in util.listify(paths):
|
||||
try:
|
||||
device = VirtualDisk.DEVICE_DISK
|
||||
if not path:
|
||||
device = VirtualDisk.DEVICE_CDROM
|
||||
|
||||
disk = VirtualDisk(path, size=.0000001,
|
||||
conn=self._hyper_conn,
|
||||
conn=self.conn,
|
||||
device=device)
|
||||
disklist.append(disk)
|
||||
except Exception, e:
|
||||
raise ValueError(_("Could not use path '%s' for cloning: %s") %
|
||||
(path, str(e)))
|
||||
|
||||
if is_list:
|
||||
self._clone_virtual_disks = []
|
||||
self._clone_devices = []
|
||||
|
||||
self._clone_virtual_disks.extend(disklist)
|
||||
self._clone_devices.extend(pathlist)
|
||||
def get_clone_devices(self):
|
||||
return self._clone_devices
|
||||
clone_devices = property(get_clone_devices, set_clone_devices,
|
||||
self._clone_disks = disklist
|
||||
def get_clone_paths(self):
|
||||
return [d.path for d in self.clone_disks]
|
||||
clone_paths = property(get_clone_paths, set_clone_paths,
|
||||
doc="Paths to use for the new disk locations.")
|
||||
|
||||
def get_clone_virtual_disks(self):
|
||||
return self._clone_virtual_disks
|
||||
clone_virtual_disks = property(get_clone_virtual_disks,
|
||||
doc="VirtualDisk instances for the new"
|
||||
" disk paths")
|
||||
|
||||
def set_clone_mac(self, mac):
|
||||
is_list, maclist = _listify(mac)
|
||||
def get_clone_disks(self):
|
||||
return self._clone_disks
|
||||
clone_disks = property(get_clone_disks,
|
||||
doc="VirtualDisk instances for the new"
|
||||
" disk paths")
|
||||
|
||||
def set_clone_macs(self, mac):
|
||||
maclist = util.listify(mac)
|
||||
for m in maclist:
|
||||
VirtualNetworkInterface(m, conn=self.original_conn)
|
||||
VirtualNetworkInterface(m, conn=self.conn)
|
||||
|
||||
if is_list:
|
||||
self._clone_mac = []
|
||||
self._clone_macs = maclist
|
||||
def get_clone_macs(self):
|
||||
return self._clone_macs
|
||||
clone_macs = property(get_clone_macs, set_clone_macs,
|
||||
doc="MAC address for the new guest clone.")
|
||||
|
||||
self._clone_mac.extend(maclist)
|
||||
def get_clone_mac(self):
|
||||
return self._clone_mac
|
||||
clone_mac = property(get_clone_mac, set_clone_mac,
|
||||
doc="MAC address for the new guest clone.")
|
||||
|
||||
def get_clone_bs(self):
|
||||
return self._clone_bs
|
||||
def set_clone_bs(self, rate):
|
||||
self._clone_bs = rate
|
||||
clone_bs = property(get_clone_bs, set_clone_bs,
|
||||
doc="Block size to use when cloning guest storage.")
|
||||
|
||||
def get_original_devices_size(self):
|
||||
ret = []
|
||||
for disk in self.original_virtual_disks:
|
||||
ret.append(disk.size)
|
||||
return ret
|
||||
original_devices_size = property(get_original_devices_size,
|
||||
doc="Size of the original guest's disks."
|
||||
" DEPRECATED: Get this info from"
|
||||
" original_virtual_disks")
|
||||
|
||||
def get_original_devices(self):
|
||||
ret = []
|
||||
for disk in self.original_virtual_disks:
|
||||
ret.append(disk.path)
|
||||
return ret
|
||||
original_devices = property(get_original_devices,
|
||||
doc="Original disk paths that will be cloned. "
|
||||
"DEPRECATED: Get this info from "
|
||||
"original_virtual_disks")
|
||||
|
||||
def get_original_virtual_disks(self):
|
||||
return self._original_virtual_disks
|
||||
original_virtual_disks = property(get_original_virtual_disks,
|
||||
doc="VirtualDisk instances of the "
|
||||
"original disks being cloned.")
|
||||
|
||||
def get_hyper_conn(self):
|
||||
return self._hyper_conn
|
||||
def set_hyper_conn(self, conn):
|
||||
self._hyper_conn = conn
|
||||
original_conn = property(get_hyper_conn, set_hyper_conn,
|
||||
doc="Libvirt virConnect instance we are cloning "
|
||||
"on")
|
||||
|
||||
def get_original_dom(self):
|
||||
return self._original_dom
|
||||
original_dom = property(get_original_dom,
|
||||
doc="Libvirt virDomain instance of the original "
|
||||
"guest. May not be available if cloning from "
|
||||
"XML.")
|
||||
def get_original_disks(self):
|
||||
return self._original_disks
|
||||
original_disks = property(get_original_disks,
|
||||
doc="VirtualDisk instances of the "
|
||||
"original disks being cloned.")
|
||||
|
||||
def get_clone_xml(self):
|
||||
return self._clone_xml
|
||||
@ -337,7 +197,7 @@ class CloneDesign(object):
|
||||
doc="It true, preserve ALL disk devices for the "
|
||||
"NEW guest. This means no storage cloning. "
|
||||
"This is a convenience access for "
|
||||
"(not CloneManager.preserve)")
|
||||
"(not Cloner.preserve)")
|
||||
|
||||
def set_force_target(self, dev):
|
||||
if type(dev) is list:
|
||||
@ -348,7 +208,7 @@ class CloneDesign(object):
|
||||
return self._force_target
|
||||
force_target = property(get_force_target, set_force_target,
|
||||
doc="List of disk targets that we force cloning "
|
||||
"despite CloneManager's recommendation.")
|
||||
"despite Cloner's recommendation.")
|
||||
|
||||
def set_skip_target(self, dev):
|
||||
if type(dev) is list:
|
||||
@ -359,7 +219,7 @@ class CloneDesign(object):
|
||||
return self._skip_target
|
||||
skip_target = property(get_skip_target, set_skip_target,
|
||||
doc="List of disk targets that we skip cloning "
|
||||
"despite CloneManager's recommendation. This "
|
||||
"despite Cloner's recommendation. This "
|
||||
"takes precedence over force_target.")
|
||||
|
||||
def set_clone_policy(self, policy_list):
|
||||
@ -386,7 +246,7 @@ class CloneDesign(object):
|
||||
def _set_replace(self, val):
|
||||
self._valid_guest.replace = bool(val)
|
||||
replace = property(_get_replace, _set_replace,
|
||||
doc="f enabled, don't check for clone name collision, "
|
||||
doc="If enabled, don't check for clone name collision, "
|
||||
"simply undefine any conflicting guest.")
|
||||
# Functional methods
|
||||
|
||||
@ -400,25 +260,27 @@ class CloneDesign(object):
|
||||
raise RuntimeError(_("Original guest name or xml is required."))
|
||||
|
||||
if self.original_guest is not None and not self.original_xml:
|
||||
self._original_dom = self._lookup_vm(self.original_guest)
|
||||
self.original_xml = self._original_dom.XMLDesc(0)
|
||||
self.original_dom = self._lookup_vm(self.original_guest)
|
||||
self.original_xml = self.original_dom.XMLDesc(0)
|
||||
|
||||
logging.debug("Original XML:\n%s", self.original_xml)
|
||||
|
||||
self._guest = Guest(conn=self._hyper_conn,
|
||||
self._guest = Guest(conn=self.conn,
|
||||
parsexml=self.original_xml)
|
||||
self._guest.replace = self.replace
|
||||
|
||||
# Pull clonable storage info from the original xml
|
||||
self._original_virtual_disks = self._get_original_devices_info()
|
||||
self._original_disks = self._get_original_disks_info()
|
||||
|
||||
logging.debug("Original paths: %s", self.original_devices)
|
||||
logging.debug("Original sizes: %s", self.original_devices_size)
|
||||
logging.debug("Original paths: %s",
|
||||
[d.path for d in self.original_disks])
|
||||
logging.debug("Original sizes: %s",
|
||||
[d.size for d in self.original_disks])
|
||||
|
||||
# If domain has devices to clone, it must be 'off' or 'paused'
|
||||
if (not self.clone_running and
|
||||
(self._original_dom and len(self.original_devices) != 0)):
|
||||
status = self._original_dom.info()[0]
|
||||
(self.original_dom and len(self.original_disks) != 0)):
|
||||
status = self.original_dom.info()[0]
|
||||
|
||||
if status not in [libvirt.VIR_DOMAIN_SHUTOFF,
|
||||
libvirt.VIR_DOMAIN_PAUSED]:
|
||||
@ -481,17 +343,17 @@ class CloneDesign(object):
|
||||
|
||||
self._clone_xml = self.original_xml
|
||||
|
||||
if len(self.clone_virtual_disks) < len(self.original_virtual_disks):
|
||||
if len(self.clone_disks) < len(self.original_disks):
|
||||
raise ValueError(_("More disks to clone than new paths specified. "
|
||||
"(%(passed)d specified, %(need)d needed") %
|
||||
{"passed" : len(self.clone_virtual_disks),
|
||||
"need" : len(self.original_virtual_disks)})
|
||||
{"passed" : len(self.clone_disks),
|
||||
"need" : len(self.original_disks)})
|
||||
|
||||
logging.debug("Clone paths: %s", self._clone_devices)
|
||||
logging.debug("Clone paths: %s", [d.path for d in self.clone_disks])
|
||||
|
||||
self._guest.name = self._clone_name
|
||||
self._guest.uuid = self._clone_uuid
|
||||
self._clone_mac.reverse()
|
||||
self._clone_macs.reverse()
|
||||
for dev in self._guest.get_devices("graphics"):
|
||||
if dev.port and dev.port != -1:
|
||||
logging.warn(_("Setting the graphics device port to autoport, "
|
||||
@ -500,12 +362,12 @@ class CloneDesign(object):
|
||||
for iface in self._guest.get_devices("interface"):
|
||||
iface.target_dev = None
|
||||
|
||||
if self._clone_mac:
|
||||
mac = self._clone_mac.pop()
|
||||
if self._clone_macs:
|
||||
mac = self._clone_macs.pop()
|
||||
else:
|
||||
while 1:
|
||||
mac = util.randomMAC(self.original_conn.getType().lower(),
|
||||
conn=self.original_conn)
|
||||
mac = util.randomMAC(self.conn.getType().lower(),
|
||||
conn=self.conn)
|
||||
ignore, msg = self._check_mac(mac)
|
||||
if msg is not None:
|
||||
continue
|
||||
@ -515,9 +377,9 @@ class CloneDesign(object):
|
||||
iface.macaddr = mac
|
||||
|
||||
# Changing storage XML
|
||||
for i in range(len(self._original_virtual_disks)):
|
||||
orig_disk = self._original_virtual_disks[i]
|
||||
clone_disk = self._clone_virtual_disks[i]
|
||||
for i in range(len(self._original_disks)):
|
||||
orig_disk = self._original_disks[i]
|
||||
clone_disk = self._clone_disks[i]
|
||||
|
||||
for disk in self._guest.get_devices("disk"):
|
||||
if disk.target == orig_disk.target:
|
||||
@ -546,17 +408,111 @@ class CloneDesign(object):
|
||||
def remove_original_vm(self, force=None):
|
||||
return self._valid_guest.remove_original_vm(force=force)
|
||||
|
||||
# Private helper functions
|
||||
def start_duplicate(self, meter=None):
|
||||
"""
|
||||
Actually perform the duplication: cloning disks if needed and defining
|
||||
the new clone xml.
|
||||
"""
|
||||
|
||||
logging.debug("Starting duplicate.")
|
||||
|
||||
if not meter:
|
||||
meter = progress.BaseMeter()
|
||||
|
||||
dom = None
|
||||
try:
|
||||
# Replace orig VM if required
|
||||
self.remove_original_vm()
|
||||
|
||||
# Define domain early to catch any xml errors before duping storage
|
||||
dom = self.conn.defineXML(self.clone_xml)
|
||||
|
||||
if self.preserve:
|
||||
for dst_dev in self.clone_disks:
|
||||
if dst_dev.clone_path == "/dev/null":
|
||||
# Not really sure why this check is here,
|
||||
# but keeping for compat
|
||||
logging.debug("Source dev was /dev/null. Skipping")
|
||||
continue
|
||||
elif dst_dev.clone_path == dst_dev.path:
|
||||
logging.debug("Source and destination are the "
|
||||
"same. Skipping.")
|
||||
continue
|
||||
|
||||
# VirtualDisk.setup_dev handles everything
|
||||
dst_dev.setup_dev(meter=meter)
|
||||
|
||||
except Exception, e:
|
||||
logging.debug("Duplicate failed: %s", str(e))
|
||||
if dom:
|
||||
dom.undefine()
|
||||
raise
|
||||
|
||||
logging.debug("Duplicating finished.")
|
||||
|
||||
def generate_clone_disk_path(self, origpath, newname=None):
|
||||
origname = self.original_guest
|
||||
newname = newname or self.clone_name
|
||||
path = origpath
|
||||
suffix = ""
|
||||
|
||||
# Try to split the suffix off the existing disk name. Ex.
|
||||
# foobar.img -> foobar-clone.img
|
||||
#
|
||||
# If the suffix is greater than 7 characters, assume it isn't
|
||||
# a file extension and is part of the disk name, at which point
|
||||
# just stick '-clone' on the end.
|
||||
if origpath.count(".") and len(origpath.rsplit(".", 1)[1]) <= 7:
|
||||
path, suffix = origpath.rsplit(".", 1)
|
||||
suffix = "." + suffix
|
||||
|
||||
dirname = os.path.dirname(path)
|
||||
basename = os.path.basename(path)
|
||||
|
||||
clonebase = basename + "-clone"
|
||||
if origname and basename == origname:
|
||||
clonebase = newname
|
||||
|
||||
clonebase = os.path.join(dirname, clonebase)
|
||||
return util.generate_name(
|
||||
clonebase,
|
||||
lambda p: VirtualDisk.path_exists(self.conn, p),
|
||||
suffix,
|
||||
lib_collision=False)
|
||||
|
||||
def generate_clone_name(self):
|
||||
# If the orig name is "foo-clone", we don't want the clone to be
|
||||
# "foo-clone-clone", we want "foo-clone1"
|
||||
basename = self.original_guest
|
||||
|
||||
match = re.search("-clone[1-9]*$", basename)
|
||||
start_num = 0
|
||||
if match:
|
||||
num_match = re.search("[1-9]+$", match.group())
|
||||
if num_match:
|
||||
start_num = int(str(num_match.group()))
|
||||
basename = basename.replace(match.group(), "")
|
||||
|
||||
basename = basename + "-clone"
|
||||
return util.generate_name(basename,
|
||||
self.conn.lookupByName,
|
||||
sep="", start_num=start_num)
|
||||
|
||||
|
||||
|
||||
############################
|
||||
# Private helper functions #
|
||||
############################
|
||||
|
||||
# Check if new mac address is valid
|
||||
def _check_mac(self, mac):
|
||||
nic = VirtualNetworkInterface(macaddr=mac, conn=self.original_conn)
|
||||
return nic.is_conflict_net(self._hyper_conn)
|
||||
nic = VirtualNetworkInterface(macaddr=mac, conn=self.conn)
|
||||
return nic.is_conflict_net(self.conn)
|
||||
|
||||
# Parse disk paths that need to be cloned from the original guest's xml
|
||||
# Return a list of VirtualDisk instances pointing to the original
|
||||
# storage
|
||||
def _get_original_devices_info(self):
|
||||
def _get_original_disks_info(self):
|
||||
clonelist = []
|
||||
retdisks = []
|
||||
|
||||
@ -571,7 +527,7 @@ class CloneDesign(object):
|
||||
|
||||
try:
|
||||
if (disk.path and validate and
|
||||
not VirtualDisk.path_exists(self._hyper_conn, disk.path)):
|
||||
not VirtualDisk.path_exists(self.conn, disk.path)):
|
||||
raise ValueError(_("Disk '%s' does not exist.") %
|
||||
disk.path)
|
||||
|
||||
@ -580,7 +536,7 @@ class CloneDesign(object):
|
||||
# Tell VirtualDisk we are a cdrom to allow empty media
|
||||
device = VirtualDisk.DEVICE_CDROM
|
||||
|
||||
d = VirtualDisk(disk.path, conn=self._hyper_conn,
|
||||
d = VirtualDisk(disk.path, conn=self.conn,
|
||||
device=device, driverType=disk.driver_type,
|
||||
validate=validate)
|
||||
d.target = disk.target
|
||||
@ -625,56 +581,6 @@ class CloneDesign(object):
|
||||
# Simple wrapper for checking a vm exists and returning the domain
|
||||
def _lookup_vm(self, name):
|
||||
try:
|
||||
return self._hyper_conn.lookupByName(name)
|
||||
return self.conn.lookupByName(name)
|
||||
except libvirt.libvirtError:
|
||||
raise ValueError(_("Domain '%s' was not found.") % str(name))
|
||||
|
||||
|
||||
def start_duplicate(design, meter=None):
|
||||
"""
|
||||
Actually perform the duplication: cloning disks if needed and defining
|
||||
the new clone xml.
|
||||
"""
|
||||
|
||||
logging.debug("Starting duplicate.")
|
||||
|
||||
if not meter:
|
||||
meter = progress.BaseMeter()
|
||||
|
||||
dom = None
|
||||
try:
|
||||
# Replace orig VM if required
|
||||
design.remove_original_vm()
|
||||
|
||||
# Define domain early to catch any xml errors before duping storage
|
||||
dom = design.original_conn.defineXML(design.clone_xml)
|
||||
|
||||
if design.preserve:
|
||||
_do_duplicate(design, meter)
|
||||
|
||||
except Exception, e:
|
||||
logging.debug("Duplicate failed: %s", str(e))
|
||||
if dom:
|
||||
dom.undefine()
|
||||
raise
|
||||
|
||||
logging.debug("Duplicating finished.")
|
||||
|
||||
# Iterate over the list of disks, and clone them using the appropriate
|
||||
# clone method
|
||||
|
||||
|
||||
def _do_duplicate(design, meter):
|
||||
|
||||
# Now actually do the cloning
|
||||
for dst_dev in design.clone_virtual_disks:
|
||||
if dst_dev.clone_path == "/dev/null":
|
||||
# Not really sure why this check was here, but keeping for compat
|
||||
logging.debug("Source dev was /dev/null. Skipping")
|
||||
continue
|
||||
elif dst_dev.clone_path == dst_dev.path:
|
||||
logging.debug("Source and destination are the same. Skipping.")
|
||||
continue
|
||||
|
||||
# VirtualDisk.setup_dev handles everything
|
||||
dst_dev.setup_dev(meter=meter)
|
||||
|
@ -45,7 +45,7 @@ from virtinst.LiveCDInstaller import LiveCDInstaller
|
||||
from virtinst.ImportInstaller import ImportInstaller
|
||||
from virtinst.ImageInstaller import ImageInstaller
|
||||
from virtinst.Installer import ContainerInstaller
|
||||
from virtinst.CloneManager import CloneDesign
|
||||
from virtinst.CloneManager import Cloner
|
||||
from virtinst.User import User
|
||||
from virtinst.Clock import Clock
|
||||
from virtinst.CPU import CPU, CPUFeature
|
||||
|
Loading…
x
Reference in New Issue
Block a user