Пример #1
0
    def __init__(self, url, prefix="", features=None, **kwargs):
        """
    Init.

    :param url: url of RESTful API
    :type url: str
    :param prefix: URL prefix
    :type prefix: str
    :param features: limitation anf filter parameters for the Adapter class
    :type features: dict
    :return: None
    """
        AbstractRESTAdapter.__init__(self,
                                     base_url=url,
                                     prefix=prefix,
                                     **kwargs)
        AbstractESCAPEAdapter.__init__(self, **kwargs)
        log.debug("Init %s - type: %s, domain: %s, URL: %s" %
                  (self.__class__.__name__, self.type, self.domain_name, url))
        # Converter object
        self.converter = NFFGConverter(
            unique_bb_id=CONFIG.ensure_unique_bisbis_id(),
            unique_nf_id=CONFIG.ensure_unique_vnf_id(),
            domain=self.domain_name,
            logger=log)
        self.features = features if features is not None else {}
        # Cache for parsed Virtualizer
        self.__last_virtualizer = None
        self.__last_request = None
        self.__original_virtualizer = None
Пример #2
0
  def __init__ (self, url=None, rpc=None, timeout=DEFAULT_TIMEOUT,
                instance_id=None):
    """
    Init.

    :param url: URL of the remote server
    :type url: str
    :param rpc: RPC name
    :type rpc: str
    :param timeout: connections timeout
    :type timeout: int
    :param instance_id: additional id to join to the end of the id
    :type instance_id: str
    :return: None
    """
    super(RemoteVisualizer, self).__init__()
    self.log = core.getLogger("visualizer")
    if url is None:
      url = CONFIG.get_visualization_url()
    if rpc is None:
      rpc = CONFIG.get_visualization_rpc()
    self._url = urlparse.urljoin(url, rpc)
    if self._url is None:
      raise RuntimeError("Missing URL from %s" % self.__class__.__name__)
    self._timeout = timeout
    if instance_id is None:
      self.instance_id = CONFIG.get_visualization_instance_id()
    self.log.info("Setup remote Visualizer with URL: %s" % self._url)
    # Store the last request
    self._response = None
    self.converter = NFFGConverter(domain="ESCAPE", logger=log,
                                   unique_bb_id=CONFIG.ensure_unique_bisbis_id(),
                                   unique_nf_id=CONFIG.ensure_unique_vnf_id())
    # Suppress low level logging
    self.__suppress_requests_logging()
Пример #3
0
 def initialize(self):
     """
 .. seealso::
   :func:`AbstractAPI.initialize() <escape.util.api.AbstractAPI.initialize>`
 """
     log.debug("Initializing Service Layer...")
     self.__sid = CONFIG.get_service_layer_id()
     if self.__sid is not None:
         log.debug("Setup ID for Service Layer: %s" % self.__sid)
     else:
         self.__sid = self.LAYER_ID
         log.error(
             "Missing ID of Service Layer from config. Using default value: %s"
             % self.__sid)
     # Set element manager
     self.elementManager = ClickManager()
     # Init central object of Service layer
     self.service_orchestrator = ServiceOrchestrator(self)
     # Read input from file if it's given and initiate SG
     if self._sg_file:
         try:
             stats.init_request_measurement(request_id=self._sg_file)
             service_request = self._read_data_from_file(self._sg_file)
             log.info("Graph representation is loaded successfully!")
             if service_request.startswith('{'):
                 log.debug(
                     "Detected format: JSON - Parsing from NFFG format...")
                 nffg = NFFG.parse(raw_data=service_request)
             elif service_request.startswith('<'):
                 log.debug(
                     "Detected format: XML - Parsing from Virtualizer format..."
                 )
                 converter = NFFGConverter(domain="INTERNAL", logger=log)
                 nffg = converter.parse_from_Virtualizer(
                     vdata=service_request)
             else:
                 log.warning("Detected unexpected format...")
                 return
             if nffg.mode is not None:
                 log.info('Detected mapping mode in NFFG: %s' % nffg.mode)
             else:
                 nffg.mode = NFFG.MODE_ADD
                 log.info("No mapping mode has been detected in NFFG! "
                          "Set default mode: %s" % nffg.mode)
             log.info("Schedule service request delayed by %d seconds..." %
                      SCHEDULED_SERVICE_REQUEST_DELAY)
             stats.set_request_id(request_id=nffg.id)
             self.api_sas_sg_request_delayed(service_nffg=nffg)
         except (ValueError, IOError, TypeError) as e:
             log.error("Can't load service request from file because of: " +
                       str(e))
             quit_with_error(msg=str(e), logger=log)
     else:
         # Init REST-API if no input file is given
         self._initiate_rest_api()
     # Init GUI
     if self._gui:
         self._initiate_gui()
     log.info("Service Layer has been initialized!")
