Commit 255dcebd authored by Iustin Pop's avatar Iustin Pop
Browse files

Big rewrite of the OS-related functions



Currently the OSes have a special, customized error handling: the OS
object can represent either a valid OS, or an invalid OS. The associated
function, instead of raising other exception or failing, create custom
OS objects representing failed OSes.

While this was good when no other RPC had failure handling, it's
extremely different from how other function in backend.py expect
failures to be signalled.

This patch reworks this completely:
  - the OS object always represents valid OSes (the next patch will
    remove the valid/invalid field and associated constants)
  - the call_os_diagnose returns instead of a list of OS objects, a list
    of (name, path, status, diagnose_msg); the status is then used in
    cmdlib to determine validity and the status and diagnose_msg values
    are used in gnt-os for display
  - call_os_get returns either a valid OS or a RPC remote failure (with
    the error message)
  - the other functions in backend.py now just call backend.OSFromDisk()
    which will return either a valid OS object or raise an exception
  - the bulk of the OSFromDisk was moved to _TryOSFromDisk which returns
    status, value for the functions which don't want an exception raised

The gnt-os list and diagnose commands still work after this patch.
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
parent c8457ce7
......@@ -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 -----------------------
......
......@@ -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.
......
......@@ -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)
......
......@@ -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"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment