6 from multiprocessing import Process, Pipe
7 from pickle import dumps
12 from framework import VppTestCase
13 from aenum import Enum
16 class SerializableClassCopy(object):
18 Empty class used as a basis for a serializable copy of another class.
23 return '<SerializableClassCopy dict=%s>' % self.__dict__
26 class RemoteClassAttr(object):
28 Wrapper around attribute of a remotely executed class.
31 def __init__(self, remote, attr):
32 self._path = [attr] if attr else []
35 def path_to_str(self):
36 return '.'.join(self._path)
38 def get_remote_value(self):
39 return self._remote._remote_exec(RemoteClass.GET, self.path_to_str())
42 return self._remote._remote_exec(RemoteClass.REPR, self.path_to_str())
45 return self._remote._remote_exec(RemoteClass.STR, self.path_to_str())
47 def __getattr__(self, attr):
49 if not (attr.startswith('__') and attr.endswith('__')):
50 raise AttributeError('tried to get private attribute: %s ',
52 self._path.append(attr)
55 def __setattr__(self, attr, val):
57 if not (attr.startswith('__') and attr.endswith('__')):
58 super(RemoteClassAttr, self).__setattr__(attr, val)
60 self._path.append(attr)
61 self._remote._remote_exec(RemoteClass.SETATTR, self.path_to_str(),
64 def __call__(self, *args, **kwargs):
65 return self._remote._remote_exec(RemoteClass.CALL, self.path_to_str(),
69 class RemoteClass(Process):
71 This class can wrap around and adapt the interface of another class,
72 and then delegate its execution to a newly forked child process.
74 # Create a remotely executed instance of MyClass
75 object = RemoteClass(MyClass, arg1='foo', arg2='bar')
77 # Access the object normally as if it was an instance of your class.
78 object.my_attribute = 20
79 print object.my_attribute
80 print object.my_method(object.my_attribute)
81 object.my_attribute.nested_attribute = 'test'
82 # If you need the value of a remote attribute, use .get_remote_value
83 method. This method is automatically called when needed in the context
84 of a remotely executed class. E.g.:
85 if (object.my_attribute.get_remote_value() > 20):
86 object.my_attribute2 = object.my_attribute
87 # Destroy the instance
92 GET = 0 # Get attribute remotely
93 CALL = 1 # Call method remotely
94 SETATTR = 2 # Set attribute remotely
95 REPR = 3 # Get representation of a remote object
96 STR = 4 # Get string representation of a remote object
97 QUIT = 5 # Quit remote execution
99 PIPE_PARENT = 0 # Parent end of the pipe
100 PIPE_CHILD = 1 # Child end of the pipe
102 DEFAULT_TIMEOUT = 2 # default timeout for an operation to execute
104 def __init__(self, cls, *args, **kwargs):
105 super(RemoteClass, self).__init__()
108 self._kwargs = kwargs
109 self._timeout = RemoteClass.DEFAULT_TIMEOUT
110 self._pipe = Pipe() # pipe for input/output arguments
113 return moves.reprlib.repr(RemoteClassAttr(self, None))
116 return str(RemoteClassAttr(self, None))
118 def __call__(self, *args, **kwargs):
119 return self.RemoteClassAttr(self, None)()
121 def __getattr__(self, attr):
122 if attr[0] == '_' or not self.is_alive():
123 if not (attr.startswith('__') and attr.endswith('__')):
124 if hasattr(super(RemoteClass, self), '__getattr__'):
125 return super(RemoteClass, self).__getattr__(attr)
126 raise AttributeError('missing: %s', attr)
127 return RemoteClassAttr(self, attr)
129 def __setattr__(self, attr, val):
130 if attr[0] == '_' or not self.is_alive():
131 if not (attr.startswith('__') and attr.endswith('__')):
132 super(RemoteClass, self).__setattr__(attr, val)
134 setattr(RemoteClassAttr(self, None), attr, val)
136 def _remote_exec(self, op, path=None, *args, **kwargs):
138 Execute given operation on a given, possibly nested, member remotely.
140 # automatically resolve remote objects in the arguments
141 mutable_args = list(args)
142 for i, val in enumerate(mutable_args):
143 if isinstance(val, RemoteClass) or \
144 isinstance(val, RemoteClassAttr):
145 mutable_args[i] = val.get_remote_value()
146 args = tuple(mutable_args)
147 for key, val in six.iteritems(kwargs):
148 if isinstance(val, RemoteClass) or \
149 isinstance(val, RemoteClassAttr):
150 kwargs[key] = val.get_remote_value()
152 args = self._make_serializable(args)
153 kwargs = self._make_serializable(kwargs)
154 self._pipe[RemoteClass.PIPE_PARENT].send((op, path, args, kwargs))
155 timeout = self._timeout
156 # adjust timeout specifically for the .sleep method
157 if path is not None and path.split('.')[-1] == 'sleep':
158 if args and isinstance(args[0], (long, int)):
160 elif 'timeout' in kwargs:
161 timeout += kwargs['timeout']
162 if not self._pipe[RemoteClass.PIPE_PARENT].poll(timeout):
165 rv = self._pipe[RemoteClass.PIPE_PARENT].recv()
166 rv = self._deserialize(rv)
171 def _get_local_object(self, path):
173 Follow the path to obtain a reference on the addressed nested attribute
177 obj = getattr(obj, attr)
180 def _get_local_value(self, path):
182 return self._get_local_object(path)
183 except AttributeError:
186 def _call_local_method(self, path, *args, **kwargs):
188 method = self._get_local_object(path)
189 return method(*args, **kwargs)
190 except AttributeError:
193 def _set_local_attr(self, path, value):
195 obj = self._get_local_object(path[:-1])
196 setattr(obj, path[-1], value)
197 except AttributeError:
201 def _get_local_repr(self, path):
203 obj = self._get_local_object(path)
204 return moves.reprlib.repr(obj)
205 except AttributeError:
208 def _get_local_str(self, path):
210 obj = self._get_local_object(path)
212 except AttributeError:
215 def _serializable(self, obj):
216 """ Test if the given object is serializable """
223 def _make_obj_serializable(self, obj):
225 Make a serializable copy of an object.
226 Members which are difficult/impossible to serialize are stripped.
228 if self._serializable(obj):
229 return obj # already serializable
231 copy = SerializableClassCopy()
234 Dictionaries can hold complex values, so we split keys and values into
235 separate lists and serialize them individually.
237 if (type(obj) is dict):
238 copy.type = type(obj)
241 for k, v in obj.items():
242 copy.k_list.append(self._make_serializable(k))
243 copy.v_list.append(self._make_serializable(v))
246 # copy at least serializable attributes and properties
247 for name, member in inspect.getmembers(obj):
248 # skip private members and non-writable dunder methods.
250 if name in ['__weakref__']:
252 if not (name.startswith('__') and name.endswith('__')):
254 if callable(member) and not isinstance(member, property):
256 if not self._serializable(member):
258 setattr(copy, name, member)
261 def _make_serializable(self, obj):
263 Make a serializable copy of an object or a list/tuple of objects.
264 Members which are difficult/impossible to serialize are stripped.
266 if (type(obj) is list) or (type(obj) is tuple):
269 rv.append(self._make_serializable(item))
270 if type(obj) is tuple:
273 elif (isinstance(obj, Enum)):
276 return self._make_obj_serializable(obj)
278 def _deserialize_obj(self, obj):
279 if (hasattr(obj, 'type')):
282 for k, v in zip(obj.k_list, obj.v_list):
283 _obj[self._deserialize(k)] = self._deserialize(v)
287 def _deserialize(self, obj):
288 if (type(obj) is list) or (type(obj) is tuple):
291 rv.append(self._deserialize(item))
292 if type(obj) is tuple:
296 return self._deserialize_obj(obj)
298 def start_remote(self):
299 """ Start remote execution """
302 def quit_remote(self):
303 """ Quit remote execution """
304 self._remote_exec(RemoteClass.QUIT, None)
306 def get_remote_value(self):
307 """ Get value of a remotely held object """
308 return RemoteClassAttr(self, None).get_remote_value()
310 def set_request_timeout(self, timeout):
311 """ Change request timeout """
312 self._timeout = timeout
316 Create instance of the wrapped class and execute operations
317 on it as requested by the parent process.
319 self._instance = self._cls(*self._args, **self._kwargs)
323 # get request from the parent process
325 kwargs) = self._pipe[RemoteClass.PIPE_CHILD].recv()
326 args = self._deserialize(args)
327 kwargs = self._deserialize(kwargs)
328 path = path.split('.') if path else []
329 if op == RemoteClass.GET:
330 rv = self._get_local_value(path)
331 elif op == RemoteClass.CALL:
332 rv = self._call_local_method(path, *args, **kwargs)
333 elif op == RemoteClass.SETATTR and 'value' in kwargs:
334 self._set_local_attr(path, kwargs['value'])
335 elif op == RemoteClass.REPR:
336 rv = self._get_local_repr(path)
337 elif op == RemoteClass.STR:
338 rv = self._get_local_str(path)
339 elif op == RemoteClass.QUIT:
344 if not self._serializable(rv):
345 rv = self._make_serializable(rv)
346 self._pipe[RemoteClass.PIPE_CHILD].send(rv)
349 self._instance = None # destroy the instance
352 @unittest.skip("Remote Vpp Test Case Class")
353 class RemoteVppTestCase(VppTestCase):
354 """ Re-use VppTestCase to create remote VPP segment
360 # fork new process before client connects to VPP
361 cls.remote_test = RemoteClass(RemoteVppTestCase)
363 # start remote process
364 cls.remote_test.start_remote()
366 # set up your test case
367 super(MyTestCase, cls).setUpClass()
370 cls.remote_test.setUpClass(cls.tempdir)
373 def tearDownClass(cls):
374 # tear down remote test
375 cls.remote_test.tearDownClass()
377 # stop remote process
378 cls.remote_test.quit_remote()
380 # tear down your test case
381 super(MyTestCase, cls).tearDownClass()
385 super(RemoteVppTestCase, self).__init__("emptyTest")
387 # Note: __del__ is a 'Finalizer" not a 'Destructor'.
388 # https://docs.python.org/3/reference/datamodel.html#object.__del__
390 if hasattr(self, "vpp"):
392 if self.vpp.returncode is None:
394 self.vpp.communicate()
397 def setUpClass(cls, tempdir):
398 # disable features unsupported in remote VPP
399 orig_env = dict(os.environ)
400 if 'STEP' in os.environ:
401 del os.environ['STEP']
402 if 'DEBUG' in os.environ:
403 del os.environ['DEBUG']
404 cls.tempdir_prefix = os.path.basename(tempdir) + "/"
405 super(RemoteVppTestCase, cls).setUpClass()
406 os.environ = orig_env
409 def tearDownClass(cls):
410 super(RemoteVppTestCase, cls).tearDownClass()
412 @unittest.skip("Empty test")
417 def setTestFunctionInfo(self, name, doc):
419 Store the name and documentation string of currently executed test
420 in the main VPP for logging purposes.
422 self._testMethodName = name
423 self._testMethodDoc = doc