Пример #4
0
    def get_config(self):
        """
    Response configuration.

    :return: None
    """
        self.log.info("Call %s function: get-config" % self.LOGGER_NAME)
        # Forward call to main layer class
        config = self._proceed_API_call(self.API_CALL_GET_CONFIG)
        if config is None:
            self.send_error(404, message="Resource info is missing!")
            return
        # Setup OK status for HTTP response
        self.send_response(200)
        # Global resource has not changed -> respond with the cached topo
        if config is False:
            self.log.info(
                "Global resource has not changed! Respond with cached topology..."
            )
            if self.virtualizer_format_enabled:
                data = self.server.last_response.xml()
            else:
                data = self.server.last_response.dump()
        else:
            # Convert required NFFG if needed
            if self.virtualizer_format_enabled:
                self.log.debug("Convert internal NFFG to Virtualizer...")
                converter = NFFGConverter(domain=None, logger=log)
                v_topology = converter.dump_to_Virtualizer(nffg=config)
                # Cache converted data for edit-config patching
                self.log.debug("Cache converted topology...")
                self.server.last_response = v_topology
                # Dump to plain text format
                data = v_topology.xml()
                # Setup HTTP response format
            else:
                self.log.debug("Cache acquired topology...")
                self.server.last_response = config
                data = config.dump()
        if self.virtualizer_format_enabled:
            self.send_header('Content-Type', 'application/xml')
        else:
            self.send_header('Content-Type', 'application/json')
        self.log.log(VERBOSE,
                     "Responded topology for 'get-config':\n%s" % data)
        # Setup length for HTTP response
        self.send_header('Content-Length', len(data))
        self.end_headers()
        self.log.info("Send back topology description...")
        self.wfile.write(data)
        self.log.debug("%s function: get-config ended!" % self.LOGGER_NAME)
Пример #5
0
  def topology (self):
    """
    Provide internal topology description

    Same functionality as "get-config" in UNIFY interface.

    :return: None
    """
    self.log.info("Call %s function: topology" % self.LOGGER_NAME)
    # Forward call to main layer class
    topology = self._proceed_API_call('api_sas_get_topology')
    if topology is None:
      self.send_error(404, message="Resource info is missing!")
      return
    # Setup OK status for HTTP response
    self.send_response(200)
    if topology is False:
      self.log.info(
        "Requested resource has not changed! Respond with cached topology...")
      if self.virtualizer_format_enabled:
        data = self.server.last_response.xml()
      else:
        data = self.server.last_response.dump()
    else:
      if self.virtualizer_format_enabled:
        self.log.debug("Convert internal NFFG to Virtualizer...")
        converter = NFFGConverter(domain=None, logger=log)
        # Dump to plain text format
        v_topology = converter.dump_to_Virtualizer(nffg=topology)
        # Cache converted data for edit-config patching
        self.log.debug("Cache converted topology...")
        self.server.last_response = v_topology
        # Dump to plain text format
        data = v_topology.xml()
        # Setup HTTP response format
      else:
        self.log.debug("Cache converted topology...")
        self.server.last_response = topology
        data = topology.dump()
    if self.virtualizer_format_enabled:
      self.send_header('Content-Type', 'application/xml')
    else:
      self.send_header('Content-Type', 'application/json')
    self.log.log(VERBOSE, "Responded topology for 'get-config':\n%s" % data)
    # Setup length for HTTP response
    self.send_header('Content-Length', len(data))
    self.end_headers()
    self.log.info("Send back topology description...")
    self.wfile.write(data)
    self.log.debug("%s function: get-config ended!" % self.LOGGER_NAME)
Пример #6
0
    def __init__(self, domain_name=None, path=None, diff=None, **kwargs):
        """
    Init.

    :param path: file path offered as the domain topology
    :type path: str
    :return: None
    """
        log.debug(
            "Init %s - type: %s, domain: %s, path: %s, diff: %s" %
            (self.__class__.__name__, self.type, domain_name, path, diff))
        # Converter object
        self.converter = NFFGConverter(
            unique_bb_id=CONFIG.ensure_unique_bisbis_id(),
            unique_nf_id=CONFIG.ensure_unique_vnf_id(),
            domain=domain_name,
            logger=log)
        super(VirtualizerBasedStaticFileAdapter,
              self).__init__(domain_name=domain_name, path=path, **kwargs)
        self.diff = diff
Пример #7
0
  def sg (self):
    """
    Main API function for Service Graph initiation.

    Same functionality as "get-config" in UNIFY interface.

    Bounded to POST HTTP verb.

    :return: None
    """
    self.log.debug("Called REST-API function: sg")
    self.log.info(int(round(time.time() * 1000)))
    # Obtain NFFG from request body
    log.debug("Detected response format: %s" % self.headers.get("Content-Type"))
    body = self._get_body()
    # log.getChild("REST-API").debug("Request body:\n%s" % body)
    if body is None or not body:
      log.warning("Received data is empty!")
      self.send_error(400, "Missing body!")
      return
    # Expect XML format --> need to convert first
    if self.virtualizer_format_enabled:
      if self.headers.get("Content-Type") != "application/xml" or \
         not body.startswith("<?xml version="):
        log.error("Received data is not in XML format despite of the UNIFY "
                  "interface is enabled!")
        self.send_error(415)
        return
      # Convert response's body to NFFG
      nffg = NFFGConverter(domain="INTERNAL",
                           logger=log).parse_from_Virtualizer(vdata=body)
    else:
      try:
        nffg = NFFG.parse(body)  # Initialize NFFG from JSON representation
      except Exception as e:
        self.log.error(
          "Abort request! Received exception during payload parsing: %s" % e)
        return
    self.log.debug("Parsed service request: %s" % nffg)
    self._proceed_API_call('api_sas_sg_request', nffg)
    self.send_acknowledge()
    self.log.debug("%s function: get-config ended!" % self.LOGGER_NAME)
