mirror of
https://github.com/virt-manager/virt-manager.git
synced 2026-09-03 20:52:52 -05:00
(crobinso: compress to one commit, and adjust string concat issues) Signed-off-by: Pino Toscano <ptoscano@redhat.com>
248 lines
7.5 KiB
Python
248 lines
7.5 KiB
Python
# 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)
|