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
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
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))
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
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
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
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)
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
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
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
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
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