+ def start_service(self):
+ """
+ This methods creates and starts a new service instance.
+ It computes placements, iterates over all VNFDs, and starts
+ each VNFD as a Docker container in the data center selected
+ by the placement algorithm.
+ :return:
+ """
+ LOG.info("Starting service %r" % self.uuid)
+
+ # 1. each service instance gets a new uuid to identify it
+ instance_uuid = str(uuid.uuid4())
+ # build a instances dict (a bit like a NSR :))
+ self.instances[instance_uuid] = dict()
+ self.instances[instance_uuid]["vnf_instances"] = list()
+
+ # 2. compute placement of this service instance (adds DC names to
+ # VNFDs)
+ if not GK_STANDALONE_MODE:
+ # self._calculate_placement(FirstDcPlacement)
+ self._calculate_placement(RoundRobinDcPlacementWithSAPs)
+ # 3. start all vnfds that we have in the service (except SAPs)
+ for vnf_id in self.vnfds:
+ vnfd = self.vnfds[vnf_id]
+ vnfi = None
+ if not GK_STANDALONE_MODE:
+ vnfi = self._start_vnfd(vnfd, vnf_id)
+ self.instances[instance_uuid]["vnf_instances"].append(vnfi)
+
+ # 4. start all SAPs in the service
+ for sap in self.saps:
+ self._start_sap(self.saps[sap], instance_uuid)
+
+ # 5. Deploy E-Line and E_LAN links
+ # Attention: Only done if ""forwarding_graphs" section in NSD exists,
+ # even if "forwarding_graphs" are not used directly.
+ if "virtual_links" in self.nsd and "forwarding_graphs" in self.nsd:
+ vlinks = self.nsd["virtual_links"]
+ # constituent virtual links are not checked
+ # fwd_links = self.nsd["forwarding_graphs"][0]["constituent_virtual_links"]
+ eline_fwd_links = [l for l in vlinks if (
+ l["connectivity_type"] == "E-Line")]
+ elan_fwd_links = [l for l in vlinks if (
+ l["connectivity_type"] == "E-LAN")]
+
+ GK.net.deployed_elines.extend(eline_fwd_links)
+ GK.net.deployed_elans.extend(elan_fwd_links)
+
+ # 5a. deploy E-Line links
+ self._connect_elines(eline_fwd_links, instance_uuid)
+
+ # 5b. deploy E-LAN links
+ self._connect_elans(elan_fwd_links, instance_uuid)
+
+ # 6. run the emulator specific entrypoint scripts in the VNFIs of this
+ # service instance
+ self._trigger_emulator_start_scripts_in_vnfis(
+ self.instances[instance_uuid]["vnf_instances"])
+
+ LOG.info("Service started. Instance id: %r" % instance_uuid)
+ return instance_uuid
+
+ def stop_service(self, instance_uuid):
+ """
+ This method stops a running service instance.
+ It iterates over all VNF instances, stopping them each
+ and removing them from their data center.
+
+ :param instance_uuid: the uuid of the service instance to be stopped
+ """
+ LOG.info("Stopping service %r" % self.uuid)
+ # get relevant information
+ # instance_uuid = str(self.uuid.uuid4())
+ vnf_instances = self.instances[instance_uuid]["vnf_instances"]
+
+ # trigger stop skripts in vnf instances and wait a few seconds for
+ # completion
+ self._trigger_emulator_stop_scripts_in_vnfis(vnf_instances)
+ time.sleep(VNF_STOP_WAIT_TIME)
+
+ for v in vnf_instances:
+ self._stop_vnfi(v)
+
+ for sap_name in self.saps_ext:
+ ext_sap = self.saps[sap_name]
+ target_dc = ext_sap.get("dc")
+ target_dc.removeExternalSAP(sap_name)
+ LOG.info("Stopping the SAP instance: %r in DC %r" %
+ (sap_name, target_dc))
+
+ if not GK_STANDALONE_MODE:
+ # remove placement?
+ # self._remove_placement(RoundRobinPlacement)
+ None
+
+ # last step: remove the instance from the list of all instances
+ del self.instances[instance_uuid]
+
+ def _start_vnfd(self, vnfd, vnf_id, **kwargs):
+ """
+ Start a single VNFD of this service
+ :param vnfd: vnfd descriptor dict
+ :param vnf_id: unique id of this vnf in the nsd
+ :return:
+ """
+ # the vnf_name refers to the container image to be deployed
+ vnf_name = vnfd.get("name")
+
+ # iterate over all deployment units within each VNFDs
+ for u in vnfd.get("virtual_deployment_units"):
+ # 1. get the name of the docker image to start and the assigned DC
+ if vnf_id not in self.remote_docker_image_urls:
+ raise Exception("No image name for %r found. Abort." % vnf_id)
+ docker_name = self.remote_docker_image_urls.get(vnf_id)
+ target_dc = vnfd.get("dc")
+ # 2. perform some checks to ensure we can start the container
+ assert(docker_name is not None)
+ assert(target_dc is not None)
+ if not self._check_docker_image_exists(docker_name):
+ raise Exception(
+ "Docker image %r not found. Abort." % docker_name)
+
+ # 3. get the resource limits
+ res_req = u.get("resource_requirements")
+ cpu_list = res_req.get("cpu").get("cores")
+ if cpu_list is None:
+ cpu_list = res_req.get("cpu").get("vcpus")
+ if cpu_list is None:
+ cpu_list = "1"
+ cpu_bw = res_req.get("cpu").get("cpu_bw")
+ if not cpu_bw:
+ cpu_bw = 1
+ mem_num = str(res_req.get("memory").get("size"))
+ if len(mem_num) == 0:
+ mem_num = "2"
+ mem_unit = str(res_req.get("memory").get("size_unit"))
+ if str(mem_unit) == 0:
+ mem_unit = "GB"
+ mem_limit = float(mem_num)
+ if mem_unit == "GB":
+ mem_limit = mem_limit * 1024 * 1024 * 1024
+ elif mem_unit == "MB":
+ mem_limit = mem_limit * 1024 * 1024
+ elif mem_unit == "KB":
+ mem_limit = mem_limit * 1024
+ mem_lim = int(mem_limit)
+ cpu_period, cpu_quota = self._calculate_cpu_cfs_values(
+ float(cpu_bw))
+
+ # check if we need to deploy the management ports
+ intfs = vnfd.get("connection_points", [])
+ mgmt_intf_names = []
+ if USE_DOCKER_MGMT:
+ mgmt_intfs = [vnf_id + ':' + intf['id']
+ for intf in intfs if intf.get('type') == 'management']
+ # check if any of these management interfaces are used in a
+ # management-type network in the nsd
+ for nsd_intf_name in mgmt_intfs:
+ vlinks = [l["connection_points_reference"]
+ for l in self.nsd.get("virtual_links", [])]
+ for link in vlinks:
+ if nsd_intf_name in link and self.check_mgmt_interface(
+ link):
+ # this is indeed a management interface and can be
+ # skipped
+ vnf_id, vnf_interface, vnf_sap_docker_name = parse_interface(
+ nsd_intf_name)
+ found_interfaces = [
+ intf for intf in intfs if intf.get('id') == vnf_interface]
+ intfs.remove(found_interfaces[0])
+ mgmt_intf_names.append(vnf_interface)
+
+ # 4. generate the volume paths for the docker container
+ volumes = list()
+ # a volume to extract log files
+ docker_log_path = "/tmp/results/%s/%s" % (self.uuid, vnf_id)
+ LOG.debug("LOG path for vnf %s is %s." % (vnf_id, docker_log_path))
+ if not os.path.exists(docker_log_path):
+ LOG.debug("Creating folder %s" % docker_log_path)
+ os.makedirs(docker_log_path)
+
+ volumes.append(docker_log_path + ":/mnt/share/")
+
+ # 5. do the dc.startCompute(name="foobar") call to run the container
+ # TODO consider flavors, and other annotations
+ # TODO: get all vnf id's from the nsd for this vnfd and use those as dockername
+ # use the vnf_id in the nsd as docker name
+ # so deployed containers can be easily mapped back to the nsd
+ LOG.info("Starting %r as %r in DC %r" %
+ (vnf_name, vnf_id, vnfd.get("dc")))
+ LOG.debug("Interfaces for %r: %r" % (vnf_id, intfs))
+ vnfi = target_dc.startCompute(
+ vnf_id,
+ network=intfs,
+ image=docker_name,
+ flavor_name="small",
+ cpu_quota=cpu_quota,
+ cpu_period=cpu_period,
+ cpuset=cpu_list,
+ mem_limit=mem_lim,
+ volumes=volumes,
+ type=kwargs.get('type', 'docker'))
+
+ # rename the docker0 interfaces (eth0) to the management port name
+ # defined in the VNFD
+ if USE_DOCKER_MGMT:
+ for intf_name in mgmt_intf_names:
+ self._vnf_reconfigure_network(
+ vnfi, 'eth0', new_name=intf_name)
+
+ return vnfi
+
+ def _stop_vnfi(self, vnfi):
+ """
+ Stop a VNF instance.
+
+ :param vnfi: vnf instance to be stopped
+ """
+ # Find the correct datacenter
+ status = vnfi.getStatus()
+ dc = vnfi.datacenter
+
+ # stop the vnfi
+ LOG.info("Stopping the vnf instance contained in %r in DC %r" %
+ (status["name"], dc))
+ dc.stopCompute(status["name"])
+
+ def _get_vnf_instance(self, instance_uuid, vnf_id):
+ """
+ Returns the Docker object for the given VNF id (or Docker name).
+ :param instance_uuid: UUID of the service instance to search in.
+ :param name: VNF name or Docker name. We are fuzzy here.
+ :return:
+ """
+ dn = vnf_id
+ for vnfi in self.instances[instance_uuid]["vnf_instances"]:
+ if vnfi.name == dn:
+ return vnfi
+ LOG.warning("No container with name: {0} found.".format(dn))
+ return None
+
+ @staticmethod
+ def _vnf_reconfigure_network(vnfi, if_name, net_str=None, new_name=None):
+ """
+ Reconfigure the network configuration of a specific interface
+ of a running container.
+ :param vnfi: container instance
+ :param if_name: interface name
+ :param net_str: network configuration string, e.g., 1.2.3.4/24
+ :return:
+ """
+
+ # assign new ip address
+ if net_str is not None:
+ intf = vnfi.intf(intf=if_name)
+ if intf is not None:
+ intf.setIP(net_str)
+ LOG.debug("Reconfigured network of %s:%s to %r" %
+ (vnfi.name, if_name, net_str))
+ else:
+ LOG.warning("Interface not found: %s:%s. Network reconfiguration skipped." % (
+ vnfi.name, if_name))
+
+ if new_name is not None:
+ vnfi.cmd('ip link set', if_name, 'down')
+ vnfi.cmd('ip link set', if_name, 'name', new_name)
+ vnfi.cmd('ip link set', new_name, 'up')
+ LOG.debug("Reconfigured interface name of %s:%s to %s" %
+ (vnfi.name, if_name, new_name))
+
+ def _trigger_emulator_start_scripts_in_vnfis(self, vnfi_list):
+ for vnfi in vnfi_list:
+ config = vnfi.dcinfo.get("Config", dict())
+ env = config.get("Env", list())
+ for env_var in env:
+ var, cmd = map(str.strip, map(str, env_var.split('=', 1)))
+ LOG.debug("%r = %r" % (var, cmd))
+ if var == "SON_EMU_CMD":
+ LOG.info("Executing entry point script in %r: %r" %
+ (vnfi.name, cmd))
+ # execute command in new thread to ensure that GK is not
+ # blocked by VNF
+ t = threading.Thread(target=vnfi.cmdPrint, args=(cmd,))
+ t.daemon = True
+ t.start()
+
+ def _trigger_emulator_stop_scripts_in_vnfis(self, vnfi_list):
+ for vnfi in vnfi_list:
+ config = vnfi.dcinfo.get("Config", dict())
+ env = config.get("Env", list())
+ for env_var in env:
+ var, cmd = map(str.strip, map(str, env_var.split('=', 1)))
+ if var == "SON_EMU_CMD_STOP":
+ LOG.info("Executing stop script in %r: %r" %
+ (vnfi.name, cmd))
+ # execute command in new thread to ensure that GK is not
+ # blocked by VNF
+ t = threading.Thread(target=vnfi.cmdPrint, args=(cmd,))
+ t.daemon = True
+ t.start()