Пример #8
0
    def edit_config(self):
        """
    Receive configuration and initiate orchestration.

    :return: None
    """
        self.log.info("Call %s function: edit-config" % self.LOGGER_NAME)
        # Obtain NFFG from request body
        self.log.debug("Detected response format: %s" %
                       self.headers.get("Content-Type"))
        raw_body = self._get_body()
        # log.getChild("REST-API").debug("Request body:\n%s" % body)
        if raw_body is None or not raw_body:
            log.warning("Received data is empty!")
            self.send_error(400, "Missing body!")
            return
        # Expect XML format --> need to convert first
        if self.virtualizer_format_enabled:
            if self.headers.get("Content-Type") != "application/xml" and \
               not raw_body.startswith("<?xml version="):
                self.log.error(
                    "Received data is not in XML format despite of the "
                    "UNIFY interface is enabled!")
                self.send_error(415)
                return
            # Get received Virtualizer
            received_cfg = Virtualizer.parse_from_text(text=raw_body)
            self.log.log(VERBOSE,
                         "Received request for 'edit-config':\n%s" % raw_body)
            # If there was not get-config request so far
            if self.DEFAULT_DIFF:
                if self.server.last_response is None:
                    self.log.info(
                        "Missing cached Virtualizer! Acquiring topology now..."
                    )
                    config = self._proceed_API_call(self.API_CALL_GET_CONFIG)
                    if config is None:
                        self.log.error("Requested resource info is missing!")
                        self.send_error(404,
                                        message="Resource info is missing!")
                        return
                    elif config is False:
                        self.log.warning(
                            "Requested info is unchanged but has not found!")
                        self.send_error(404,
                                        message="Resource info is missing!")
                    else:
                        # Convert required NFFG if needed
                        if self.virtualizer_format_enabled:
                            self.log.debug(
                                "Convert internal NFFG to Virtualizer...")
                            converter = NFFGConverter(domain=None, logger=log)
                            v_topology = converter.dump_to_Virtualizer(
                                nffg=config)
                            # Cache converted data for edit-config patching
                            self.log.debug("Cache converted topology...")
                            self.server.last_response = v_topology
                        else:
                            self.log.debug("Cache acquired topology...")
                            self.server.last_response = config
                # Perform patching
                full_cfg = self.__recreate_full_request(diff=received_cfg)
            else:
                full_cfg = received_cfg
            self.log.log(VERBOSE, "Generated request:\n%s" % full_cfg.xml())
            # Convert response's body to NFFG
            self.log.info("Converting full request data...")
            converter = NFFGConverter(domain="REMOTE", logger=log)
            nffg = converter.parse_from_Virtualizer(vdata=full_cfg)
        else:
            if self.headers.get("Content-Type") != "application/json":
                self.log.error(
                    "Received data is not in JSON format despite of the "
                    "UNIFY interface is disabled!")
                self.send_error(415)
                return
            # Initialize NFFG from JSON representation
            self.log.info("Parsing request into internal NFFG format...")
            nffg = NFFG.parse(raw_body)
        self.log.debug("Parsed NFFG install request: %s" % nffg)
        self._proceed_API_call(self.API_CALL_EDIT_CONFIG, nffg)
        self.send_acknowledge()
        self.log.debug("%s function: edit-config ended!" % self.LOGGER_NAME)
