def version(self, params): """ Return with version :return: None """ self.log.debug("Call REST-API function: version") self.send_json_response({ "name": __project__, "version": get_escape_version() })
def version(self, params): """ Return with version :param params: additional params (skipped) :return: None """ self.log.debug("Call REST-API function: version") body = {"name": __project__, "version": get_escape_version()} self.send_json_response(body) self.log.debug("Sent response: %s" % body)
def main(): # Implement parser options parser = argparse.ArgumentParser( description="ESCAPEv2: Extensible Service ChAin Prototyping Environment " "using Mininet, Click, NETCONF and POX", add_help=True, version=get_escape_version(), prefix_chars="-+") # Add optional arguments escape = parser.add_argument_group("ESCAPEv2 arguments") escape.add_argument( "-a", "--agent", action="store_true", default=False, help="run in AGENT mode: start the infrastructure layer " "with the ROS REST-API (without the Service " "sublayer (SAS))") escape.add_argument( "-b", "--bypassapi", action="store_true", default=False, help="start the REST-API to bypass embedding and access " "to the DoV directly") escape.add_argument("-c", "--config", metavar="path", type=str, help="use external config file to extend the default " "configuration") escape.add_argument("-d", "--debug", action="count", default=0, help="run the ESCAPE in debug mode (can use multiple " "times for more verbose logging)") escape.add_argument("-e", "--environment", action="store_true", default=False, help="run ESCAPEv2 in the pre-defined virtualenv") escape.add_argument("-f", "--full", action="store_true", default=False, help="run the infrastructure layer also") escape.add_argument( "-g", "--gui", action="store_true", default=False, help="(OBSOLETE) initiate the graph-viewer GUI app which " "automatically connects to the ROS REST-API") escape.add_argument("-i", "--interactive", action="store_true", default=False, help="run an interactive shell for observing internal " "states") escape.add_argument("-l", "--log", metavar="file", type=str, help="define log file path explicitly " "(default: log/escape.log)") escape.add_argument( "--log_folder", metavar="path", type=str, help="define log folder path explicitly (default: log/)") escape.add_argument("-m", "--mininet", metavar="file", type=str, help="read the Mininet topology from the given file") escape.add_argument("-n", "--nosignal", action="store_true", default=False, help="run ESCAPE in a sub-shell that prevents " "propagation of received SIGNALs") escape.add_argument("-o", "--openflow", action="store", type=int, nargs="?", const=6633, metavar="port", help="initiate internal OpenFlow module with given " "listening port (default: 6633)") escape.add_argument("-q", "--quit", action="store_true", default=False, help="quit right after the first service request has " "processed") escape.add_argument("+q", "++quit", action="store_false", default=False, help="explicitly disable quit mode") escape.add_argument("-r", "--rosapi", action="store_true", default=False, help="start REST-API for the Resource Orchestration " "sublayer (ROS)") escape.add_argument("-s", "--service", metavar="file", type=str, help="skip the SAS REST-API initiation and read the " "service request from the given file") escape.add_argument("-t", "--test", action="store_true", default=False, help="run in test mode") escape.add_argument( "-x", "--clean", action="store_true", default=False, help="run the cleanup task standalone and kill remained " "programs, interfaces, veth parts and junk files") escape.add_argument("-V", "--visualization", action="store_true", default=False, help="run the visualization module to send data to a " "remote server") # Add remaining POX modules escape.add_argument("modules", metavar="...", nargs=argparse.REMAINDER, help="optional POX modules") # Parsing arguments args = parser.parse_args() if args.clean: return clean() # Construct POX init command according to argument basic command cmd = [os.path.join(PROJECT_ROOT, "pox/pox.py"), MAIN_CONTAINER_LAYER_NAME] # Run ESCAPE in VERBOSE logging level if it is needed if args.debug == 1: # Set logging level to DEBUG cmd.append("--loglevel=DEBUG") elif args.debug > 1: # Setup logging level to specific VERBOSE cmd.append("--loglevel=VERBOSE") else: # Use default loglevel: INFO pass # Run the Infrastructure Layer with the required root privilege if args.full: cmd.insert(0, "sudo") cmd.append("--full") if args.test: cmd.append("--test") if args.log: cmd.append("--log=%s" % args.log) if args.log_folder: cmd.append("--log_folder=%s" % args.log_folder) if args.quit: cmd.append("--quit") # Initiate the rudimentary GUI if args.gui: cmd.append("--gui") # Read the service request NFFG from a file and start the mapping process if args.service: cmd.append("--sg_file=%s" % os.path.abspath(args.service)) # Override optional external config file if args.config: cmd.append("--config=%s" % os.path.abspath(args.config)) # Skip the Service Layer initiation and start the ROS agent REST-API if args.agent: cmd.append("--agent") # AGENT mode is only useful with --full -> initiate infrastructure if needed if not args.full: cmd.insert(0, "sudo") cmd.append("--full") # Start the REST-API for the ROS layer if args.rosapi or args.gui: cmd.append("--rosapi") # Start an REST-API for the Adaptation Layer if args.bypassapi: cmd.append("--dovapi") # Enable Visualization if args.visualization: cmd.append("--visualization") # Add topology file if --full is set if args.mininet: if args.full or args.agent: cmd.append("--mininet=%s" % os.path.abspath(args.mininet)) else: parser.error( message= "-m/--mininet can be used only with Infrastructure layer! " "(with -f/--full or -a/--agent flag)") # Add the interactive shell if needed if args.interactive: cmd.append("py") cmd.append("--completion") if args.openflow: cmd.extend(("openflow.of_01", "--port=%s" % args.openflow)) # Add optional POX modules cmd.extend(args.modules) def __activate_virtualenv(): """ Activate virtualenv based on activate script under bin/ :return: None """ try: activate_this = PROJECT_ROOT + '/bin/activate_this.py' execfile(activate_this, dict(__file__=activate_this)) except IOError as e: print "Virtualenv is not set properly:\n%s" % e print "Remove the '.set_virtualenv' file or configure the virtualenv!" exit(os.EX_DATAERR) # Activate virtual environment if necessary if args.environment: __activate_virtualenv() else: for entry in os.listdir(PROJECT_ROOT): if entry.upper().startswith(".USE_VIRTUALENV"): __activate_virtualenv() break # Starting ESCAPEv2 (as a POX module) print "Starting %s..." % parser.description if args.nosignal: # Create command cmd = " ".join(cmd) if args.debug: print "Command: %s" % cmd try: return os.system(cmd) except KeyboardInterrupt: # Catch KeyboardInterrupt generated by own SIGINT signal pass else: # sys.path[0] = pox_base pox_params = cmd[2:] if cmd[0] == "sudo" else cmd[1:] if args.debug: print "POX parameters: %s" % pox_params # Start POX from pox.boot import boot return boot(argv=pox_params)
def get_escape_version(): from escape.util.misc import get_escape_version return get_escape_version()
class AbstractRequestHandler(BaseHTTPRequestHandler, object): """ Minimalistic RESTful API for Layer APIs. Handle /escape/* URLs. Method calling permissions represented in escape_intf dictionary. .. warning:: This class is out of the context of the recoco's co-operative thread context! While you don't need to worry much about synchronization between recoco tasks, you do need to think about synchronization between recoco task and normal threads. Synchronisation is needed to take care manually - use relevant helper function of core object: :func:`callLater()`/ :func:`raiseLater()` or use :func:`schedule_as_coop_task() <escape.util.misc.schedule_as_coop_task>` decorator defined in :mod:`escape.util.misc` on the called function! """ # For HTTP Response messages server_version = "ESCAPE/" + get_escape_version() """server version for HTTP Response messages""" static_prefix = "escape" # Bound HTTP verbs to UNIFY's API functions request_perm = { 'GET': ('ping', 'version', 'operations'), 'POST': ('ping', ) } """Bound HTTP verbs to UNIFY's API functions""" # Name of the layer API to which the server bounded bounded_layer = None """Name of the layer API to which the server bounded""" # Name mapper to avoid Python naming constraint (dict: rpc-name: mapped name) rpc_mapper = None """Name mapper to avoid Python naming constraint""" # Logger name LOGGER_NAME = "REST-API" """Logger name""" # Logger. Should be overrided in child classes log = core.getLogger("[%s]" % LOGGER_NAME) # Use Virtualizer format virtualizer_format_enabled = False """Use Virtualizer format""" # Default communication approach format = "FULL" """Default communication approach""" MESSAGE_ID_NAME = "message-id" def do_GET(self): """ Get information about an entity. R for CRUD convention. """ self._process_url() def do_POST(self): """ Create an entity. C for CRUD convention. :return: None """ self._process_url() def do_PUT(self): """ Update an entity. U for CRUD convention. :return: None """ self._process_url() def do_DELETE(self): """ Delete an entity. D for CRUD convention. :return: None """ self._process_url() # Unsupported HTTP verbs def do_OPTIONS(self): """ Handling unsupported HTTP verbs. :return: None """ self.send_error(httplib.NOT_IMPLEMENTED) self.wfile.close() def do_HEAD(self): """ Handling unsupported HTTP verbs. :return: None """ # self.send_error(501) self.wfile.close() def do_TRACE(self): """ Handling unsupported HTTP verbs. :return: None """ self.send_error(httplib.NOT_IMPLEMENTED) self.wfile.close() def do_CONNECT(self): """ Handling unsupported HTTP verbs. :return: None """ self.send_error(httplib.NOT_IMPLEMENTED) self.wfile.close() def get_request_params(self): params = {} query = urlparse.urlparse(self.path).query if query: query = query.split('&') for param in query: if '=' in param: name, value = param.split('=', 1) params[name] = value else: params[param] = True # Check message-id in headers as backup if self.MESSAGE_ID_NAME not in params: if self.MESSAGE_ID_NAME in self.headers: params[self.MESSAGE_ID_NAME] = self.headers[ self.MESSAGE_ID_NAME] self.log.debug("Detected message id: %s" % params[self.MESSAGE_ID_NAME]) else: params[self.MESSAGE_ID_NAME] = str(uuid.uuid1()) self.log.debug("No message-id! Generated id: %s" % params[self.MESSAGE_ID_NAME]) else: self.log.debug("Detected message id: %s" % params[self.MESSAGE_ID_NAME]) self.log.debug("Detected request parameters: %s" % params) return params def _process_url(self): """ Split HTTP path and call the carved function if it is defined in this class and in request_perm. :return: None """ self.log.debug(">>> Got HTTP request: %s" % str(self.raw_requestline).rstrip()) http_method = self.command.upper() real_path = urlparse.urlparse(self.path).path try: prefix = '/%s/' % self.static_prefix if real_path.startswith(prefix): self.func_name = real_path[len(prefix):].split('/')[0] if self.rpc_mapper: try: self.func_name = self.rpc_mapper[self.func_name] except KeyError: # No need for RPC name mapping, continue pass if http_method in self.request_perm: if self.func_name in self.request_perm[http_method]: if hasattr(self, self.func_name): # Response is assembled, and sent back by handler functions params = self.get_request_params() getattr(self, self.func_name)(params=params) else: self.send_error( httplib.INTERNAL_SERVER_ERROR, "Missing handler for actual request!") else: self.send_error(httplib.NOT_ACCEPTABLE) else: self.send_error(httplib.NOT_IMPLEMENTED) else: self.send_error(httplib.NOT_FOUND, "URL path is not valid or misconfigured!") except RESTError as e: # Handle all the errors if e.code: self.send_error(e.code, e.msg) else: self.send_error(httplib.INTERNAL_SERVER_ERROR, e.msg) except: # Got unexpected exception self.send_error(httplib.INTERNAL_SERVER_ERROR) raise finally: self.func_name = None self.wfile.flush() self.wfile.close() self.log.debug(">>> HTTP request: %s ended!" % str(self.raw_requestline).rstrip()) def _get_body(self): """ Parse HTTP request body as a plain text. .. note:: Call only once by HTTP request. .. note:: Parsed JSON object is Unicode. GET, DELETE messages don't have body - return empty dict by default. :return: request body in str format :rtype: str """ charset = 'utf-8' # json.loads returns an empty dict if it's called with an empty string # but this check we can avoid to respond with unnecessary missing # content-* error if self.command.upper() in ('GET', 'DELETE'): return {} try: splitted_type = self.headers['Content-Type'].split('charset=') if len(splitted_type) > 1: charset = splitted_type[1] content_len = int(self.headers['Content-Length']) raw_data = self.rfile.read(size=content_len).encode(charset) # Avoid missing param exception by hand over an empty json data return raw_data if content_len else "{}" except KeyError as e: # Content-Length header is not defined # or charset is not defined in Content-Type header. if e.args[0] == 'Content-Type': self.log.warning("Missing header from request: %s" % e.args[0]) if e.args[0] == 'Content-Length': # 411: ('Length Required', 'Client must specify Content-Length.'), raise RESTError(code=httplib.LENGTH_REQUIRED) else: raise RESTError(code=httplib.PRECONDITION_FAILED, msg="Missing header from request: %s" % e.args[0]) except ValueError as e: # Failed to parse request body to JSON self.log_error("Request parsing failed: %s", e) raise RESTError(code=httplib.UNSUPPORTED_MEDIA_TYPE, msg="Request parsing failed: %s" % e) def send_REST_headers(self): """ Set the allowed REST verbs as an HTTP header (Allow). :return: None """ try: if self.func_name: self.send_header( 'Allow', ','.join([ str(verbs) for verbs, f in self.request_perm.iteritems() if self.func_name in f ])) except KeyError: pass def send_acknowledge(self, code=None, message_id=None): """ Send back acknowledge message. :param message_id: response body :param message_id: dict :return: None """ if code: self.send_response(int(code)) else: self.send_response(httplib.ACCEPTED) if message_id: self.send_header('message-id', message_id) self.send_REST_headers() self.end_headers() def send_raw_response(self, raw_data, code=None, content="text/plain", encoding='utf-8'): """ Send requested data. :param raw_data: data in JSON format :type raw_data: dict :param encoding: Set data encoding (optional) :type encoding: str :return: None """ if code: self.send_response(int(code)) else: self.send_response(httplib.OK) self.send_header('Content-Type', '%s; charset=%s' % (content, encoding)) self.send_header('Content-Length', len(raw_data)) self.send_REST_headers() self.end_headers() self.wfile.write(raw_data) def send_json_response(self, data, code=None, encoding='utf-8'): """ Send requested data in JSON format. :param data: data in JSON format :type data: dict :param encoding: Set data encoding (optional) :type encoding: str :return: None """ response_body = json.dumps(data, encoding=encoding) return self.send_raw_response(raw_data=response_body, code=code, content="application/json", encoding=encoding) error_content_type = "text/json" """Content-Type for error responses""" def send_error(self, code, message=None): """ Override original function to send back allowed HTTP verbs and set format to JSON. :param code: error code :type code: int :param message: error message :type message: str :return: None """ try: short, long = self.responses[code] except KeyError: short, long = '???', '???' if message is None: message = short explain = long self.log_error("code %d, message %s", code, message) # using _quote_html to prevent Cross Site Scripting attacks (see bug # #1100201) content = { "title": "Error response", 'Error code': code, 'Message': message, 'Explanation': explain } self.send_response(code, message) self.send_header("Content-Type", self.error_content_type) self.send_header('Connection', 'close') # self.send_REST_headers() self.end_headers() if self.command != 'HEAD' and code >= 200 and code not in (204, 304): self.wfile.write(json.dumps(content)) def log_error(self, mformat, *args): """ Overwritten to use POX logging mechanism. :param mformat: message format :type mformat: str :return: None """ self.log.warning( "%s - - [%s] %s" % (self.client_address[0], self.log_date_time_string(), mformat % args)) def log_message(self, mformat, *args): """ Disable logging of incoming messages. :param mformat: message format :type mformat: str :return: None """ pass def log_full_message(self, mformat, *args): """ Overwritten to use POX logging mechanism. :param mformat: message format :type mformat: str :return: None """ self.log.debug( "%s - - [%s] %s" % (self.client_address[0], self.log_date_time_string(), mformat % args)) def _proceed_API_call(self, function, *args, **kwargs): """ Fail-safe method to call API function. The cooperative micro-task context is handled by actual APIs. Should call this with params, not directly the function of actual API. :param function: function name :type function: str :param args: Optional params :type args: tuple :param kwargs: Optional named params :type kwargs: dict :return: None """ if core.core.hasComponent(self.bounded_layer): layer = core.components[self.bounded_layer] if hasattr(layer, function): return getattr(layer, function)(*args, **kwargs) else: self.log.warning( 'Mistyped or not implemented API function call: %s ' % function) raise RESTError( msg='Mistyped or not implemented API function call: %s ' % function) else: self.log.error( 'Error: No component has registered with the name: %s, ' 'ABORT function call!' % self.bounded_layer) ############################################################################## # Basic REST-API functions ############################################################################## def ping(self, params): """ For testing REST API aliveness and reachability. :return: None """ response_body = "OK" self.send_response(httplib.OK) self.send_header('Content-Type', 'text/plain') self.send_header('Content-Length', len(response_body)) self.send_REST_headers() self.end_headers() self.wfile.write(response_body) def version(self, params): """ Return with version :return: None """ self.log.debug("Call REST-API function: version") self.send_json_response({ "name": __project__, "version": get_escape_version() }) def operations(self, params): """ Return with allowed operations :return: None """ self.log.debug("Call REST-API function: operations") self.send_json_response(self.request_perm)
def version(): return Response(get_escape_version() + "\n")