Add registry_id to container

Partial-Bug: #1702830
Change-Id: I9de5e9786a6cddce9e31b1de7b71ceaa553b1fd9
This commit is contained in:
Hongbin Lu 2019-01-06 23:01:42 +00:00
parent c88568d05b
commit f608300050
20 changed files with 160 additions and 39 deletions

View File

@ -75,7 +75,7 @@ class ContainerCollection(collection.Collection):
context = pecan.request.context
collection = ContainerCollection()
collection.containers = \
[view.format_container(context, url, p.as_dict())
[view.format_container(context, url, p)
for p in rpc_containers]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
@ -267,7 +267,7 @@ class ContainersController(base.Controller):
raise exception.ServerNotUsable
return view.format_container(context, pecan.request.host_url,
container.as_dict())
container)
def _generate_name_for_container(self):
"""Generate a random name like: zeta-22-container."""
@ -413,6 +413,12 @@ class ContainersController(base.Controller):
exposed_ports = utils.build_exposed_ports(exposed_ports)
container_dict['exposed_ports'] = exposed_ports
registry = container_dict.pop('registry', None)
if registry:
api_utils.version_check('registry', '1.31')
registry = utils.get_registry(registry)
container_dict['registry_id'] = registry.id
container_dict['status'] = consts.CREATING
extra_spec = {}
extra_spec['hints'] = container_dict.get('hints', None)
@ -435,7 +441,7 @@ class ContainersController(base.Controller):
new_container.uuid)
pecan.response.status = 202
return view.format_container(context, pecan.request.host_url,
new_container.as_dict())
new_container)
def _check_container_quotas(self, context, container_delta_dict,
update_container=False):
@ -706,7 +712,7 @@ class ContainersController(base.Controller):
compute_api = pecan.request.compute_api
container = compute_api.container_update(context, container, patch)
return view.format_container(context, pecan.request.host_url,
container.as_dict())
container)
@base.Controller.api_version("1.1", "1.13")
@pecan.expose('json')
@ -727,7 +733,7 @@ class ContainersController(base.Controller):
context = pecan.request.context
container.save(context)
return view.format_container(context, pecan.request.host_url,
container.as_dict())
container)
@base.Controller.api_version("1.19")
@pecan.expose('json')
@ -752,7 +758,7 @@ class ContainersController(base.Controller):
compute_api.resize_container(context, container, kwargs)
pecan.response.status = 202
return view.format_container(context, pecan.request.host_url,
container.as_dict())
container)
@pecan.expose('json')
@exception.wrap_pecan_controller_exception

View File

