diff --git a/zun/db/etcd/api.py b/zun/db/etcd/api.py index dd75f5be3..020550de1 100644 --- a/zun/db/etcd/api.py +++ b/zun/db/etcd/api.py @@ -31,11 +31,12 @@ from zun.db.etcd import models LOG = log.getLogger(__name__) +CONF = zun.conf.CONF def get_connection(): - connection = EtcdAPI(host=zun.conf.CONF.etcd.etcd_host, - port=zun.conf.CONF.etcd.etcd_port) + connection = EtcdAPI(host=CONF.etcd.etcd_host, + port=CONF.etcd.etcd_port) return connection @@ -159,13 +160,39 @@ class EtcdAPI(object): return self._process_list_result(filtered_containers, limit=limit, sort_key=sort_key) + def _validate_unique_container_name(self, context, name): + if not CONF.compute.unique_container_name_scope: + return + lowername = name.lower() + filters = {'name': name} + if CONF.compute.unique_container_name_scope == 'project': + filters['project_id'] = context.project_id + elif CONF.compute.unique_container_name_scope == 'global': + pass + else: + return + + try: + containers = self.list_container(context, filters=filters) + except etcd.EtcdKeyNotFound: + return + except Exception as e: + LOG.error(_LE('Error occurred while retrieving container: %s'), + six.text_type(e)) + raise + if len(containers) > 0: + raise exception.ContainerAlreadyExists(field='name', + value=lowername) + def create_container(self, context, container_data): # ensure defaults are present for new containers - # TODO(pksingh): need to add validation for same container - # name validation in project and global scope if not container_data.get('uuid'): container_data['uuid'] = uuidutils.generate_uuid() + if container_data.get('name'): + self._validate_unique_container_name(context, + container_data['name']) + container = models.Container(container_data) try: container.save() diff --git a/zun/tests/unit/db/test_container.py b/zun/tests/unit/db/test_container.py index 198059b3e..fc51fcce9 100644 --- a/zun/tests/unit/db/test_container.py +++ b/zun/tests/unit/db/test_container.py @@ -23,6 +23,7 @@ import six from zun.common import exception import zun.conf from zun.db import api as dbapi +from zun.db.etcd.api import EtcdAPI as etcd_api from zun.tests.unit.db import base from zun.tests.unit.db import utils @@ -254,6 +255,9 @@ class EtcdDbContainerTestCase(base.DbTestCase): @mock.patch.object(etcd_client, 'read') @mock.patch.object(etcd_client, 'write') def test_create_container_already_exists(self, mock_write, mock_read): + CONF.set_override("unique_container_name_scope", "", + group="compute", + enforce_type=True) mock_read.side_effect = etcd.EtcdKeyNotFound utils.create_test_container(context=self.context) mock_read.side_effect = lambda *args: None @@ -446,3 +450,62 @@ class EtcdDbContainerTestCase(base.DbTestCase): self.assertRaises(exception.InvalidParameterValue, dbapi.Connection.update_container, self.context, container.id, {'uuid': ''}) + + @mock.patch.object(etcd_client, 'read') + @mock.patch.object(etcd_client, 'write') + @mock.patch.object(etcd_api, 'list_container') + def test_create_container_already_exists_in_project_name_space( + self, mock_list_container, mock_write, mock_read): + mock_read.side_effect = etcd.EtcdKeyNotFound + mock_list_container.return_value = [] + CONF.set_override("unique_container_name_scope", "project", + group="compute", + enforce_type=True) + container1 = utils.create_test_container( + context=self.context, name='cont1') + mock_list_container.return_value = [container1] + with self.assertRaisesRegexp(exception.ContainerAlreadyExists, + 'A container with name.*'): + utils.create_test_container(uuid=uuidutils.generate_uuid(), + context=self.context, + name='cont1') + + @mock.patch.object(etcd_client, 'read') + @mock.patch.object(etcd_client, 'write') + @mock.patch.object(etcd_api, 'list_container') + def test_create_container_already_exists_in_global_name_space( + self, mock_list_container, mock_write, mock_read): + mock_read.side_effect = etcd.EtcdKeyNotFound + mock_list_container.return_value = [] + CONF.set_override("unique_container_name_scope", "global", + group="compute", + enforce_type=True) + container1 = utils.create_test_container( + context=self.context, name='cont1') + self.context.project_id = 'fake_project_1' + self.context.user_id = 'fake_user_1' + mock_list_container.return_value = [container1] + with self.assertRaisesRegexp(exception.ContainerAlreadyExists, + 'A container with name.*'): + utils.create_test_container(uuid=uuidutils.generate_uuid(), + context=self.context, + name='cont1') + + @mock.patch.object(etcd_client, 'read') + @mock.patch.object(etcd_client, 'write') + @mock.patch.object(etcd_api, 'list_container') + def test_create_container_already_exists_in_default_name_space( + self, mock_list_container, mock_write, mock_read): + mock_read.side_effect = etcd.EtcdKeyNotFound + mock_list_container.return_value = [] + CONF.set_override("unique_container_name_scope", "", + group="compute", + enforce_type=True) + container1 = utils.create_test_container( + context=self.context, name='cont1', + uuid=uuidutils.generate_uuid()) + mock_list_container.return_value = [container1] + self.context.project_id = 'fake_project_1' + self.context.user_id = 'fake_user_1' + utils.create_test_container( + context=self.context, name='cont1', uuid=uuidutils.generate_uuid())