示例#1
0
  def manage_deployment(*, service_id, node_ip, source):
    """Manages deployment of service on node based on source. Returns ActiveService or None"""
    logger.debug(f"Deploy {service_id} on node {node_ip} with source {source}")
    
    base_service_id = source.get_base()

    if base_service_id == "FVE001": # TODO avoid hardcoding of ID
      # send deployment request to node
      response = requests.post(f"http://{node_ip}:6001/services/{service_id}",
        json={"base": source.get_base(), "image": source.get_name()}
        )
      response_code = response.status_code
      if response_code == 201:
        response_json = response.json()
        active_s = forch.ActiveService(service_id=service_id, instance_name=response_json["name"])
        src_port_list = source.get_port_list()
        if len(src_port_list) == 0:
          active_s.add_node(ipv4=node_ip)
        elif len(src_port_list) == 1:
          port = src_port_list[0]
          active_s.add_node(ipv4=node_ip, port=int(response_json["port_mappings"][port]))
        else:
          # TODO handle this case: what's better? Add a separate ServiceNode per port of the service, or a single ServiceNode with multiple ports? (in the latter case, probably need to modify ServiceNode)
          raise NotImplementedError
          # for port in src_port_list:
          #   active_s.add_node(ipv4=node_ip, port=int(response_json["port_mappings"][port]))
        return active_s
      else:
        # TODO handle case of wrong or unexpected status code of response
        return None
    else:
      # TODO handle case of unknown base_service_id -- is it even possible?
      pass
示例#2
0
 def manage_allocation(*, service_id, node_ip):
   logger.debug(f"Allocate {service_id} on node {node_ip}")
   # TODO interact with the node and ensure allocation of service (allocation is not deployment)
   # s = FORS.get_instance().get_service(service_id)
   # sn = s.get_node_by_id(node_id)
   active_s = forch.ActiveService(service_id=service_id, node_ip=node_ip)
   return active_s
示例#3
0
 def find_active_services(self, *args, **kwargs):
   """Find currently active services on known nodes"""
   service_list = self.get_service_list(*args, **kwargs)
   for s in service_list:
     for sn in s.get_node_list():
       node_ip = sn.get_ip()
       # query node
       # TODO do this through FOVIM
       response = requests.get(f"http://{node_ip}:6001/services")
       resp_json = response.json()
       sn_service_id_list = resp_json["services"]
       for sn_service_id in sn_service_id_list:
         logger.debug(f"Found active service {sn_service_id}")
         self.update_active_service_list(forch.ActiveService(service_id=sn_service_id, node_ip=node_ip))
示例#4
0
 def find_active_services(self):
   # find pre-existing services
   # TODO avoid hardcoding of IDs
   # cycle over known base services
   for base_service_id in ["FVE001"]:
     if base_service_id == "FVE001":
       # Docker-specific
       for cont_s_id in self.list_containerized_services_docker():
         logger.debug(f"Found active service {cont_s_id} base FVE001")
         self.update_active_service_list(forch.ActiveService(service_id=cont_s_id, base_service_id="FVE001"))
     elif base_service_id == "FVExxx":
       pass
     else:
       pass
示例#5
0
  def deploy_service_docker(self, service_id, image_name):

    container_name = self.__generate_container_name(service_id, image_name)

    logger.debug(f"Deploy service {service_id} with container {container_name} using image {image_name}")

    container = self.docker_container_run(image_name, name=container_name, hostname=container_name, detach=True, stdin_open=True, tty=True, publish_all_ports=True, command=None, entrypoint=None)

    if container is None:
      return None

    # just before returning, update active service list
    # TODO avoid hardcoded base_service_id FVE001
    self.update_active_service_list(forch.ActiveService(service_id=service_id, base_service_id="FVE001"))

    return container
示例#6
0
 def manage_allocation(*, service_id, node_ip):
     logger.debug(f"Allocate {service_id} on node {node_ip}")
     # TODO interact with the node (PUT) and ensure allocation of service (allocation is not deployment)
     # s = FORS.get_instance().get_service(service_id)
     # sn = s.get_node_by_id(node_id)
     resp_json, resp_code = http_put(
         f"{node_ip}:{forch.get_fog_node_main_port()}/services/{service_id}",
         {"test": "dummy"})
     # TODO check response code and handle errors
     if resp_json is None:
         logger.error(f"Failed to allocate service on node at {node_ip}")
         # TODO handle error
     # TODO check resp code and act accordingly
     logger.debug("Allocation response: {}".format(json.dumps(resp_json)))
     active_s = forch.ActiveService(service_id=service_id)
     active_s.add_node(ipv4=node_ip, port=int(resp_json["port"]))
     return active_s