@ -39,14 +39,6 @@ RESOURCE_NAME = 'registry'
COLLECTION_NAME = 'registries'
def _get_registry(registry_ident):
registry = api_utils.get_resource('Registry', registry_ident)
if not registry:
raise exception.RegistryNotFound(registry=registry_ident)
return registry
def check_policy_on_registry(registry, action):
context = pecan.request.context
policy.enforce(context, action, registry, action=action)
@ -160,7 +152,7 @@ class RegistryController(base.Controller):
context = pecan.request.context
if context.is_admin:
context.all_projects = True
registry = _get_registry(registry_ident)
registry = utils.get_registry(registry_ident)
policy_action = policies.REGISTRY % 'get_one'
check_policy_on_registry(registry.as_dict(), policy_action)
return RegistryItem.render_response(registry)
@ -181,7 +173,7 @@ class RegistryController(base.Controller):
# Set the HTTP Location Header
pecan.response.location = link.build_url(COLLECTION_NAME,
new_registry.uuid)
pecan.response.status = 202
pecan.response.status = 201
return RegistryItem.render_response(new_registry)
@pecan.expose('json')
@ -193,7 +185,7 @@ class RegistryController(base.Controller):
:param registry_ident: UUID or name of a registry.
:param registry_dict: a json document to apply to this registry.
"""
registry = _get_registry(registry_ident)
registry = utils.get_registry(registry_ident)
context = pecan.request.context
policy_action = policies.REGISTRY % 'update'
check_policy_on_registry(registry.as_dict(), policy_action)
@ -220,7 +212,7 @@ class RegistryController(base.Controller):
context = pecan.request.context
if context.is_admin:
context.all_projects = True
registry = _get_registry(registry_ident)
registry = utils.get_registry(registry_ident)
policy_action = policies.REGISTRY % 'delete'
check_policy_on_registry(registry.as_dict(), policy_action)
registry.destroy(context)

View File

@ -41,6 +41,7 @@ _legacy_container_properties = {
'privileged': parameter_types.boolean,
'healthcheck': parameter_types.healthcheck,
'exposed_ports': parameter_types.exposed_ports,
'registry': parameter_types.container_registry,
}
legacy_container_create = {

View File

@ -572,3 +572,10 @@ registry_password = {
'type': ['string'],
'minLength': 1,
}
container_registry = {
'type': ['string'],
'minLength': 2,
'maxLength': 255,
'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
}

View File

@ -55,7 +55,7 @@ def format_capsule(url, capsule, context):
bookmark=True)])
elif key == 'containers':
containers = []
for c in value:
for c in capsule.containers:
container = containers_view.format_container(
context, None, c)
containers.append(container)

View File

@ -49,6 +49,7 @@ _basic_keys = (
'privileged',
'healthcheck',
'cpu_policy',
'registry_id',
)
@ -69,8 +70,14 @@ def format_container(context, url, container):
'bookmark', url,
'containers', value,
bookmark=True)])
elif key == 'registry_id':
if value:
# the value is an internal id so replace it with the
# user-facing uuid
value = container.registry.uuid
yield ('registry_id', value)
else:
yield (key, value)
return dict(itertools.chain.from_iterable(
transform(k, v) for k, v in container.items()))
transform(k, v) for k, v in container.as_dict().items()))

View File

@ -63,10 +63,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 1.28 - Add support cpuset
* 1.29 - Add enable_cpu_pinning to compute_node
* 1.30 - Introduce API resource for representing private registry
* 1.31 - Add 'registry_id' to containers
"""
BASE_VER = '1.1'
CURRENT_MAX_VER = '1.30'
CURRENT_MAX_VER = '1.31'
class Version(object):

View File

@ -54,7 +54,7 @@ user documentation.
1.5
---
Add a new attribure 'runtime' to the request to create a container.
Add a new attribute 'runtime' to the request to create a container.
Users can use this attribute to choose runtime for their containers.
The specified runtime should be configured by admin to run with Zun.
The default runtime for Zun is runc.
@ -235,3 +235,9 @@ user documentation.
----
Introduce API endpoint for create/read/update/delete private registry.
1.31
----
Add 'registry_id' to container resource.
This attribute indicate the registry from which the container pulls images.

View File

@ -488,6 +488,14 @@ def get_image(image_id):
return image
def get_registry(registry_id):
registry = api_utils.get_resource('Registry', registry_id)
if not registry:
raise exception.RegistryNotFound(registry=registry_id)
return registry
def check_for_restart_policy(container_dict):
"""Check for restart policy input

View File

@ -1179,6 +1179,18 @@ def create_registry(context, values):
return _get_dbdriver_instance().create_registry(context, values)
@profiler.trace("db")
def get_registry_by_id(context, registry_id):
"""Return a registry.
:param context: The security context
:param registry_id: The id of a registry.
:returns: A registry.
"""
return _get_dbdriver_instance().get_registry_by_id(
context, registry_id)
@profiler.trace("db")
def get_registry_by_uuid(context, registry_uuid):
"""Return a registry.

View File

