# # List of OS Specific data # # Copyright 2006-2008, 2013-2014 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. import datetime import logging import re import gi gi.require_version('Libosinfo', '1.0') from gi.repository import Libosinfo as libosinfo ################### # Sorting helpers # ################### def _remove_older_point_releases(distro_list): ret = distro_list[:] def _get_minor_version(osobj): return int(osobj.name.rsplit(".", 1)[-1]) def _find_latest(prefix): """ Given a prefix like 'rhel4', find the latest 'rhel4.X', and remove the rest from the os list """ latest_os = None first_id = None for osobj in ret[:]: if not re.match("%s\.\d+" % prefix, osobj.name): continue if first_id is None: first_id = ret.index(osobj) ret.remove(osobj) if (latest_os and _get_minor_version(latest_os) > _get_minor_version(osobj)): continue latest_os = osobj if latest_os: ret.insert(first_id, latest_os) _find_latest("rhel4") _find_latest("rhel5") _find_latest("rhel6") _find_latest("rhel7") _find_latest("freebsd9") _find_latest("freebsd10") return ret def _sort(tosort, sortpref=None, limit_point_releases=False): sortby_mappings = {} distro_mappings = {} retlist = [] sortpref = sortpref or [] for key, osinfo in tosort.items(): # Libosinfo has some duplicate version numbers here, so append .1 # if there's a collision sortby = osinfo.sortby while sortby_mappings.get(sortby): sortby = sortby + ".1" sortby_mappings[sortby] = key # Group distros by their urldistro value first, so debian is clumped # together, and fedora, etc. 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() # Move the sortpref values to the front of the list 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) # Build the final list of sorted os objects 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]) # Filter out older point releases if limit_point_releases: retlist = _remove_older_point_releases(retlist) return retlist class _OSDB(object): """ Entry point for the public API """ def __init__(self): self.__os_loader = None self.__all_variants = None # This is only for back compatibility with pre-libosinfo support. # This should never change. _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", "virtio26": "fedora10", "vista" : "winvista", "winxp64" : "winxp", # Old --os-type values "linux" : "generic", "windows" : "winxp", "solaris" : "solaris10", "unix": "freebsd9.0", "other": "generic", } ################# # Internal APIs # ################# def _make_default_variants(self): ret = {} # Generic variant v = _OsVariant(None) ret[v.name] = v return ret @property def _os_loader(self): if not self.__os_loader: loader = libosinfo.Loader() loader.process_default_path() self.__os_loader = loader return self.__os_loader @property def _all_variants(self): if not self.__all_variants: loader = self._os_loader allvariants = self._make_default_variants() db = loader.get_db() oslist = db.get_os_list() for os in range(oslist.get_length()): osi = _OsVariant(oslist.get_nth(os)) allvariants[osi.name] = osi self.__all_variants = allvariants return self.__all_variants ############### # Public APIs # ############### def lookup_os(self, key): key = self._aliases.get(key) or key return self._all_variants.get(key) def lookup_os_by_media(self, location): media = libosinfo.Media.create_from_location(location, None) ret = self._os_loader.get_db().guess_os_from_media(media) if not (ret and len(ret) > 0 and ret[0]): return None osname = ret[0].get_short_id() if osname == "fedora-unknown": osname = self.latest_fedora_version() logging.debug("Detected location=%s as os=fedora-unknown. " "Converting that to the latest fedora OS version=%s", location, osname) return osname def list_types(self): approved_types = ["linux", "windows", "unix", "solaris", "other", "generic"] return approved_types def list_os(self, typename=None, only_supported=False, sortpref=None): """ List all OSes in the DB :param typename: Only list OSes of this type :param only_supported: Only list OSses where self.supported == True :param sortpref: Sort these OSes at the front of the list """ sortmap = {} for name, osobj in self._all_variants.items(): if typename and typename != osobj.get_typename(): continue if only_supported and not osobj.get_supported(): continue sortmap[name] = osobj return _sort(sortmap, sortpref=sortpref, limit_point_releases=only_supported) def latest_fedora_version(self): for osinfo in self.list_os(): if (osinfo.name.startswith("fedora") and "unknown" not in osinfo.name): # First fedora* occurrence should be the newest return osinfo.name ##################### # OsVariant classes # ##################### class _OsVariant(object): def __init__(self, o): self._os = o self._family = self._os and self._os.get_family() or None self.name = self._os and self._os.get_short_id() or "generic" self.label = self._os and self._os.get_name() or "Generic" self.codename = self._os and self._os.get_codename() or "" self.sortby = self._get_sortby() self.urldistro = self._get_urldistro() self._supported = None ######################## # Internal helper APIs # ######################## def _is_related_to(self, related_os_list, os=None, check_derives=True, check_upgrades=True, check_clones=True): os = os or self._os if not os: return False if os.get_short_id() in related_os_list: return True check_list = [] def _extend(newl): for obj in newl: if obj not in check_list: check_list.append(obj) if check_derives: _extend(os.get_related( libosinfo.ProductRelationship.DERIVES_FROM).get_elements()) if check_clones: _extend(os.get_related( libosinfo.ProductRelationship.CLONES).get_elements()) if check_upgrades: _extend(os.get_related( libosinfo.ProductRelationship.UPGRADES).get_elements()) for checkobj in check_list: if (checkobj.get_short_id() in related_os_list or self._is_related_to(related_os_list, os=checkobj, check_upgrades=check_upgrades, check_derives=check_derives, check_clones=check_clones)): return True return False ############### # Cached APIs # ############### def _get_sortby(self): if not self._os: return "1" 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): if not self._os: return True eol_date = self._os.get_eol_date_string() if eol_date: return (datetime.datetime.strptime(eol_date, "%Y-%m-%d") > datetime.datetime.now()) if self.name == "fedora-unknown": return False # 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 self._is_related_to(["fedora20", "rhel7.0", "debian6", "ubuntu13.04", "win8", "win2k12"], check_clones=False, check_derives=False): return True return False def _get_urldistro(self): if not self._os: return None urldistro = self._os.get_distro() remap = { "opensuse" : "suse", "sles" : "suse", "mes" : "mandriva" } if remap.get(urldistro): return remap[urldistro] return urldistro ############### # Public APIs # ############### def get_supported(self): if self._supported is None: self._supported = self._get_supported() return self._supported def get_typename(self): """ Streamline the family name for use in the virt-manager UI """ if not self._os: return "generic" if self._family in ['linux']: return "linux" if self._family in ['win9x', 'winnt', 'win16']: return "windows" if self._family in ['solaris']: return "solaris" if self._family in ['openbsd', 'freebsd', 'netbsd']: return "unix" return "other" def is_windows(self): return self.get_typename() == "windows" def need_old_xen_disable_acpi(self): return self._is_related_to(["winxp", "win2k"], check_upgrades=False) def broken_x2apic(self): # x2apic breaks networking in solaris10 # https://bugs.launchpad.net/bugs/1395217 return self.name in ('solaris10', 'solaris11') def get_clock(self): if self.is_windows() or self._family in ['solaris']: return "localtime" return "utc" def supports_virtiommio(self): return self._is_related_to(["fedora19"]) def default_netmodel(self): """ Default non-virtio net-model, since we check for that separately """ if not self._os: return None fltr = libosinfo.Filter() fltr.add_constraint("class", "net") devs = self._os.get_all_devices(fltr) for idx in range(devs.get_length()): devname = devs.get_nth(idx).get_name() if devname in ["pcnet", "ne2k_pci", "rtl8139", "e1000"]: return devname return None def supports_usbtablet(self): if not self._os: return False fltr = libosinfo.Filter() fltr.add_constraint("class", "input") fltr.add_constraint("name", "tablet") devs = self._os.get_all_devices(fltr) for idx in range(devs.get_length()): if devs.get_nth(idx).get_bus_type() == "usb": return True return False def supports_virtiodisk(self): if self._os: 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 False def supports_virtionet(self): if self._os: 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 False def supports_qemu_ga(self): return self._is_related_to(["debian8", "fedora18", "rhel6.0", "sles11sp4"]) def default_videomodel(self, guest): if guest.os.is_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. # # But in my testing ubuntu-15.04 works _better_ with qxl (installer # resolution is huge with vmvga but reasonable with qxl), and given # that the qemu vmvga code is not supposed to be of high quality, # let's use qxl for newer ubuntu if (self._os and self._os.get_distro() == "ubuntu" and not self._is_related_to("ubuntu15.04")): return "vmvga" if guest.has_spice() and guest.os.is_x86(): if guest.has_gl(): return "virtio" else: return "qxl" if self.is_windows(): return "vga" return None def get_recommended_resources(self, guest): ret = {} if not self._os: return ret def read_resource(resources, minimum, arch): # If we are reading the "minimum" block, allocate more # resources. ram_scale = minimum and 2 or 1 n_cpus_scale = minimum and 2 or 1 storage_scale = minimum and 2 or 1 for i in range(resources.get_length()): r = resources.get_nth(i) if r.get_architecture() == arch: ret["ram"] = r.get_ram() * ram_scale ret["cpu"] = r.get_cpu() ret["n-cpus"] = r.get_n_cpus() * n_cpus_scale ret["storage"] = r.get_storage() * storage_scale break # libosinfo may miss the recommended resources block for some OS, # in this case read first the minimum resources (if present) # and use them. read_resource(self._os.get_minimum_resources(), True, "all") read_resource(self._os.get_minimum_resources(), True, guest.os.arch) read_resource(self._os.get_recommended_resources(), False, "all") read_resource(self._os.get_recommended_resources(), False, guest.os.arch) # QEMU TCG doesn't gain anything by having extra VCPUs if guest.type == "qemu": ret["n-cpus"] = 1 return ret OSDB = _OSDB()