Skip to content

Container Registry

Bases: ComponentManager, Agent

Class that represents a container registry.

Source code in edge_sim_py/components/container_registry.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
class ContainerRegistry(ComponentManager, Agent):
    """Class that represents a container registry."""

    # Class attributes that allow this class to use helper methods from the ComponentManager
    _instances = []
    _object_count = 0

    def __init__(self, obj_id: int = None, cpu_demand: int = 0, memory_demand: int = 0) -> object:
        """Creates a ContainerRegistry object.

        Args:
            obj_id (int, optional): Object identifier. Defaults to None.
            cpu_demand (int, optional): Registry's CPU demand. Defaults to 0.
            memory_demand (int, optional): Registry's Memory demand. Defaults to 0.

        Returns:
            object: Created ContainerRegistry object.
        """
        # Adding the new object to the list of instances of its class
        self.__class__._instances.append(self)

        # Object's class instance ID
        self.__class__._object_count += 1
        if obj_id is None:
            obj_id = self.__class__._object_count
        self.id = obj_id

        # Registry's CPU and memory demand
        self.cpu_demand = cpu_demand
        self.memory_demand = memory_demand

        # Registry's available status (set to false when the registry is being provisioned)
        self.available = True

        # Server that hosts the registry
        self.server = None

        # List that stores metadata about each migration experienced by the registry throughout the simulation
        self.__migrations = []

        # Model-specific attributes (defined inside the model's "initialize()" method)
        self.model = None
        self.unique_id = None

    def _to_dict(self) -> dict:
        """Method that overrides the way the object is formatted to JSON."

        Returns:
            dict: JSON-friendly representation of the object as a dictionary.
        """
        dictionary = {
            "attributes": {
                "id": self.id,
                "cpu_demand": self.cpu_demand,
                "memory_demand": self.memory_demand,
            },
            "relationships": {
                "server": {"class": type(self.server).__name__, "id": self.server.id} if self.server else None,
            },
        }
        return dictionary

    def collect(self) -> dict:
        """Method that collects a set of metrics for the object.

        Returns:
            metrics (dict): Object metrics.
        """
        metrics = {
            "Available": self.available,
            "CPU Demand": self.cpu_demand,
            "RAM Demand": self.memory_demand,
            "Server": self.server.id if self.server else None,
            "Images": [image.id for image in self.server.container_images] if self.server else [],
            "Layers": [layer.id for layer in self.server.container_layers] if self.server else [],
        }
        return metrics

    def step(self):
        """Method that executes the events involving the object at each time step."""
        if not self.available:
            # Gathering a template registry container image
            registry_image = ContainerImage.find_by(attribute_name="name", attribute_value="registry")

            # Checking if the host has the container layers that compose the container registry image
            layers_hosted_by_server = 0
            for layer_digest in registry_image.layers_digests:
                if any([layer_digest == layer.digest for layer in self.server.container_layers]):
                    layers_hosted_by_server += 1

            # Checking if the host has the container registry image
            server_images_digests = [image.digest for image in self.server.container_images]
            if (
                layers_hosted_by_server == len(registry_image.layers_digests)
                and registry_image.digest not in server_images_digests
            ):
                self.server._add_container_image(template_container_image=registry_image)

            # Updating registry's availability status if its provisioning process has ended
            if not self.available and registry_image.digest in [image.digest for image in self.server.container_images]:
                self.available = True

    @classmethod
    def provision(cls, target_server: object, registry_cpu_demand: int = None, registry_memory_demand: int = None) -> object:
        """Provisions a new container registry on a given server.

        Args:
            target_server (object): Server that will host the new container registry.
            registry_cpu_demand (int, optional): CPU demand that will be assigned to the new registry. Defaults to None.
            registry_memory_demand (int, optional): Memory demand that will be assigned to the new registry. Defaults to None.

        Returns:
            registry (object): New container registry.
        """
        # If no information on CPU or memory demand are specified within the parameters,
        # those values are taken from random container registry within the infrastructure
        template_container_registry = choice(ContainerRegistry.all())
        cpu_demand = registry_cpu_demand if registry_cpu_demand != None else template_container_registry.cpu_demand
        memory_demand = registry_memory_demand if registry_memory_demand != None else template_container_registry.memory_demand

        # Creating the container registry object
        registry = ContainerRegistry(cpu_demand=cpu_demand, memory_demand=memory_demand)
        target_server.model.initialize_agent(agent=registry)

        # The new container is set as unavailable until it is completely provisioned in the target server
        registry.available = False

        # Creating relationship between the edge server and the new registry
        registry.server = target_server
        target_server.container_registries.append(registry)

        # Updating the host's resource usage
        target_server.cpu_demand += registry.cpu_demand
        target_server.memory_demand += registry.memory_demand

        # Gathering a template registry image
        registry_image = ContainerImage.find_by(attribute_name="name", attribute_value="registry")

        # Checking if the target server already has a registry container image on it
        if registry_image.digest not in [image.digest for image in target_server.container_images]:
            # Gathering layers present in the target server (layers, download_queue, waiting_queue)
            layers_downloaded = [layer for layer in target_server.container_layers]
            layers_on_download_queue = [flow.metadata["object"] for flow in target_server.download_queue]
            layers_on_waiting_queue = [layer for layer in target_server.waiting_queue]

            layers_on_target_server = layers_downloaded + layers_on_download_queue + layers_on_waiting_queue

            # Adding the registry's container image layers into the target server's waiting queue if they are not cached in there
            for layer_digest in registry_image.layers_digests:
                if not any(layer.digest == layer_digest for layer in layers_on_target_server):
                    # Creating a new layer object that will be pulled to the target server
                    layer_template = ContainerLayer.find_by(attribute_name="digest", attribute_value=layer_digest)
                    layer = ContainerLayer(
                        digest=layer_template.digest,
                        size=layer_template.size,
                        instruction=layer_template.instruction,
                    )
                    target_server.model.initialize_agent(agent=layer)

                    # Reserving the layer disk demand inside the target server
                    target_server.disk_demand += layer.size

                    # Adding the layer to the target server's waiting queue (layers it must download at some point)
                    target_server.waiting_queue.append(layer)

        return registry

    def deprovision(self, purge_images: bool = False):
        """Deprovisions a container registry, releasing the allocated resources on its host server.

        Args:
            purge_images (bool, optional): Removes all container images and associated layers from the server. Defaults to False.
        """
        # Checking if the registry has a host server and if is not currently being used to pull any container layer
        flows_using_the_registry = [
            flow for flow in NetworkFlow.all() if flow.status == "active" and flow.metadata["container_registry"] == self
        ]
        if self.server and len(flows_using_the_registry) == 0:
            # Removing unused container images and associated layers from the server if the "purge_images" flag is True
            if purge_images:
                # Gathering the list of unused container images and layers
                unused_images = list(self.server.container_images)
                unused_layers = list(self.server.container_layers)
                for service in Service.all():
                    service_target = service._Service__migrations[-1]["target"] if len(service._Service__migrations) > 0 else None
                    if service.server == self.server or service_target == self.server:
                        image = next((img for img in unused_images if img.digest == service.image_digest), None)
                        # Removing the used image from the "unused_images" list
                        unused_images.remove(image)

                        # Removing used layers from the "unused_layers" list
                        for layer in unused_layers:
                            if layer.digest in image.layers_digests:
                                unused_layers.remove(layer)

                # Removing unused images
                for image in unused_images:
                    # Removing the unused image from its host
                    image.server.container_images.remove(image)
                    image.server = None

                    # Removing the unused image from the simulator's agent list and from its class instance list
                    image.model.schedule.remove(image)
                    image.__class__._instances.remove(image)

                # Removing unused layers
                for layer in unused_layers:
                    # Removing the unused layer from its host
                    layer.server.disk_demand -= layer.size
                    layer.server.container_layers.remove(layer)
                    layer.server = None

                    # Removing the unused layer from the simulator's agent list and from its class instance list
                    layer.model.schedule.remove(layer)
                    layer.__class__._instances.remove(layer)

            # Removing relationship between the registry and its server
            self.server.container_registries.remove(self)
            self.server.memory_demand -= self.memory_demand
            self.server.cpu_demand -= self.cpu_demand
            self.server = None

            # Removing the registry
            self.model.schedule.remove(self)
            self.__class__._instances.remove(self)