@ -0,0 +1,36 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""add registry_id to container
Revision ID: 1bc34e18180b
Revises: 5ffc1cabe6b4
Create Date: 2019-01-06 21:45:57.505152
"""
# revision identifiers, used by Alembic.
revision = '1bc34e18180b'
down_revision = '5ffc1cabe6b4'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('container',
sa.Column('registry_id', sa.Integer(),
nullable=True))
op.create_foreign_key(
None, 'container', 'registry', ['registry_id'], ['id'])

View File

@ -1426,6 +1426,17 @@ class Connection(object):
ref.update(values)
return ref
def get_registry_by_id(self, context, registry_id):
query = model_query(models.Registry)
query = self._add_project_filters(context, query)
query = query.filter_by(id=registry_id)
try:
result = query.one()
result['password'] = crypt.decrypt(result['password'])
return result
except NoResultFound:
raise exception.RegistryNotFound(registry=registry_id)
def get_registry_by_uuid(self, context, registry_uuid):
query = model_query(models.Registry)
query = self._add_project_filters(context, query)
@ -1457,6 +1468,11 @@ class Connection(object):
with session.begin():
query = model_query(models.Registry, session=session)
query = add_identity_filter(query, registry_uuid)
count = query.delete()
if count != 1:
raise exception.RegistryNotFound(registry=registry_uuid)
try:
count = query.delete()
if count != 1:
raise exception.RegistryNotFound(registry=registry_uuid)
except db_exc.DBReferenceError:
raise exception.Conflict('Failed to delete registry '
'%(registry)s since it is in use.',
registry=registry_uuid)

View File

@ -175,6 +175,9 @@ class Container(Base):
privileged = Column(Boolean, default=False)
healthcheck = Column(JSONEncodedDict)
exposed_ports = Column(JSONEncodedDict)
registry_id = Column(Integer,
ForeignKey('registry.id'),
nullable=True)
class VolumeMapping(Base):

View File

@ -20,12 +20,13 @@ from zun.objects import base
from zun.objects import exec_instance as exec_inst
from zun.objects import fields as z_fields
from zun.objects import pci_device
from zun.objects import registry
LOG = logging.getLogger(__name__)
CONTAINER_OPTIONAL_ATTRS = ["pci_devices", "exec_instances"]
CONTAINER_OPTIONAL_ATTRS = ["pci_devices", "exec_instances", "registry"]
@base.ZunObjectRegistry.register
@ -96,7 +97,8 @@ class Container(base.ZunPersistentObject, base.ZunObject):
# Version 1.36: Add 'get_count' method
# Version 1.37: Add 'exposed_ports' attribute
# Version 1.38: Add 'cpuset' attribute
VERSION = '1.37'
# Version 1.39: Add 'register' and 'registry_id' attributes
VERSION = '1.39'
fields = {
'id': fields.IntegerField(),
@ -143,13 +145,15 @@ class Container(base.ZunPersistentObject, base.ZunObject):
nullable=True),
'privileged': fields.BooleanField(nullable=True),
'healthcheck': z_fields.JsonField(nullable=True),
'registry_id': fields.IntegerField(nullable=True),
'registry': fields.ObjectField("Registry", nullable=True),
}
@staticmethod
def _from_db_object(container, db_container):
"""Converts a database entity to a formal object."""
for field in container.fields:
if field in ['pci_devices', 'exec_instances']:
if field in ['pci_devices', 'exec_instances', 'registry']:
continue
if field == 'cpuset':
container.cpuset = Cpuset._from_dict(
@ -349,6 +353,9 @@ class Container(base.ZunPersistentObject, base.ZunObject):
if attrname == 'exec_instances':
self._load_exec_instances()
if attrname == 'registry':
self._load_registry()
self.obj_reset_changes([attrname])
def _load_pci_devices(self):
@ -359,6 +366,12 @@ class Container(base.ZunPersistentObject, base.ZunObject):
self.exec_instances = exec_inst.ExecInstance.list_by_container_id(
self._context, self.id)
def _load_registry(self):
self.registry = None
if self.registry_id:
self.registry = registry.Registry.get_by_id(
self._context, self.registry_id)
@base.remotable_classmethod
def get_count(cls, context, project_id, flag):
"""Get the counts of Container objects in the database.

View File