示例#7
0
 def find_active_services(self, *args, **kwargs):
     """Find currently active services on known nodes"""
     service_list = self.get_service_list(*args, **kwargs)
     for s in service_list:
         for sn in s.get_node_list():
             node_ip = sn.get_ip()
             # query node
             # TODO do this through FOVIM
             resp_json, resp_code = http_get(
                 f"{node_ip}:{forch.get_fog_node_main_port()}/services")
             if resp_json is None:
                 logger.warning(
                     f"Failed to get services from node {sn.get_id()} at {node_ip}"
                 )
                 continue
             sn_service_list = resp_json["services"]
             for s_dict in sn_service_list:  # every element is expected to be a dict with parameters of ActiveService
                 s_dict.update(node_ip=node_ip)
                 active_s = forch.ActiveService(**s_dict)
                 logger.info(
                     f"Found active service {active_s.get_service_id()}")
                 self.update_active_service_list(active_s)
示例#8
0
 def put(self, s_id):
     """Allocate service."""
     # TODO improve!!
     # request_json = flask.request.get_json(force=True)
     port = 0
     s_list = FNVI.get_instance().get_service_list()
     for s in s_list:
         if s.get_id() == s_id:
             port = s.get_node_list()[0].get_port(
             )  # TODO improve! e.g. avoid accessing list by index
             active_s = forch.ActiveService(service_id=s_id)
             active_s.add_node(ipv4=FNVI.get_instance().get_ipv4(),
                               port=port)
             FNVI.get_instance().update_active_service_list(active_s)
             break
     return {
         "message": f"Allocated service {s_id}",
         "port": port
         # "type": "FN_ALLC_OK",
         # "name": container_name,
         # "ip": container_ip, # TODO change in IP visible from outside
         # "port_mappings": port_mappings
     }, 200
示例#9
0
    def deploy_container_docker(self, service_id, image_name, **kwargs):

        container_name = self.__generate_container_name(service_id)

        logger.debug(
            f"Deploy service {service_id} with container {container_name} using image {image_name}"
        )

        container = self.docker_container_run(image_name,
                                              name=container_name,
                                              hostname=container_name,
                                              **kwargs)

        if container is None:
            return None

        # just before returning, update active service list
        self.update_active_service_list(
            forch.ActiveService(
                service_id=service_id,
                base_service_id=forch.FogServiceID.DOCKER.value,
                instance_name=container_name))

        return container
示例#10
0
 def find_active_services(self, *, service_category_list=["APP", "SDP"]):
     # find pre-existing services
     # cycle over known base services
     for base_service_id in [forch.FogServiceID.DOCKER.value]:
         if base_service_id == forch.FogServiceID.DOCKER.value:
             # Docker-specific
             for cont_name in [
                     c.name for c in self.docker_container_list() if any(
                         c.name.startswith(service_category)
                         for service_category in service_category_list)
             ]:
                 cont_s_id = cont_name.split("-")[0]
                 logger.debug(
                     f"Found active service {cont_s_id} base {forch.FogServiceID.DOCKER.value} on container {cont_name}"
                 )
                 self.update_active_service_list(
                     forch.ActiveService(
                         service_id=cont_s_id,
                         base_service_id=forch.FogServiceID.DOCKER.value,
                         instance_name=cont_name))
         elif base_service_id == "FVExxx":
             pass
         else:
             pass
示例#11
0
    def manage_deployment(*, service_id, node_ip, source, project):
        """Manages deployment of service on node based on source.
    Returns: ActiveService or None
    """
        logger.debug(
            f"Deploy {service_id} on node {node_ip} with source {source}")

        base_service_id = source.get_base()

        if base_service_id == forch.FogServiceID.DOCKER.value:
            # build JSON
            request_json = dict(base=source.get_base(),
                                image=source.get_name())
            # extract additional project configurations for this instance
            # then relate them to Docker, and serialize them into the JSON of the POST
            if project.get_instance_configuration_dict():
                instance_conf_dict = {
                    forch.DockerContainerConfiguration[conf_name].value:
                    conf_value
                    for conf_name, conf_value in
                    project.get_instance_configuration_dict().items()
                }
                request_json[
                    "instance_conf"] = instance_conf_dict  # TODO avoid hardcoding string
                # network configuration
                if forch.InstanceConfiguration.ATTACH_TO_NETWORK.value in project.get_instance_configuration_dict(
                ):
                    network_conf_dict = {
                        forch.DockerNetworkConfiguration[conf_name].value:
                        conf_value
                        for conf_name, conf_value in
                        project.get_network_configuration_dict().items()
                    }
                    request_json[
                        "network_conf"] = network_conf_dict  # TODO avoid hardcoding string
            # send deployment request to node
            logger.debug("Deployment request: {}".format(
                json.dumps(request_json)))
            response_json, response_code = http_post(
                f"{node_ip}:{forch.get_fog_node_main_port()}/services/{service_id}",
                request_json)
            if response_code == 201:
                logger.debug("Deployment response: {}".format(
                    json.dumps(response_json)))
                active_s = forch.ActiveService(
                    service_id=service_id,
                    instance_name=response_json["name"],
                    instance_ip=response_json["ip"])
                src_port_list = source.get_port_list()
                if len(src_port_list) == 0:
                    active_s.add_node(ipv4=node_ip)
                elif len(src_port_list) == 1:
                    port = src_port_list[0]
                    active_s.add_node(
                        ipv4=node_ip,
                        port=int(response_json["port_mappings"][port]))
                else:
                    # TODO handle this case: what's better? Add a separate ServiceNode per port of the service, or a single ServiceNode with multiple ports? (in the latter case, probably need to modify ServiceNode)
                    raise NotImplementedError
                    # for port in src_port_list:
                    #   active_s.add_node(ipv4=node_ip, port=int(response_json["port_mappings"][port]))
                return active_s
            else:
                # TODO handle case of wrong or unexpected status code of response
                return None
        else:
            # TODO handle case of unknown base_service_id -- is it even possible?
            pass
