Add field to release for storing valid deploy mode

Field is called `modes`. Cluster validation stops creating the
environment with mode that is not in the `modes` field of cluster
release.

One cluster validation test seemed to have reversed logic
(test_release_non_exists_validation). It is fixed now.

Change-Id: Iafeb988a91f44e29ea0491422b8348997d950bc4
Closes-Bug: #1456540
This commit is contained in:
Maciej Kwiek 2015-05-20 12:14:33 +02:00
parent 1d59c270f5
commit 9e3f07bd92
9 changed files with 107 additions and 55 deletions

View File

@ -49,12 +49,21 @@ class ClusterValidator(BasicValidator):
@classmethod
def _validate_common(cls, data, instance=None):
d = cls.validate_json(data)
release_id = d.get("release", d.get("release_id"))
if release_id:
if not objects.Release.get_by_uid(release_id):
release = objects.Release.get_by_uid(release_id)
if not release:
raise errors.InvalidData(
"Invalid release ID", log_message=True)
mode = d.get('mode')
if mode and mode not in release.modes:
raise errors.InvalidData(
"Cannot deploy in {0} mode in current release."
" Need to be one of {1}".format(
mode, release.modes),
log_message=True
)
pend_release_id = d.get("pending_release_id")
if pend_release_id:
pend_release = objects.Release.get_by_uid(pend_release_id,
@ -75,6 +84,7 @@ class ClusterValidator(BasicValidator):
"it cannot update current release",
log_message=True
)
return d
@classmethod

View File

@ -178,6 +178,9 @@ def upgrade_schema():
op.add_column(
'releases',
sa.Column('vmware_attributes_metadata', fields.JSON(), nullable=True))
op.add_column(
'releases',
sa.Column('modes', fields.JSON(), nullable=True))
op.create_table(
'vmware_attributes',
sa.Column('id', sa.Integer(), nullable=False),
@ -405,6 +408,7 @@ def downgrade_schema():
op.drop_column('node_nic_interfaces', 'driver')
op.drop_column('node_nic_interfaces', 'bus_info')
op.drop_column('releases', 'deployment_tasks')
op.drop_column('releases', 'modes')
op.drop_constraint('node_roles_node_fkey', 'node_roles')
op.create_foreign_key(
'node_roles_node_fkey', 'node_roles', 'nodes', ['node'], ['id'])
@ -462,6 +466,11 @@ def upgrade_data():
networks=jsonutils.dumps(networks_meta),
)
update_modes = text(
'UPDATE releases SET modes = :modes')
connection.execute(update_modes, modes=jsonutils.dumps(
['ha_compact', 'multinode']))
upgrade_master_node_settings(connection)
upgrade_6_0_to_6_1_plugins_cluster_attrs_use_ids_mapping(connection)
upgrade_ubuntu_cobbler_profile_6_0_to_6_1(connection)

View File

@ -61,6 +61,7 @@ class Release(Base):
is_deployable = Column(Boolean, default=True, nullable=False)
deployment_tasks = Column(JSON, default=[])
vmware_attributes_metadata = Column(JSON, default=[])
modes = Column(JSON, default=[])
role_list = relationship(
"Role",

View File

@ -1165,6 +1165,7 @@
vcenter_password: ""
datacenter: ""
datastore: ""
modes: ['ha_compact']
- pk: 1
extend: *base_release
fields:

View File

@ -72,7 +72,8 @@ class Release(NailgunObject):
"roles": {"type": "array"},
"clusters": {"type": "array"},
"is_deployable": {"type": "boolean"},
"vmware_attributes_metadata": {"type": "object"}
"vmware_attributes_metadata": {"type": "object"},
"modes": {"type": "array"}
}
}

View File

@ -149,6 +149,7 @@ class EnvironmentManager(object):
'version': version,
'description': u"release_desc" + version,
'roles': self.get_default_roles(),
'modes': ['ha_compact', 'multinode'],
})
if kwargs.get('deployment_tasks') is None:

View File