Пример #9
0
class RemoteVisualizer(Session):
    """
  Main object for remote Visualization.
  """
    # Singleton
    __metaclass__ = Singleton
    """Singleton"""
    # name form POXCore
    _core_name = "visualizer"
    """Name form POXCore"""

    # Bindings of Layer IDs
    ID_MAPPER = {
        SERVICE: "ESCAPE-SERVICE",
        ORCHEST: "ESCAPE-ORCHESTRATION",
        ADAPT: "ESCAPE-ADAPTATION"
    }
    """Bindings of Layer IDs"""

    # Basic HTTP headers
    basic_headers = {
        'User-Agent': "ESCAPE/" + __version__,
        'Content-Type': "application/xml"
    }
    """Basic HTTP headers"""

    # Default timeout value in sec
    DEFAULT_TIMEOUT = 1
    """Default timeout value in sec"""
    def __init__(self,
                 url=None,
                 rpc=None,
                 timeout=DEFAULT_TIMEOUT,
                 instance_id=None):
        """
    Init.

    :param url: URL of the remote server
    :type url: str
    :param rpc: RPC name
    :type rpc: str
    :param timeout: connections timeout
    :type timeout: int
    :param instance_id: additional id to join to the end of the id
    :type instance_id: str
    :return: None
    """
        super(RemoteVisualizer, self).__init__()
        self.log = core.getLogger("visualizer")
        if url is None:
            url = CONFIG.get_visualization_url()
        if rpc is None:
            rpc = CONFIG.get_visualization_rpc()
        self._url = urlparse.urljoin(url, rpc)
        if self._url is None:
            raise RuntimeError("Missing URL from %s" % self.__class__.__name__)
        self._timeout = timeout
        if instance_id is None:
            self.instance_id = CONFIG.get_visualization_instance_id()
        self.log.info("Setup remote Visualizer with URL: %s" % self._url)
        # Store the last request
        self._response = None
        self.converter = NFFGConverter(domain="ESCAPE", logger=self.log)
        # Suppress low level logging
        self.__suppress_requests_logging()

    @staticmethod
    def __suppress_requests_logging(level=None):
        """
    Suppress annoying and detailed logging of `requests` and `urllib3` packages.

    :param level: level of logging (default: WARNING)
    :type level: str
    :return: None
    """
        level = level if level is not None else logging.WARNING
        logging.getLogger("requests").setLevel(level)
        logging.getLogger("urllib3").setLevel(level)

    def send_notification(self, data, url=None, **kwargs):
        """
    Send given data to a remote server for visualization.
    Convert given NFFG into Virtualizer format if needed.

    :param data: topology description need to send
    :type data: :class:`NFFG` or Virtualizer
    :param id: id of the data, needs for the remote server
    :type id: str
    :param url: additional URL (optional)
    :type url: str
    :param kwargs: additional params to request
    :type kwargs: dict
    :return: response text
    :rtype: str
    """
        if url is None:
            url = self._url
        if url is None:
            self.log.error(
                "Missing URL for remote visualizer! Skip notification...")
            return
        if 'timeout' not in kwargs:
            kwargs['timeout'] = self._timeout
        self.log.debug("Send visualization notification to %s" % self._url)
        try:
            if data is None:
                self.log.warning(
                    "Missing data! Skip notifying remote visualizer.")
                return False
            elif isinstance(data, NFFG):
                data = self.converter.dump_to_Virtualizer(nffg=data)
            elif not isinstance(data, Virtualizer.Virtualizer):
                self.log.warning(
                    "Unsupported data type: %s! Skip notification..." %
                    type(data))
                return
            # If additional params is not empty dict -> override the basic params
            if 'headers' in kwargs:
                kwargs['headers'].update(self.basic_headers)
            else:
                kwargs['headers'] = self.basic_headers.copy()
            kwargs['headers'].update(CONFIG.get_visualization_headers())
            if "params" in kwargs:
                kwargs['params'].update(CONFIG.get_visualization_params())
            else:
                kwargs['params'] = CONFIG.get_visualization_params()
            self.log.debug("Sending visualization notification...")
            self._response = self.request(method='POST',
                                          url=url,
                                          data=data.xml(),
                                          **kwargs)
            self._response.raise_for_status()
            return self._response.text
        except (ConnectionError, HTTPError, KeyboardInterrupt) as e:
            self.log.warning(
                "Got exception during notifying remote Visualizer: %s!" % e)
            return False
        except Timeout:
            self.log.warning(
                "Got timeout(%ss) during notify remote Visualizer!" %
                kwargs['timeout'])
            return True
