# # List of OS Specific data # # Copyright 2006-2008, 2013 Red Hat, Inc. # Jeremy Katz # # 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., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA. _SENTINEL = -1234 _allvariants = {} from datetime import datetime from gi.repository import Libosinfo as libosinfo from inspect import isfunction _aliases = { "altlinux" : "altlinux1.0", "debianetch" : "debian4", "debianlenny" : "debian5", "debiansqueeze" : "debian6", "debianwheezy" : "debian7", "freebsd10" : "freebsd10.0", "freebsd6" : "freebsd6.0", "freebsd7" : "freebsd7.0", "freebsd8" : "freebsd8.0", "freebsd9" : "freebsd9.0", "mandriva2009" : "mandriva2009.0", "mandriva2010" : "mandriva2010.0", "mbs1" : "mbs1.0", "msdos" : "msdos6.22", "openbsd4" : "openbsd4.2", "opensolaris" : "opensolaris2009.06", "opensuse11" : "opensuse11.4", "opensuse12" : "opensuse12.3", "rhel4" : "rhel4.0", "rhel5" : "rhel5.0", "rhel6" : "rhel6.0", "rhel7" : "rhel7.0", "ubuntuhardy" : "ubuntu8.04", "ubuntuintrepid" : "ubuntu8.10", "ubuntujaunty" : "ubuntu9.04", "ubuntukarmic" : "ubuntu9.10", "ubuntulucid" : "ubuntu10.04", "ubuntumaverick" : "ubuntu10.10", "ubuntunatty" : "ubuntu11.04", "ubuntuoneiric" : "ubuntu11.10", "ubuntuprecise" : "ubuntu12.04", "ubuntuquantal" : "ubuntu12.10", "ubunturaring" : "ubuntu13.04", "ubuntusaucy" : "ubuntu13.10", "vista" : "winvista", "winxp64" : "winxp", } def _sort(tosort, sortpref=None): sortby_mappings = {} distro_mappings = {} retlist = [] sortpref = sortpref or [] # Make sure we are sorting by 'sortby' if specified, and group distros # by their 'distro' tag first and foremost for key, osinfo in tosort.items(): sortby = osinfo.sortby or key # Hack to allow "sortby" duplicates. Remove when this never happens # with libosinfo while sortby_mappings.get(sortby): sortby = sortby + ".1" sortby_mappings[sortby] = key distro = osinfo.urldistro or "zzzzzzz" if distro not in distro_mappings: distro_mappings[distro] = [] distro_mappings[distro].append(sortby) # We want returned lists to be sorted descending by 'distro', so we get # debian5, debian4, fedora14, fedora13 # rather than # debian4, debian5, fedora13, fedora14 for distro_list in distro_mappings.values(): distro_list.sort() distro_list.reverse() sorted_distro_list = distro_mappings.keys() sorted_distro_list.sort() sortpref.reverse() for prefer in sortpref: if prefer not in sorted_distro_list: continue sorted_distro_list.remove(prefer) sorted_distro_list.insert(0, prefer) for distro in sorted_distro_list: distro_list = distro_mappings[distro] for key in distro_list: orig_key = sortby_mappings[key] retlist.append(tosort[orig_key]) return retlist class _OSVariant(object): """ Object tracking guest OS specific configuration bits. @name: name of the object. This must be lowercase. This becomes part of the virt-install command line API so we cannot remove any existing name (we could probably add aliases though) @label: Pretty printed label. This is used in the virt-manager UI. We can tweak this. @is_type: virt-install historically had a distinction between an os 'type' (windows, linux, etc), and an os 'variant' (fedora18, winxp, etc). Back in 2009 we actually required the user to specify --os-type if specifying an --os-variant even though we could figure it out easily. This distinction isn't needed any more, though it's still baked into the virt-manager UI where it is still pretty useful, so we fake it here. New types should not be added often. @parent: Name of a pre-created variant that we want to extend. So fedoraFOO would have parent fedoraFOO-1. It's used for inheriting values. @typename: The family of the OS, e.g. "linux", "windows", "unix". @sortby: A different key to use for sorting the distro list. By default it's 'name', so this doesn't need to be specified. @urldistro: This is a distro class. It's wired up in urlfetcher to give us a shortcut when detecting OS type from a URL. @supported: If this distro is supported by it's owning organization, like is it still receiving updates. We use this to limit the distros we show in virt-manager by default, so old distros aren't squeezing out current ones. @three_stage_install: If True, this VM has a 3 stage install, AKA windows. @virtionet: If True, this OS supports virtionet out of the box @virtiodisk: If True, this OS supports virtiodisk out of the box @virtiommio: If True, this OS supports virtio-mmio out of the box, which provides virtio for certain ARM configurations @virtioconsole: If True, this OS supports virtio-console out of the box, and we should use it as the default console. @xen_disable_acpi: If True, disable acpi/apic for this OS if on old xen. This corresponds with the SUPPORT_CONN_CAN_DEFAULT_ACPI check @qemu_ga: If True, this distro has qemu_ga available by default @hyperv_features: If True, this distro prefers Hyper-V enlightenments The rest of the parameters are about setting device/guest defaults based on the OS. They should be self explanatory. See guest.py for their usage. """ _os = None def __init__(self, name, label, is_type=False, sortby=None, parent=_SENTINEL, typename=_SENTINEL, urldistro=_SENTINEL, supported=_SENTINEL, three_stage_install=_SENTINEL, acpi=_SENTINEL, apic=_SENTINEL, clock=_SENTINEL, netmodel=_SENTINEL, diskbus=_SENTINEL, inputtype=_SENTINEL, inputbus=_SENTINEL, virtionet=_SENTINEL, virtiodisk=_SENTINEL, virtiommio=_SENTINEL, virtioconsole=_SENTINEL, xen_disable_acpi=_SENTINEL, qemu_ga=_SENTINEL, hyperv_features=_SENTINEL): if is_type: if parent != _SENTINEL: raise RuntimeError("OS types must not specify parent") parent = None elif parent == _SENTINEL: raise RuntimeError("Must specify explicit parent") else: parent = _allvariants[parent] def _get_default(name, val, default=_SENTINEL): if val == _SENTINEL: if not parent: return default return getattr(parent, name) return val if name != name.lower(): raise RuntimeError("OS dictionary wants lowercase name, not " "'%s'" % name) self.name = name self.label = label self.sortby = sortby self.is_type = bool(is_type) self.typename = typename if typename == _SENTINEL: self.typename = _get_default("typename", self.is_type and self.name or _SENTINEL) # 'types' should rarely be altered, this check will make # doubly sure that a new type isn't accidentally added _approved_types = ["linux", "windows", "unix", "solaris", "other"] if self.typename not in _approved_types: raise RuntimeError("type '%s' for variant '%s' not in list " "of approved distro types %s" % (self.typename, self.name, _approved_types)) self.urldistro = _get_default("urldistro", urldistro, None) self.supported = _get_default("supported", supported, False) self.three_stage_install = _get_default("three_stage_install", three_stage_install) self.acpi = _get_default("acpi", acpi) self.apic = _get_default("apic", apic) self.clock = _get_default("clock", clock) self.netmodel = _get_default("netmodel", netmodel) self.diskbus = _get_default("diskbus", diskbus) self.inputtype = _get_default("inputtype", inputtype) self.inputbus = _get_default("inputbus", inputbus) self.xen_disable_acpi = _get_default("xen_disable_acpi", xen_disable_acpi) self.virtiodisk = _get_default("virtiodisk", virtiodisk) self.virtionet = _get_default("virtionet", virtionet) self.virtiommio = _get_default("virtiommio", virtiommio) self.virtioconsole = _get_default("virtioconsole", virtioconsole) self.qemu_ga = _get_default("qemu_ga", qemu_ga) self.hyperv_features = _get_default("hyperv_features", hyperv_features) def get_recommended_resources(self, arch): ignore1 = arch return None def get_videomodel(self, guest): if guest.os.is_ppc64() and guest.os.machine == "pseries": return "vga" # Marc Deslauriers of canonical had previously patched us # to use vmvga for ubuntu, see fb76c4e5. And Fedora users report # issues with ubuntu + qxl for as late as 14.04, so carry the vmvga # default forward until someone says otherwise. In 2014-09 I contacted # Marc offlist and he said this was fine for now. if self._os and self._os.get_distro() == "ubuntu": return "vmvga" if guest.has_spice() and guest.os.is_x86(): return "qxl" if self._os and _OsVariantOsInfo.is_windows(self._os): return "vga" return None def _add_type(*args, **kwargs): kwargs["is_type"] = True _t = _OSVariant(*args, **kwargs) _allvariants[_t.name] = _t def _add_var(*args, **kwargs): v = _OSVariant(*args, **kwargs) _allvariants[v.name] = v class _OsVariantOsInfo(_OSVariant): @staticmethod def is_windows(o): return o.get_family() in ['win9x', 'winnt', 'win16'] def _is_three_stage_install(self): if _OsVariantOsInfo.is_windows(self._os): return True return _SENTINEL def _get_clock(self): if _OsVariantOsInfo.is_windows(self._os) or \ self._os.get_family() in ['solaris']: return "localtime" return _SENTINEL def _is_acpi(self): if self._os.get_family() in ['msdos']: return False return _SENTINEL def _is_apic(self): if self._os.get_family() in ['msdos']: return False return _SENTINEL def _get_netmodel(self): if self._os.get_distro() == "fedora": return _SENTINEL fltr = libosinfo.Filter() fltr.add_constraint("class", "net") devs = self._os.get_all_devices(fltr) if devs.get_length(): return devs.get_nth(0).get_name() return _SENTINEL def _get_inputtype(self): fltr = libosinfo.Filter() fltr.add_constraint("class", "input") devs = self._os.get_all_devices(fltr) if devs.get_length(): return devs.get_nth(0).get_name() return _SENTINEL def get_inputbus(self): fltr = libosinfo.Filter() fltr.add_constraint("class", "input") devs = self._os.get_all_devices(fltr) if devs.get_length(): return devs.get_nth(0).get_bus_type() return _SENTINEL def _get_diskbus(self): return _SENTINEL @staticmethod def is_os_related_to(o, related_os_list): if o.get_short_id() in related_os_list: return True related = o.get_related(libosinfo.ProductRelationship.DERIVES_FROM) clones = o.get_related(libosinfo.ProductRelationship.CLONES) for r in related.get_elements() + clones.get_elements(): if r.get_short_id() in related_os_list or \ _OsVariantOsInfo.is_os_related_to(r, related_os_list): return True return False def _get_xen_disable_acpi(self): if _OsVariantOsInfo.is_os_related_to(self._os, ["winxp", "win2k"]): return True return _SENTINEL def _is_virtiodisk(self): if self._os.get_distro() == "fedora": if self._os.get_version() == "unknown": return _SENTINEL return int(self._os.get_version() >= 10) or _SENTINEL fltr = libosinfo.Filter() fltr.add_constraint("class", "block") devs = self._os.get_all_devices(fltr) for dev in range(devs.get_length()): d = devs.get_nth(dev) if d.get_name() == "virtio-block": return True return _SENTINEL def _is_virtionet(self): if self._os.get_distro() == "fedora": if self._os.get_version() == "unknown": return _SENTINEL return int(self._os.get_version() >= 9) or _SENTINEL fltr = libosinfo.Filter() fltr.add_constraint("class", "net") devs = self._os.get_all_devices(fltr) for dev in range(devs.get_length()): d = devs.get_nth(dev) if d.get_name() == "virtio-net": return True return _SENTINEL def _is_virtioconsole(self): # We used to enable this for Fedora 18+, because systemd would # autostart a getty on /dev/hvc0 which made 'virsh console' work # out of the box for a login prompt. However now in Fedora # virtio-console is compiled as a module, and systemd doesn't # detect it in time to start a getty. So the benefit of using # it as the default is erased, and we reverted to this. # https://bugzilla.redhat.com/show_bug.cgi?id=1039742 return _SENTINEL def _is_virtiommio(self): if _OsVariantOsInfo.is_os_related_to(self._os, ["fedora19"]): return True return _SENTINEL def _is_qemu_ga(self): if self._os.get_distro() == "fedora": if self._os.get_version() == "unknown": return _SENTINEL return int(self._os.get_version()) >= 18 or _SENTINEL return _SENTINEL def _is_hyperv_features(self): if _OsVariantOsInfo.is_windows(self._os): return True return _SENTINEL def _get_typename(self): if self._os.get_family() in ['linux']: return "linux" if self._os.get_family() in ['win9x', 'winnt', 'win16']: return "windows" if self._os.get_family() in ['solaris']: return "solaris" if self._os.get_family() in ['openbsd', 'freebsd', 'netbsd']: return "unix" return "other" def _get_sortby(self): version = self._os.get_version() try: t = version.split(".") t = t[:min(4, len(t))] + [0] * (4 - min(4, len(t))) new_version = "" for n in t: new_version = new_version + ("%.4i" % int(n)) version = new_version except: pass distro = self._os.get_distro() return "%s-%s" % (distro, version) def _get_supported(self): d = self._os.get_eol_date_string() name = self._os.get_short_id() if d: return datetime.strptime(d, "%Y-%m-%d") > datetime.now() # As of libosinfo 2.11, many clearly EOL distros don't have an # EOL date. So assume None == EOL, add some manual work arounds. # We should fix this in a new libosinfo version, and then drop # this hack if name in ["rhel7.0", "rhel7.1", "fedora19", "fedora20", "fedora21", "debian6", "debian7", "ubuntu13.04", "ubuntu13.10", "ubuntu14.04", "ubuntu14.10", "win8", "win8.1", "win2k12", "win2k12r2"]: return True return False def _get_urldistro(self): urldistro = self._os.get_distro() remap = { "opensuse" : "suse", "sles" : "suse", "mes" : "mandriva" } if remap.get(urldistro): return remap[urldistro] return urldistro def _get_name(self): return self._os.get_short_id() def get_label(self): return self._os.get_name() def __init__(self, o): self._os = o name = self._get_name() label = self.get_label() sortby = self._get_sortby() is_type = False typename = self._get_typename() urldistro = self._get_urldistro() supported = self._get_supported() three_stage_install = self._is_three_stage_install() acpi = self._is_acpi() apic = self._is_apic() clock = self._get_clock() xen_disable_acpi = self._get_xen_disable_acpi() virtiommio = self._is_virtiommio() qemu_ga = self._is_qemu_ga() hyperv_features = self._is_hyperv_features() virtioconsole = lambda: self._is_virtioconsole() netmodel = lambda: self._get_netmodel() diskbus = lambda: self._get_diskbus() inputtype = lambda: self._get_inputtype() inputbus = lambda: self.get_inputbus() virtiodisk = lambda: self._is_virtiodisk() virtionet = lambda: self._is_virtionet() _OSVariant.__init__(self, name=name, label=label, is_type=is_type, typename=typename, sortby=sortby, parent="generic", urldistro=urldistro, supported=supported, three_stage_install=three_stage_install, acpi=acpi, apic=apic, clock=clock, netmodel=netmodel, diskbus=diskbus, inputtype=inputtype, inputbus=inputbus, virtionet=virtionet, virtiodisk=virtiodisk, virtiommio=virtiommio, virtioconsole=virtioconsole, xen_disable_acpi=xen_disable_acpi, qemu_ga=qemu_ga, hyperv_features=hyperv_features) def get_recommended_resources(self, arch): ret = {} def read_resource(resources, arch): for i in range(resources.get_length()): r = resources.get_nth(i) if r.get_architecture() == arch: ret["ram"] = r.get_ram() ret["cpu"] = r.get_cpu() ret["n-cpus"] = r.get_n_cpus() ret["storage"] = r.get_storage() break read_resource(self._os.get_recommended_resources(), "all") read_resource(self._os.get_recommended_resources(), arch) return ret _add_type("linux", "Linux") _add_type("windows", "Windows", clock="localtime", three_stage_install=True, inputtype="tablet", inputbus="usb") _add_type("solaris", "Solaris", clock="localtime") _add_type("unix", "UNIX") _add_type("other", "Other") _add_var("generic", "Generic", supported=True, parent="other") _os_data_loaded = False _os_loader = None def _get_os_loader(): global _os_loader if _os_loader: return _os_loader _os_loader = libosinfo.Loader() _os_loader.process_default_path() return _os_loader def _load_os_data(): global _os_data_loaded if _os_data_loaded: return loader = _get_os_loader() db = loader.get_db() oslist = db.get_os_list() for os in range(oslist.get_length()): osi = _OsVariantOsInfo(oslist.get_nth(os)) _allvariants[osi.name] = osi _os_data_loaded = True def lookup_os(key): _load_os_data() key = _aliases.get(key) or key ret = _allvariants.get(key) if ret is None: return ret return ret def list_os(list_types=False, typename=None, filtervars=None, only_supported=False, **kwargs): _load_os_data() sortmap = {} filtervars = filtervars or [] for key, osinfo in _allvariants.items(): if list_types and not osinfo.is_type: continue if not list_types and osinfo.is_type: continue if typename and typename != osinfo.typename: continue if filtervars: filtervars = [lookup_os(x).name for x in filtervars] if osinfo.name not in filtervars: continue if only_supported and not osinfo.supported: continue sortmap[key] = osinfo return _sort(sortmap, **kwargs) def lookup_osdict_key(variant, key, default): _load_os_data() val = _SENTINEL if variant is not None: os = lookup_os(variant) if not hasattr(os, key): raise ValueError("Unknown osdict property '%s'" % key) val = getattr(os, key) if isfunction(val): val = val() if val == _SENTINEL: val = default return val def get_recommended_resources(variant, arch): _load_os_data() v = _allvariants.get(variant) if v is None: return None return v.get_recommended_resources(arch) def lookup_os_by_media(location): loader = _get_os_loader() media = libosinfo.Media.create_from_location(location, None) ret = loader.get_db().guess_os_from_media(media) if ret and len(ret) > 0 and ret[0]: return ret[0].get_short_id() return None