Add Commands For Docker Registry Image Cleanup

Add commands for
registry-image-list, to list the existing images in the repo
registry-image-tags, to list the tags of a specified image
registry-image-delete, to delete a tag of a specified image
registry-garbage-collect, to delete space on the filesystem from
images that do not have any tags referencing them

It is currently impossible to delete images from the local Docker
registry on the controllers. This can be an issue if images are
continuously pushed. The only solution was to grow the file system.
This commit provides commands for a user to find unwanted images and
to delete them from the controller local Docker registry in order to
free up space on the file system.

There is a possibility of the registry getting stuck in read-only mode
due to garbage collect if puppet dies or there is a swact while
garbage collect is running. In this situation, the user should run
garbage collect again. Getting stuck in read-only mode does not
prevent image pulls or kubernetes deployments from existing images
from starting or completing.

Story: 2002840
Task: 28621

Change-Id: I4bb9301a1165db8b860418c413aa3238169bab03
Signed-off-by: Jerry Sun <jerry.sun@windriver.com>
This commit is contained in:
Jerry Sun 2019-05-10 09:34:27 -04:00
parent a8b23796fe
commit 92f1658c29
16 changed files with 645 additions and 10 deletions

View File

@ -10741,3 +10741,137 @@ itemNotFound (404)
}
This operation does not accept a request body.
---------------
Docker Registry
---------------
These APIs allow the display and delete of images in the local Docker registry
*****************************************
List images in the local Docker registry
*****************************************
.. rest_method:: GET /v1/registry_image
**Normal response codes**
200
**Error response codes**
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
itemNotFound (404)
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"registry_images (Optional)", "plain", "xsd:list", "The list of images in the local Docker registry."
"tag (Optional)", "plain", "xsd:string", "The image tag for the image"
"name (Optional)", "plain", "xsd:string", "The name of the image, including full repo"
::
{
"registry_images": [
{
"tag": null,
"name": "docker.io/port/ceph-config-helper"
},
{
"tag": null,
"name": "quay.io/external_storage/rbd-provisioner"
}
]
}
This operation does not accept a request body.
********************************************************
List tags of a given image in the local Docker registry
********************************************************
.. rest_method:: GET /v1/registry_image
**Normal response codes**
200
**Error response codes**
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
itemNotFound (404)
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"image_name", "plain", "csapi:string", "The name of the image, including full repo"
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"registry_images (Optional)", "plain", "xsd:list", "The list of images in the local Docker registry."
"tag (Optional)", "plain", "xsd:string", "The image tag for the image"
"name (Optional)", "plain", "xsd:string", "The name of the image, including full repo"
::
{
"registry_images": [
{
"tag": "v1.10.3",
"name": "docker.io/port/ceph-config-helper"
}
]
}
**********************************************
Deletes an image in the local Docker registry
**********************************************
.. rest_method:: DELETE /v1/registry_image
**Normal response codes**
204
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"image_name_and_tag", "plain", "csapi:string", "The image name and tag of the form name:tag"
This operation does not accept a request body.
******************************************
Run the Docker registry garbage collector
******************************************
.. rest_method:: POST /v1/registry_image
**Normal response codes**
204
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"garbage_collect", "plain", "csapi:bool", "run the garbage collect?"
This operation does not accept a request body.

View File

