def calculate(self, expression): """ :param expression: string with expression to send to Calc App :return: ProtocolReply """ start_time = time.time() if self._mock: try: result = evaluate(expression) except ZeroDivisionError: result = None except Exception as e: result = "Platform {}: exception occurred on calculate: {}".format( self.name, e) exprint() tprint("calculate (with fail) elapsed {}".format(time.time() - start_time)) return proto_failure(result, -2) tprint("calculate elapsed {}".format(time.time() - start_time)) return proto_success(result) # NOTE: mock code just ended here. To avoid nesting there is no else, just flat code # TODO: optimize code - now it's way to hard (just send/receive and so much code!!!) c = self.request(new_message(self._io_interface, "send", expression), None, [], {}, timeout=2.0) # TODO: decrease timeout c_state = self._pop_request_state(c) if not self._request_state_is_success(c_state): tprint( "calculate (with fail result) elapsed {}".format(time.time() - start_time)) return proto_failure("IO failed to send data") c = self.request(new_message(self._io_interface, "receive"), None, [], {}, timeout=2.0) # TODO: decrease timeout c_state = self._pop_request_state(c) if not self._request_state_is_success(c_state): tprint( "calculate (with fail result) elapsed {}".format(time.time() - start_time)) return proto_failure("IO failed to receive response") # TODO: convert from string to number tprint("calculate elapsed {}".format(time.time() - start_time)) result = PM.parse(c_state["__message__"]).reply_data["value"] if isinstance( result, (list, tuple) ): # NOTE: softwarerunner returns list but stream_io returns single item result = result[0] if result.strip() == 'None': result = None else: try: result = int(result) except ValueError: result = float(result) return proto_success(result)
def rpyc_send(self, data): """ Sends data to app via stdin :param data: Data to send over stdin. Could be an item like str, bytearray or list/tuple of items :return: True if data were sent successfully, otherwise - False """ start_time = time.time() if (self._mock is None or data == 'exit') and self._connection is None: return proto_failure("No connection. Ensure start is complete") try: if not isinstance(data, (list, tuple)): data = data, for m in data: if self._mock is None or data == 'exit': # TODO: why data=='exit' overrides self._mock? self._connection.root.send(m) else: try: if self._mock_eval: r = evaluate(m) else: r = m except Exception as e: r = None self._mock.append(str(r)) if self._mock is None or data == 'exit': # TODO: why data=='exit' overrides self._mock? self._connection.root.flush() except Exception as e: eprint("Platform {}: exception occurred during send: {}".format( self.name, e)) exprint() return proto_failure( "Failed to send due to exception {}".format(e), -2) tprint("rpyc_send elapsed {}".format(time.time() - start_time)) return proto_success(None)
def _run(self, context, runs=None): # TODO: threaded arg if runs is None: runs = self._runs if runs <= 0: runs = 0 self._complete = 0 self._remaining = runs if self._remaining > 0: self._reply(context, PM.notify("started sequence")) while self._remaining > 0: assert self._expr is not None, "Sequencer {} wasn't started!".format(self.name) start_time = time.time() expr_result = next(self._expr) # TODO: replace assertions with proto_fails? assert isinstance(expr_result, (list, tuple)), "Expression should return list or tuple" assert len(expr_result) > 0, "Expression should return list or tuple with length at least" \ "3 if there is no channel specification and " \ "4 if there is channel specification" if isinstance(expr_result[0], str) and expr_result[0][0] in ('@', '#'): channel = expr_result.pop(0) else: channel = None assert len(expr_result) >= 3, "Expression should return list or tupple with length at least" \ "3 if there is no channel specification and " \ "4 if there is channel specification" request_message = new_message(*expr_result) c = self.request(request_message, None, [], {}, channel=channel, store_state=False) # NOTE: used default request handler (which just waits for success or failure reply) tprint("sequencer {} request elapsed {}".format(self.name, time.time()-start_time)) # TODO: option to treat request completed when specific message is passed by over special interface self._complete += 1 self._remaining -= 1 return proto_success({"breaked": runs != self._complete, "runs_completed": self._complete}, None)
def _stop(self, reply_contexts): """ Stopping worker method. Does all necessary actions to stop platform after all stop conditions were met Derrived classes should call this in the end when overriding _stop method :return: True if successfully stopped - subject were stopped (whatever that means) and disconnected, otherwise - False """ self._running = False self._stopping = False vprint("platform {} has been stopped".format(self.name)) return proto_success(None)
def _start(self, reply_contexts): """ Startup worker method. Does all necessary actions to start platform instance after all start conditions were met Derrived classes should call this in the end when overriding _start method :return: True if successfully started (binded to subject and subject is in operational state), otherwise - False """ self._running = True self._start_in_progress = False self._starting = False vprint("platform {} just started".format(self.name)) return proto_success(None)
def rpyc_log(self): if self._connection is None: return proto_failure("No connection. Ensure start is complete") try: data = self._connection.root.log except Exception as e: eprint( "Platform {}: exception occurred while getting log: {}".format( self.name, e)) exprint() return proto_failure( "Failed to get log due to exception {}".format(e), -2) return proto_success(data)
def _platformix_report(self, context, fake_reply, kind): """ Reports about platform's state Currently only running and is_running supported * running - replies with retval True if platform is running, and False otherwise * is_running - successful reply if platform is running, and failure otherwise :param context: :param kind: :return: """ if kind == "running": self._reply(context, proto_success(["False", "True"][self._worker.running]), fake_reply) return elif kind == "is_running": if self._worker.running: self._reply(context, proto_success("True", "state"), fake_reply) else: self._reply(context, proto_failure("False"), fake_reply) else: self._reply(context, proto_failure("Unknown report request"), fake_reply)
def _platformix_get(self, context, fake_reply, prop): """ Get host's property. Value is returned in a success message as item with index same as property name :param context: messaging context :param prop: property symbolic name :return: None """ if hasattr(self.host, prop): self._reply(context, proto_success(getattr(self.host, prop), prop), fake_reply) else: self._reply( context, proto_failure("Property {} not found on {}".format( prop, self.host.name)), fake_reply)
def rpyc_receive(self, count=1, timeout=1.0): """ Get's data that were sent by app via stdout :param count: Amount of lines to receive. set count to 0 to receive as much as possible, and at least one message set count to -1 to receive as much as possible, but nothing is acceptable too :param timeout: Time in seconds to wait for data :return: True if successfully received Data, otherwise - False. Data itself is contained in a reply to channel and Data is list of strings """ start_time = time.time() if not isinstance(count, int) or count < -1: raise ValueError( "Count should be an integer in a range from -1 and up to +infinity" ) if self._mock is None and self._connection is None: return proto_failure("No connection. Ensure start is complete") try: data = [] if self._mock is not None: data = self._mock[:count] self._mock = self._mock[count:] else: while len(data) < count or count == 0 or count == -1: received = self._connection.root.receive(timeout) if received is None or len(received) == 0: break if isinstance(received, (list, tuple)): data += received else: data.append(received) except Exception as e: eprint("Platform {}: exception occurred during receive: {}".format( self.name, e)) exprint() return proto_failure( "Failed to receive due to exception {}".format(e), -2) tprint("rpyc_receive elapsed {}".format(time.time() - start_time)) if 0 < count != len(data): return proto_failure("Not all requested data were received") # TODO: need a way to return partially received data elif count == 0 and len(data) == 0: return proto_failure("No data were received") else: return proto_success(data)
def _platformix_call(self, context, fake_reply, method, *args, **kwargs): """ Calls host's method Call result is returned in a success message as value item :param context: messaging context :param method: method symbolic name :param args: args to method call :param kwargs: kwargs to method call :return: None """ if hasattr(self.host, method): if not callable(getattr(self.host, method)): self._reply( context, proto_failure("Attribute {} of {} is a property".format( property, self.host.name)), fake_reply) return try: result = getattr(self.host, method)(*args, **kwargs) except Exception as e: eprint( "Platformix protocol: failed to call method {} of {} with args {}, kwargs {} " "due to exception {}".format(method, self.host.name, args, kwargs, e)) exprint() self._reply( context, proto_failure( "Failed to call method {} of {} with args {}, kwargs {} " "due to exception {}".format(method, self.host.name, args, kwargs, e)), fake_reply) return self._reply(context, proto_success(result), fake_reply) else: self._reply( context, proto_failure("Method {} not found on {}".format( property, self.host.name)), fake_reply)
def _platformix_set(self, context, fake_reply, prop, value): """ Set host's property to a value :param context: messaging context :param prop: property symbolic name :param value: value to set :return: None """ if hasattr(self.host, prop): if not callable(getattr(self.host, prop)): try: setattr(self.host, prop, value) except Exception as e: eprint( "Platformix protocol: failed to set attribute {} of {} to value {} " "due to exception {}".format(prop, self.host.name, value, e)) exprint() self._reply( context, proto_failure( "Failed to set attribute {} of {} to value {} " "due to exception {}".format( prop, self.host.name, value, e)), fake_reply) return self._reply(context, proto_success(getattr(self.host, prop), prop), fake_reply) else: self._reply( context, proto_failure("Attribute {} of {} is a method".format( prop, self.host.name)), fake_reply) else: self._reply( context, proto_failure("Property {} not found on {}".format( prop, self.host.name)), fake_reply)
def tcp_send(self, data): """ Sends data to app via stdin :param data: Data to send over stdin. Could be an item like str, bytearray or list/tuple of items :return: True if data were sent successfully, otherwise - False """ start_time = time.time() if self._mock is None and self._sock is None: return proto_failure("No connection. Ensure start is complete") try: if not isinstance(data, (list, tuple)): data = data, for m in data: if self._mock is None: if not isinstance(m, bytearray): if isinstance(m, str): m = m.encode('UTF-8') else: return proto_failure("Send data is expected to be a string, bytearray or " "list/tuple of strings and bytearrays") self._sock.sendall(m) else: try: if self._mock_eval and isinstance(m, str): r = evaluate(m) else: r = m except Exception as e: r = None self._mock.append(str(r)) except Exception as e: eprint("Platform {} failed to send due to exception {}".format(self.name, e)) exprint() return proto_failure("Failed to send due to exception {}".format(e), -2) tprint("tcp_send elapsed {}".format(time.time() - start_time)) return proto_success(None)
def _do_break(self, context): # NOTE: make's sense only in threaded run or if among reactions to sequencer request would be sequencer break # NOTE: just for LOL - try to use sequencer to test sequencer self._remaining = 0 return proto_success("Breaked sequence. After last issued request is complete sequencer would stop", retval_name="state")
def tcp_receive(self, count=0, timeout=None, decode='UTF-8'): """ Get's data that were sent by app via stdout :param count: Amount of bytes to receive. set count to 0 to receive as much as possible, at least something set count to -1 to receive as much as possible, but nothing is acceptable too :param timeout: Time in seconds to wait for data. If None then TCP socket timeout is used :param deoode: If not None then received data is decoded into string using specified decoder. Default: 'UTF-8' :return: True if successfully received Data, otherwise - False. Data itself is contained in a reply to channel and Data is list of strings """ start_time = time.time() if not isinstance(count, int) or count < -1: raise ValueError("Count should be an integer in a range from -1 and up to +infinity") if self._mock is None and self._sock is None: return proto_failure("No connection. Ensure start is complete") try: if self._mock is not None: data = self._mock.pop(0) else: data = bytearray() if count < 1: recv_size = 1024 else: recv_size = count if timeout is not None: timeout = time.time() + timeout while len(data) < count or count == 0 or count == -1: try: received = self._sock.recv(recv_size) except TimeoutError: received = None except socket.timeout: received = None if received is None: if count < 0: break if count == 0 and len(data) > 0: break else: data += received if timeout is not None and time.time() > timeout: break if count > 0: recv_size = count - len(data) if decode is not None: data = data.decode(decode) except Exception as e: eprint("Platform {} failed to receive due to exception {}".format(self.name, e)) exprint() return proto_failure("Failed to receive due to exception {}".format(e), -2) tprint("rpyc_receive elapsed {}".format(time.time() - start_time)) if 0 < count != len(data): return proto_failure("Not all requested data were received") # TODO: need a way to return partially received data elif count == 0 and len(data) == 0: return proto_failure("No data were received") else: return proto_success(data)
def _platformix_stop(self, context, fake_reply): # TODO: Force parameter """ Stops platform instance Breaks startup process if the platform is in middle of startup Waits before nested platforms are stopped before actually do stop # TODO: also should wait platforms which are waiting for this one on start If parent platform or platforms in wait list are not running yet - will wait for them If stop were deferred due to waiting of other platforms then stop method should be called again later Usually it happens automatically after other platforms are replying on their's stopping end :param context: messaging context :return: None """ assert fake_reply is None, "platformix_stop replies shouldn't be faked!" stopping = self._worker.stopping # Store current stopping state need_stop = self._worker.stopping = self._worker.running or self._worker.starting self._worker.stopping = True # Set _stopping right in the beginning new_thread = False if not stopping and self._context is not None: # Break startup process if necessary self._reply_all(self._context["reply_to"], proto_failure("interrupted by stop"), None) if self._worker.starting: self._worker.starting = False self._worker.start_in_progress = False self._context = None if not stopping and not need_stop: # If not running and not starting - do nothing more self._worker.stopping = False self._reply(context, proto_success("already stopped", "state"), None) return if stopping: # If were already stopping - update reply list if context not in self._context["reply_to"]: new_thread = True self._context["reply_to"].append(context) else: # Otherwise initiate context new_thread = True self._context = { "action": "stop", "reply_to": [context], "waiting_for": [], "wait_ignore": [] } self._notify(context, "received stop signal") # TODO: do recursive stop? parent->childs? and call only root platforms stop? assert self._worker.stopping, "At this point stopping should be True" # Update waiting list # TODO: also wait those that are depends on this one self._context["waiting_for"] = [ w.name for w in self.host.subplatforms + self.host.depended if w.running is True or w.stopping is True and w.name not in self._context["wait_ignore"] ] # If there is some platforms to wait - notify about this if self.waiting_count > 0 and new_thread: self._worker.register_reply_handler( context, self._platformix_stop_reply_handler, [], {}, timeout=self._worker.stop_max_wait, force=True) self._notify_all(self._context["reply_to"], "waiting") # If no one left to wait for - do stop at last elif not self._worker.stop_in_progress and self.waiting_count == 0: for c in self._context["reply_to"]: self._worker.unregister_reply_handler(c, True, {}, dont_check=True) self._worker.running = False self._worker.stop_in_progress = True self._notify_all(self._context["reply_to"], "stopping") result = self._worker.stop(self._context["reply_to"]) reply_to = self._context["reply_to"] self._context = None assert isinstance( result, ProtocolReply ), "Worker should return result as ProtocolReply instance" if result.success: self._reply_all(reply_to, proto_success(None), None) else: self._reply_all(reply_to, result, None)
def _platformix_start( self, context, fake_reply): # TODO: Recursive option to start nested platforms """ Starts platform instance (actually checks that necessary conditions are met and calls startup worker method) If parent platform or platforms in wait list are not running yet - will wait for them If start were deferred due to waiting of other platforms then start method should be called again later Usually it happens automatically after other platforms are replying on their's startup end :param context: messaging context :return: None """ assert fake_reply is None, "platformix_start replies shouldn't be faked!" if self._worker.running: # If already running - just do nothing self._reply(context, proto_success("already running", "state"), None) return if self._worker.stopping: # If in the middle of stop - do nothing self._reply(context, proto_failure("stop is in progress"), None) return new_thread = False if self._worker.starting: # If were already starting - update reply list if context not in self._context["reply_to"]: new_thread = True self._context["reply_to"].append(context) self._notify(context, "waiting") else: new_thread = True self._worker.starting = True self._context = { "action": "start", "reply_to": [context], "waiting_for": [], "wait_ignore": [] } self._notify(context, "received start signal") # TODO: do recursive start? parent->childs? and call only root platforms to get up and running? # TODO: lock as validation can intersect with stop action since stop can be called from different threads if not self._validate_context({"action": "start"}): return if not self._worker.starting: # NOTE: in case if starting were interrupted by stop - just return return # Update waiting list self._context["waiting_for"] = [ w for w in self._worker.wait if self._worker.farm.is_running(w) is False ] # If there is some platforms to wait - notify about this if self.waiting_count > 0 and new_thread: self._worker.register_reply_handler( context, self._platformix_start_reply_handler, [], {}, timeout=self._worker.start_max_wait, force=True) self._notify(context, "waiting") # If no one left to wait for - do stop at last elif not self._worker.start_in_progress and self.waiting_count == 0: for c in self._context["reply_to"]: try: self._worker.unregister_reply_handler(c, True, {}, dont_check=True) except AssertionError: pass self._worker.start_in_progress = True self._notify_all(self._context["reply_to"], "launching") if self._validate_context({"action": "start"}): result = self._worker.start(self._context["reply_to"]) result_error = not isinstance(result, ProtocolReply) else: result = None result_error = False if self._validate_context({"action": "start"}): reply_to = self._context["reply_to"] self._context = None else: return # TODO: probably need to fid a way to reply failure in that case assert result_error is False, "Worker should return result as ProtocolReply instance" if result is not None: if result.success: self._reply_all(reply_to, result, None) else: self._reply_all(reply_to, result, None)