@ -99,7 +99,7 @@ class TestInstallationInfo(BaseTestCase):
self.env.create(
cluster_kwargs={
'release_id': release[0].id,
'mode': consts.CLUSTER_MODES.ha_full,
'mode': consts.CLUSTER_MODES.ha_compact,
'net_provider': consts.CLUSTER_NET_PROVIDERS.nova_network},
nodes_kwargs=nodes_params
)
@ -121,7 +121,7 @@ class TestInstallationInfo(BaseTestCase):
self.env.create(
cluster_kwargs={
'release_id': release[0].id,
'mode': consts.CLUSTER_MODES.ha_full,
'mode': consts.CLUSTER_MODES.ha_compact,
'net_provider': consts.CLUSTER_NET_PROVIDERS.nova_network},
nodes_kwargs=nodes_params
)
@ -134,7 +134,7 @@ class TestInstallationInfo(BaseTestCase):
self.assertEquals(len(nodes_params), len(cluster_info['nodes']))
self.assertEquals(len(nodes_params), cluster_info['nodes_num'])
self.assertEquals(consts.CLUSTER_MODES.ha_full,
self.assertEquals(consts.CLUSTER_MODES.ha_compact,
cluster_info['mode'])
self.assertEquals(consts.CLUSTER_NET_PROVIDERS.nova_network,
cluster_info['net_provider'])
@ -173,7 +173,7 @@ class TestInstallationInfo(BaseTestCase):
# Checking nova network configuration
nova = consts.CLUSTER_NET_PROVIDERS.nova_network
self.env.create(cluster_kwargs={
'mode': consts.CLUSTER_MODES.ha_full,
'mode': consts.CLUSTER_MODES.ha_compact,
'net_provider': nova
})
clusters_info = info.get_clusters_info()
@ -188,7 +188,7 @@ class TestInstallationInfo(BaseTestCase):
# Checking neutron network configuration
neutron = consts.CLUSTER_NET_PROVIDERS.neutron
self.env.create(cluster_kwargs={
'mode': consts.CLUSTER_MODES.ha_full,
'mode': consts.CLUSTER_MODES.ha_compact,
'net_provider': neutron
})
clusters_info = info.get_clusters_info()

View File

@ -13,66 +13,60 @@
# License for the specific language governing permissions and limitations
# under the License.
from contextlib import nested
from mock import Mock
from mock import patch
from oslo.serialization import jsonutils
from nailgun.api.v1.validators.cluster import ClusterValidator
from nailgun import consts
from nailgun.errors import errors
from nailgun.test.base import BaseTestCase
class TestClusterValidator(BaseTestCase):
def setUp(self):
super(TestClusterValidator, self).setUp()
self.cluster_data = '{"name": "test", "release": 1}'
@classmethod
def setUpClass(cls):
super(TestClusterValidator, cls).setUpClass()
cls.cluster_data = jsonutils.dumps({
"name": "test",
"release": 1,
"mode": consts.CLUSTER_MODES.ha_compact})
def test_cluster_exists_validation(self):
with nested(
patch('nailgun.api.v1.validators.cluster.objects.'
'ClusterCollection'),
patch('nailgun.api.v1.validators.cluster.objects.Release')
) as (cc, r):
r.get_by_uid.return_value = 'release'
cc.filter_by.return_value.first.return_value = 'cluster'
self.assertRaises(errors.AlreadyExists,
ClusterValidator.validate, self.cluster_data)
@patch('nailgun.api.v1.validators.cluster.objects'
'.ClusterCollection.filter_by')
@patch('nailgun.api.v1.validators.cluster.objects.Release.get_by_uid')
def test_cluster_exists_validation(self, release_get_by_uid, cc_filter_by):
release_get_by_uid.return_value = Mock(modes=['ha_compact'])
cc_filter_by.return_value.first.return_value = 'cluster'
self.assertRaises(errors.AlreadyExists,
ClusterValidator.validate, self.cluster_data)
def test_cluster_non_exists_validation(self):
with nested(
patch(
'nailgun.api.v1.validators.cluster.objects.ClusterCollection',
Mock()
),
patch('nailgun.api.v1.validators.cluster.objects.Release', Mock())
) as (cc, r):
try:
cc.filter_by.return_value.first.return_value = None
r.get_by_uuid.return_value = 'release'
ClusterValidator.validate(self.cluster_data)
except errors.AlreadyExists as e:
self.fail(
'Cluster exists validation failed: {0}'.format(e)
)
@patch('nailgun.api.v1.validators.cluster.objects'
'.ClusterCollection.filter_by')
@patch('nailgun.api.v1.validators.cluster.objects.Release.get_by_uid')
def test_cluster_does_not_exist_validation(self, release_get_by_uid,
cc_filter_by):
try:
cc_filter_by.return_value.first.return_value = None
release_get_by_uid.return_value = Mock(modes=['ha_compact'])
ClusterValidator.validate(self.cluster_data)
except errors.AlreadyExists as e:
self.fail(
'Cluster exists validation failed: {0}'.format(e)
)
def test_release_exists_validation(self):
with patch(
'nailgun.api.v1.validators.cluster.objects.ClusterCollection',
Mock()
) as cc:
cc.filter_by.return_value.first.return_value = None
self.assertRaises(errors.InvalidData,
ClusterValidator.validate, self.cluster_data)
@patch('nailgun.api.v1.validators.cluster.objects'
'.ClusterCollection.filter_by')
def test_release_exists_validation(self, cc_filter_by):
cc_filter_by.return_value.first.return_value = None
self.assertRaises(errors.InvalidData,
ClusterValidator.validate, self.cluster_data)
def test_release_non_exists_validation(self):
with patch('nailgun.api.v1.validators.cluster.objects.Release',
Mock()) as r:
try:
r.get_by_uuid.return_value = None
ClusterValidator.validate(self.cluster_data)
except errors.InvalidData as e:
self.fail('Release exists validation failed: {0}'.format(e))
@patch('nailgun.api.v1.validators.cluster.objects.Release.get_by_uid')
def test_release_non_exists_validation(self, release_get_by_uid):
release_get_by_uid.return_value = None
self.assertRaises(errors.InvalidData,
ClusterValidator.validate, self.cluster_data)
def test_pending_release_validation_success(self):
curr_release = Mock(
@ -154,3 +148,27 @@ class TestClusterValidator(BaseTestCase):
pend_release, curr_release
)
)
@patch('nailgun.api.v1.validators.cluster.objects'
'.ClusterCollection.filter_by')
@patch('nailgun.api.v1.validators.cluster.objects.Release.get_by_uid')
def test_mode_check_passes(self, release_get_by_uid, cc_filter_by):
release_get_by_uid.return_value = Mock(modes=['ha_compact'])
cc_filter_by.return_value.first.return_value = None
try:
ClusterValidator.validate(self.cluster_data)
except errors.InvalidData as e:
self.fail('test_mode_check failed: {0}'.format(e))
@patch('nailgun.api.v1.validators.cluster.objects'
'.ClusterCollection.filter_by')
@patch('nailgun.api.v1.validators.cluster.objects.Release.get_by_uid')
def test_mode_check_fails(self, release_get_by_uid, cc_filter_by):
release_get_by_uid.return_value = Mock(modes=['trolomod', 'multinode'])
cc_filter_by.return_value.first.return_value = None
self.assertRaisesRegexp(errors.InvalidData,
"Cannot deploy in .* mode in current release",
ClusterValidator.validate,
self.cluster_data)

View File

@ -330,3 +330,14 @@ class TestOldReleasesAreNotDeployable(base.BaseAlembicMigrationTest):
for row in result:
self.assertFalse(row['is_deployable'])
class TestAddingModesToReleases(base.BaseAlembicMigrationTest):
def test_release_modes_are_added(self):
result = db.execute(
sa.select([self.meta.tables['releases'].c.modes]))
for row in result:
self.assertItemsEqual(
jsonutils.loads(row['modes']),
['ha_compact', 'multinode'])