Update of resource values/overrides implemented

Now it is able to add, update, delete keys in the resource values
and overrides.

Change-Id: I26a68f40d1c3f36dfbedfd93efaf4f79e86acd85
This commit is contained in:
Alexander Kislitsky 2016-08-17 18:05:57 +03:00
parent b2a878e536
commit b7369eaa26
9 changed files with 376 additions and 11 deletions

View File

@ -59,12 +59,25 @@ api.add_resource(
'/environments/<int:environment_id>/<levels:levels>resources/'
'<id_or_name:resource_id_or_name>/values'
)
api.add_resource(
resource_values.ResourceValuesKeys,
'/environments/<int:environment_id>/<levels:levels>resources/'
'<id_or_name:resource_id_or_name>/values/'
'keys/<keys_operation:operation>'
)
# Resource overrides
api.add_resource(
resource_overrides.ResourceOverrides,
'/environments/<int:environment_id>/'
'<levels:levels>resources/<id_or_name:resource_id_or_name>/overrides')
'<levels:levels>resources/<id_or_name:resource_id_or_name>/overrides'
)
api.add_resource(
resource_overrides.ResourceOverridesKeys,
'/environments/<int:environment_id>/'
'<levels:levels>resources/<id_or_name:resource_id_or_name>/overrides/'
'keys/<keys_operation:operation>'
)
# Environments
api.add_resource(environments.EnvironmentsCollection, '/environments')

View File

@ -14,6 +14,7 @@ from sqlalchemy.orm import exc as sa_exc
from tuning_box import db
from tuning_box import errors
from tuning_box.library import levels_hierarchy
def load_objects(model, ids):
@ -71,3 +72,31 @@ def get_resource_definition(id_or_name, environment_id):
raise sa_exc.MultipleResultsFound
return result[0]
def get_resource_values(environment, levels, res_def):
level_value = levels_hierarchy.get_environment_level_value(
environment, levels)
res_values = db.ResourceValues.query.filter_by(
environment_id=environment.id,
resource_definition_id=res_def.id,
level_value_id=level_value.id,
).all()
if not res_values:
raise errors.TuningboxNotFound(
"Resource values not found by environment {0}, "
"resource definition {1}, level {2} with value {3}".format(
environment.id, res_def.id, level_value.level.name,
level_value.value
)
)
elif len(res_values) > 1:
raise errors.TuningboxIntegrityError(
"Found more than one resource values for environment {0}, "
"resource definition {1}, level {2} with value {3}".format(
environment.id, res_def.id, level_value.level.name,
level_value.value
)
)
return res_values[0]

View File

@ -12,7 +12,11 @@
import copy
import flask
from tuning_box import db
from tuning_box import errors
from tuning_box import library
class KeysOperationMixin(object):
@ -116,3 +120,28 @@ class KeysOperationMixin(object):
"Unknown operation: {0}. "
"Allowed operations: {1}".format(operation, self.OPERATIONS)
)
class ResourceKeysMixin(KeysOperationMixin):
@db.with_transaction
def _do_update(self, environment_id, levels,
resource_id_or_name, operation, storage_name):
environment = db.Environment.query.get_or_404(environment_id)
res_def = library.get_resource_definition(
resource_id_or_name, environment_id)
if res_def.id != resource_id_or_name:
from tuning_box.app import api
return flask.redirect(api.url_for(
self.__class__,
environment_id=environment_id,
levels=levels,
resource_id_or_name=res_def.id,
), code=308)
res_values = library.get_resource_values(environment, levels, res_def)
result = self.perform_operation(
operation, getattr(res_values, storage_name), flask.request.json)
setattr(res_values, storage_name, result)

View File