@ -51,6 +51,18 @@ class Registry(base.ZunPersistentObject, base.ZunObject):
return [Registry._from_db_object(cls(context), obj)
for obj in db_objects]
@base.remotable_classmethod
def get_by_id(cls, context, id):
"""Find a registry based on id and return a :class:`Registry` object.
:param id: the id of a registry.
:param context: Security context
:returns: a :class:`Registry` object.
"""
db_registry = dbapi.get_registry_by_id(context, id)
registry = Registry._from_db_object(cls(context), db_registry)
return registry
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
"""Find a registry based on uuid and return a :class:`Registry` object.

View File

@ -26,7 +26,7 @@ from zun.tests.unit.db import base
PATH_PREFIX = '/v1'
CURRENT_VERSION = "container 1.30"
CURRENT_VERSION = "container 1.31"
class FunctionalTest(base.DbTestCase):

View File

@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest):
'default_version':
{'id': 'v1',
'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}],
'max_version': '1.30',
'max_version': '1.31',
'min_version': '1.1',
'status': 'CURRENT'},
'description': 'Zun is an OpenStack project which '
@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest):
'versions': [{'id': 'v1',
'links': [{'href': 'http://localhost/v1/',
'rel': 'self'}],
'max_version': '1.30',
'max_version': '1.31',
'min_version': '1.1',
'status': 'CURRENT'}]}

View File

@ -31,7 +31,7 @@ class TestRegistryController(api_base.FunctionalTest):
params=params,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertEqual(201, response.status_int)
self.assertEqual(1, len(response.json))
r = response.json['registry']
self.assertIsNotNone(r.get('uuid'))
@ -57,7 +57,7 @@ class TestRegistryController(api_base.FunctionalTest):
params=params,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertEqual(201, response.status_int)
self.assertEqual(1, len(response.json))
r = response.json['registry']
self.assertIsNotNone(r.get('uuid'))
@ -82,7 +82,7 @@ class TestRegistryController(api_base.FunctionalTest):
response = self.post('/v1/registries/',
params=params,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertEqual(201, response.status_int)
response = self.get('/v1/registries/')
self.assertEqual(200, response.status_int)
self.assertEqual(2, len(response.json))
@ -175,7 +175,7 @@ class TestRegistryController(api_base.FunctionalTest):
response = self.post('/v1/registries/',
params=params,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertEqual(201, response.status_int)
# get by uuid
registry_uuid = response.json['registry']['uuid']
response = self.get('/v1/registries/%s/' % registry_uuid)
@ -218,7 +218,7 @@ class TestRegistryController(api_base.FunctionalTest):
response = self.post('/v1/registries/',
params=params,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertEqual(201, response.status_int)
registry_uuid = response.json['registry']['uuid']
params = {'registry': {'name': 'new-name', 'domain': 'new-domain',
'username': 'new-username', 'password': 'new-pass'}}
@ -242,7 +242,7 @@ class TestRegistryController(api_base.FunctionalTest):
response = self.post('/v1/registries/',
params=params,
content_type='application/json')
self.assertEqual(202, response.status_int)
self.assertEqual(201, response.status_int)
registry_uuid = response.json['registry']['uuid']
response = self.delete('/v1/registries/%s/' % registry_uuid)
self.assertEqual(204, response.status_int)

View File

@ -120,6 +120,7 @@ def get_test_container(**kwargs):
'exposed_ports': kwargs.get('exposed_ports', {"80/tcp": {}}),
'cpu_policy': kwargs.get('cpu_policy', None),
'cpuset': kwargs.get('cpuset', None),
'registry_id': kwargs.get('registry_id', None),
}

View File

@ -344,7 +344,7 @@ class TestObject(test_base.TestCase, _TestObject):
# For more information on object version testing, read
# https://docs.openstack.org/zun/latest/
object_data = {
'Container': '1.37-193d8cd6635760882a27142760931af9',
'Container': '1.39-6a7bc5bcd85277c30982c1106f10c336',
'Cpuset': '1.0-06c4e6335683c18b87e2e54080f8c341',
'Volume': '1.0-4ec18c39ea49f898cc354f9ca178dfb7',
'VolumeMapping': '1.5-57febc66526185a75a744637e7a387c7',
@ -368,7 +368,7 @@ object_data = {
'ContainerActionEvent': '1.0-2974d0a6f5d4821fd4e223a88c10181a',
'Network': '1.1-26e8d37a54e5fc905ede657744a221d9',
'ExecInstance': '1.0-59464e7b96db847c0abb1e96d3cec30a',
'Registry': '1.0-21ed56234497120755c60deba7c9e1dc',
'Registry': '1.0-36c2053fbc30e0021630e657dd1699c9',
}