19
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
20
# MA 02110-1301 USA.
24
25
import logging.handlers
25
import gettext, locale
27
from optparse import OptionValueError, OptionParser
29
from optparse import OptionValueError, OptionParser, OptionGroup
32
from virtinst import CapabilitiesParser, VirtualNetworkInterface, \
33
VirtualGraphics, VirtualAudio, VirtualDisk, User, \
37
from _util import listify
38
from virtinst import VirtualNetworkInterface, Guest, \
39
VirtualGraphics, VirtualAudio, VirtualDisk, User
35
40
from virtinst import _virtinst as _
41
def check_if_test_uri_remote(uri):
42
magic = "__virtinst_test_remote__"
43
if uri and uri.startswith(magic):
44
uri = uri.replace(magic, "")
47
log_exception = _util.log_exception
48
_virtinst_uri_magic = "__virtinst_test__"
50
def _is_virtinst_test_uri(uri):
51
return uri and uri.startswith(_virtinst_uri_magic)
53
def _open_test_uri(uri):
55
This hack allows us to fake various drivers via passing a magic
56
URI string to virt-*. Helps with testing
58
uri = uri.replace(_virtinst_uri_magic, "")
59
ret = uri.split(",", 1)
61
ignore, opts = parse_optstr(len(ret) > 1 and ret[1] or "")
63
conn = open_connection(uri)
65
def sanitize_xml(xml):
67
xml = re.sub("arch='.*'", "arch='i686'", xml)
68
xml = re.sub("domain type='.*'", "domain type='test'", xml)
69
xml = re.sub("machine type='.*'", "", xml)
71
logging.debug("virtinst test sanitizing diff\n:%s" %
72
"\n".join(difflib.unified_diff(orig.split("\n"),
76
# Need tmpfile names to be deterministic
77
if "predictable" in opts:
78
def fakemkstemp(prefix, *args, **kwargs):
81
filename = os.path.join(".", prefix)
82
return os.open(filename, os.O_RDWR | os.O_CREAT), filename
83
tempfile.mkstemp = fakemkstemp
85
_util.randomMAC = lambda type_: "00:11:22:33:44:55"
86
_util.uuidToString = lambda r: "00000000-1111-2222-3333-444444444444"
45
90
_util.is_uri_remote = lambda uri_: True
94
capsxml = file(opts["caps"]).read()
95
conn.getCapabilities = lambda: capsxml
97
if "qemu" in opts or "xen" in opts:
98
conn.getVersion = lambda: 10000000000
100
origcreate = conn.createLinux
101
origdefine = conn.defineXML
102
def newcreate(xml, flags):
103
xml = sanitize_xml(xml)
104
origcreate(xml, flags)
106
xml = sanitize_xml(xml)
108
conn.createLinux = newcreate
109
conn.defineXML = newdefine
112
conn.getURI = lambda: "qemu+abc:///system"
114
conn.getURI = lambda: "xen+abc:///"
48
118
class VirtStreamHandler(logging.StreamHandler):
134
206
gettext.bindtextdomain(virtinst.gettext_app, virtinst.gettext_dir)
135
207
gettext.install(virtinst.gettext_app, virtinst.gettext_dir)
137
def setupLogging(appname, debug=False):
210
logging.basicConfig(level=logging.DEBUG, format='%(message)s')
212
def setupLogging(appname, debug=False, do_quiet=False):
139
216
vi_dir = os.path.expanduser("~/.virtinst")
140
217
if not os.access(vi_dir, os.W_OK):
141
218
if os.path.exists(vi_dir):
151
228
dateFormat = "%a, %d %b %Y %H:%M:%S"
152
fileFormat = "[%(asctime)s " + appname + " %(process)d] %(levelname)s (%(module)s:%(lineno)d) %(message)s"
229
fileFormat = ("[%(asctime)s " + appname + " %(process)d] "
230
"%(levelname)s (%(module)s:%(lineno)d) %(message)s")
153
231
streamDebugFormat = "%(asctime)s %(levelname)-8s %(message)s"
154
232
streamErrorFormat = "%(levelname)-8s %(message)s"
155
233
filename = os.path.join(vi_dir, appname + ".log")
157
235
rootLogger = logging.getLogger()
238
for handler in rootLogger.handlers:
239
rootLogger.removeHandler(handler)
158
241
rootLogger.setLevel(logging.DEBUG)
159
fileHandler = logging.handlers.RotatingFileHandler(filename, "a",
242
fileHandler = logging.handlers.RotatingFileHandler(filename, "ae",
162
245
fileHandler.setFormatter(logging.Formatter(fileFormat,
191
278
# Log the app command string
192
279
logging.debug("Launched with command line:\n%s" % " ".join(sys.argv))
281
def fail(msg, do_exit=True):
195
282
"""Convenience function when failing in cli app"""
196
283
logging.error(msg)
197
_util.log_exception()
288
def print_stdout(msg, do_force=False):
289
if do_force or not quiet:
292
def print_stderr(msg):
294
print >> sys.stderr, msg
200
296
def _fail_exit():
204
print _("Exiting at user request.")
300
print_stdout(_("Exiting at user request."))
303
def virsh_start_cmd(guest):
304
return ("virsh --connect %s start %s" % (guest.conn.getURI(), guest.name))
306
def install_fail(guest):
307
virshcmd = virsh_start_cmd(guest)
310
_("Domain installation does not appear to have been successful.\n"
311
"If it was, you can restart your domain by running:\n"
313
"otherwise, please restart your installation.") % virshcmd)
207
316
# Connection opening helper functions
208
def getConnection(connect):
210
not User.current().has_priv(User.PRIV_CREATE_DOMAIN, connect)):
317
def getConnection(uri):
318
if (uri and not User.current().has_priv(User.PRIV_CREATE_DOMAIN, uri)):
211
319
fail(_("Must be root to create Xen guests"))
213
# Hack to facilitate remote unit testing
214
connect = check_if_test_uri_remote(connect)
321
# Hack to facilitate virtinst unit testing
322
if _is_virtinst_test_uri(uri):
323
return _open_test_uri(uri)
216
logging.debug("Requesting libvirt URI %s" % (connect or "default"))
217
conn = open_connection(connect)
325
logging.debug("Requesting libvirt URI %s" % (uri or "default"))
326
conn = open_connection(uri)
218
327
logging.debug("Received libvirt URI %s" % conn.getURI())
343
452
# We already failed validation in a previous function, just exit
347
if not force and not msg.count("--prompt"):
348
# msg wasn't already appended to from yes_or_no
349
msg = noprompt_err + " " + _("(use --prompt to run interactively)")
457
print_stdout(prompt + " ", do_force=True)
353
458
return sys.stdin.readline().strip()
460
def yes_or_no_convert(s):
357
462
if s in ("y", "yes", "1", "true", "t"):
359
464
elif s in ("n", "no", "0", "false", "f"):
361
raise ValueError, "A yes or no response is required"
469
ret = yes_or_no_convert(s)
471
raise ValueError(_("A yes or no response is required"))
363
474
def prompt_for_yes_or_no(warning, question):
364
475
"""catches yes_or_no errors and ensures a valid bool return"""
401
512
passed_val = None
515
def vcpu_cli_options(grp):
516
grp.add_option("", "--vcpus", type="string", dest="vcpus",
517
help=_("Number of vcpus to configure for your guest. Ex:\n"
519
"--vcpus 5,maxcpus=10\n"
520
"--vcpus sockets=2,cores=4,threads=2"))
521
grp.add_option("", "--cpuset", type="string", dest="cpuset",
522
action="callback", callback=check_before_store,
523
help=_("Set which physical CPUs Domain can use."))
524
grp.add_option("", "--cpu", type="string", dest="cpu",
525
action="callback", callback=check_before_store,
526
help=_("CPU model and features. Ex: --cpu coreduo,+x2apic"))
527
grp.add_option("", "--check-cpu", action="store_true", dest="check_cpu",
528
help=optparse.SUPPRESS_HELP)
404
530
# Register vnc + sdl options for virt-install and virt-image
405
531
def graphics_option_group(parser):
406
from optparse import OptionGroup
408
533
vncg = OptionGroup(parser, _("Graphics Configuration"))
534
vncg.add_option("", "--graphics", type="string", dest="graphics",
535
action="callback", callback=check_before_store,
536
help=_("Specify display configuration. Ex:\n"
538
"--graphics spice,port=1,tlsport=2\n"
540
"--graphics vnc,password=foobar,port=5910,keymap=ja"))
409
541
vncg.add_option("", "--vnc", action="store_true", dest="vnc",
410
help=_("Use VNC for graphics support"))
542
help=optparse.SUPPRESS_HELP)
411
543
vncg.add_option("", "--vncport", type="int", dest="vncport",
412
help=_("Port to use for VNC"))
544
help=optparse.SUPPRESS_HELP)
413
545
vncg.add_option("", "--vnclisten", type="string", dest="vnclisten",
414
help=_("Address to listen on for VNC connections."))
546
help=optparse.SUPPRESS_HELP)
415
547
vncg.add_option("-k", "--keymap", type="string", dest="keymap",
416
548
action="callback", callback=check_before_store,
417
help=_("set up keymap for the VNC console"))
549
help=optparse.SUPPRESS_HELP)
418
550
vncg.add_option("", "--sdl", action="store_true", dest="sdl",
419
help=_("Use SDL for graphics support"))
551
help=optparse.SUPPRESS_HELP)
420
552
vncg.add_option("", "--nographics", action="store_true",
421
help=_("Don't set up a graphical console for the guest."))
553
help=optparse.SUPPRESS_HELP)
424
556
# Specific function for disk prompting. Returns a validated VirtualDisk
451
583
msg = _("What would you like to use as the disk (file path)?")
452
584
if not size is None:
453
585
msg = _("Please enter the path to the file you would like to "
454
"use for storage. It will have size %sGB.") %(size,)
586
"use for storage. It will have size %sGB.") % size
456
588
if not no_path_needed:
457
589
path = prompt_for_input(patherr, prompt_txt or msg, passed_path)
460
592
arg_dict["path"] = path
593
path_exists = VirtualDisk.path_exists(conn, path)
462
595
sizeerr = _("A size must be specified for non-existent disks.")
463
596
if path and not size and prompt_size:
487
620
askmsg = _("Do you really want to use this disk (yes or no)")
489
622
# Prompt if disk file already exists and preserve mode is not used
490
if warn_overwrite and os.path.exists(dev.path):
491
msg = (_("This will overwrite the existing path '%s'!\n" %
623
does_collide = (path_exists and
624
dev.type == dev.TYPE_FILE and
625
dev.device == dev.DEVICE_DISK)
626
if (does_collide and (warn_overwrite or is_prompt())):
627
msg = (_("This will overwrite the existing path '%s'" %
493
629
if not prompt_for_yes_or_no(msg, askmsg):
496
632
# Check disk conflicts
497
633
if dev.is_conflict_disk(conn) is True:
498
msg = (_("Disk %s is already in use by another guest!\n" %
634
msg = (_("Disk %s is already in use by another guest" %
500
636
if not prompt_for_yes_or_no(msg, askmsg):
551
683
except ValueError, e:
554
def get_vcpus(vcpus, check_cpu, guest, conn, image_vcpus=None):
686
def _build_set_param(inst, opts):
687
def _set_param(paramname, keyname, val=None):
688
val = get_opt_param(opts, keyname, val)
691
setattr(inst, paramname, val)
695
def parse_vcpu_option(guest, optstring, default_vcpus):
697
Helper to parse --vcpu string
699
vcpus, opts = parse_optstr(optstring, remove_first=True)
700
vcpus = vcpus or default_vcpus
702
set_param = _build_set_param(guest, opts)
703
set_cpu_param = _build_set_param(guest.cpu, opts)
704
has_vcpus = ("vcpus" in opts or vcpus)
706
set_param("vcpus", "vcpus", vcpus)
707
set_param("maxvcpus", "maxvcpus")
709
set_cpu_param("sockets", "sockets")
710
set_cpu_param("cores", "cores")
711
set_cpu_param("threads", "threads")
714
guest.vcpus = guest.cpu.vcpus_from_topology()
717
raise ValueError(_("Unknown options %s") % opts.keys())
720
def get_vcpus(vcpus, check_cpu, guest, image_vcpus=None):
721
if vcpus is None and image_vcpus is not None:
722
vcpus = int(image_vcpus)
724
parse_vcpu_option(guest, vcpus, image_vcpus)
556
728
hostinfo = conn.getInfo()
557
729
cpu_num = hostinfo[4] * hostinfo[5] * hostinfo[6] * hostinfo[7]
564
736
if not prompt_for_yes_or_no(msg, askmsg):
567
if vcpus is None and image_vcpus is not None:
568
vcpus = int(image_vcpus)
569
if vcpus is not None:
572
except ValueError, e:
575
def get_cpuset(cpuset, mem, guest, conn):
740
def get_cpuset(cpuset, mem, guest):
576
742
if cpuset and cpuset != "auto":
577
743
guest.cpuset = cpuset
578
745
elif cpuset == "auto":
579
caps = CapabilitiesParser.parse(conn.getCapabilities())
580
if caps.host.topology is None:
581
logging.debug("No topology section in caps xml. Skipping cpuset")
584
cells = caps.host.topology.cells
586
logging.debug("Capabilities only show <= 1 cell. Not NUMA capable")
589
cell_mem = conn.getCellsFreeMemory(0, len(cells))
592
for i in range(len(cells)):
593
if cell_mem[i] > mem and len(cells[i].cpus) != 0:
594
# Find smallest cell that fits
595
if cell_id < 0 or cell_mem[i] < cell_mem[cell_id]:
598
logging.debug("Could not find any usable NUMA cell/cpu combinations. Not setting cpuset.")
603
for cpu in cells[cell_id].cpus:
606
cpustr += str(cpu.id)
607
logging.debug("Auto cpuset is: %s" % cpustr)
608
guest.cpuset = cpustr
748
tmpset = Guest.generate_cpuset(conn, mem)
750
logging.debug("Not setting cpuset", str(e))
753
logging.debug("Auto cpuset is: %s" % tmpset)
754
guest.cpuset = tmpset
758
def parse_cpu(guest, optstring):
766
model, opts = parse_optstr(optstring,
767
basedict=default_dict,
770
# Convert +feature, -feature into expected format
771
for key, value in opts.items():
773
if value or len(key) == 1:
776
if key.startswith("+"):
778
elif key.startswith("-"):
783
opts[policy].append(key[1:])
785
set_param = _build_set_param(guest.cpu, opts)
786
def set_features(policy):
787
for name in opts.get(policy):
788
guest.cpu.add_feature(name, policy)
792
guest.cpu.copy_host_cpu()
795
set_param("model", "model", model)
796
set_param("match", "match")
797
set_param("vendor", "vendor")
799
set_features("force")
800
set_features("require")
801
set_features("optional")
802
set_features("disable")
803
set_features("forbid")
806
raise ValueError(_("Unknown options %s") % opts.keys())
611
808
def get_network(net_kwargs, guest):
612
809
n = VirtualNetworkInterface(**net_kwargs)
613
810
guest.nics.append(n)
623
820
if (distro_variant and str(distro_variant).lower() != "none"):
624
821
guest.set_os_variant(distro_variant)
627
def parse_optstr(optstr, basedict=None):
823
def parse_optstr_tuples(optstr):
629
Helper function for parsing opt strings of the form
630
opt1=val1,opt2=val2,...
631
'basedict' is a starting dictionary, so the caller can easily set
634
Returns a dictionary of {'opt1': 'val1', 'opt2': 'val2'}
825
Parse optstr into a list of ordered tuples
636
827
optstr = str(optstr or "")
637
optdict = basedict or {}
639
830
args = optstr.split(",")
646
837
if opt.count("="):
647
838
opt_type, opt_val = opt.split("=", 1)
648
optdict[opt_type.lower()] = opt_val.lower()
650
optdict[opt.lower()] = None
839
optlist.append((opt_type.lower(), opt_val))
841
optlist.append((opt.lower(), None))
845
def parse_optstr(optstr, basedict=None, remove_first=False):
847
Helper function for parsing opt strings of the form
848
opt1=val1,opt2=val2,...
850
@param basedict: starting dictionary, so the caller can easily set
852
@param remove_first: If true, remove the first options off the string
853
and return it seperately. For example,
854
--serial pty,foo=bar returns ("pty", {"foo" : "bar"})
856
Returns a dictionary of {'opt1': 'val1', 'opt2': 'val2'}
858
optlist = parse_optstr_tuples(optstr)
859
optdict = basedict or {}
862
if remove_first and optlist:
863
first_tuple = optlist[0]
864
if first_tuple[1] == None:
865
first = first_tuple[0]
866
optlist.remove(first_tuple)
868
for opt, val in optlist:
869
if type(optdict.get(opt)) is list:
870
optdict[opt].append(val)
874
return (first, optdict)
654
876
def parse_network_opts(conn, mac, network):
691
913
if mac == "RANDOM":
693
return { "conn" : conn, "type" : net_type, "bridge": bridge_name,
694
"network" : network_name, "model" : model , "macaddr" : mac }
915
return {"conn" : conn, "type" : net_type, "bridge": bridge_name,
916
"network" : network_name, "model" : model , "macaddr" : mac}
696
def digest_networks(conn, macs, bridges, networks, nics = 0):
918
def digest_networks(conn, macs, bridges, networks, nics=0):
697
919
macs = listify(macs)
698
920
bridges = listify(bridges)
699
921
networks = listify(networks)
716
938
if len(macs) > len(networks):
717
939
fail(_("Cannot pass more mac addresses than networks."))
719
for dummy in range (len(macs),len(networks)):
941
for dummy in range(len(macs), len(networks)):
720
942
macs.append(None)
722
944
# Create extra networks up to the number of nics requested
723
945
if len(macs) < nics:
724
for dummy in range(len(macs),nics):
946
for dummy in range(len(macs), nics):
725
947
if User.current().has_priv(User.PRIV_CREATE_NETWORK, conn.getURI()):
726
948
net = _util.default_network(conn)
727
949
networks.append(net[0] + ":" + net[1])
738
960
return net_init_dicts
962
def sanitize_keymap(keymap):
968
if keymap.lower() == "local":
969
use_keymap = virtinst.VirtualGraphics.KEYMAP_LOCAL
971
elif keymap.lower() != "none":
972
use_keymap = _util.check_keytable(keymap)
974
raise ValueError(_("Didn't match keymap '%s' in keytable!") %
979
def parse_graphics(guest, optstring, basedict):
980
if optstring is None and not basedict:
983
# Peel the model type off the front
984
gtype, opts = parse_optstr(optstring, basedict, remove_first=True)
985
if gtype == "none" or basedict.get("type") == "none":
987
dev = VirtualGraphics(conn=guest.conn)
989
def set_param(paramname, dictname, val=None):
990
val = get_opt_param(opts, dictname, val)
994
if paramname == "keymap":
995
val = sanitize_keymap(val)
996
setattr(dev, paramname, val)
998
set_param("type", "type", gtype)
999
set_param("port", "port")
1000
set_param("tlsPort", "tlsport")
1001
set_param("listen", "listen")
1002
set_param("keymap", "keymap")
1003
set_param("passwd", "password")
1004
set_param("passwdValidTo", "passwordvalidto")
1007
raise ValueError(_("Unknown options %s") % opts.keys())
740
1011
def get_graphics(vnc, vncport, vnclisten, nographics, sdl, keymap,
741
video_models, guest):
1012
video_models, graphics, guest):
742
1013
video_models = video_models or []
744
if ((vnc and nographics) or
746
(sdl and nographics)):
747
raise ValueError, _("Can't specify more than one of VNC, SDL, "
750
for model in video_models:
751
dev = virtinst.VirtualVideoDevice(guest.conn)
752
dev.model_type = model
753
guest.add_device(dev)
755
if not (vnc or nographics or sdl):
1015
if graphics and (vnc or sdl or keymap or vncport or vnclisten):
1016
fail(_("Cannot mix --graphics and old style graphical options"))
1018
# If not graphics specified, choose a default
1019
if not (vnc or nographics or sdl or graphics):
756
1020
if "DISPLAY" in os.environ.keys():
757
1021
logging.debug("DISPLAY is set: graphics defaulting to VNC.")
760
1024
logging.debug("DISPLAY is not set: defaulting to nographics.")
761
1025
nographics = True
763
if nographics is not None:
764
guest.graphics_dev = None
1027
if (sum(map(int, map(bool, [vnc, nographics, sdl, graphics])))) > 1:
1028
raise ValueError(_("Can't specify more than one of VNC, SDL, "
1029
"--graphics or --nographics"))
1031
# Build an initial graphics argument dict
1033
"type" : ((vnc and "vnc") or
1035
(nographics and "none")),
1036
"listen" : vnclisten,
1042
dev = parse_graphics(guest, graphics, basedict)
1043
except Exception, e:
1044
fail(_("Error in graphics device parameters: %s") % str(e))
1048
guest.graphics_dev = dev
767
# After this point, we are using graphics, so add a video device
768
# if one wasn't passed
1050
# At this point we are definitely using graphics, so setup a default
1051
# video card if necc.
769
1052
if not video_models:
770
guest.add_device(VirtualVideoDevice(conn=guest.conn))
773
guest.graphics_dev = VirtualGraphics(type=VirtualGraphics.TYPE_SDL)
777
guest.graphics_dev = VirtualGraphics(type=VirtualGraphics.TYPE_VNC)
779
guest.graphics_dev.port = vncport
781
guest.graphics_dev.listen = vnclisten
786
if keymap.lower() == "local":
787
use_keymap = virtinst.VirtualGraphics.KEYMAP_LOCAL
789
elif keymap.lower() != "none":
790
use_keymap = _util.check_keytable(keymap)
792
raise ValueError(_("Didn't match keymap '%s' in keytable!") %
795
guest.graphics_dev.keymap = use_keymap
1053
video_models.append(None)
1054
for model in video_models:
1055
vdev = virtinst.VirtualVideoDevice(guest.conn)
1057
vdev.model_type = model
1058
guest.add_device(vdev)
797
1060
def get_sound(old_sound_bool, sound_opts, guest):
798
1061
if not sound_opts:
819
1082
### Option parsing
820
1083
def check_before_store(option, opt_str, value, parser):
821
1084
if len(value) == 0:
822
raise OptionValueError, _("%s option requires an argument") %opt_str
1085
raise OptionValueError(_("%s option requires an argument") % opt_str)
823
1086
setattr(parser.values, option.dest, value)
825
1088
def check_before_append(option, opt_str, value, parser):
826
1089
if len(value) == 0:
827
raise OptionValueError, _("%s option requires an argument") %opt_str
1090
raise OptionValueError(_("%s option requires an argument") % opt_str)
828
1091
parser.values.ensure_value(option.dest, []).append(value)
1093
def get_opt_param(opts, dictnames, val=None):
1094
if type(dictnames) is not list:
1095
dictnames = [dictnames]
1097
for key in dictnames:
1105
def partition(string, sep):
1107
return (None, None, None)
1109
if string.count(sep):
1110
splitres = string.split(sep, 1)
1111
ret = (splitres[0], sep, splitres[1])
1113
ret = (string, None, None)