__init__(obj_id=None, cpu_demand=0, memory_demand=0)

Creates a ContainerRegistry object.

Parameters:

Name Type Description Default
obj_id int

Object identifier. Defaults to None.

None
cpu_demand int

Registry's CPU demand. Defaults to 0.

0
memory_demand int

Registry's Memory demand. Defaults to 0.

0

Returns:

Name Type Description
object object

Created ContainerRegistry object.

Source code in edge_sim_py/components/container_registry.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def __init__(self, obj_id: int = None, cpu_demand: int = 0, memory_demand: int = 0) -> object:
    """Creates a ContainerRegistry object.

    Args:
        obj_id (int, optional): Object identifier. Defaults to None.
        cpu_demand (int, optional): Registry's CPU demand. Defaults to 0.
        memory_demand (int, optional): Registry's Memory demand. Defaults to 0.

    Returns:
        object: Created ContainerRegistry object.
    """
    # Adding the new object to the list of instances of its class
    self.__class__._instances.append(self)

    # Object's class instance ID
    self.__class__._object_count += 1
    if obj_id is None:
        obj_id = self.__class__._object_count
    self.id = obj_id

    # Registry's CPU and memory demand
    self.cpu_demand = cpu_demand
    self.memory_demand = memory_demand

    # Registry's available status (set to false when the registry is being provisioned)
    self.available = True

    # Server that hosts the registry
    self.server = None

    # List that stores metadata about each migration experienced by the registry throughout the simulation
    self.__migrations = []

    # Model-specific attributes (defined inside the model's "initialize()" method)
    self.model = None
    self.unique_id = None