@ -16,6 +16,7 @@ import flask_restful
from tuning_box import db
from tuning_box import library
from tuning_box.library import levels_hierarchy
from tuning_box.library import resource_keys_operation
class ResourceOverrides(flask_restful.Resource):
@ -23,15 +24,15 @@ class ResourceOverrides(flask_restful.Resource):
@db.with_transaction
def put(self, environment_id, levels, resource_id_or_name):
environment = db.Environment.query.get_or_404(environment_id)
resdef = library.get_resource_definition(
res_def = library.get_resource_definition(
resource_id_or_name, environment_id)
if resdef.id != resource_id_or_name:
if res_def.id != resource_id_or_name:
from tuning_box.app import api
return flask.redirect(api.url_for(
ResourceOverrides,
environment_id=environment_id,
levels=levels,
resource_id_or_name=resdef.id,
resource_id_or_name=res_def.id,
), code=308)
level_value = levels_hierarchy.get_environment_level_value(
@ -39,7 +40,7 @@ class ResourceOverrides(flask_restful.Resource):
esv = db.get_or_create(
db.ResourceValues,
environment=environment,
resource_definition=resdef,
resource_definition=res_def,
level_value=level_value,
)
esv.overrides = flask.request.json
@ -48,25 +49,38 @@ class ResourceOverrides(flask_restful.Resource):
@db.with_transaction
def get(self, environment_id, resource_id_or_name, levels):
environment = db.Environment.query.get_or_404(environment_id)
resdef = library.get_resource_definition(
res_def = library.get_resource_definition(
resource_id_or_name, environment_id)
if resdef.id != resource_id_or_name:
if res_def.id != resource_id_or_name:
from tuning_box.app import api
url = api.url_for(
ResourceOverrides,
environment_id=environment_id,
levels=levels,
resource_id_or_name=resdef.id,
resource_id_or_name=res_def.id,
)
return flask.redirect(url, code=308)
level_value = levels_hierarchy.get_environment_level_value(
environment, levels)
res_values = db.ResourceValues.query.filter_by(
resource_definition=resdef,
resource_definition=res_def,
environment=environment,
level_value=level_value,
).one_or_none()
if not res_values:
return {}
return res_values.overrides
class ResourceOverridesKeys(flask_restful.Resource,
resource_keys_operation.ResourceKeysMixin):
def put(self, environment_id, levels, resource_id_or_name, operation):
return self.patch(environment_id, levels,
resource_id_or_name, operation)
def patch(self, environment_id, levels, resource_id_or_name, operation):
self._do_update(environment_id, levels, resource_id_or_name,
operation, 'overrides')
return None, 204

View File

@ -17,6 +17,7 @@ import itertools
from tuning_box import db
from tuning_box import library
from tuning_box.library import levels_hierarchy
from tuning_box.library import resource_keys_operation
class ResourceValues(flask_restful.Resource):
@ -94,3 +95,16 @@ class ResourceValues(flask_restful.Resource):
if not resource_values:
return {}
return resource_values.values
class ResourceValuesKeys(flask_restful.Resource,
resource_keys_operation.ResourceKeysMixin):
def put(self, environment_id, levels, resource_id_or_name, operation):
return self.patch(environment_id, levels,
resource_id_or_name, operation)
def patch(self, environment_id, levels, resource_id_or_name, operation):
self._do_update(environment_id, levels, resource_id_or_name,
operation, 'values')
return None, 204

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from tuning_box.app import db
from tuning_box import errors
from tuning_box import library
from tuning_box.tests.test_app import BaseTest
@ -76,3 +77,31 @@ class TestLibrary(BaseTest):
self.assertEqual(res_id, actual_res.id)
self.assertEqual(res_name, actual_res.name)
self.assertEqual(component_id, actual_res.component_id)
def test_get_resource_values(self):
self._fixture()
res_def_id = 5
environment_id = 9
values = {'k': 'v'}
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
self._add_resource_values(environment_id, res_def_id, levels, values)
with self.app.app_context(), db.db.session.begin():
environment = db.Environment.query.get(environment_id)
res_def = db.ResourceDefinition.query.get(res_def_id)
res_values = library.get_resource_values(
environment, levels, res_def)
self.assertEqual(values, res_values.values)
def test_get_resource_values_not_found(self):
self._fixture()
res_def_id = 5
environment_id = 9
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
with self.app.app_context(), db.db.session.begin():
environment = db.Environment.query.get(environment_id)
res_def = db.ResourceDefinition.query.get(res_def_id)
self.assertRaises(errors.TuningboxNotFound,
library.get_resource_values, environment,
levels, res_def)

View File

@ -10,12 +10,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
from tuning_box import db
from tuning_box.tests.test_app import BaseTest
class TestResourceOverrides(BaseTest):
object_url = '/environments/{0}/{1}/resources/{2}/overrides'
object_keys_url = object_url + '/keys/{3}'
def test_put_resource_values_overrides_root(self):
self._fixture()
res = self.client.put('/environments/9/resources/5/overrides',
@ -43,17 +48,19 @@ class TestResourceOverrides(BaseTest):
self.assertIsNotNone(resource_values)
self.assertEqual(resource_values.overrides, {'k': 'v'})
level_value = resource_values.level_value
self.assertIsNotNone(level_value)
self.assertEqual(level_value.level.name, 'lvl2')
self.assertEqual(level_value.value, 'val2')
level_value = level_value.parent
self.assertIsNotNone(level_value)
self.assertEqual(level_value.level.name, 'lvl1')
self.assertEqual(level_value.value, 'val1')
self.assertIsNone(level_value.parent)
def test_get_resource_values_local_override(self):
self._fixture()
res = self.client.put('/environments/9/lvl1/1/resources/5/values',
data={'key': 'value1'})
self.client.put('/environments/9/lvl1/1/resources/5/values',
data={'key': 'value1'})
res = self.client.put('/environments/9/lvl1/1/resources/5/overrides',
data={'key': 'value2'})
self.assertEqual(res.status_code, 204)
@ -122,3 +129,100 @@ class TestResourceOverrides(BaseTest):
'http://localhost'
'/environments/9/lvl1/val1/lvl2/val2/resources/5/overrides',
)
def test_put_resource_overrides_set_operation_error(self):
self.app.config["PROPAGATE_EXCEPTIONS"] = True
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
overrides = {'key': 'val_overridden'}
self._add_resource_overrides(environment_id, res_def_id, levels,
overrides)
data = [['a', 'b', 'c', 'value']]
obj_keys_url = self.object_keys_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id,
'set'
)
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(409, res.status_code)
def test_put_resource_overrides_set(self):
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
overrides = {'key': 'val_overridden'}
self._add_resource_overrides(environment_id, res_def_id, levels,
overrides)
obj_url = self.object_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id
)
obj_keys_url = obj_url + '/keys/set'
data = [['key', 'key_over'], ['key_x', 'key_x_over']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(204, res.status_code)
res = self.client.get(obj_url)
self.assertEqual(200, res.status_code)
actual = res.json
self.assertEqual({'key': 'key_over', 'key_x': 'key_x_over'},
actual)
def test_put_resource_overrides_delete(self):
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
overrides = {'key_0': 'val_0', 'key_1': 'val_1'}
self._add_resource_overrides(environment_id, res_def_id, levels,
overrides)
obj_url = self.object_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id
)
obj_keys_url = obj_url + '/keys/delete'
data = [['key_0']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(204, res.status_code)
res = self.client.get(obj_url)
self.assertEqual(200, res.status_code)
actual = res.json
self.assertEqual({'key_1': 'val_1'}, actual)
def test_put_resource_overrides_delete_operation_error(self):
self.app.config["PROPAGATE_EXCEPTIONS"] = True
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
overrides = {'key_0': 'val_0', 'key_1': 'val_1'}
self._add_resource_overrides(environment_id, res_def_id, levels,
overrides)
obj_keys_url = self.object_keys_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id,
'delete'
)
data = [['fake_key']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(409, res.status_code)
data = [['key_0', 'val_0']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(409, res.status_code)

View File

@ -10,12 +10,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
from tuning_box import db
from tuning_box.tests.test_app import BaseTest
class TestResourceValues(BaseTest):
object_url = '/environments/{0}/{1}/resources/{2}/values'
object_keys_url = object_url + '/keys/{3}'
def test_put_resource_values_root(self):
self._fixture()
res = self.client.put('/environments/9/resources/5/values',
@ -129,3 +134,106 @@ class TestResourceValues(BaseTest):
'http://localhost'
'/environments/9/lvl1/val1/lvl2/val2/resources/5/values',
)
def test_put_resource_values_not_found(self):
self.app.config["PROPAGATE_EXCEPTIONS"] = True
self._fixture()
res = self.client.put(
'/environments/9/lvl1/val1/resources/5/values/keys/set',
data={}
)
self.assertEqual(404, res.status_code)
def test_put_resource_values_set_operation_error(self):
self.app.config["PROPAGATE_EXCEPTIONS"] = True
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
values = {'key': 'val'}
self._add_resource_values(environment_id, res_def_id, levels, values)
data = [['a', 'b', 'c', 'value']]
obj_keys_url = self.object_keys_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id,
'set'
)
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(409, res.status_code)
def test_put_resource_values_set(self):
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
values = {'key': 'val'}
self._add_resource_values(environment_id, res_def_id, levels, values)
obj_url = self.object_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id
)
obj_keys_url = obj_url + '/keys/set'
data = [['key', 'key_value'], ['key_x', 'key_x_value']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(204, res.status_code)
res = self.client.get(obj_url)
self.assertEqual(200, res.status_code)
actual = res.json
self.assertEqual({'key': 'key_value', 'key_x': 'key_x_value'},
actual)
def test_put_resource_values_delete(self):
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
values = {'key_0': 'val_0', 'key_1': 'val_1'}
self._add_resource_values(environment_id, res_def_id, levels, values)
obj_url = self.object_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id
)
obj_keys_url = obj_url + '/keys/delete'
data = [['key_0']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(204, res.status_code)
res = self.client.get(obj_url)
self.assertEqual(200, res.status_code)
actual = res.json
self.assertEqual({'key_1': 'val_1'}, actual)
def test_put_resource_values_delete_operation_error(self):
self.app.config["PROPAGATE_EXCEPTIONS"] = True
self._fixture()
environment_id = 9
res_def_id = 5
levels = (('lvl1', 'val1'), ('lvl2', 'val2'))
values = {'key_0': 'val_0', 'key_1': 'val_1'}
self._add_resource_values(environment_id, res_def_id, levels, values)
obj_keys_url = self.object_keys_url.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id,
'delete'
)
data = [['fake_key']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(409, res.status_code)
data = [['key_0', 'val_0']]
res = self.client.put(obj_keys_url, data=data)
self.assertEqual(409, res.status_code)

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import json
from flask import testing
@ -72,6 +73,30 @@ class BaseTest(base.TestCase):
environment.hierarchy_levels = hierarchy_levels
db.db.session.add(environment)
def _add_resource_values(self, environment_id, res_def_id,
levels, values):
res = self.client.put(
'/environments/{0}/{1}/resources/{2}/values'.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id
),
data=values
)
self.assertEqual(res.status_code, 204)
def _add_resource_overrides(self, environment_id, res_def_id,
levels, overrides):
res = self.client.put(
'/environments/{0}/{1}/resources/{2}/overrides'.format(
environment_id,
'/'.join(itertools.chain.from_iterable(levels)),
res_def_id
),
data=overrides
)
self.assertEqual(res.status_code, 204)
def _assert_db_effect(self, model, key, fields, expected):
with self.app.app_context():
obj = model.query.get(key)