Пример #10
0
class UnifyRESTAdapter(AbstractRESTAdapter, AbstractESCAPEAdapter,
                       DefaultUnifyDomainAPI):
    """
  Implement the unified way to communicate with "Unify" domains which are
  using REST-API and the "Virtualizer" XML-based format.
  """
    # Set custom header
    custom_headers = {
        'User-Agent': "ESCAPE/" + __version__,
        # XML-based Virtualizer format
        'Accept': "application/xml"
    }
    # Adapter name used in CONFIG and ControllerAdapter class
    name = "UNIFY-REST"
    # type of the Adapter class - use this name for searching Adapter config
    type = AbstractESCAPEAdapter.TYPE_REMOTE
    MESSAGE_ID_NAME = "message-id"
    CALLBACK_NAME = "call-back"
    SKIPPED_REQUEST = 0

    def __init__(self, url, prefix="", features=None, **kwargs):
        """
    Init.

    :param url: url of RESTful API
    :type url: str
    :param prefix: URL prefix
    :type prefix: str
    :param features: limitation anf filter parameters for the Adapter class
    :type features: dict
    :return: None
    """
        AbstractRESTAdapter.__init__(self,
                                     base_url=url,
                                     prefix=prefix,
                                     **kwargs)
        AbstractESCAPEAdapter.__init__(self, **kwargs)
        log.debug("Init %s - type: %s, domain: %s, URL: %s" %
                  (self.__class__.__name__, self.type, self.domain_name, url))
        # Converter object
        self.converter = NFFGConverter(
            unique_bb_id=CONFIG.ensure_unique_bisbis_id(),
            unique_nf_id=CONFIG.ensure_unique_vnf_id(),
            domain=self.domain_name,
            logger=log)
        self.features = features if features is not None else {}
        # Cache for parsed Virtualizer
        self.__last_virtualizer = None
        self.__last_request = None
        self.__original_virtualizer = None

    @property
    def last_virtualizer(self):
        """
    :return: Return the last received topology.
    :rtype: :class:`Virtualizer`
    """
        return self.__last_virtualizer

    @property
    def last_request(self):
        """
    :return: Return the last sent request .
    :rtype: :class:`Virtualizer`
    """
        return self.__last_request

    def ping(self):
        """
    Call the ping RPC.

    :return: response text (should be: 'OK')
    :rtype: str
    """
        try:
            log.log(VERBOSE,
                    "Send ping request to remote agent: %s" % self._base_url)
            return self.send_quietly(self.GET, 'ping')
        except RequestException:
            # Any exception is bad news -> return None
            return None

    def get_config(self, filter=None):
        """
    Queries the infrastructure view with a netconf-like "get-config" command.

    Remote domains always send full-config if ``filter`` is not set.

    Return topology description in the original format.

    :param filter: request a filtered description instead of full
    :type filter: str
    :return: infrastructure view in the original format
    :rtype: :class:`Virtualizer`
    """
        log.debug("Send get-config request to remote agent: %s" %
                  self._base_url)
        # Get topology from remote agent handling every exception
        data = self.send_no_error(self.POST, 'get-config')
        if data:
            # Got data
            log.debug("Received config from remote %s domain agent at %s" %
                      (self.domain_name, self._base_url))
            log.debug(
                "Detected response format: %s" %
                self.get_last_response_headers().get("Content-Type", "None"))
            log.debug("Parse and load received data...")
            # Try to define content type from HTTP header or the first line of body
            if not self.is_content_type("xml") and \
               not data.startswith("<?xml version="):
                log.error("Received data is not in XML format!")
                return
            virt = Virtualizer.parse_from_text(text=data)
            log.log(
                VERBOSE,
                "Received message to 'get-config' request:\n%s" % virt.xml())
            self.__cache_topology(virt)
            return virt
        else:
            log.error("No data has been received from remote agent at %s!" %
                      self._base_url)
            return

    def edit_config(self,
                    data,
                    diff=False,
                    message_id=None,
                    callback=None,
                    full_conversion=False):
        """
    Send the requested configuration with a netconf-like "edit-config" command.

    Remote domains always expect diff of mapping changes.

    :param data: whole domain view
    :type data: :class:`NFFG`
    :param diff: send the diff of the mapping request (default: False)
    :param diff: bool
    :param message_id: optional message id
    :type message_id: str
    :param callback: callback URL
    :type callback: str
    :param full_conversion: do full conversion instead of adapt (default: False)
    :type full_conversion: bool
    :return: response data / empty str if request was successful else None
    :rtype: str
    """
        log.debug("Prepare edit-config request for remote agent at: %s" %
                  self._base_url)
        if isinstance(data, Virtualizer):
            # Nothing to do
            vdata = data
        elif isinstance(data, NFFG):
            log.debug("Convert NFFG to XML/Virtualizer format...")
            if self.last_virtualizer is None:
                log.debug("Missing last received Virtualizer! "
                          "Updating last topology view now...")
                self.get_config()
                if self.last_virtualizer is None:
                    return False
            stats.add_measurement_start_entry(type=stats.TYPE_CONVERSION,
                                              info="%s-deploy" %
                                              self.domain_name)
            if full_conversion:
                vdata = self.converter.dump_to_Virtualizer(nffg=data)
            else:
                vdata = self.converter.adapt_mapping_into_Virtualizer(
                    virtualizer=self.last_virtualizer,
                    nffg=data,
                    reinstall=diff)
            stats.add_measurement_end_entry(type=stats.TYPE_CONVERSION,
                                            info="%s-deploy" %
                                            self.domain_name)
            log.log(VERBOSE, "Adapted Virtualizer:\n%s" % vdata.xml())
        else:
            raise RuntimeError(
                "Not supported config format: %s for 'edit-config'!" %
                type(data))
        self.__cache_request(data=vdata)
        if diff:
            log.debug(
                "DIFF is enabled. Calculating difference of mapping changes..."
            )
            stats.add_measurement_start_entry(type=stats.TYPE_PROCESSING,
                                              info="%s-DIFF" %
                                              self.domain_name)
            vdata = self.__calculate_diff(vdata)
            stats.add_measurement_end_entry(type=stats.TYPE_PROCESSING,
                                            info="%s-DIFF" % self.domain_name)
        else:
            log.debug("Using given Virtualizer as full mapping request")
        plain_data = vdata.xml()
        log.log(VERBOSE, "Generated Virtualizer:\n%s" % plain_data)
        if is_empty(virtualizer=vdata):
            log.info("Generated edit-config request is empty! Skip sending...")
            return self.SKIPPED_REQUEST
        log.debug("Send request to %s domain agent at %s..." %
                  (self.domain_name, self._base_url))
        params = {}
        if message_id is not None:
            params[self.MESSAGE_ID_NAME] = message_id
            log.debug("Using explicit message-id: %s" % message_id)
        if callback is not None:
            params[self.CALLBACK_NAME] = callback
            log.debug("Using explicit callback: %s" % callback)
        MessageDumper().dump_to_file(data=plain_data,
                                     unique="%s-edit-config" %
                                     self.domain_name)
        try:
            response = self.send_with_timeout(method=self.POST,
                                              url='edit-config',
                                              body=plain_data,
                                              params=params)
        except Timeout:
            log.warning(
                "Reached timeout(%ss) while waiting for 'edit-config' response!"
                " Ignore exception..." % self.CONNECTION_TIMEOUT)
            # Ignore exception - assume the request was successful -> return True
            return True
        if response is not None:
            log.debug("Deploy request has been sent successfully!")
        return response

    def get_last_message_id(self):
        """
    :return: Return the last sent request id
    :rtype: str or int
    """
        if self._response is not None:
            return self._response.headers.get(self.MESSAGE_ID_NAME, None)
        else:
            return None

    def info(self, info, callback=None, message_id=None):
        """
    Send the given Info request to the domain orchestrator.

    :param info: Info object
    :type info: :class:`Info`
    :param callback: callback URL
    :type callback: str
    :param message_id: optional message id
    :type message_id: str
    :return: status code or the returned message-id if it is set
    :rtype: str
    """
        log.log(VERBOSE, "Generated Info:\n%s" % info.xml())
        params = {}
        if message_id is not None:
            params[self.MESSAGE_ID_NAME] = message_id
            log.debug("Using explicit message-id: %s" % message_id)
        if callback is not None:
            params[self.CALLBACK_NAME] = callback
            log.debug("Using explicit callback: %s" % callback)
        MessageDumper().dump_to_file(data=info.xml(),
                                     unique="%s-info" % self.domain_name)
        try:
            status = self.send_with_timeout(method=self.POST,
                                            url='info',
                                            body=info.xml(),
                                            params=params)
        except Timeout:
            log.warning(
                "Reached timeout(%ss) while waiting for 'info' response!"
                " Ignore exception..." % self.CONNECTION_TIMEOUT)
            # Ignore exception - assume the request was successful -> return True
            return True
        if status is not None:
            log.debug("Info request has been sent successfully!")
        return status

    def get_original_topology(self):
        """
    Return the original topology as a Virtualizer.

    :return: the original topology
    :rtype: :class:`Virtualizer`
    """
        return self.__original_virtualizer

    def check_domain_reachable(self):
        """
    Checker function for domain polling. Check the remote domain agent is
    reachable.

    Use the ping RPC call.

    :return: the remote domain is detected or not
    :rtype: bool
    """
        return self.ping() is not None

    def get_topology_resource(self):
        """
    Return with the topology description as an :class:`NFFG`.

    :return: the topology description of the remote domain
    :rtype: :class:`NFFG`
    """
        # Get full topology as a Virtualizer
        virt = self.get_config()
        # If not data is received or converted return with None
        if virt is None:
            return
        MessageDumper().dump_to_file(data=virt.xml(),
                                     unique="%s-get-config" % self.domain_name)
        # Convert from XML-based Virtualizer to NFFG
        nffg = self.converter.parse_from_Virtualizer(vdata=virt)
        self.__process_features(nffg=nffg)
        log.log(VERBOSE,
                "Converted NFFG of 'get-config' response:\n%s" % nffg.dump())
        # If first get-config
        if self.__original_virtualizer is None:
            log.debug(
                "Store Virtualizer(id: %s, name: %s) as the original domain "
                "config..." % (virt.id.get_value(), virt.name.get_value()))
            # self.__original_virtualizer = virt.full_copy()
            # Copy reference instead of full_copy to avoid overhead
            self.__original_virtualizer = virt
        log.debug("Used domain name for conversion: %s" % self.domain_name)
        return nffg

    def __process_features(self, nffg):
        """
    Process features config and transform collected topo according to these.

    :param nffg: base NFFG
    :type nffg: :class:`NFFG`
    :return: None
    """
        if self.features:
            self.log.debug("Adding features: %s to infra nodes..." %
                           self.features)
            for infra in nffg.infras:
                infra.mapping_features.update(self.features)

    def check_topology_changed(self):
        """
    Check the last received topology and return ``False`` if there was no
    changes, ``None`` if domain was unreachable and the converted topology if
    the domain changed.

    Detection of changes is based on the ``reduce()`` function of the
    ``Virtualizer``.

    :return: the received topology is different from cached one
    :rtype: bool or None or :class:`NFFG`
    """
        # Get full topology as a Virtualizer
        data = self.send_no_error(self.POST, 'get-config')
        # Got data
        if data:
            # Check the content type or try to recognize the standard XML opening tag
            if not self.is_content_type("xml") and \
               not data.startswith("<?xml version="):
                log.error("Received data is not in XML format!")
                return None
            virt = Virtualizer.parse_from_text(text=data)
        else:
            # If no data is received, exception was raised or converted return with
            # None.
            log.warning(
                "No data is received or parsed into Virtualizer during "
                "topology change detection!")
            return None
        if self.last_virtualizer is None:
            log.warning("Missing last received Virtualizer!")
            return None
        # Get the changes happened since the last get-config
        if not self.__is_changed(virt):
            return False
        else:
            log.info("Received changed topology from domain: %s" %
                     self.domain_name)
            log.log(
                VERBOSE, "Changed domain topology from: %s:\n%s" %
                (self.domain_name, virt.xml()))
            MessageDumper().dump_to_file(data=virt.xml(),
                                         unique="%s-get-config-changed" %
                                         self.domain_name)
            # Cache new topo
            self.__cache_topology(virt)
            # Return with the changed topo in NFFG
            changed_topo = self.converter.parse_from_Virtualizer(vdata=virt)
            self.__process_features(nffg=changed_topo)
            return changed_topo

    def __cache_topology(self, data):
        """
    Cache last received Virtualizer topology.

    :param data: received Virtualizer
    :type data: :class:`Virtualizer`
    :return: None
    """
        log.debug("Cache received 'get-config' response...")
        # self.__last_virtualizer = data.full_copy()
        # Copy reference instead of full_copy to avoid overhead
        self.__last_virtualizer = data

    def get_topo_cache(self):
        return self.__last_virtualizer

    def __cache_request(self, data):
        """
    Cache calculated and converted request.

    :param data: request Virtualizer
    :type data: :class:`Virtualizer`
    :return: None
    """
        log.debug("Cache generated 'edit-config' request...")
        # self.__last_request = data.full_copy()
        # Copy reference instead of full_copy to avoid overhead
        self.__last_request = data

    def __is_changed(self, new_data):
        """
    Return True if the given ``new_data`` is different compared to cached
    ``last_virtualizer``.

    :param new_data: new Virtualizer object
    :type new_data: :class:`Virtualizer`
    :return: is different or not
    :rtype: bool
    """
        return not is_identical(base=self.last_virtualizer, new=new_data)

    def __calculate_diff(self, changed):
        """
    Calculate the difference of the given Virtualizer compared to the most
    recent Virtualizer acquired by get-config.

    :param changed: Virtualizer containing new install
    :type changed: :class:`Virtualizer`
    :return: the difference
    :rtype: :class:`Virtualizer`
    """
        # base = Virtualizer.parse_from_text(text=self.last_virtualizer.xml())
        base = self.last_virtualizer
        base.bind(relative=True)
        changed.bind(relative=True)
        # Use fail-safe workaround of diff to avoid bugs in Virtualizer library
        # diff = base.diff(changed)
        diff = base.diff_failsafe(changed)
        return diff
