Adding ability to edit cluster templates
Changes to allow the updating of existing cluster templates. Change-Id: I040fdcad6e3bd5ba8e8f841e0de554d4c296ec3f Partial-Implements: bp support-template-editing
This commit is contained in:
parent
48e5a3ef8d
commit
b64142ae3a
@ -1283,6 +1283,8 @@ Also cluster scoped configurations can be defined in a Cluster Template.
|
||||
+-----------------+-------------------------------------------------------------------+-------------------------------------------------------+
|
||||
| DELETE | /v1.0/{tenant_id}/cluster-templates/<cluster_template_id> | Deletes an existing Cluster Template by id. |
|
||||
+-----------------+-------------------------------------------------------------------+-------------------------------------------------------+
|
||||
| PUT | /v1.0/{tenant_id}/cluster-templates/<cluster_template_id> | Updates an existing Cluster Template by id. |
|
||||
+-----------------+-------------------------------------------------------------------+-------------------------------------------------------+
|
||||
|
||||
**Examples**
|
||||
|
||||
@ -1845,6 +1847,172 @@ This operation does not require a request body.
|
||||
HTTP/1.1 204 NO CONTENT
|
||||
Content-Type: application/json
|
||||
|
||||
5.5 Update Cluster Template
|
||||
---------------------------
|
||||
|
||||
.. http:put:: /v1.0/{tenant_id}/cluster-templates/{cluster_template_id}
|
||||
|
||||
Normal Response Code: 202 (ACCEPTED)
|
||||
|
||||
Errors: none
|
||||
|
||||
This operation returns the updated Cluster Template.
|
||||
|
||||
**Example**:
|
||||
**request**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PUT http://sahara/v1.0/775181/cluster-templates/1beae95b-fd20-47c0-a745-5125dccbd560
|
||||
|
||||
.. sourcecode:: json
|
||||
|
||||
{
|
||||
"cluster_template": {
|
||||
"neutron_management_network": "0b001fb7-b172-43f0-8c99-444672fd0513",
|
||||
"description": null,
|
||||
"cluster_configs": {},
|
||||
"created_at": "2014-08-28 20:00:40",
|
||||
"default_image_id": null,
|
||||
"updated_at": null,
|
||||
"plugin_name": "vanilla",
|
||||
"anti_affinity": [],
|
||||
"tenant_id": "28a4d0e49b024dc0875ed6a862b129f0",
|
||||
"node_groups": [
|
||||
{
|
||||
"count": 3,
|
||||
"name": "worker",
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"auto_security_group": null,
|
||||
"created_at": "2014-08-28 20:00:40",
|
||||
"updated_at": null,
|
||||
"floating_ip_pool": "cdeaa720-5517-4878-860e-71a1926744aa",
|
||||
"image_id": null,
|
||||
"volumes_size": 0,
|
||||
"node_processes": [
|
||||
"datanode",
|
||||
"nodemanager"
|
||||
],
|
||||
"node_group_template_id": "3b975888-42d4-43d3-be70-8e4401e3cb65",
|
||||
"volumes_per_node": 0,
|
||||
"node_configs": {
|
||||
"HDFS": {
|
||||
"DataNode Heap Size": 1024
|
||||
},
|
||||
"YARN": {
|
||||
"NodeManager Heap Size": 2048
|
||||
}
|
||||
},
|
||||
"security_groups": null,
|
||||
"flavor_id": "3"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"name": "master",
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"auto_security_group": null,
|
||||
"created_at": "2014-08-28 20:00:40",
|
||||
"updated_at": null,
|
||||
"floating_ip_pool": "cdeaa720-5517-4878-860e-71a1926744aa",
|
||||
"image_id": null,
|
||||
"volumes_size": 0,
|
||||
"node_processes": [
|
||||
"namenode",
|
||||
"resourcemanager",
|
||||
"oozie",
|
||||
"historyserver"
|
||||
],
|
||||
"node_group_template_id": "208f2d53-69c3-48c3-9830-986db4c29c95",
|
||||
"volumes_per_node": 0,
|
||||
"node_configs": {},
|
||||
"security_groups": null,
|
||||
"flavor_id": "3"
|
||||
}
|
||||
],
|
||||
"hadoop_version": "2.4.1",
|
||||
"id": "1beae95b-fd20-47c0-a745-5125dccbd560",
|
||||
"name": "cluster-template"
|
||||
}
|
||||
}
|
||||
|
||||
**response**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 202 ACCEPTED
|
||||
Content-Type: application/json
|
||||
|
||||
.. sourcecode:: json
|
||||
|
||||
{
|
||||
"cluster_template": {
|
||||
"neutron_management_network": "0b001fb7-b172-43f0-8c99-444672fd0513",
|
||||
"description": null,
|
||||
"cluster_configs": {},
|
||||
"created_at": "2014-08-28 20:00:40",
|
||||
"default_image_id": null,
|
||||
"updated_at": "2015-02-26 14:50:32.354180",
|
||||
"plugin_name": "vanilla",
|
||||
"anti_affinity": [],
|
||||
"tenant_id": "28a4d0e49b024dc0875ed6a862b129f0",
|
||||
"node_groups": [
|
||||
{
|
||||
"count": 3,
|
||||
"name": "worker",
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"auto_security_group": null,
|
||||
"created_at": "2014-08-28 20:00:40",
|
||||
"updated_at": null,
|
||||
"floating_ip_pool": "cdeaa720-5517-4878-860e-71a1926744aa",
|
||||
"image_id": null,
|
||||
"volumes_size": 0,
|
||||
"node_processes": [
|
||||
"datanode",
|
||||
"nodemanager"
|
||||
],
|
||||
"node_group_template_id": "3b975888-42d4-43d3-be70-8e4401e3cb65",
|
||||
"volumes_per_node": 0,
|
||||
"node_configs": {
|
||||
"HDFS": {
|
||||
"DataNode Heap Size": 1024
|
||||
},
|
||||
"YARN": {
|
||||
"NodeManager Heap Size": 2048
|
||||
}
|
||||
},
|
||||
"security_groups": null,
|
||||
"flavor_id": "3"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"name": "master",
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"auto_security_group": null,
|
||||
"created_at": "2014-08-28 20:00:40",
|
||||
"updated_at": null,
|
||||
"floating_ip_pool": "cdeaa720-5517-4878-860e-71a1926744aa",
|
||||
"image_id": null,
|
||||
"volumes_size": 0,
|
||||
"node_processes": [
|
||||
"namenode",
|
||||
"resourcemanager",
|
||||
"oozie",
|
||||
"historyserver"
|
||||
],
|
||||
"node_group_template_id": "208f2d53-69c3-48c3-9830-986db4c29c95",
|
||||
"volumes_per_node": 0,
|
||||
"node_configs": {},
|
||||
"security_groups": null,
|
||||
"flavor_id": "3"
|
||||
}
|
||||
],
|
||||
"hadoop_version": "2.4.1",
|
||||
"id": "1beae95b-fd20-47c0-a745-5125dccbd560",
|
||||
"name": "updated-cluster-template-name"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6 Clusters
|
||||
==========
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
from oslo_log import log as logging
|
||||
|
||||
from sahara.api import acl
|
||||
import sahara.api.base as b
|
||||
from sahara.service import api
|
||||
from sahara.service import validation as v
|
||||
from sahara.service.validations import cluster_templates as v_ct
|
||||
@ -100,8 +99,11 @@ def cluster_templates_get(cluster_template_id):
|
||||
@rest.put('/cluster-templates/<cluster_template_id>')
|
||||
@acl.enforce("cluster-templates:modify")
|
||||
@v.check_exists(api.get_cluster_template, 'cluster_template_id')
|
||||
@v.validate(None, v_ct.check_cluster_template_update)
|
||||
def cluster_templates_update(cluster_template_id, data):
|
||||
return b.not_implemented()
|
||||
return u.render(
|
||||
api.update_cluster_template(
|
||||
cluster_template_id, data).to_wrapped_dict())
|
||||
|
||||
|
||||
@rest.delete('/cluster-templates/<cluster_template_id>')
|
||||
|
@ -187,6 +187,16 @@ class LocalApi(object):
|
||||
self._manager.cluster_template_destroy(context,
|
||||
_get_id(cluster_template))
|
||||
|
||||
@r.wrap(r.ClusterTemplateResource)
|
||||
def cluster_template_update(self, context, id, cluster_template):
|
||||
"""Update the cluster template or raise if it does not exist.
|
||||
|
||||
:returns: the updated cluster template
|
||||
"""
|
||||
return self._manager.cluster_template_update(context,
|
||||
id,
|
||||
cluster_template)
|
||||
|
||||
# Node Group Template ops
|
||||
|
||||
@r.wrap(r.NodeGroupTemplateResource)
|
||||
|
@ -251,6 +251,17 @@ class ConductorManager(db_base.Base):
|
||||
"""Destroy the cluster_template or raise if it does not exist."""
|
||||
self.db.cluster_template_destroy(context, cluster_template)
|
||||
|
||||
def cluster_template_update(self, context, id, values):
|
||||
"""Update a cluster_template from the values dictionary."""
|
||||
values = copy.deepcopy(values)
|
||||
values = _apply_defaults(values, CLUSTER_DEFAULTS)
|
||||
values['tenant_id'] = context.tenant_id
|
||||
values['id'] = id
|
||||
|
||||
values['node_groups'] = self._populate_node_groups(context, values)
|
||||
|
||||
return self.db.cluster_template_update(context, values)
|
||||
|
||||
# Node Group Template ops
|
||||
|
||||
def node_group_template_get(self, context, node_group_template):
|
||||
|
@ -214,6 +214,12 @@ def cluster_template_destroy(context, cluster_template):
|
||||
IMPL.cluster_template_destroy(context, cluster_template)
|
||||
|
||||
|
||||
@to_dict
|
||||
def cluster_template_update(context, values):
|
||||
"""Update a cluster_template from the values dictionary."""
|
||||
return IMPL.cluster_template_update(context, values)
|
||||
|
||||
|
||||
# Node Group Template ops
|
||||
|
||||
@to_dict
|
||||
|
@ -495,6 +495,51 @@ def cluster_template_destroy(context, cluster_template_id):
|
||||
session.delete(cluster_template)
|
||||
|
||||
|
||||
def cluster_template_update(context, values):
|
||||
node_groups = values.pop("node_groups", [])
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
cluster_template_id = values['id']
|
||||
cluster_template = (_cluster_template_get(
|
||||
context, session, cluster_template_id))
|
||||
if not cluster_template:
|
||||
raise ex.NotFoundException(
|
||||
cluster_template_id,
|
||||
_("Cluster Template id '%s' not found!"))
|
||||
|
||||
name = values.get('name')
|
||||
if name:
|
||||
same_name_tmpls = model_query(
|
||||
m.ClusterTemplate, context).filter_by(
|
||||
name=name).all()
|
||||
if (len(same_name_tmpls) > 0 and
|
||||
same_name_tmpls[0].id != cluster_template_id):
|
||||
raise ex.DBDuplicateEntry(
|
||||
_("Cluster Template can not be updated. "
|
||||
"Another cluster template with name %s already exists.")
|
||||
% name
|
||||
)
|
||||
|
||||
if len(cluster_template.clusters) > 0:
|
||||
raise ex.UpdateFailedException(
|
||||
cluster_template_id,
|
||||
_("Cluster Template id '%s' can not be updated. "
|
||||
"It is referenced by at least one cluster.")
|
||||
)
|
||||
cluster_template.update(values)
|
||||
|
||||
model_query(m.TemplatesRelation, context).filter_by(
|
||||
cluster_template_id=cluster_template_id).delete()
|
||||
for ng in node_groups:
|
||||
node_group = m.TemplatesRelation()
|
||||
node_group.update(ng)
|
||||
node_group.update({"cluster_template_id": cluster_template_id})
|
||||
node_group.save(session=session)
|
||||
|
||||
return cluster_template
|
||||
|
||||
|
||||
# Node Group Template ops
|
||||
|
||||
def _node_group_template_get(context, session, node_group_template_id):
|
||||
|
@ -149,6 +149,10 @@ def terminate_cluster_template(id):
|
||||
return conductor.cluster_template_destroy(context.ctx(), id)
|
||||
|
||||
|
||||
def update_cluster_template(id, values):
|
||||
return conductor.cluster_template_update(context.ctx(), id, values)
|
||||
|
||||
|
||||
# NodeGroupTemplate ops
|
||||
|
||||
def get_node_group_templates(**kwargs):
|
||||
|
@ -137,3 +137,26 @@ def check_cluster_template_usage(cluster_template_id, **kwargs):
|
||||
_("Cluster template %(id)s in use by %(clusters)s") %
|
||||
{'id': cluster_template_id,
|
||||
'clusters': ', '.join(users)})
|
||||
|
||||
|
||||
def check_cluster_template_update(data, **kwargs):
|
||||
if data.get('plugin_name'):
|
||||
b.check_plugin_name_exists(data['plugin_name'])
|
||||
|
||||
if data.get('plugin_name') and data.get('hadoop_version'):
|
||||
b.check_plugin_supports_version(data['plugin_name'],
|
||||
data['hadoop_version'])
|
||||
b.check_all_configurations(data)
|
||||
|
||||
if data.get('default_image_id'):
|
||||
b.check_image_registered(data['default_image_id'])
|
||||
b.check_required_image_tags(data['plugin_name'],
|
||||
data['hadoop_version'],
|
||||
data['default_image_id'])
|
||||
|
||||
if data.get('anti_affinity'):
|
||||
b.check_node_processes(data['plugin_name'], data['hadoop_version'],
|
||||
data['anti_affinity'])
|
||||
|
||||
if data.get('neutron_management_network'):
|
||||
b.check_network_exists(data['neutron_management_network'])
|
||||
|
@ -13,12 +13,14 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
import testtools
|
||||
|
||||
from sahara.conductor import manager
|
||||
from sahara import context
|
||||
from sahara import exceptions as ex
|
||||
import sahara.tests.unit.conductor.base as test_base
|
||||
import sahara.tests.unit.conductor.manager.test_clusters as cluster_tests
|
||||
|
||||
|
||||
SAMPLE_NGT = {
|
||||
@ -266,3 +268,37 @@ class ClusterTemplates(test_base.ConductorManagerTestCase):
|
||||
# Invalid field
|
||||
lst = self.api.cluster_template_get_all(ctx, **{'badfield': 'junk'})
|
||||
self.assertEqual(len(lst), 0)
|
||||
|
||||
def test_clt_update(self):
|
||||
ctx = context.ctx()
|
||||
clt = self.api.cluster_template_create(ctx, SAMPLE_CLT)
|
||||
clt_id = clt["id"]
|
||||
|
||||
UPDATE_NAME = "UpdatedClusterTemplate"
|
||||
update_values = {"name": UPDATE_NAME}
|
||||
updated_clt = self.api.cluster_template_update(ctx,
|
||||
clt_id,
|
||||
update_values)
|
||||
self.assertEqual(UPDATE_NAME, updated_clt["name"])
|
||||
|
||||
updated_clt = self.api.cluster_template_get(ctx, clt_id)
|
||||
self.assertEqual(UPDATE_NAME, updated_clt["name"])
|
||||
|
||||
# check duplicate name handling
|
||||
clt = self.api.cluster_template_create(ctx, SAMPLE_CLT)
|
||||
clt_id = clt["id"]
|
||||
with testtools.ExpectedException(ex.DBDuplicateEntry):
|
||||
self.api.cluster_template_update(ctx, clt_id, update_values)
|
||||
|
||||
with testtools.ExpectedException(ex.NotFoundException):
|
||||
self.api.cluster_template_update(ctx, -1, update_values)
|
||||
|
||||
# create a cluster and try updating the referenced cluster template
|
||||
cluster_val = copy.deepcopy(cluster_tests.SAMPLE_CLUSTER)
|
||||
cluster_val['name'] = "ClusterTempalteUpdateTestCluster"
|
||||
cluster_val['cluster_template_id'] = clt['id']
|
||||
self.api.cluster_create(ctx, cluster_val)
|
||||
update_values = {"name": "noUpdateInUseName"}
|
||||
|
||||
with testtools.ExpectedException(ex.UpdateFailedException):
|
||||
self.api.cluster_template_update(ctx, clt['id'], update_values)
|
||||
|
Loading…
Reference in New Issue
Block a user