collect()

Method that collects a set of metrics for the object.

Returns:

Name Type Description
metrics dict

Object metrics.

Source code in edge_sim_py/components/container_registry.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def collect(self) -> dict:
    """Method that collects a set of metrics for the object.

    Returns:
        metrics (dict): Object metrics.
    """
    metrics = {
        "Available": self.available,
        "CPU Demand": self.cpu_demand,
        "RAM Demand": self.memory_demand,
        "Server": self.server.id if self.server else None,
        "Images": [image.id for image in self.server.container_images] if self.server else [],
        "Layers": [layer.id for layer in self.server.container_layers] if self.server else [],
    }
    return metrics

deprovision(purge_images=False)

Deprovisions a container registry, releasing the allocated resources on its host server.

Parameters:

Name Type Description Default
purge_images bool

Removes all container images and associated layers from the server. Defaults to False.

False
Source code in edge_sim_py/components/container_registry.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def deprovision(self, purge_images: bool = False):
    """Deprovisions a container registry, releasing the allocated resources on its host server.

    Args:
        purge_images (bool, optional): Removes all container images and associated layers from the server. Defaults to False.
    """
    # Checking if the registry has a host server and if is not currently being used to pull any container layer
    flows_using_the_registry = [
        flow for flow in NetworkFlow.all() if flow.status == "active" and flow.metadata["container_registry"] == self
    ]
    if self.server and len(flows_using_the_registry) == 0:
        # Removing unused container images and associated layers from the server if the "purge_images" flag is True
        if purge_images:
            # Gathering the list of unused container images and layers
            unused_images = list(self.server.container_images)
            unused_layers = list(self.server.container_layers)
            for service in Service.all():
                service_target = service._Service__migrations[-1]["target"] if len(service._Service__migrations) > 0 else None
                if service.server == self.server or service_target == self.server:
                    image = next((img for img in unused_images if img.digest == service.image_digest), None)
                    # Removing the used image from the "unused_images" list
                    unused_images.remove(image)

                    # Removing used layers from the "unused_layers" list
                    for layer in unused_layers:
                        if layer.digest in image.layers_digests:
                            unused_layers.remove(layer)

            # Removing unused images
            for image in unused_images:
                # Removing the unused image from its host
                image.server.container_images.remove(image)
                image.server = None

                # Removing the unused image from the simulator's agent list and from its class instance list
                image.model.schedule.remove(image)
                image.__class__._instances.remove(image)

            # Removing unused layers
            for layer in unused_layers:
                # Removing the unused layer from its host
                layer.server.disk_demand -= layer.size
                layer.server.container_layers.remove(layer)
                layer.server = None

                # Removing the unused layer from the simulator's agent list and from its class instance list
                layer.model.schedule.remove(layer)
                layer.__class__._instances.remove(layer)

        # Removing relationship between the registry and its server
        self.server.container_registries.remove(self)
        self.server.memory_demand -= self.memory_demand
        self.server.cpu_demand -= self.cpu_demand
        self.server = None

        # Removing the registry
        self.model.schedule.remove(self)
        self.__class__._instances.remove(self)