Пример #11
0
class VirtualizerBasedStaticFileAdapter(StaticFileAdapter):
    """
  Adapter class to return the topology description parsed from a static file in
  Virtualizer format.
  """
    name = "STATIC-VIRTUALIZER-TOPO"
    type = AbstractESCAPEAdapter.TYPE_TOPOLOGY

    def __init__(self, domain_name=None, path=None, diff=None, **kwargs):
        """
    Init.

    :param path: file path offered as the domain topology
    :type path: str
    :return: None
    """
        log.debug(
            "Init %s - type: %s, domain: %s, path: %s, diff: %s" %
            (self.__class__.__name__, self.type, domain_name, path, diff))
        # Converter object
        self.converter = NFFGConverter(
            unique_bb_id=CONFIG.ensure_unique_bisbis_id(),
            unique_nf_id=CONFIG.ensure_unique_vnf_id(),
            domain=domain_name,
            logger=log)
        super(VirtualizerBasedStaticFileAdapter,
              self).__init__(domain_name=domain_name, path=path, **kwargs)
        self.diff = diff

    def _read_topo_from_file(self, path):
        """
    Load a pre-defined topology from an NFFG stored in a file.
    The file path is searched in CONFIG with tha name ``SDN-TOPO``.

    :param path: additional file path
    :type path: str
    :return: None
    """
        try:
            path = os.path.join(PROJECT_ROOT, path)
            log.debug("Load topology from file: %s" % path)
            self.virtualizer = Virtualizer.parse_from_file(filename=path)
            log.log(VERBOSE, "Loaded topology:\n%s" % self.virtualizer.xml())
            nffg = self.converter.parse_from_Virtualizer(
                vdata=self.virtualizer)
            self.topo = self.rewrite_domain(nffg)
            log.log(VERBOSE, "Converted topology:\n%s" % self.topo.dump())
        except IOError:
            log.warning("Topology file not found: %s" % path)
        except ValueError as e:
            log.error("An error occurred when load topology from file: %s" %
                      e.message)
            raise TopologyLoadException("File parsing error!")

    def __calculate_diff(self, changed):
        """
    Calculate the difference of the given Virtualizer compared to the most
    recent Virtualizer acquired by get-config.

    :param changed: Virtualizer containing new install
    :type changed: :class:`Virtualizer`
    :return: the difference
    :rtype: :class:`Virtualizer`
    """
        # base = Virtualizer.parse_from_text(text=self.last_virtualizer.xml())
        base = self.virtualizer
        base.bind(relative=True)
        changed.bind(relative=True)
        # Use fail-safe workaround of diff to avoid bugs in Virtualizer library
        # diff = base.diff(changed)
        diff = base.diff_failsafe(changed)
        return diff

    def dump_to_file(self, nffg):
        """
    Dump received :class:`NFFG` into a file.

    :param nffg: received NFFG need to be deployed
    :type nffg: :class:`NFFG`
    :return: successful install (True)
    :rtype: bool
    """
        vdata = self.converter.adapt_mapping_into_Virtualizer(
            virtualizer=self.virtualizer, nffg=nffg, reinstall=self.diff)
        if self.diff:
            log.debug(
                "DIFF is enabled. Calculating difference of mapping changes..."
            )
            vdata = self.__calculate_diff(vdata)
        return self._dump_to_file(file_name='out-%s-edit_config.xml' %
                                  self.domain_name,
                                  data=vdata.xml())
