package org.ovirt.engine.core.utils; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.common.businessentities.ArchitectureType; import org.ovirt.engine.core.common.businessentities.ChipsetType; import org.ovirt.engine.core.common.businessentities.DisplayType; import org.ovirt.engine.core.common.businessentities.GraphicsType; import org.ovirt.engine.core.common.businessentities.UsbControllerModel; import org.ovirt.engine.core.common.businessentities.VmWatchdogType; import org.ovirt.engine.core.common.config.Config; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.osinfo.MapBackedPreferences; import org.ovirt.engine.core.common.osinfo.OsRepository; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.compat.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is holding all Virtual OSs information. */ public enum OsRepositoryImpl implements OsRepository { INSTANCE; private static final Logger log = LoggerFactory.getLogger(OsRepositoryImpl.class); private static final String OS_ROOT_NODE = "/os/"; private static final String BACKWARD_COMPATIBILITY_ROOT_NODE = "/backwardCompatibility"; /** * the configuration tree holding all the os data. */ private MapBackedPreferences preferences; private Preferences emptyNode; /** * lookup table to get the os id from the uniquename and vise-versa os.rhel6.id.value = 8 means its id in the engine * db is 8 and the unique name is "rhel6" */ private Map<Integer, String> idToUnameLookup; private Map<String, Integer> backwardCompatibleNamesToIds; private static Map<ArchitectureType, Integer> defaultOsMap = new HashMap<>(2); static { defaultOsMap.put(ArchitectureType.x86_64, DEFAULT_X86_OS); defaultOsMap.put(ArchitectureType.ppc64, DEFAULT_PPC_OS); } public void init(MapBackedPreferences preferences) { INSTANCE.preferences = preferences; emptyNode = preferences.node("emptyNode"); buildIdToUnameLookup(); buildBackCompatMapping(); validateTree(); if (log.isDebugEnabled()) { log.debug("Osinfo Repository:\n{}", this); } } private void validateTree() { try { String[] uniqueNames = preferences.node("/os").childrenNames(); for (String uniqueName : Arrays.asList(uniqueNames)) { Preferences node = getKeyNode(uniqueName, "derivedFrom", null); String id = getKeyNode(uniqueName, "id", null).get("value", "0"); if (node != null) { String derivedFrom = node.get("value", null); if (derivedFrom != null && !idToUnameLookup.containsValue(derivedFrom)) { idToUnameLookup.remove(Integer.valueOf(id)); preferences.node("/os/" + uniqueName).removeNode(); log.warn("Illegal parent for os '{}'", uniqueName); } } } } catch (BackingStoreException e) { log.warn("Failed to validate Os Repository due to {}", e.getMessage()); log.debug("Exception", e); throw new RuntimeException("Failed to validate Os Repository due to " + e); } } private void buildIdToUnameLookup() { try { String[] uniqueNames = preferences.node("/os").childrenNames(); idToUnameLookup = new HashMap<>(uniqueNames.length); for (String uniqueName : uniqueNames) { Preferences idNode = getKeyNode(uniqueName, "id", null); if (idNode != null) { int osId = idNode.getInt("value", 0); if (idNode != emptyNode && idToUnameLookup.containsKey(osId)) { throw new RuntimeException(String.format("colliding os id %s at node %s", osId, idNode.absolutePath())); } else { idToUnameLookup.put(osId, uniqueName); } } } } catch (BackingStoreException e) { throw new RuntimeException("Failed to initialize Os Repository due to " + e); } } private void buildBackCompatMapping() { try { String[] entries = preferences.node(BACKWARD_COMPATIBILITY_ROOT_NODE).keys(); backwardCompatibleNamesToIds = new HashMap<>(entries.length); for (String oldOsName : entries) { backwardCompatibleNamesToIds.put(oldOsName, preferences.node(BACKWARD_COMPATIBILITY_ROOT_NODE).getInt(oldOsName, 0)); } } catch (BackingStoreException e) { throw new RuntimeException("Failed to initialize Os Repository Backward Compatibility mappings due to " + e); } } @Override public List<Integer> getOsIds() { return new ArrayList<>(idToUnameLookup.keySet()); } @Override public Map<Integer, String> getUniqueOsNames() { // return a defensive copy to avoid modification of this map. return new HashMap<>(idToUnameLookup); } @Override public Map<Integer, String> getOsNames() { Map<Integer, String> osNames = new HashMap<>(); for (int osId : getOsIds()) { String name = getValueByVersion(idToUnameLookup.get(osId), "name", null); if (name != null) { osNames.put(osId, name); } } return osNames; } @Override public Map<Pair<Integer, Version>, Boolean> getNicHotplugSupportMap() { List<Version> versions = new ArrayList<>(Config.<HashSet<Version>>getValue(ConfigValues.SupportedClusterLevels)); Map<Pair<Integer, Version>, Boolean> hotplugSupportOsIdVersionMap = new HashMap<>(); for (Integer osId : getOsIds()) { for (Version version : versions) { hotplugSupportOsIdVersionMap.put( new Pair<>(osId, version), hasNicHotplugSupport(osId, version)); } } return hotplugSupportOsIdVersionMap; } @Override public Map<Pair<Integer, Version>, Set<String>> getDiskHotpluggableInterfacesMap() { Set<Version> versionsWithNull = new HashSet<>(Version.ALL); versionsWithNull.add(null); Map<Pair<Integer, Version>, Set<String>> diskHotpluggableInterfacesMap = new HashMap<>(); for (Integer osId : getOsIds()) { for (Version version : versionsWithNull) { diskHotpluggableInterfacesMap.put( new Pair<>(osId, version), getDiskHotpluggableInterfaces(osId, version)); } } return diskHotpluggableInterfacesMap; } @Override public String getOsName(int osId) { return getOsNames().get(osId); } @Override public String getOsFamily(int osId) { return getValueByVersion(idToUnameLookup.get(osId), "family", null); } @Override public List<Integer> getLinuxOss() { List<Integer> oss = new ArrayList<>(); for (int osId : getOsIds()) { if (getOsFamily(osId).equalsIgnoreCase("linux")) { oss.add(osId); } } return oss; } @Override public List<Integer> get64bitOss() { List<Integer> oss = new ArrayList<>(); for (int osId : getOsIds()) { String bus = getValueByVersion(idToUnameLookup.get(osId), "bus", null); if ("64".equalsIgnoreCase(bus)) { oss.add(osId); } } return oss; } @Override public List<Integer> getWindowsOss() { List<Integer> oss = new ArrayList<>(); for (int osId : getOsIds()) { if (isWindows(osId)) { oss.add(osId); } } return oss; } @Override public Map<Integer, ArchitectureType> getOsArchitectures() { Map<Integer, ArchitectureType> osArchitectures = new HashMap<>(); for (int osId : getOsIds()) { String architecture = getValueByVersion(idToUnameLookup.get(osId), "cpuArchitecture", null); if (architecture != null) { osArchitectures.put(osId, ArchitectureType.valueOf(architecture)); } } return osArchitectures; } @Override public ArchitectureType getArchitectureFromOS(int osId) { String architecture = getValueByVersion(idToUnameLookup.get(osId), "cpuArchitecture", null); return ArchitectureType.valueOf(architecture); } @Override public boolean isWindows(int osId) { return getOsFamily(osId).equalsIgnoreCase("windows"); } @Override public List<String> getDiskInterfaces(int osId, Version version) { String devices = getValueByVersion(idToUnameLookup.get(osId), "devices.diskInterfaces", version); return trimElements(devices.split(",")); } @Override public List<String> getNetworkDevices(int osId, Version version) { String devices = getValueByVersion(idToUnameLookup.get(osId), "devices.network", version); return trimElements(devices.split(",")); } @Override public Set<String> getDiskHotpluggableInterfaces(int osId, Version version) { String devices = getValueByVersion(idToUnameLookup.get(osId), "devices.disk.hotpluggableInterfaces", version); return new HashSet<>(trimElements(devices.split(","))); } @Override public List<String> getWatchDogModels(int osId, Version version) { String models = getValueByVersion(idToUnameLookup.get(osId), "devices.watchdog.models", version); return trimElements(models.split(",")); } @Override public Set<VmWatchdogType> getVmWatchdogTypes(int osId, Version version) { Set<VmWatchdogType> vmWatchdogTypes = new HashSet<>(); for (String watchDogModel : getWatchDogModels(osId, version)) { vmWatchdogTypes.add(VmWatchdogType.getByName(watchDogModel)); } return vmWatchdogTypes; } @Override public boolean isLinux(int osId) { return getOsFamily(osId).equalsIgnoreCase("linux"); } @Override public int getMinimumRam(int osId, Version version) { return getInt(getValueByVersion(idToUnameLookup.get(osId), "resources.minimum.ram", version), -1); } @Override public int getMaximumRam(int osId, Version version) { return getInt(getValueByVersion(idToUnameLookup.get(osId), "resources.maximum.ram", version), -1); } @Override public Map<Integer, Map<Version, List<Pair<GraphicsType, DisplayType>>>> getGraphicsAndDisplays() { Map<Integer, Map<Version, List<Pair<GraphicsType, DisplayType>>>> supportedGraphicsAndDisplaysMap = new HashMap<>(); Set<Version> versionsWithNull = new HashSet<>(Version.ALL); versionsWithNull.add(null); for (Integer osId : getOsIds()) { supportedGraphicsAndDisplaysMap.put(osId, new HashMap<>()); for (Version ver : versionsWithNull) { List<Pair<GraphicsType, DisplayType>> displayTypeList = getGraphicsAndDisplays(osId, ver); supportedGraphicsAndDisplaysMap.get(osId).put(ver, displayTypeList); } } return supportedGraphicsAndDisplaysMap; } public List<Pair<GraphicsType, DisplayType>> getGraphicsAndDisplays(int osId, Version version) { return parseDisplayProtocols(osId, version); } private List<Pair<GraphicsType, DisplayType>> parseDisplayProtocols(int osId, Version version) { List<Pair<GraphicsType, DisplayType>> graphicsAndDisplays = new ArrayList<>(); String displayAndGraphicsLine = getValueByVersion(idToUnameLookup.get(osId), "devices.display.protocols", version); // todo - use different key? for (String displayAndGraphics : displayAndGraphicsLine.split(",")) { Pair<String, String> pair = parseSlashSeparatedPair(displayAndGraphics); if (pair != null) { GraphicsType graphics = GraphicsType.fromString(pair.getFirst()); DisplayType display = DisplayType.valueOf(pair.getSecond()); graphicsAndDisplays.add(new Pair<>(graphics, display)); } } return graphicsAndDisplays; } private static Pair<String, String> parseSlashSeparatedPair(String slashSeparatedString) { List<String> splitted = trimElements(slashSeparatedString.split("/")); return (splitted.size() == 2) ? new Pair<>(splitted.get(0), splitted.get(1)) : null; } @Override public int getVramMultiplier(int osId) { return getInt(getValueByVersion(idToUnameLookup.get(osId), "devices.display.vramMultiplier", null), 0); } @Override public Map<Integer, Map<Version, Boolean>> getBalloonSupportMap() { Map<Integer, Map<Version, Boolean>> balloonSupportMap = new HashMap<>(); Set<Version> versionsWithNull = new HashSet<>(Version.ALL); versionsWithNull.add(null); Set<Integer> osIds = new HashSet<>(getOsIds()); for (Integer osId : osIds) { balloonSupportMap.put(osId, new HashMap<>()); for (Version ver : versionsWithNull) { balloonSupportMap.get(osId).put(ver, isBalloonEnabled(osId, ver)); } } return balloonSupportMap; } @Override public boolean isBalloonEnabled(int osId, Version version) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "devices.balloon.enabled", version), false); } @Override public boolean hasNicHotplugSupport(int osId, Version version) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "devices.network.hotplugSupport", version), false); } @Override public String getSysprepPath(int osId, Version version) { return EngineLocalConfig.getInstance().expandString(getValueByVersion(idToUnameLookup.get(osId), "sysprepPath", version)); } @Override public String getSysprepFileName(int osId, Version version) { return getValueByVersion(idToUnameLookup.get(osId), "sysprepFileName", version); } @Override public String getProductKey(int osId, Version version) { return getValueByVersion(idToUnameLookup.get(osId), "productKey", version); } @Override public String getSoundDevice(int osId, Version version) { return getValueByVersion(idToUnameLookup.get(osId), "devices.audio", version); } @Override public int getMaxPciDevices(int osId, Version version) { return getInt(getValueByVersion(idToUnameLookup.get(osId), "devices.maxPciDevices", version), -1); } @Override public String getCdInterface(int osId, Version version, ChipsetType chipset) { String line = getValueByVersion(idToUnameLookup.get(osId), "devices.cdInterface", version); String defaultInterface = null; for (String element : line.split(",")) { Pair<String, String> pair = parseSlashSeparatedPair(element); if (pair == null) { defaultInterface = element.trim().toLowerCase(); } else if (chipset != null && chipset.getChipsetName().equalsIgnoreCase(pair.getFirst())) { return pair.getSecond().toLowerCase(); } } return defaultInterface; } @Override public boolean isFloppySupported(int osId, Version version) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "devices.floppy.support", version), false); } @Override public boolean isTimezoneValueInteger(int osId, Version version) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "isTimezoneTypeInteger", version), false); } @Override public boolean isHypervEnabled(int osId, Version version) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "devices.hyperv.enabled", version), false); } @Override public Map<Pair<Integer, Version>, Set<String>> getUnsupportedCpus() { Set<Version> versionsWithNull = new HashSet<>(Version.ALL); versionsWithNull.add(null); Map<Pair<Integer, Version>, Set<String>> unsupportedCpus = new HashMap<>(); for (int osId : getOsIds()) { for (Version version : versionsWithNull) { unsupportedCpus.put( new Pair<>(osId, version), getUnsupportedCpus(osId, version) ); } } return unsupportedCpus; } @Override public boolean isCpuSupported(int osId, Version version, String cpuId) { return !getUnsupportedCpus(osId, version).contains(cpuId.toLowerCase()); } @Override public boolean isCpuHotplugSupported(int osId) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "cpu.hotplugSupport", null), true); } @Override public boolean isCpuHotunplugSupported(int osId) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "cpu.hotunplugSupport", null), false); } @Override public Map<Integer, Map<Version, Boolean>> getSoundDeviceSupportMap() { Map<Integer, Map<Version, Boolean>> soundDeviceSupportMap = new HashMap<>(); Set<Version> versionsWithNull = new HashSet<>(Version.ALL); versionsWithNull.add(null); for (Integer osId : getOsIds()) { soundDeviceSupportMap.put(osId, new HashMap<>()); for (Version ver : versionsWithNull) { soundDeviceSupportMap.get(osId).put(ver, isSoundDeviceEnabled(osId, ver)); } } return soundDeviceSupportMap; } @Override public boolean isSoundDeviceEnabled(int osId, Version version) { return getBoolean(getValueByVersion(idToUnameLookup.get(osId), "devices.audio.enabled", version), false); } @Override public UsbControllerModel getOsUsbControllerModel(int osId, Version version) { final String osInfoName = getValueByVersion(getUniqueOsNames().get(osId), "devices.usb.controller", version); if (StringUtils.isEmpty(osInfoName)) { return null; } return UsbControllerModel.fromLibvirtName(osInfoName); } @Override public int getOsIdByUniqueName(String uniqueOsName) { for (Map.Entry<Integer, String> entry : getUniqueOsNames().entrySet()) { if (entry.getValue().equals(uniqueOsName)) { return entry.getKey(); } } if (getBackwardCompatibleNamesToIds().containsKey(uniqueOsName)) { return getBackwardCompatibleNamesToIds().get(uniqueOsName); } return 0; } @Override public Set<String> getUnsupportedCpus(int osId, Version version) { return new HashSet<>(trimElements( getValueByVersion( idToUnameLookup.get(osId), "cpu.unsupported", version) .toLowerCase().split(","))); } private boolean getBoolean(String value, boolean defaultValue) { return value == null ? defaultValue : Boolean.parseBoolean(value); } private int getInt(String value, int defaultValue) { try { return value == null ? defaultValue : Integer.parseInt(value); } catch (NumberFormatException ex) { return defaultValue; } } /** * get the value of the key specified by its version or the default version if not exist. see * {@link OsRepositoryImpl#getKeyNode} */ private String getValueByVersion(String uniqueOsName, String relativeKeyPath, Version version) { Preferences keyNode = getKeyNode(uniqueOsName, relativeKeyPath, version); if (keyNode == emptyNode) { version = null; keyNode = getKeyNode(uniqueOsName, relativeKeyPath, null); } return keyNode.get(versionedValuePath(version), ""); } /** * * @param uniqueOsName * is the os.{String} section of the key path. \e.g "rhel6" is the unique os name of os.rhel6.description * key * @param version * the value version. e.g ver/os/rhel6/devices/sound/version.3.3 = ac97 * @return the node of the specified key for the given osId or its derived parent. Essentially this method will * recursively be called till no parent with the exact path is found. * */ private Preferences getKeyNode(String uniqueOsName, String relativeKeyPath, Version version) { if (uniqueOsName == null) { return emptyNode; } // first try direct OS node try { Preferences node = getNodeIfExist(uniqueOsName, relativeKeyPath); if (node != null && Arrays.asList(node.keys()).contains(versionedValuePath(version))) { return node; } else { // if not exist directly on the OS consult the one its derived from String derivedFromOs = preferences.node(OS_ROOT_NODE + uniqueOsName + "/derivedFrom").get("value", null); return derivedFromOs == null ? emptyNode : getKeyNode(derivedFromOs, relativeKeyPath, version); } } catch (BackingStoreException e) { // our preferences impl should use storage to back the data structure // throwing unchecked exception here to make sure this anomality is noticed throw new RuntimeException(e); } } /** * * @param osId unique name identifier. this is NOT the "id" attribute which is kept for backward compatibility. * @return the node which its path is /os/$osId/path/to/key otherwise null */ private Preferences getNodeIfExist(String osId, String key) throws BackingStoreException { // make a full path name of some.key to os/$osId/some/key String pathName = OS_ROOT_NODE + osId + "/" + key.replaceAll("\\.", "/"); if (preferences.nodeExists(pathName)) { return preferences.node(pathName); } return null; } /** * helper method to retrieve a list of trimmed elements<br> * <p> * <code><b> foo, bar , baz </b></code> results <code><b>foo,bar,baz</b></code> * </p> * * @param elements * vararg of string elements. * @return new list where each value its whitespaces trimmed, and * is not added empty values. */ private static List<String> trimElements(String... elements) { List<String> list = new ArrayList<>(elements.length); for (String e : elements) { e = e.trim(); if (e.length() > 0) { list.add(e); } } return list; } /** * a key can have several values per version. a null version represents the default while other are specific one: * key.value = someval // the default value. the path returned is "value" key.value.3.1 = otherval // the 3.1 * version val. the path returned is "value.3.1" * * @return the string representation of the value path. for key.value.3.1 = otherval "value.3.1" should be returned. */ private String versionedValuePath(Version version) { return version == null ? "value" : "value." + version.toString(); } @Override public boolean isSingleQxlDeviceEnabled(int osId) { return isLinux(osId); } public Map<String, Integer> getBackwardCompatibleNamesToIds() { return backwardCompatibleNamesToIds; } @Override public Map<ArchitectureType, Integer> getDefaultOSes() { return defaultOsMap; } @Override public String toString() { StringBuilder sb = new StringBuilder(); try { walkTree(sb, preferences); } catch (BackingStoreException e) { log.error("Error traversing OS tree: {}", e.getMessage()); log.debug("Exception", e); } return sb.toString(); } private void walkTree(StringBuilder sb, Preferences node) throws BackingStoreException { if (node.childrenNames().length == 0) { sb.append( node.absolutePath() .replaceFirst("/", "") .replace("/", ".")); for (String k : node.keys()) { sb.append("\n\t") .append(k) .append("=") .append(node.get(k, "")); } sb.append("\n"); } else { for (String nodePath : node.childrenNames()) { walkTree(sb, node.node(nodePath)); } } } }