Source code for perfsim.service_chain.microservice

#  Copyright (C) 2020 Michel Gokan Khan
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License along
#  with this program; if not, write to the Free Software Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#  This file is a part of the PerfSim project, which is now open source and available under the GPLv2.
#  Written by Michel Gokan Khan, February 2020


from __future__ import annotations

from typing import TYPE_CHECKING, Dict, List, Union

import numpy as np

from perfsim import LoadBalancer, MicroserviceReplica, MicroservicePrototype, \
    MicroserviceEndpointFunction, ResourceAllocationScenario, Host

if TYPE_CHECKING:
    pass


[docs] class Microservice(MicroservicePrototype): """ This class represents a microservice in a service chain. It has a name, a list of endpoints, a list of replicas, a load balancer, and a resource allocation scenario. """ #: The name of the microservice name: str #: The list of endpoints of the microservice endpoint_functions: Dict[str, MicroserviceEndpointFunction] # TODO: Change the list to a dictionary with replica name as key #: The list of replicas of the microservice __replicas: List[MicroserviceReplica] #: The load balancer of the microservice that is responsible for the load balancing of the replicas load_balancer: LoadBalancer #: The resource allocation scenario of the microservice resource_allocation_scenario: ResourceAllocationScenario #: List of hosts containing at least a replica of this microservice __hosts: List[Host] #: The number of replicas of the microservice (the original number of replicas) __replica_count: int #: The CPU cpu_requests_share of the microservice _cpu_requests: int #: The cpu_limits of the microservice _cpu_limits: int #: The ingress bandwidth of the microservice ingress_bw: int #: The egress bandwidth of the microservice egress_bw: int #: The ingress latency of the microservice ingress_latency: Union[int, float] #: The egress latency of the microservice egress_latency: Union[int, float] #: The storage capacity of the microservice blkio_capacity: int #: The memory request of the microservice memory_requests: int def __init__(self, name: str, endpoint_functions: Dict[str, MicroserviceEndpointFunction] = None, replica_count: int = 0, cpu_requests: int = -1, cpu_limits: int = -1, memory_requests: int = 0, ingress_bw: int = float('inf'), egress_bw: int = float('inf'), ingress_latency: float = 0, egress_latency: float = 0, blkio_capacity: int = 0, resource_allocation_scenario: ResourceAllocationScenario = None): if endpoint_functions is None: endpoint_functions = {} super().__init__(name=name) self.name = name self.endpoint_functions = endpoint_functions self.__replica_count = replica_count self.cpu_requests = cpu_requests self.cpu_limits = cpu_limits self.memory_requests = memory_requests self.ingress_bw = ingress_bw self.egress_bw = egress_bw self.ingress_latency = ingress_latency self.egress_latency = egress_latency self.blkio_capacity = blkio_capacity self.resource_allocation_scenario = resource_allocation_scenario self.__ms_affinity_rules = set() self.__ms_antiaffinity_rules = set() self.__host_affinity_rules = set() self.__host_antiaffinity_rules = set() self.__replicas = [] self.__hosts = [] #: list of hosts containing at least a replica of this microservice self.load_balancer = LoadBalancer(items=self.__replicas, algorithm="round_robin") self.current_replica_id = 0 self._init_replicas(replica_count=self.replica_count)
[docs] @classmethod def from_prototype(cls, name: str, prototype: MicroservicePrototype, replica_count: int = 0, cpu_requests: int = -1, cpu_limits: int = -1, memory_requests: int = -1, ingress_bw: int = float('inf'), egress_bw: int = float('inf'), ingress_latency: float = 0, egress_latency: float = 0, blkio_capacity: int = -1): _cls = cls(name=name, endpoint_functions=None, replica_count=replica_count, cpu_requests=cpu_requests, cpu_limits=cpu_limits, memory_requests=memory_requests, ingress_bw=ingress_bw, egress_bw=egress_bw, ingress_latency=ingress_latency, egress_latency=egress_latency, blkio_capacity=blkio_capacity) endpoint_functions = {} for _function_id, _function_name in enumerate(prototype.endpoint_function_prototypes_dict): _microservice_prototype = prototype.endpoint_function_prototypes_dict[_function_name] _endpoint_function = MicroserviceEndpointFunction.from_prototype(name=_function_name, id=_function_id, prototype=_microservice_prototype) endpoint_functions[_function_name] = _endpoint_function _endpoint_function.microservice = _cls _cls.endpoint_functions = endpoint_functions return _cls
[docs] def is_best_effort(self) -> bool: return self.cpu_limits == self.cpu_requests == -1
[docs] def is_guaranteed(self) -> bool: return self.cpu_limits == self.cpu_requests != -1
[docs] def is_burstable(self) -> bool: return self.cpu_limits != self.cpu_requests and self.cpu_requests != -1
[docs] def is_unlimited_burstable(self) -> bool: return self.cpu_limits != self.cpu_requests and self.cpu_requests != -1 and self.cpu_limits == -1
[docs] def is_limited_burstable(self) -> bool: return self.cpu_limits != self.cpu_requests and self.cpu_limits != -1
def _init_replicas(self, replica_count) -> None: self.__replicas = [] for replica_id in np.arange(0, replica_count): self.__replicas.append(MicroserviceReplica(name=str(self.name) + "_" + str(replica_id), microservice=self))
[docs] def next_replica(self, increase_replica_id: bool = True) -> MicroserviceReplica: replica_id = self.current_replica_id % len(self.__replicas) if increase_replica_id: self.current_replica_id += 1 # _id = randint(0, len(self.__replicas) - 1) return self.__replicas[replica_id]
[docs] def add_microservice_affinity_with(self, ms: Microservice) -> None: if ms is not None: if isinstance(ms, Microservice): self.__ms_affinity_rules = self.__ms_affinity_rules.union({ms}) else: raise Exception("Given microservice affinity rule to add is not valid!" " The affinity object is not an instance of Microservice class!")
[docs] def add_host_affinity_with(self, host: Host) -> None: if host is not None: if isinstance(host, Host): self.__host_affinity_rules = self.__host_affinity_rules.union({host}) else: raise Exception("Given host affinity rule to add is not valid!" " The affinity object is not an instance of Host class!")
[docs] def delete_microservice_affinity_with(self, ms: Microservice) -> None: if ms is not None: if isinstance(ms, Microservice): self.__ms_affinity_rules -= {ms} else: raise Exception("Given microservice affinity rule to delete is not valid!" " The affinity object is not an instance of Microservice class!")
[docs] def delete_host_affinity_with(self, host: Host) -> None: if host is not None: if isinstance(host, Host): self.__host_affinity_rules -= {host} else: raise Exception("Given host affinity rule to delete is not valid!" " The affinity object is not an instance of Host class!")
[docs] def add_microservice_anti_affinity_with(self, ms: Microservice) -> None: if ms is not None: if isinstance(ms, Microservice): self.__ms_antiaffinity_rules = self.__ms_antiaffinity_rules.union({ms}) else: raise Exception("Given microservice anti-affinity rule to add is not valid!" " The anti-affinity object is not an instance of Microservice class!")
[docs] def add_host_anti_affinity_with(self, host: Host) -> None: if host is not None: if isinstance(host, Host): self.__host_antiaffinity_rules = self.__host_antiaffinity_rules.union({host}) else: raise Exception("Given host anti-affinity rule to add is not valid!" " The anti-affinity object is not an instance of Host class!")
[docs] def delete_microservice_anti_affinity_with(self, ms: Microservice) -> None: if ms is not None: if isinstance(ms, Microservice): self.__ms_antiaffinity_rules -= {ms} else: raise Exception("Given microservice anti-affinity rule to delete is not valid!" " The anti-affinity object is not an instance of Microservice class!")
[docs] def delete_host_anti_affinity_with(self, host: Host) -> None: if host is not None: if isinstance(host, Host): self.__host_antiaffinity_rules -= {host} else: raise Exception("Given host anti-affinity rule to delete is not valid!" " The anti-affinity object is not an instance of Host class!")
@property def ms_affinity_rules(self): return self.__ms_affinity_rules @property def ms_antiaffinity_rules(self): return self.__ms_antiaffinity_rules @property def host_affinity_rules(self): return self.__host_affinity_rules @property def host_antiaffinity_rules(self): return self.__host_antiaffinity_rules @property def replicas(self): return self.__replicas @property def replica_count(self): return self.__replica_count @property def hosts(self): return self.__hosts @ms_affinity_rules.setter def ms_affinity_rules(self, v): raise Exception( "You can't directly change microservice affinity rules. " "Use the add_microservice_affinity_with(ms) or delete_microservice_affinity_with(ms) methods instead.") @ms_antiaffinity_rules.setter def ms_antiaffinity_rules(self, v): raise Exception( "You can't directly change microservice anti-affinity rules. " "Use the add_microservice_anti_affinity_with(ms) or delete_microservice_anti_affinity_with(ms) " "methods instead.") @host_affinity_rules.setter def host_affinity_rules(self, v): raise Exception( "You can't directly change host affinity rules. " "Use the add_host_affinity_with(ms) or delete_host_affinity_with(ms) methods instead.") @host_antiaffinity_rules.setter def host_antiaffinity_rules(self, v): raise Exception( "You can't directly change host anti-affinity rules. " "Use the add_host_anti_affinity_with(ms) or delete_host_anti_affinity_with(ms) " "methods instead.") @replicas.setter def replicas(self, v): raise Exception("You can't change replicas of a microservice. Sorry!") @hosts.setter def hosts(self, v): raise Exception("You can't change hosts of a microservice! It's the job of the scheduler.") @replica_count.setter def replica_count(self, v): self._init_replicas(replica_count=v) self.__replica_count = v def __str__(self): return self.name @property def cpu_requests(self): return self._cpu_requests @cpu_requests.setter def cpu_requests(self, v): if hasattr(self, "_cpu_limits") and self._cpu_limits != -1 and v != -1 and v > self._cpu_limits: raise Exception("CPU requests can't be greater than CPU limits!") elif v != -1 and v <= 0: raise Exception("CPU requests must be greater than 0!") old_data = self._cpu_requests if hasattr(self, "_cpu_requests") else None self._cpu_requests = v if old_data is not None and old_data != v: for replica in self.replicas: replica.reinit() @property def cpu_limits(self): return self._cpu_limits @cpu_limits.setter def cpu_limits(self, v): if hasattr(self, "_cpu_requests") and self._cpu_requests != -1 and v != -1 and v < self._cpu_requests: raise Exception("CPU limits can't be less than CPU requests!") elif v != -1 and v <= 0: raise Exception("CPU limits must be greater than 0!") old_data = self._cpu_limits if hasattr(self, "_cpu_limits") else None self._cpu_limits = v if old_data is not None and old_data != v: for replica in self.replicas: replica.reinit()
# @staticmethod # def create_random_instances(ms_count: int = None, # replica_count: int = None, # cpu_requests: int = None, # cpu_limits: int = None, # replica_reserved_network_bandwidth: int = None, # replica_thread_instructions: List[int] = [], # replica_thread_avg_cpi: List[int] = [], # replica_average_cpu_usage: List[float] = [], # replica_thread_count: int = None, # ms_request_size_in_chain: int = None, # ) -> List[Microservice]: # microservices = [] # _ms_count = Settings.get_random_chunk("ms_count") \ # if ms_count is None else ms_count # # for i in np.arange(0, _ms_count): # _replica_count = Settings.get_random_chunk("ms_replica_count") \ # if replica_count is None else replica_count # _cpu_shares = Settings.get_random_chunk( # "ms_replica_cpu_shares") if cpu_requests is None else cpu_requests # # _cpu_limits = Settings.get_random_chunk( # "ms_replica_cpu_limits", # _cpu_shares) if cpu_limits is None else cpu_limits # _replica_reserved_network_bandwidth = Settings.get_random_chunk( # "ms_replica_reserved_network_bandwidth") if replica_reserved_network_bandwidth is None else replica_reserved_network_bandwidth # # if replica_thread_instructions is None or \ # replica_thread_avg_cpi is None or \ # replica_average_cpu_usage is None: # if replica_thread_count is None: # replica_thread_count = Settings.get_random_chunk("ms_replica_thread_count") # elif len(replica_thread_instructions) != len(replica_thread_avg_cpi) \ # != len(replica_average_cpu_usage) != replica_thread_count: # raise Exception("Mismatch in number of requested threads and size of instructions/cpis/cpu usages " # "lists.") # else: # for _thread in range(replica_thread_count): # replica_thread_instructions.append(Settings.get_random_chunk("ms_replica_thread_instructions")) # replica_thread_avg_cpi.append(Settings.get_random_chunk("ms_replica_thread_avg_cpi")) # replica_average_cpu_usage.append(Settings.get_random_chunk("ms_replica_average_cpu_usage")) # # _ms_request_size_in_chain = Settings.get_random_chunk( # "sc_edge_bytes") if ms_request_size_in_chain is None else ms_request_size_in_chain # microservices.append(Microservice(name="microservice" + str(i), # id=_replica_count, # _cpu_shares, # cpu_limits=_cpu_limits, # egress_bw=_replica_reserved_network_bandwidth, # ingress_bw=_replica_reserved_network_bandwidth, # replica_thread_count, # replica_thread_instructions, # replica_thread_avg_cpi, # replica_average_cpu_usage, # _ms_request_size_in_chain # )) # # return microservices