Пример #12
0
 def __init__(self, **kwargs):
     self.converter = NFFGConverter(**kwargs)
     self.request_cache = RequestCache()
     self.topology_revision = None
     self.last_response = None
Пример #13
0
  def _deploy_flowrules (self, nffg_part):
    """
    Install the flowrules given in the NFFG.

    If a flowrule is already defined it will be updated.

    :param nffg_part: NF-FG need to be deployed
    :type nffg_part: :any:`NFFG`
    :return: deploy was successful or not
    :rtype: bool
    """
    log.debug("Deploy flowrules into the domain: %s..." % self.domain_name)
    result = True
    # Remove unnecessary SG and Requirement links to avoid mess up port
    # definition of NFs
    nffg_part.clear_links(NFFG.TYPE_LINK_SG)
    nffg_part.clear_links(NFFG.TYPE_LINK_REQUIREMENT)
    # Get physical topology description from POX adapter
    topo = self.topoAdapter.get_topology_resource()
    if topo is None:
      log.warning("Missing topology description from %s domain! "
                  "Skip deploying flowrules..." % self.domain_name)
      return False
    # Iter through the container INFRAs in the given mapped NFFG part
    for infra in nffg_part.infras:
      if infra.infra_type not in (
         NFFG.TYPE_INFRA_EE, NFFG.TYPE_INFRA_STATIC_EE,
         NFFG.TYPE_INFRA_SDN_SW):
        log.debug("Infrastructure Node: %s (type: %s) is not Switch or "
                  "Container type! Continue to next Node..." %
                  (infra.id, infra.infra_type))
        continue
      # If the actual INFRA isn't in the topology(NFFG) of this domain -> skip
      if infra.id not in (n.id for n in topo.infras):
        log.error("Infrastructure Node: %s is not found in the %s domain! Skip "
                  "flowrule install on this Node..." %
                  (infra.id, self.domain_name))
        result = False
        continue
      # Check the OF connection is alive
      try:
        dpid = self.controlAdapter.infra_to_dpid[infra.id]
      except KeyError as e:
        log.warning("Missing DPID for Infra(id: %s)! "
                    "Skip deploying flowrules for Infra" % e)
        result = False
        continue
      if self.controlAdapter.openflow.getConnection(dpid) is None:
        log.warning("Skipping INSTALL flowrule! Cause: connection for %s - "
                    "DPID: %s is not found!" % (infra, dpid_to_str(dpid)))
        result = False
        continue
      for port in infra.ports:
        for flowrule in port.flowrules:
          try:
            match = NFFGConverter.field_splitter(type="MATCH",
                                                 field=flowrule.match)
            if "in_port" not in match:
              log.warning("Missing in_port field from match field! "
                          "Using container port number...")
              match["in_port"] = port.id
            action = NFFGConverter.field_splitter(type="ACTION",
                                                  field=flowrule.action)
          except RuntimeError as e:
            log.warning("Wrong format in match/action field: %s" % e)
            result = False
            continue
          log.debug("Assemble OpenFlow flowrule from: %s" % flowrule)
          self.controlAdapter.install_flowrule(infra.id, match=match,
                                               action=action)
    log.debug("Flowrule deploy result: %s" %
              ("SUCCESS" if result else "FAILURE"))
    return result