diff --git a/daemons/ganeti-noded b/daemons/ganeti-noded index 15af1014c4a80d3e36a10d488ac90d854d584f95..b9b51df80d72b009ac661d3c23dcd749ef274f36 100755 --- a/daemons/ganeti-noded +++ b/daemons/ganeti-noded @@ -608,7 +608,7 @@ class NodeHttpServer(http.server.HttpServer): """Query detailed information about existing OSes. """ - return True, [os_obj.ToDict() for os_obj in backend.DiagnoseOS()] + return backend.DiagnoseOS() @staticmethod def perspective_os_get(params): @@ -616,11 +616,8 @@ class NodeHttpServer(http.server.HttpServer): """ name = params[0] - try: - os_obj = backend.OSFromDisk(name) - except errors.InvalidOS, err: - os_obj = objects.OS.FromInvalidOS(err) - return os_obj.ToDict() + os_obj = backend.OSFromDisk(name) + return True, os_obj.ToDict() # hooks ----------------------- diff --git a/lib/backend.py b/lib/backend.py index e5077c1717f6e6c8a56cd11443e37dc7b7e2e889..bf6316865fa77088aaf89ac351905ab80283b4c4 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -718,15 +718,8 @@ def InstanceOsAdd(instance, reinstall): @return: the success of the operation """ - try: - inst_os = OSFromDisk(instance.os) - except errors.InvalidOS, err: - os_name, os_dir, os_err = err.args - if os_dir is None: - return (False, "Can't find OS '%s': %s" % (os_name, os_err)) - else: - return (False, "Error parsing OS '%s' in directory %s: %s" % - (os_name, os_dir, os_err)) + inst_os = OSFromDisk(instance.os) + create_env = OSEnvironment(instance) if reinstall: @@ -1530,12 +1523,12 @@ def _OSOndiskVersion(name, os_dir): try: st = os.stat(api_file) except EnvironmentError, err: - raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file not" - " found (%s)" % _ErrnoOrStr(err)) + return False, ("Required file 'ganeti_api_version' file not" + " found under path %s: %s" % (os_dir, _ErrnoOrStr(err))) if not stat.S_ISREG(stat.S_IFMT(st.st_mode)): - raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file is not" - " a regular file") + return False, ("File 'ganeti_api_version' file at %s is not" + " a regular file" % os_dir) try: f = open(api_file) @@ -1544,17 +1537,17 @@ def _OSOndiskVersion(name, os_dir): finally: f.close() except EnvironmentError, err: - raise errors.InvalidOS(name, os_dir, "error while reading the" - " API version (%s)" % _ErrnoOrStr(err)) + return False, ("Error while reading the API version file at %s: %s" % + (api_file, _ErrnoOrStr(err))) api_versions = [version.strip() for version in api_versions] try: api_versions = [int(version) for version in api_versions] except (TypeError, ValueError), err: - raise errors.InvalidOS(name, os_dir, - "API version is not integer (%s)" % str(err)) + return False, ("API version(s) can't be converted to integer: %s" % + str(err)) - return api_versions + return True, api_versions def DiagnoseOS(top_dirs=None): @@ -1565,8 +1558,12 @@ def DiagnoseOS(top_dirs=None): search (if not given defaults to L{constants.OS_SEARCH_PATH}) @rtype: list of L{objects.OS} - @return: an OS object for each name in all the given - directories + @return: a list of tuples (name, path, status, diagnose) + for all (potential) OSes under all search paths, where: + - name is the (potential) OS name + - path is the full path to the OS + - status True/False is the validity of the OS + - diagnose is the error message for an invalid OS, otherwise empty """ if top_dirs is None: @@ -1581,16 +1578,18 @@ def DiagnoseOS(top_dirs=None): logging.exception("Can't list the OS directory %s", dir_name) break for name in f_names: - try: - os_inst = OSFromDisk(name, base_dir=dir_name) - result.append(os_inst) - except errors.InvalidOS, err: - result.append(objects.OS.FromInvalidOS(err)) + os_path = os.path.sep.join([dir_name, name]) + status, os_inst = _TryOSFromDisk(name, base_dir=dir_name) + if status: + diagnose = "" + else: + diagnose = os_inst + result.append((name, os_path, status, diagnose)) - return result + return True, result -def OSFromDisk(name, base_dir=None): +def _TryOSFromDisk(name, base_dir=None): """Create an OS instance from disk. This function will return an OS instance if the given name is a @@ -1600,24 +1599,26 @@ def OSFromDisk(name, base_dir=None): @type base_dir: string @keyword base_dir: Base directory containing OS installations. Defaults to a search in all the OS_SEARCH_PATH dirs. - @rtype: L{objects.OS} - @return: the OS instance if we find a valid one - @raise errors.InvalidOS: if we don't find a valid OS + @rtype: tuple + @return: success and either the OS instance if we find a valid one, + or error message """ if base_dir is None: os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir) if os_dir is None: - raise errors.InvalidOS(name, None, "OS dir not found in search path") + return False, "Directory for OS %s not found in search path" % name else: os_dir = os.path.sep.join([base_dir, name]) - api_versions = _OSOndiskVersion(name, os_dir) + status, api_versions = _OSOndiskVersion(name, os_dir) + if not status: + # push the error up + return status, api_versions if constants.OS_API_VERSION not in api_versions: - raise errors.InvalidOS(name, os_dir, "API version mismatch" - " (found %s want %s)" - % (api_versions, constants.OS_API_VERSION)) + return False, ("API version mismatch for path '%s': found %s, want %s." % + (os_dir, api_versions, constants.OS_API_VERSION)) # OS Scripts dictionary, we will populate it with the actual script names os_scripts = dict.fromkeys(constants.OS_SCRIPTS) @@ -1628,24 +1629,51 @@ def OSFromDisk(name, base_dir=None): try: st = os.stat(os_scripts[script]) except EnvironmentError, err: - raise errors.InvalidOS(name, os_dir, "'%s' script missing (%s)" % - (script, _ErrnoOrStr(err))) + return False, ("Script '%s' under path '%s' is missing (%s)" % + (script, os_dir, _ErrnoOrStr(err))) if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR: - raise errors.InvalidOS(name, os_dir, "'%s' script not executable" % - script) + return False, ("Script '%s' under path '%s' is not executable" % + (script, os_dir)) if not stat.S_ISREG(stat.S_IFMT(st.st_mode)): - raise errors.InvalidOS(name, os_dir, "'%s' is not a regular file" % - script) + return False, ("Script '%s' under path '%s' is not a regular file" % + (script, os_dir)) + + os_obj = objects.OS(name=name, path=os_dir, status=constants.OS_VALID_STATUS, + create_script=os_scripts[constants.OS_SCRIPT_CREATE], + export_script=os_scripts[constants.OS_SCRIPT_EXPORT], + import_script=os_scripts[constants.OS_SCRIPT_IMPORT], + rename_script=os_scripts[constants.OS_SCRIPT_RENAME], + api_versions=api_versions) + return True, os_obj + + +def OSFromDisk(name, base_dir=None): + """Create an OS instance from disk. + + This function will return an OS instance if the given name is a + valid OS name. Otherwise, it will raise an appropriate + L{RPCFail} exception, detailing why this is not a valid OS. + + This is just a wrapper over L{_TryOSFromDisk}, which doesn't raise + an exception but returns true/false status data. + + @type base_dir: string + @keyword base_dir: Base directory containing OS installations. + Defaults to a search in all the OS_SEARCH_PATH dirs. + @rtype: L{objects.OS} + @return: the OS instance if we find a valid one + @raise RPCFail: if we don't find a valid OS + + """ + status, payload = _TryOSFromDisk(name, base_dir) + + if not status: + _Fail(payload) + return payload - return objects.OS(name=name, path=os_dir, status=constants.OS_VALID_STATUS, - create_script=os_scripts[constants.OS_SCRIPT_CREATE], - export_script=os_scripts[constants.OS_SCRIPT_EXPORT], - import_script=os_scripts[constants.OS_SCRIPT_IMPORT], - rename_script=os_scripts[constants.OS_SCRIPT_RENAME], - api_versions=api_versions) def OSEnvironment(instance, debug=0): """Calculate the environment for an os script. diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 0676c2383c1714b8e295d40fc9bd49c4739672be..d983365b9507165bccf417b6822326e04540eaae 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -1801,10 +1801,11 @@ class LUDiagnoseOS(NoHooksLU): @rtype: dict @return: a dictionary with osnames as keys and as value another map, with - nodes as keys and list of OS objects as values, eg:: + nodes as keys and tuples of (path, status, diagnose) as values, eg:: - {"debian-etch": {"node1": [<object>,...], - "node2": [<object>,]} + {"debian-etch": {"node1": [(/usr/lib/..., True, ""), + (/srv/..., False, "invalid api")], + "node2": [(/srv/..., True, "")]} } """ @@ -1817,15 +1818,14 @@ class LUDiagnoseOS(NoHooksLU): for node_name, nr in rlist.items(): if nr.RemoteFailMsg() or not nr.payload: continue - for os_serialized in nr.payload: - os_obj = objects.OS.FromDict(os_serialized) - if os_obj.name not in all_os: + for name, path, status, diagnose in nr.payload: + if name not in all_os: # build a list of nodes for this os containing empty lists # for each node in node_list - all_os[os_obj.name] = {} + all_os[name] = {} for nname in good_nodes: - all_os[os_obj.name][nname] = [] - all_os[os_obj.name][node_name].append(os_obj) + all_os[name][nname] = [] + all_os[name][node_name].append((path, status, diagnose)) return all_os def Exec(self, feedback_fn): @@ -1842,11 +1842,12 @@ class LUDiagnoseOS(NoHooksLU): if field == "name": val = os_name elif field == "valid": - val = utils.all([osl and osl[0] for osl in os_data.values()]) + val = utils.all([osl and osl[0][1] for osl in os_data.values()]) elif field == "node_status": + # this is just a copy of the dict val = {} - for node_name, nos_list in os_data.iteritems(): - val[node_name] = [(v.status, v.path) for v in nos_list] + for node_name, nos_list in os_data.items(): + val[node_name] = nos_list else: raise errors.ParameterError(field) row.append(val) @@ -3156,10 +3157,11 @@ class LUReinstallInstance(LogicalUnit): raise errors.OpPrereqError("Primary node '%s' is unknown" % self.op.pnode) result = self.rpc.call_os_get(pnode.name, self.op.os_type) - result.Raise() - if not isinstance(result.data, objects.OS): + msg = result.RemoteFailMsg() + if msg: raise errors.OpPrereqError("OS '%s' not in supported OS list for" - " primary node" % self.op.os_type) + " primary node %s: %s" % + (self.op.os_type, pnode.pname, msg)) self.instance = instance @@ -4843,10 +4845,11 @@ class LUCreateInstance(LogicalUnit): # os verification result = self.rpc.call_os_get(pnode.name, self.op.os_type) - result.Raise() - if not isinstance(result.data, objects.OS): + msg = result.RemoteFailMsg() + if msg: raise errors.OpPrereqError("OS '%s' not in supported os list for" - " primary node" % self.op.os_type) + " primary node %s: %s" % + (self.op.os_type, pnode.name, msg)) _CheckNicsBridgesExist(self, self.nics, self.pnode.name) diff --git a/scripts/gnt-os b/scripts/gnt-os index a0c09a7abac0d4c49fb56ad437bacf22550ab6ff..4cfa07bf0e307fd0feaa366df6bdfe77aedd9d4e 100755 --- a/scripts/gnt-os +++ b/scripts/gnt-os @@ -64,6 +64,22 @@ def ListOS(opts, args): return 0 +def _OsStatus(status, diagnose): + """Beautifier function for OS status. + + @type status: boolean + @param status: is the OS valid + @type diagnose: string + @param diagnose: the error message for invalid OSes + @rtype: string + @return: a formatted status + + """ + if status: + return "valid" + else: + return "invalid - %s" % diagnose + def DiagnoseOS(opts, args): """Analyse all OSes on this cluster. @@ -91,16 +107,17 @@ def DiagnoseOS(opts, args): for node_name, node_info in node_data.iteritems(): nodes_hidden[node_name] = [] if node_info: # at least one entry in the per-node list - first_os_status, first_os_path = node_info.pop(0) - first_os_msg = ("%s (path: %s)" % - (first_os_status, first_os_path)) - if first_os_status == constants.OS_VALID_STATUS: + first_os_path, first_os_status, first_os_msg = node_info.pop(0) + first_os_msg = ("%s (path: %s)" % (_OsStatus(first_os_status, + first_os_msg), + first_os_path)) + if first_os_status: nodes_valid[node_name] = first_os_msg else: nodes_bad[node_name] = first_os_msg - for hstatus, hpath in node_info: + for hpath, hstatus, hmsg in node_info: nodes_hidden[node_name].append(" [hidden] path: %s, status: %s" % - (hpath, hstatus)) + (hpath, _OsStatus(hstatus, hmsg))) else: nodes_bad[node_name] = "OS not found"