@ -2,6 +2,20 @@ class platform::dockerdistribution::params (
$registry_ks_endpoint = undef,
) {}
define platform::dockerdistribution::write_config (
$registry_readonly = false,
$file_path = '/etc/docker-distribution/registry/runtime_config.yml',
$docker_registry_ip = undef,
){
file { $file_path:
ensure => present,
owner => 'root',
group => 'root',
mode => '0644',
content => template('platform/dockerdistribution.conf.erb'),
}
}
class platform::dockerdistribution::config
inherits ::platform::dockerdistribution::params {
include ::platform::params
@ -11,6 +25,8 @@ class platform::dockerdistribution::config
include ::platform::docker::params
$docker_registry_ip = $::platform::network::mgmt::params::controller_address
$runtime_config = '/etc/docker-distribution/registry/runtime_config.yml'
$used_config = '/etc/docker-distribution/registry/config.yml'
# check insecure registries
if $::platform::docker::params::insecure_registry {
@ -35,12 +51,18 @@ class platform::dockerdistribution::config
content => template('platform/insecuredockerregistry.conf.erb'),
}
file { '/etc/docker-distribution/registry/config.yml':
ensure => present,
owner => 'root',
group => 'root',
mode => '0644',
content => template('platform/dockerdistribution.conf.erb'),
platform::dockerdistribution::write_config { 'runtime_config':
docker_registry_ip => $docker_registry_ip
}
-> exec { 'use runtime config file':
command => "ln -fs ${runtime_config} ${used_config}",
}
platform::dockerdistribution::write_config { 'readonly_config':
registry_readonly => true,
file_path => '/etc/docker-distribution/registry/readonly_config.yml',
docker_registry_ip => $docker_registry_ip,
}
file { '/etc/docker-distribution/registry/token_server.conf':
@ -236,6 +258,32 @@ class platform::dockerdistribution::runtime {
}
}
class platform::dockerdistribution::garbagecollect {
$runtime_config = '/etc/docker-distribution/registry/runtime_config.yml'
$readonly_config = '/etc/docker-distribution/registry/readonly_config.yml'
$used_config = '/etc/docker-distribution/registry/config.yml'
exec { 'turn registry read only':
command => "ln -fs ${readonly_config} ${used_config}",
}
# it doesn't like 2 platform::sm::restart with the same name
# so we have to do 1 as a command
-> exec { 'restart docker-distribution in read only':
command => 'sm-restart-safe service docker-distribution',
}
-> exec { 'run garbage collect':
command => "/usr/bin/registry garbage-collect ${used_config}",
}
-> exec { 'turn registry back to read write':
command => "ln -fs ${runtime_config} ${used_config}",
}
-> platform::sm::restart {'docker-distribution': }
}
class platform::dockerdistribution::bootstrap
inherits ::platform::dockerdistribution::params {

View File

@ -7,6 +7,9 @@ storage:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/docker-distribution
maintenance:
readonly:
enabled: <%= @registry_readonly %>
http:
addr: <%= @docker_registry_ip %>:9001
tls:

View File

@ -1,2 +1,2 @@
SRC_DIR="cgts-client"
TIS_PATCH_VER=64
TIS_PATCH_VER=65

View File

@ -62,6 +62,7 @@ from cgtsclient.v1 import partition
from cgtsclient.v1 import pci_device
from cgtsclient.v1 import port
from cgtsclient.v1 import ptp
from cgtsclient.v1 import registry_image
from cgtsclient.v1 import remotelogging
from cgtsclient.v1 import route
from cgtsclient.v1 import sdn_controller
@ -143,6 +144,7 @@ class Client(http.HTTPClient):
self.sm_service = sm_service.SmServiceManager(self)
self.sm_servicegroup = sm_servicegroup.SmServiceGroupManager(self)
self.health = health.HealthManager(self)
self.registry_image = registry_image.RegistryImageManager(self)
self.remotelogging = remotelogging.RemoteLoggingManager(self)
self.sdn_controller = sdn_controller.SDNControllerManager(self)
self.partition = partition.partitionManager(self)

View File

@ -0,0 +1,48 @@
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# -*- encoding: utf-8 -*-
#
from cgtsclient.common import base
from cgtsclient.v1 import options
class RegistryImage(base.Resource):
def __repr__(self):
return "<registry_image %s>" % self._info
class RegistryImageManager(base.Manager):
resource_class = RegistryImage
@staticmethod
def _path(name=None):
return '/v1/registry_image/%s' % name if name else '/v1/registry_image'
def list(self):
"""Retrieve the list of images from the registry."""
return self._list(self._path(), 'registry_images')
def tags(self, image_name):
"""Retrieve the list of tags from the registry for a specified image.
:param image_name: image name
"""
path = options.build_url(self._path(), None, ['image_name=%s' % image_name])
return self._list(path, 'registry_images')
def delete(self, image_name_and_tag):
"""Delete registry image given name and tag
:param image_name_and_tag: a string of the form name:tag
"""
path = options.build_url(self._path(), None, ['image_name_and_tag=%s' % image_name_and_tag])
return self._delete(path)
def garbage_collect(self):
path = options.build_url(self._path(), None, ['garbage_collect=%s' % True])
return self._create(path, {})

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import utils
from cgtsclient import exc
def do_registry_image_list(cc, args):
"""List all images in local docker registry"""
images = cc.registry_image.list()
labels = ['Image Name']
fields = ['name']
utils.print_list(images, fields, labels, sortby=0)
@utils.arg('name', metavar='<image name>',
help="Name of an image")
def do_registry_image_tags(cc, args):
"""List all tags for a Docker image from the local registry"""
images = cc.registry_image.tags(args.name)
labels = ['Image Tag']
fields = ['tag']
utils.print_list(images, fields, labels, sortby=0)
@utils.arg('name_and_tag', metavar='<image name and tag>',
help="Name and tag of an image, in the form name:tag")
def do_registry_image_delete(cc, args):
"""Remove the specified Docker image from the local registry"""
try:
cc.registry_image.delete(args.name_and_tag)
print('Image %s deleted, please run garbage collect to free disk space.' % args.name_and_tag)
except exc.HTTPNotFound:
raise exc.CommandError('Image not found: %s' % args.name_and_tag)
def do_registry_garbage_collect(cc, args):
"""Run the registry garbage collector"""
cc.registry_image.garbage_collect()
print('Running docker registry garbage collect')

View File

@ -50,6 +50,7 @@ from cgtsclient.v1 import partition_shell
from cgtsclient.v1 import pci_device_shell
from cgtsclient.v1 import port_shell
from cgtsclient.v1 import ptp_shell
from cgtsclient.v1 import registry_image_shell
from cgtsclient.v1 import remotelogging_shell
from cgtsclient.v1 import route_shell
from cgtsclient.v1 import sdn_controller_shell
@ -106,6 +107,7 @@ COMMAND_MODULES = [
lldp_agent_shell,
lldp_neighbour_shell,
health_shell,
registry_image_shell,
remotelogging_shell,
sdn_controller_shell,
partition_shell,

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv"
TIS_PATCH_VER=317
TIS_PATCH_VER=318

View File

@ -59,6 +59,7 @@ from sysinv.api.controllers.v1 import port
from sysinv.api.controllers.v1 import profile
from sysinv.api.controllers.v1 import ptp
from sysinv.api.controllers.v1 import pv
from sysinv.api.controllers.v1 import registry_image
from sysinv.api.controllers.v1 import remotelogging
from sysinv.api.controllers.v1 import route
from sysinv.api.controllers.v1 import sdn_controller
@ -223,6 +224,9 @@ class V1(base.APIBase):
health = [link.Link]
"Links to the system health resource"
registry_image = [link.Link]
"Links to the Docker registry image resource"
remotelogging = [link.Link]
"Links to the remotelogging resource"
@ -686,6 +690,15 @@ class V1(base.APIBase):
'health', '', bookmark=True)
]
v1.registry_image = [link.Link.make_link('self',
pecan.request.host_url,
'registry_image', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'registry_image', '',
bookmark=True)
]
v1.remotelogging = [link.Link.make_link('self',
pecan.request.host_url,
'remotelogging', ''),
@ -809,6 +822,7 @@ class Controller(rest.RestController):
servicenodes = servicenode.SMServiceNodeController()
servicegroup = servicegroup.SMServiceGroupController()
health = health.HealthController()
registry_image = registry_image.RegistryImageController()
remotelogging = remotelogging.RemoteLoggingController()
sdn_controller = sdn_controller.SDNControllerController()
license = license.LicenseController()

View File

@ -0,0 +1,101 @@
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from sysinv.api.controllers.v1 import base
from sysinv.api.controllers.v1 import collection
from sysinv.common import utils as cutils
from oslo_log import log
from sysinv.openstack.common.gettextutils import _
LOG = log.getLogger(__name__)
LOCK_NAME = 'RegistryImageController'
class RegistryImage(base.APIBase):
"""API representation of a docker registry image"""
name = wtypes.text
"The Docker image name"
tag = wtypes.text
"The Docker image tag"
def __init__(self, **kwargs):
self.fields = []
# set fields manually since Registry image comes from docker registry
# and not sysinv database
for fp in ['name', 'tag']:
self.fields.append(fp)
setattr(self, fp, kwargs.get(fp, None))
@classmethod
def convert_with_links(cls, rpc_app, expand=True):
app = RegistryImage(**rpc_app)
if not expand:
app.unset_fields_except(['name', 'tag'])
return app
class RegistryImageCollection(collection.Collection):
"""API representation of a collection of registry images."""
registry_images = [RegistryImage]
"A list containing RegistryImage objects"
def __init__(self, **kwargs):
self._type = 'registry_images'
@classmethod
def convert_with_links(cls, rpc_apps, expand=False):
collection = RegistryImageCollection()
collection.registry_images = [RegistryImage.convert_with_links(n, expand)
for n in rpc_apps]
return collection
class RegistryImageController(rest.RestController):
"""REST controller for Docker registry image."""
@wsme_pecan.wsexpose(RegistryImageCollection, wtypes.text)
def get_all(self, image_name=None):
# no image_name provided, list images
if image_name is None:
images = pecan.request.rpcapi.docker_registry_image_list(pecan.request.context)
# image_name provided, list tags of provided image
else:
images = pecan.request.rpcapi.docker_registry_image_tags(pecan.request.context, image_name)
return RegistryImageCollection.convert_with_links(images)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(None, wtypes.text, status_code=204)
def delete(self, image_name_and_tag):
"""Delete the image with the given name
:param name: image name and tag of the form name:tag
"""
if len(image_name_and_tag.split(":")) != 2:
raise wsme.exc.ClientSideError(_("Image name and tag must be of form name:tag"))
return pecan.request.rpcapi.docker_registry_image_delete(pecan.request.context, image_name_and_tag)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(None, wtypes.text)
def post(self, garbage_collect=None):
"""Run the registry garbage collector"""
if garbage_collect is not None:
pecan.request.rpcapi.docker_registry_garbage_collect(pecan.request.context)

View File

@ -1313,6 +1313,7 @@ MURANO_CERT_KEY_FILE = os.path.join(CERT_MURANO_DIR, CERT_KEY_FILE)
MURANO_CERT_FILE = os.path.join(CERT_MURANO_DIR, CERT_FILE)
MURANO_CERT_CA_FILE = os.path.join(CERT_MURANO_DIR, CERT_CA_FILE)
DOCKER_REGISTRY_PORT = '9001'
DOCKER_REGISTRY_CERT_FILE = os.path.join(SSL_CERT_DIR, "registry-cert.crt")
DOCKER_REGISTRY_KEY_FILE = os.path.join(SSL_CERT_DIR, "registry-cert.key")
DOCKER_REGISTRY_PKCS1_KEY_FILE = os.path.join(SSL_CERT_DIR,

View File

@ -0,0 +1,105 @@
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import base64
import keyring
import requests
from sysinv.common import exception
CERT_PATH = '/etc/ssl/private/registry-cert.crt'
KEYRING_SERVICE = 'CGCS'
REGISTRY_USERNAME = 'admin'
def get_registry_password():
registry_password = keyring.get_password(
KEYRING_SERVICE, REGISTRY_USERNAME)
if not registry_password:
raise exception.DockerRegistryCredentialNotFound(
name=REGISTRY_USERNAME)
return registry_password
def docker_registry_authenticate(www_authenticate):
"""
returns a dictionary of headers to add as part of original request
including access_token
takes the Www-Authenticate header from the 401 response of a
registry request
like 'Bearer realm="https://192.168.204.2:9002/token/",
service="192.168.204.2:9001",scope="registry:catalog:*"'
:param www_authenticate: a Www-Authenticate header as described above
"""
# additional headers from the result of authentication
# for example, access_token
# send these along with the request to the docker registry
auth_headers = {'connection': 'close'}
# take off the "Bearer"
auth_params = www_authenticate.split(' ')
# unsupported www_authenticate header
if len(auth_params) != 2 or auth_params[0] != 'Bearer':
return {}
auth_params = auth_params[1].split(',')
# each auth_params should be an entry like
# service="192.168.204.2:9001"
for auth_param in auth_params:
auth_param = auth_param.split('=')
# we need to strip quotes from the auth challenge
# if we send the "scope" field in quotes, we will get
# "token intended for another audience" errors
auth_headers[auth_param[0]] = auth_param[1].strip('\"')
# 'realm' specifies a token server to authenticate to
if 'realm' not in auth_headers:
return {}
# make a request to the token server
# the credentials are passed as a header while the rest
# are passed as params
auth_string = base64.b64encode("%s:%s" % (REGISTRY_USERNAME, get_registry_password()))
token_server_request_headers = {"authorization": "Basic %s" % auth_string}
token_server_response = requests.get(auth_headers['realm'], verify=CERT_PATH,
params=auth_headers,
headers=token_server_request_headers)
if token_server_response.status_code == 200:
auth_headers['Authorization'] = "Bearer %s" % token_server_response.json().get("access_token")
return auth_headers
def docker_registry_get(path, registry_addr):
# we need to have this header to get the correct digest when giving the tag
headers = {"Accept": "application/vnd.docker.distribution.manifest.v2+json"}
resp = requests.get("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
# authenticated registry, need to do auth with token server
if resp.status_code == 401:
auth_headers = docker_registry_authenticate(resp.headers["Www-Authenticate"])
headers.update(auth_headers)
resp = requests.get("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
return resp
def docker_registry_delete(path, registry_addr):
headers = {}
resp = requests.delete("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
# authenticated registry, need to do auth with token server
if resp.status_code == 401:
auth_headers = docker_registry_authenticate(resp.headers["Www-Authenticate"])
headers.update(auth_headers)
resp = requests.delete("%s%s" % (registry_addr, path), verify=CERT_PATH, headers=headers)
return resp

View File

@ -64,7 +64,6 @@ TARFILE_TRANSFER_CHUNK_SIZE = 1024 * 512
DOCKER_REGISTRY_USER = 'admin'
DOCKER_REGISTRY_SERVICE = 'CGCS'
DOCKER_REGISTRY_SECRET = 'default-registry-key'
DOCKER_REGISTRY_PORT = '9001'
# Helper functions
@ -1500,7 +1499,7 @@ class DockerHelper(object):
cutils.format_address_name(constants.CONTROLLER_HOSTNAME,
constants.NETWORK_TYPE_MGMT)
).address
registry_server = '{}:{}'.format(registry_ip, DOCKER_REGISTRY_PORT)
registry_server = '{}:{}'.format(registry_ip, constants.DOCKER_REGISTRY_PORT)
return registry_server
def _get_img_tag_with_registry(self, pub_img_tag):

View File

@ -85,6 +85,7 @@ from cephclient import wrapper as ceph
from sysinv.conductor import ceph as iceph
from sysinv.conductor import kube_app
from sysinv.conductor import openstack
from sysinv.conductor import docker_registry
from sysinv.db import api as dbapi
from sysinv.objects import base as objects_base
from sysinv.objects import kube_app as kubeapp_obj
@ -1421,6 +1422,104 @@ class ConductorManager(service.PeriodicService):
}
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
def _get_docker_registry_addr(self):
registry_ip = self.dbapi.address_get_by_name(
cutils.format_address_name(constants.CONTROLLER_HOSTNAME,
constants.NETWORK_TYPE_MGMT)
).address
registry_server = 'https://{}:{}/v2/'.format(
registry_ip, constants.DOCKER_REGISTRY_PORT)
return registry_server
def docker_registry_image_list(self, context):
image_list_response = docker_registry.docker_registry_get(
"_catalog", self._get_docker_registry_addr())
if image_list_response.status_code != 200:
LOG.error("Bad response from docker registry: %s"
% image_list_response.status_code)
return []
image_list_response = image_list_response.json()
images = []
# responses from the registry looks like this
# {u'repositories': [u'meliodas/satesatesate', ...]}
# we need to turn that into what we want to return:
# [{'name': u'meliodas/satesatesate'}]
if 'repositories' not in image_list_response:
return images
image_list_response = image_list_response['repositories']
for image in image_list_response:
images.append({'name': image})
return images
def docker_registry_image_tags(self, context, image_name):
image_tags_response = docker_registry.docker_registry_get(
"%s/tags/list" % image_name, self._get_docker_registry_addr())
if image_tags_response.status_code != 200:
LOG.error("Bad response from docker registry: %s"
% image_tags_response.status_code)
return []
image_tags_response = image_tags_response.json()
tags = []
if 'tags' not in image_tags_response:
return tags
image_tags_response = image_tags_response['tags']
# in the case where all tags of an image is deleted but not
# garbage collected
# the response will contain "tags:null"
if image_tags_response is not None:
for tag in image_tags_response:
tags.append({'name': image_name, 'tag': tag})
return tags
# assumes image_name_and_tag is already error checked to contain "name:tag"
def docker_registry_image_delete(self, context, image_name_and_tag):
image_name_and_tag = image_name_and_tag.split(":")
# first get the image digest for the image name and tag provided
digest_resp = docker_registry.docker_registry_get("%s/manifests/%s"
% (image_name_and_tag[0], image_name_and_tag[1]),
self._get_docker_registry_addr())
if digest_resp.status_code != 200:
LOG.error("Bad response from docker registry: %s"
% digest_resp.status_code)
return
image_digest = digest_resp.headers['Docker-Content-Digest']
# now delete the image
image_delete_response = docker_registry.docker_registry_delete(
"%s/manifests/%s" % (image_name_and_tag[0], image_digest),
self._get_docker_registry_addr())
if image_delete_response.status_code != 202:
LOG.error("Bad response from docker registry: %s"
% digest_resp.status_code)
return
def docker_registry_garbage_collect(self, context):
"""Run garbage collector"""
active_controller = utils.HostHelper.get_active_controller(self.dbapi)
personalities = [constants.CONTROLLER]
config_uuid = self._config_update_hosts(context, personalities,
[active_controller.uuid])
config_dict = {
"personalities": personalities,
"host_uuids": [active_controller.uuid],
"classes": ['platform::dockerdistribution::garbagecollect']
}
self._config_apply_runtime_manifest(context, config_uuid, config_dict)
def get_magnum_cluster_count(self, context):
return self._openstack.get_magnum_cluster_count()

View File

@ -843,6 +843,39 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
return self.call(context,
self.make_msg('update_remotelogging_config'), timeout=timeout)
def docker_registry_image_list(self, context):
"""Synchronously, request a list of images from Docker Registry API
:param context: request context.
"""
return self.call(context,
self.make_msg('docker_registry_image_list'))
def docker_registry_image_tags(self, context, image_name):
"""Synchronously, request a list of tags from Docker Registry API for a given image
:param context: request context.
"""
return self.call(context,
self.make_msg('docker_registry_image_tags', image_name=image_name))
def docker_registry_image_delete(self, context, image_name_and_tag):
"""Synchronously, delete the given image tag from the local docker registry
:param context: request context.
"""
return self.call(context,
self.make_msg('docker_registry_image_delete',
image_name_and_tag=image_name_and_tag))
def docker_registry_garbage_collect(self, context):
"""Asynchronously, run the docker registry garbage collector
:param context: request context.
"""
return self.cast(context,
self.make_msg('docker_registry_garbage_collect'))
def get_magnum_cluster_count(self, context):
"""Synchronously, have the conductor get magnum cluster count
configuration.