1
0
mirror of https://github.com/projectatomic/atomic.git synced 2026-02-06 03:45:28 +01:00
Files
atomic/Atomic/backends/_containers_storage.py
Giuseppe Scrivano 83fce8f33a images: add cache for dangling images
do not reload the list of all the images every time we check if an image
is dangling.  This makes "images list" extremely slow when used on the
Docker backend.

On my system (with ~100 images), this patch brings down the wall clock
from ~10 seconds to ~0.5 seconds:

$ /usr/bin/time -v ./atomic images list -a 2>&1 | grep "wall clock"
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:09.84

to:

$ /usr/bin/time -v ./atomic images list -a 2>&1 | grep "wall clock"
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.58

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>

Closes: #1200
Approved by: rhatdan
2018-02-28 14:42:34 +00:00

302 lines
9.9 KiB
Python

# pylint: disable=unused-import
# pylint: disable=unused-argument
import Atomic.util as util
from abc import abstractmethod, ABCMeta, abstractproperty
from Atomic.objects.image import Image
from Atomic.objects.container import Container
from Atomic.trust import Trust
import os
import json
import datetime as DT
import time
from Atomic.backends.backend import Backend
try:
from subprocess import DEVNULL # pylint: disable=no-name-in-module
except ImportError:
DEVNULL = open(os.devnull, 'wb')
class UnderDevelopment(Exception):
def __init__(self):
super(UnderDevelopment, self).__init__("This function for containers-storage is still under development.")
class ContainersStorageBackend(Backend): #pylint: disable=metaclass-assignment
# Mark the class as abstract
__metaclass__ = ABCMeta
@property
def backend(self):
return 'containers-storage'
def _inspect_image(self, image):
return json.loads(util.kpod(['inspect', image]))
def inspect_image(self, image):
"""
Returns the results of an inspected image as an image object
:param image:
:return: img_obj
"""
inspect_data = self._inspect_image(image)
if not inspect_data:
return None
return self._make_image(image, inspect_data, deep=True)
def _inspect_container(self, container):
return json.loads(util.kpod(['inspect', container]))
def inspect_container(self, container):
"""
Inspect a container
:param container:
:return: con_obj
"""
inspect_data = self._inspect_container(container)
if inspect_data:
return self._make_container(container, inspect_data, deep=True)
return None
def pull_image(self, image, remote_image_obj, **kwargs):
"""
Pulls an image to the backend
:param image:
:param pull_args:
:return:
"""
debug = kwargs.get('debug', False)
fq_name = remote_image_obj.fq_name
registry, _, _, tag, _ = util.Decompose(fq_name).all
if not image.endswith(tag):
image += ":{}".format(tag)
if '@sha256:' in image:
image = image.replace("@sha256:", ":")
insecure = False
registries_config = util.load_registries_from_yaml()
if "insecure_registries" in registries_config:
if registry in registries_config['insecure_registries']:
insecure = True
source = "docker://{}".format(image)
dest = "containers-storage:{}".format(image)
trust = Trust()
trust.discover_sigstore(fq_name)
util.write_out("Pulling {} ...".format(fq_name))
util.skopeo_copy(source, dest, debug=debug, insecure=insecure, policy_filename=trust.policy_filename)
return 0
def install(self, image, name, **kwargs):
"""
Installs an image on a backend
:param image:
:param name:
:return:
"""
raise UnderDevelopment()
def uninstall(self, iobject, name=None, **kwargs):
"""
Uninstalls an image from a backend
:param name:
:return:
"""
raise UnderDevelopment()
def version(self, image):
"""
Return a list of layer objects
:param image:
:return: list of layer objects
"""
raise UnderDevelopment()
def update(self, name, **kwargs):
"""
Downloads latest image from registry
:param image:
:return:
"""
raise UnderDevelopment()
# This needs to be fixed if the notion of dangling becomes real
# for containers-image.
def _make_container(self, container, con_struct, deep=False):
con_obj = Container(container, backend=self)
if not deep:
con_obj.id = con_struct['id']
con_obj.image = con_struct['image_id']
con_obj.image_name = con_struct['image']
con_obj.created = time.mktime(time.strptime(con_struct['createdAt'].split(".")[0], "%Y-%m-%dT%H:%M:%S"))
con_obj.status = con_struct['status']
con_obj.state = con_obj.status.split()[0]
con_obj.name = con_struct['names']
con_obj.labels = con_struct['labels']
con_obj.running = True if con_obj.status.lower().startswith("up") else False
con_obj.runtime = "runc"
else:
con_obj.id = con_struct['ID']
con_obj.image = con_struct['ImageID']
con_obj.image_name = con_struct['Image']
con_obj.created = time.mktime(time.strptime(con_struct['State']['created'].split(".")[0], "%Y-%m-%dT%H:%M:%S"))
con_obj.command = con_struct['Config']['Cmd']
con_obj.state = con_struct['State']['status']
con_obj.running = True if con_obj.state.upper() not in ['STOPPED'] else False
con_obj.name = con_struct['Name']
con_obj.labels = con_struct['Labels']
con_obj.original_structure = con_struct
return con_obj
def get_containers(self):
"""
Get containers for the backend
:return: list of container objects
"""
containers = json.loads(util.kpod(["ps", "-a", "--format", "json"]))
container_objects = []
for container in containers:
container_objects.append(self._make_container(container['id'], container))
return container_objects
def _make_image(self, image, img_struct, deep=False, remote=False):
img_obj = Image(image, remote=remote)
img_obj.backend = self
if not remote and not deep:
img_obj.size = img_struct['size']
img_obj.virtual_size = img_obj.size
img_obj.version = img_obj.version
img_obj.repotags = img_struct['names']
if not remote:
try:
img_obj.id = img_struct['id']
except KeyError:
img_obj.id = img_struct['ID']
try:
img_obj.created = DT.datetime.strptime(img_struct['created'], "%b %d, %Y %H:%M").strftime("%Y-%m-%d %H:%M")
except KeyError:
img_obj.created = time.mktime(time.strptime(img_struct['Created'].split(".")[0], "%Y-%m-%dT%H:%M:%S"))
try:
img_obj.size = img_struct['size']
except KeyError:
img_obj.size = img_struct['Size']
try:
img_obj.digest = img_struct['digest']
except KeyError:
img_obj.digest = img_struct['Digest']
if deep:
img_obj.deep = True
img_obj.repotags = img_struct['Tags']
img_obj.config = img_struct['Config'] or {}
img_obj.labels = img_obj.config.get("Labels", None)
img_obj.os = img_struct['OS']
img_obj.arch = img_struct['Architecture']
img_obj.graph_driver = img_struct['GraphDriver']
img_obj.version = img_obj.get_label('Version')
img_obj.release = img_obj.get_label('Release')
# kpod inspect has no Parent right now
#img_obj.parent = img_struct['Parent']
img_obj.original_structure = img_struct
img_obj.cmd = img_obj.original_structure['Config']['Cmd']
return img_obj
def make_remote_image(self, image):
img_obj = self._make_remote_image(image)
img_obj.populate_remote_inspect_info()
return img_obj
def _make_remote_image(self, image):
return self._make_image(image, None, remote=True)
def get_images(self, get_all=False):
"""
Get images for the backend
:param get_all: bool
:return: list of image objects
"""
images = json.loads(util.kpod(["images", "--format", "json"]))
image_objects = []
for image in images:
image_objects.append(self._make_image(image['id'], image))
return image_objects
def get_dangling_images(self, force_update=True):
images = json.loads(util.kpod(["images", "--filter", "dangling=true", "--format", "json"]))
return [x['id'] for x in images]
def delete_image(self, image, force=False):
"""
Delete image
:param image:
:param force:
:return:
"""
assert(image is not None)
cmd = ["rmi"]
if force:
cmd.append("-f")
cmd.append(image)
util.kpod(cmd)
def delete_container(self, container, force=False):
raise UnderDevelopment()
def start_container(self, name):
raise UnderDevelopment()
def stop_container(self, con_obj, **kwargs):
# If we plant to honor stop labels here, we will
# need to implement that when kpod exec is available.
return util.kpod(["stop", con_obj.id])
def prune(self):
for iid in self.get_dangling_images():
self.delete_image(iid, force=True)
util.write_out("Removed dangling Image {}".format(iid))
def has_image(self, img):
"""
Returns an img object if backend has the image or None
:param img:
:return: img_obj or None
"""
img_obj = self.inspect_image(img)
if img_obj:
return img_obj
return None
def has_container(self, container):
"""
Returns a container obj if valid or None
:param container:
:return:
"""
con_obj = self.inspect_container(container)
if con_obj:
return con_obj
return None
def validate_layer(self, layer):
raise UnderDevelopment()
def run(self, iobject, **kwargs):
raise UnderDevelopment()
@property
def available(self):
# When the ContainersStorageBackend is feature complete, the following
# logic will need to be updated.
if 'DEV' in os.environ:
return True
return False
def tag_image(self, _src, _dest):
cmd = ['tag', _src, _dest]
return util.kpod(cmd)