provision(target_server, registry_cpu_demand=None, registry_memory_demand=None) classmethod

Provisions a new container registry on a given server.

Parameters:

Name Type Description Default
target_server object

Server that will host the new container registry.

required
registry_cpu_demand int

CPU demand that will be assigned to the new registry. Defaults to None.

None
registry_memory_demand int

Memory demand that will be assigned to the new registry. Defaults to None.

None

Returns:

Name Type Description
registry object

New container registry.

Source code in edge_sim_py/components/container_registry.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
@classmethod
def provision(cls, target_server: object, registry_cpu_demand: int = None, registry_memory_demand: int = None) -> object:
    """Provisions a new container registry on a given server.

    Args:
        target_server (object): Server that will host the new container registry.
        registry_cpu_demand (int, optional): CPU demand that will be assigned to the new registry. Defaults to None.
        registry_memory_demand (int, optional): Memory demand that will be assigned to the new registry. Defaults to None.

    Returns:
        registry (object): New container registry.
    """
    # If no information on CPU or memory demand are specified within the parameters,
    # those values are taken from random container registry within the infrastructure
    template_container_registry = choice(ContainerRegistry.all())
    cpu_demand = registry_cpu_demand if registry_cpu_demand != None else template_container_registry.cpu_demand
    memory_demand = registry_memory_demand if registry_memory_demand != None else template_container_registry.memory_demand

    # Creating the container registry object
    registry = ContainerRegistry(cpu_demand=cpu_demand, memory_demand=memory_demand)
    target_server.model.initialize_agent(agent=registry)

    # The new container is set as unavailable until it is completely provisioned in the target server
    registry.available = False

    # Creating relationship between the edge server and the new registry
    registry.server = target_server
    target_server.container_registries.append(registry)

    # Updating the host's resource usage
    target_server.cpu_demand += registry.cpu_demand
    target_server.memory_demand += registry.memory_demand

    # Gathering a template registry image
    registry_image = ContainerImage.find_by(attribute_name="name", attribute_value="registry")

    # Checking if the target server already has a registry container image on it
    if registry_image.digest not in [image.digest for image in target_server.container_images]:
        # Gathering layers present in the target server (layers, download_queue, waiting_queue)
        layers_downloaded = [layer for layer in target_server.container_layers]
        layers_on_download_queue = [flow.metadata["object"] for flow in target_server.download_queue]
        layers_on_waiting_queue = [layer for layer in target_server.waiting_queue]

        layers_on_target_server = layers_downloaded + layers_on_download_queue + layers_on_waiting_queue

        # Adding the registry's container image layers into the target server's waiting queue if they are not cached in there
        for layer_digest in registry_image.layers_digests:
            if not any(layer.digest == layer_digest for layer in layers_on_target_server):
                # Creating a new layer object that will be pulled to the target server
                layer_template = ContainerLayer.find_by(attribute_name="digest", attribute_value=layer_digest)
                layer = ContainerLayer(
                    digest=layer_template.digest,
                    size=layer_template.size,
                    instruction=layer_template.instruction,
                )
                target_server.model.initialize_agent(agent=layer)

                # Reserving the layer disk demand inside the target server
                target_server.disk_demand += layer.size

                # Adding the layer to the target server's waiting queue (layers it must download at some point)
                target_server.waiting_queue.append(layer)

    return registry

step()

Method that executes the events involving the object at each time step.

Source code in edge_sim_py/components/container_registry.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def step(self):
    """Method that executes the events involving the object at each time step."""
    if not self.available:
        # Gathering a template registry container image
        registry_image = ContainerImage.find_by(attribute_name="name", attribute_value="registry")

        # Checking if the host has the container layers that compose the container registry image
        layers_hosted_by_server = 0
        for layer_digest in registry_image.layers_digests:
            if any([layer_digest == layer.digest for layer in self.server.container_layers]):
                layers_hosted_by_server += 1

        # Checking if the host has the container registry image
        server_images_digests = [image.digest for image in self.server.container_images]
        if (
            layers_hosted_by_server == len(registry_image.layers_digests)
            and registry_image.digest not in server_images_digests
        ):
            self.server._add_container_image(template_container_image=registry_image)

        # Updating registry's availability status if its provisioning process has ended
        if not self.available and registry_image.digest in [image.digest for image in self.server.container_images]:
            self.available = True