From e9a3845281d95b7ce6b8dbbd013d14d92c563d6c Mon Sep 17 00:00:00 2001 From: Anders Ingemann Date: Sun, 25 Jan 2015 17:31:46 +0100 Subject: [PATCH] Fix serialization of CalledProcessError --- bootstrapvz/common/tools.py | 9 +++++++-- bootstrapvz/remote/__init__.py | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/bootstrapvz/common/tools.py b/bootstrapvz/common/tools.py index 1715c66..2829c51 100644 --- a/bootstrapvz/common/tools.py +++ b/bootstrapvz/common/tools.py @@ -3,9 +3,14 @@ import os def log_check_call(command, stdin=None, env=None, shell=False, cwd=None): status, stdout, stderr = log_call(command, stdin, env, shell, cwd) + from subprocess import CalledProcessError if status != 0: - from subprocess import CalledProcessError - raise CalledProcessError(status, ' '.join(command), '\n'.join(stderr)) + e = CalledProcessError(status, ' '.join(command), '\n'.join(stderr)) + # Fix Pyro4's fixIronPythonExceptionForPickle() by setting the args property, + # even though we use our own serialization (at least I think that's the problem). + # See bootstrapvz.remote.serialize_called_process_error for more info. + setattr(e, 'args', (status, ' '.join(command), '\n'.join(stderr))) + raise e return stdout diff --git a/bootstrapvz/remote/__init__.py b/bootstrapvz/remote/__init__.py index 7397a5a..ad0049d 100644 --- a/bootstrapvz/remote/__init__.py +++ b/bootstrapvz/remote/__init__.py @@ -33,6 +33,7 @@ supported_exceptions = ['bootstrapvz.common.exceptions.ManifestError', 'bootstrapvz.base.pkg.exceptions.SourceError', 'bootstrapvz.common.exceptions.UnitError', 'bootstrapvz.common.fsm_proxy.FSMProxyError', + 'subprocess.CalledProcessError', ] @@ -41,6 +42,8 @@ def register_deserialization_handlers(): SerializerBase.register_dict_to_class(supported_class, deserialize) for supported_exc in supported_exceptions: SerializerBase.register_dict_to_class(supported_exc, deserialize_exception) + import subprocess + SerializerBase.register_class_to_dict(subprocess.CalledProcessError, serialize_called_process_error) def unregister_deserialization_handlers(): @@ -73,6 +76,28 @@ def deserialize(fq_classname, data): return instance +def serialize_called_process_error(obj): + # This is by far the weirdest exception serialization. + # There is a bug in both Pyro4 and the Python subprocess module. + # CalledProcessError does not populate its args property, + # although according to https://docs.python.org/2/library/exceptions.html#exceptions.BaseException.args + # it should... + # So we populate that property during serialization instead + # (the code is grabbed directly from Pyro4's class_to_dict()) + # However, Pyro4 still cannot figure out to call the deserializer + # unless we also use setattr() on the exception to set the args below + # (before throwing it). + # Mind you, the error "__init__() takes at least 3 arguments (2 given)" + # is thrown *on the server* if we don't use setattr(). + # It's all very confusing to me and I'm not entirely + # sure what the exact problem is. Regardless - it works, so there. + return {'__class__': obj.__class__.__module__ + '.' + obj.__class__.__name__, + '__exception__': True, + 'args': (obj.returncode, obj.cmd, obj.output), + 'attributes': vars(obj) # add custom exception attributes + } + + def get_class_object(fq_classname): parts = fq_classname.split('.') module_name = '.'.join(parts[:-1])