1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
# Copyright(c) FUJITSU Limited 2007.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import argparse
import sys
from . import cli
from .cli import fail, print_stdout, print_stderr
from .cloner import Cloner
def _process_src(options):
src_name = options.src_name
src_xml = None
if options.original_xml:
src_xml = open(options.original_xml).read()
elif not src_name:
fail(_("An original machine name is required,"
" use '--original src_name' and try again."))
return src_name, src_xml
def _process_macs(options, cloner):
new_macs = options.new_mac
if not new_macs or new_macs[0] == "RANDOM":
return
for mac in new_macs:
cli.validate_mac(cloner.conn, mac)
for iface in cloner.new_guest.devices.interface[:]:
iface.macaddr = new_macs.pop(0)
def _process_disks(options, cloner):
newpaths = (options.new_diskfile or [])[:]
diskinfos = cloner.get_nonshare_diskinfos()
for diskinfo in diskinfos:
origpath = diskinfo.disk.get_source_path()
newpath = None
if newpaths:
newpath = newpaths.pop(0)
elif options.auto_clone:
break
if origpath is None:
newpath = None
diskinfo.set_new_path(newpath, options.sparse)
diskinfo.raise_error()
def _validate_disks(cloner):
# Extra CLI validation for specified disks
for diskinfo in cloner.get_diskinfos():
diskinfo.raise_error()
if not diskinfo.new_disk:
continue
warn_overwrite = not diskinfo.is_preserve_requested()
cli.validate_disk(diskinfo.new_disk,
warn_overwrite=warn_overwrite)
def parse_args():
desc = _("Duplicate a virtual machine, changing all the unique "
"host side configuration like MAC address, name, etc. \n\n"
"The VM contents are NOT altered: virt-clone does not change "
"anything _inside_ the guest OS, it only duplicates disks and "
"does host side changes. So things like changing passwords, "
"changing static IP address, etc are outside the scope of "
"this tool. For these types of changes, please see virt-sysprep(1).")
parser = cli.setupParser("%(prog)s --original [NAME] ...", desc)
cli.add_connect_option(parser)
geng = parser.add_argument_group(_("General Options"))
geng.add_argument("-o", "--original", dest="src_name",
help=_("Name of the original guest to clone."))
geng.add_argument("--original-xml",
help=_("XML file to use as the original guest."))
geng.add_argument("--auto-clone", action="store_true",
help=_("Auto generate clone name and storage paths from"
" the original guest configuration."))
geng.add_argument("-n", "--name", dest="new_name",
help=_("Name for the new guest"))
geng.add_argument("-u", "--uuid", dest="new_uuid", help=argparse.SUPPRESS)
geng.add_argument("--reflink", action="store_true",
help=_("use btrfs COW lightweight copy"))
stog = parser.add_argument_group(_("Storage Configuration"))
stog.add_argument("-f", "--file", dest="new_diskfile", action="append",
help=_("New file to use as the disk image for the "
"new guest"))
stog.add_argument("--force-copy", dest="target", action="append",
help=_("Force to copy devices (eg, if 'hdc' is a "
"readonly cdrom device, --force-copy=hdc)"))
stog.add_argument("--skip-copy", action="append",
help=_("Skip copy of the device target. (eg, if 'vda' is a "
"disk you don't want to copy and use the same path "
"in the new VM, use --skip-copy=vda)"))
stog.add_argument("--nonsparse", action="store_false", dest="sparse",
default=True,
help=_("Do not use a sparse file for the clone's "
"disk image"))
stog.add_argument("--preserve-data", dest="preserve",
action="store_true", default=False,
help=_("Do not clone storage contents to specified file paths, "
"their contents will be left untouched. "
"This requires specifying existing paths for "
"every cloneable disk image."))
stog.add_argument("--nvram", dest="new_nvram",
help=_("New file to use as storage for nvram VARS"))
netg = parser.add_argument_group(_("Networking Configuration"))
netg.add_argument("-m", "--mac", dest="new_mac", action="append",
help=_("New fixed MAC address for the clone guest. "
"Default is a randomly generated MAC"))
misc = parser.add_argument_group(_("Miscellaneous Options"))
# Just used for clone tests
misc.add_argument("--__test-nodry", action="store_true", dest="test_nodry",
default=False, help=argparse.SUPPRESS)
cli.add_misc_options(misc, prompt=True, replace=True, printxml=True)
cli.autocomplete(parser)
return parser.parse_args()
def main(conn=None):
cli.earlyLogging()
options = parse_args()
options.quiet = options.quiet or options.xmlonly
cli.setupLogging("virt-clone", options.debug, options.quiet)
cli.convert_old_force(options)
cli.parse_check(options.check)
cli.set_prompt(options.prompt)
conn = cli.getConnection(options.connect, conn=conn)
if (options.new_diskfile is None and
options.auto_clone is False):
fail(_("Either --auto-clone or --file is required,"
" use '--auto-clone or --file' and try again."))
src_name, src_xml = _process_src(options)
cloner = Cloner(conn, src_name, src_xml)
cloner.set_replace(bool(options.replace))
cloner.set_reflink(bool(options.reflink))
cloner.set_sparse(bool(options.sparse))
if options.new_uuid is not None:
cloner.set_clone_uuid(options.new_uuid)
if options.new_nvram:
cloner.set_nvram_path(options.new_nvram)
force_targets = options.target or []
skip_targets = options.skip_copy or []
for diskinfo in cloner.get_diskinfos():
if diskinfo.disk.target in force_targets:
diskinfo.set_clone_requested()
if diskinfo.disk.target in skip_targets:
diskinfo.set_share_requested()
if options.preserve:
for diskinfo in cloner.get_nonshare_diskinfos():
diskinfo.set_preserve_requested()
if cloner.nvram_diskinfo:
cloner.nvram_diskinfo.set_preserve_requested()
if options.new_name:
cloner.set_clone_name(options.new_name)
elif not options.auto_clone:
fail(_("A name is required for the new virtual machine,"
" use '--name NEW_VM_NAME' to specify one."))
_process_macs(options, cloner)
_process_disks(options, cloner)
cloner.prepare()
_validate_disks(cloner)
run = True
if options.xmlonly:
run = options.test_nodry
print_stdout(cloner.new_guest.get_xml(), do_force=True)
if run:
cloner.start_duplicate(cli.get_meter())
print_stdout("")
print_stdout(_("Clone '%s' created successfully.") % cloner.new_guest.name)
return 0
def runcli(): # pragma: no cover
try:
sys.exit(main())
except SystemExit as sys_e:
sys.exit(sys_e.code)
except KeyboardInterrupt:
print_stderr(_("Installation aborted at user request"))
except Exception as main_e:
fail(main_e)
|