示例#12
0
    def activate_service(self, service_id, *, project):
        """Takes service ID and returns an ActiveService object or None."""
        logger.info(f"Start activating instance of service {service_id}")
        s = FOA.get_instance().get_service(service_id,
                                           refresh_sc=True,
                                           refresh_meas=True)
        if s is not None:
            # it means that the service is defined in the service cache
            logger.info(f"Service {s.get_id()} found in cache")
            # need to check which node is best suited to host the service
            sn = s.get_node_by_metric(
                forch.MetricType.CPU, check="min"
            )  # (by default) returns node with minimum CPU utilization
            logger.info(f"Found node {sn.get_id()} offering {s.get_id()}")
            if sn is not None:
                # TODO check if best node is compliant with constraints (e.g.: if min CPU is lower than threshold for allocation)
                sn_metric = sn.get_metric_by_type(forch.MetricType.CPU)
                logger.info(
                    f"Node {sn.get_id()}: {forch.MetricType.CPU.value} {sn_metric.get_value()}{sn_metric.get_unit()}"
                )
                if float(sn_metric.get_value()
                         ) < 90:  # TODO avoid hardcoding threshold
                    # if so, trigger the requested allocation through FOVIM
                    logger.info(
                        f"Allocate service {s.get_id()} on node {sn.get_id()}")
                    active_s = FOVIM.get_instance().manage_allocation(
                        service_id=s.get_id(), node_ip=sn.get_ip())
                    # TODO verify response is an ActiveService with single service node and return it to user --> 200 OK

                    # just before returning, update active service list
                    self.update_active_service_list(active_s)

                    return active_s
                else:
                    # here there are no nodes that are free enough to host this service - it might still be deployable
                    logger.info(
                        f"Nodes offering service {s.get_id()} are too busy")
                    # TODO handle this case
                    pass
            else:
                # here there are no service nodes associated to this service - it might still be deployable
                logger.info(
                    f"No nodes offering registered service {s.get_id()}")
                # TODO handle this case - but is it even possible to get here? Because services are registered by nodes offering them
                pass

        # we get here if the service is not in the service cache or it is but is offered only by busy nodes
        logger.info(f"Attempt deployment of service {service_id}")
        # check if service is deployable (e.g.: "by deploying an APP on a IaaS node"), starting by looking for a source that offers the requested service
        src = self.__search_source_for_service(service_id)
        # check if there is a source that offers the requested service
        if src is not None:
            logger.info(f"Found a source for service {service_id}")
            # check if there is a service that provides the required base (SDP/FVE) for the source
            base_service_id = src.get_base()
            base_s = FOA.get_instance().get_service(base_service_id)
            if base_s is not None:
                # here the base service is present in the service cache
                logger.info(f"Base service {base_s.get_id()} found in cache")
                # check if there is a node that is free enough to host the new allocation
                sn = base_s.get_node_by_metric()
                logger.info(
                    f"Found node {sn.get_id()} offering {base_s.get_id()}")
                if sn is not None:
                    # check if best node is compliant with constraints (e.g.: if min CPU is lower than threshold for allocation)
                    sn_metric = sn.get_metric_by_type(forch.MetricType.CPU)
                    logger.info(
                        f"Node {sn.get_id()}: {forch.MetricType.CPU.value} {sn_metric.get_value()}{sn_metric.get_unit()}"
                    )
                    if float(sn_metric.get_value()
                             ) < 90:  # TODO avoid hardcoding threshold
                        # if so, deploy the source and allocate service on it
                        logger.info(
                            f"Deploy service {service_id} on node {sn.get_id()} on top of base {base_s.get_id()}"
                        )
                        active_s = FOVIM.get_instance().manage_deployment(
                            service_id=service_id,
                            project=project,
                            source=src,
                            node_ip=sn.get_ip())
                        # verify response is a service with single service node and return it to user --> 201 Created
                        assert isinstance(
                            active_s, forch.ActiveService) and len(
                                active_s.get_node_list()) == 1, ""
                        # just before returning, update active service list
                        self.update_active_service_list(active_s)
                        return active_s

                # here there are no more resources for new deployments
                logger.info(
                    f"Nodes offering base service {base_s.get_id()} are too busy"
                )
                # return service with empty node list --> 503 Service Unavailable
                return forch.ActiveService(service_id=service_id)

        # here unknown service --> 404 Not Found
        logger.info(f"Unknown service {service_id}")
        return None