Compare commits
8 Commits
3.0.0.0rc2
...
ocata-em
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1caf89686c | ||
|
|
ed3224835a | ||
|
|
8a7e316f73 | ||
|
|
112ac3bbdf | ||
|
|
d1ab697612 | ||
|
|
095ca0ffb2 | ||
|
|
e1131a65d8 | ||
|
|
5e507e56f4 |
@@ -2,3 +2,4 @@
|
|||||||
host=review.openstack.org
|
host=review.openstack.org
|
||||||
port=29418
|
port=29418
|
||||||
project=openstack/watcher.git
|
project=openstack/watcher.git
|
||||||
|
defaultbranch=stable/ocata
|
||||||
|
|||||||
2
tox.ini
2
tox.ini
@@ -6,7 +6,7 @@ skipsdist = True
|
|||||||
[testenv]
|
[testenv]
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
whitelist_externals = find
|
whitelist_externals = find
|
||||||
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
|
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/ocata} {opts} {packages}
|
||||||
setenv =
|
setenv =
|
||||||
VIRTUAL_ENV={envdir}
|
VIRTUAL_ENV={envdir}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
|||||||
@@ -50,6 +50,21 @@ from watcher.decision_engine import rpcapi
|
|||||||
from watcher import objects
|
from watcher import objects
|
||||||
|
|
||||||
|
|
||||||
|
ALLOWED_AUDIT_TRANSITIONS = {
|
||||||
|
objects.audit.State.PENDING:
|
||||||
|
[objects.audit.State.ONGOING, objects.audit.State.CANCELLED],
|
||||||
|
objects.audit.State.ONGOING:
|
||||||
|
[objects.audit.State.FAILED, objects.audit.State.SUCCEEDED,
|
||||||
|
objects.audit.State.CANCELLED],
|
||||||
|
objects.audit.State.FAILED:
|
||||||
|
[objects.audit.State.DELETED],
|
||||||
|
objects.audit.State.SUCCEEDED:
|
||||||
|
[objects.audit.State.DELETED],
|
||||||
|
objects.audit.State.CANCELLED:
|
||||||
|
[objects.audit.State.DELETED]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AuditPostType(wtypes.Base):
|
class AuditPostType(wtypes.Base):
|
||||||
|
|
||||||
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
audit_template_uuid = wtypes.wsattr(types.uuid, mandatory=False)
|
||||||
@@ -561,6 +576,17 @@ class AuditsController(rest.RestController):
|
|||||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||||
raise exception.PatchError(patch=patch, reason=e)
|
raise exception.PatchError(patch=patch, reason=e)
|
||||||
|
|
||||||
|
initial_state = audit_dict['state']
|
||||||
|
new_state = api_utils.get_patch_value(patch, 'state')
|
||||||
|
allowed_states = ALLOWED_AUDIT_TRANSITIONS.get(initial_state, [])
|
||||||
|
if new_state is not None and new_state not in allowed_states:
|
||||||
|
error_message = _("State transition not allowed: "
|
||||||
|
"(%(initial_state)s -> %(new_state)s)")
|
||||||
|
raise exception.PatchError(
|
||||||
|
patch=patch,
|
||||||
|
reason=error_message % dict(
|
||||||
|
initial_state=initial_state, new_state=new_state))
|
||||||
|
|
||||||
# Update only the fields that have changed
|
# Update only the fields that have changed
|
||||||
for field in objects.Audit.fields:
|
for field in objects.Audit.fields:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ class AuditTemplate(base.APIBase):
|
|||||||
|
|
||||||
self.fields.append('goal_id')
|
self.fields.append('goal_id')
|
||||||
self.fields.append('strategy_id')
|
self.fields.append('strategy_id')
|
||||||
|
setattr(self, 'strategy_id', kwargs.get('strategy_id', wtypes.Unset))
|
||||||
|
|
||||||
# goal_uuid & strategy_uuid are not part of
|
# goal_uuid & strategy_uuid are not part of
|
||||||
# objects.AuditTemplate.fields because they're API-only attributes.
|
# objects.AuditTemplate.fields because they're API-only attributes.
|
||||||
|
|||||||
@@ -73,6 +73,12 @@ def apply_jsonpatch(doc, patch):
|
|||||||
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
|
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
|
||||||
|
|
||||||
|
|
||||||
|
def get_patch_value(patch, key):
|
||||||
|
for p in patch:
|
||||||
|
if p['op'] == 'replace' and p['path'] == '/%s' % key:
|
||||||
|
return p['value']
|
||||||
|
|
||||||
|
|
||||||
def as_filters_dict(**filters):
|
def as_filters_dict(**filters):
|
||||||
filters_dict = {}
|
filters_dict = {}
|
||||||
for filter_name, filter_value in filters.items():
|
for filter_name, filter_value in filters.items():
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class AuditHandler(BaseAuditHandler):
|
|||||||
solution = self.do_execute(audit, request_context)
|
solution = self.do_execute(audit, request_context)
|
||||||
self.post_execute(audit, solution, request_context)
|
self.post_execute(audit, solution, request_context)
|
||||||
except exception.ActionPlanIsOngoing as e:
|
except exception.ActionPlanIsOngoing as e:
|
||||||
LOG.exception(e)
|
LOG.warning(e)
|
||||||
if audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
if audit.audit_type == objects.audit.AuditType.ONESHOT.value:
|
||||||
self.update_audit_state(audit, objects.audit.State.CANCELLED)
|
self.update_audit_state(audit, objects.audit.State.CANCELLED)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -215,8 +215,7 @@ class ModelBuilder(object):
|
|||||||
compute_node = self.model.get_node_by_uuid(
|
compute_node = self.model.get_node_by_uuid(
|
||||||
cnode_uuid)
|
cnode_uuid)
|
||||||
# Connect the instance to its compute node
|
# Connect the instance to its compute node
|
||||||
self.model.add_edge(
|
self.model.map_instance(instance, compute_node)
|
||||||
instance, compute_node, label='RUNS_ON')
|
|
||||||
except exception.ComputeNodeNotFound:
|
except exception.ComputeNodeNotFound:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import itertools
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@@ -267,7 +268,7 @@ class TestPatch(api_base.FunctionalTest):
|
|||||||
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||||
mock_utcnow.return_value = test_time
|
mock_utcnow.return_value = test_time
|
||||||
|
|
||||||
new_state = objects.audit.State.SUCCEEDED
|
new_state = objects.audit.State.CANCELLED
|
||||||
response = self.get_json('/audits/%s' % self.audit.uuid)
|
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||||
self.assertNotEqual(new_state, response['state'])
|
self.assertNotEqual(new_state, response['state'])
|
||||||
|
|
||||||
@@ -343,6 +344,128 @@ class TestPatch(api_base.FunctionalTest):
|
|||||||
self.assertTrue(response.json['error_message'])
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
|
||||||
|
ALLOWED_TRANSITIONS = [
|
||||||
|
{"original_state": objects.audit.State.PENDING,
|
||||||
|
"new_state": objects.audit.State.ONGOING},
|
||||||
|
{"original_state": objects.audit.State.PENDING,
|
||||||
|
"new_state": objects.audit.State.CANCELLED},
|
||||||
|
{"original_state": objects.audit.State.ONGOING,
|
||||||
|
"new_state": objects.audit.State.FAILED},
|
||||||
|
{"original_state": objects.audit.State.ONGOING,
|
||||||
|
"new_state": objects.audit.State.SUCCEEDED},
|
||||||
|
{"original_state": objects.audit.State.ONGOING,
|
||||||
|
"new_state": objects.audit.State.CANCELLED},
|
||||||
|
{"original_state": objects.audit.State.FAILED,
|
||||||
|
"new_state": objects.audit.State.DELETED},
|
||||||
|
{"original_state": objects.audit.State.SUCCEEDED,
|
||||||
|
"new_state": objects.audit.State.DELETED},
|
||||||
|
{"original_state": objects.audit.State.CANCELLED,
|
||||||
|
"new_state": objects.audit.State.DELETED},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TestPatchStateTransitionDenied(api_base.FunctionalTest):
|
||||||
|
|
||||||
|
STATES = [
|
||||||
|
ap_state for ap_state in objects.audit.State.__dict__
|
||||||
|
if not ap_state.startswith("_")
|
||||||
|
]
|
||||||
|
|
||||||
|
scenarios = [
|
||||||
|
(
|
||||||
|
"%s -> %s" % (original_state, new_state),
|
||||||
|
{"original_state": original_state,
|
||||||
|
"new_state": new_state},
|
||||||
|
)
|
||||||
|
for original_state, new_state
|
||||||
|
in list(itertools.product(STATES, STATES))
|
||||||
|
if original_state != new_state
|
||||||
|
and {"original_state": original_state,
|
||||||
|
"new_state": new_state} not in ALLOWED_TRANSITIONS
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPatchStateTransitionDenied, self).setUp()
|
||||||
|
obj_utils.create_test_goal(self.context)
|
||||||
|
obj_utils.create_test_strategy(self.context)
|
||||||
|
obj_utils.create_test_audit_template(self.context)
|
||||||
|
self.audit = obj_utils.create_test_audit(self.context,
|
||||||
|
state=self.original_state)
|
||||||
|
p = mock.patch.object(db_api.BaseConnection, 'update_audit')
|
||||||
|
self.mock_audit_update = p.start()
|
||||||
|
self.mock_audit_update.side_effect = self._simulate_rpc_audit_update
|
||||||
|
self.addCleanup(p.stop)
|
||||||
|
|
||||||
|
def _simulate_rpc_audit_update(self, audit):
|
||||||
|
audit.save()
|
||||||
|
return audit
|
||||||
|
|
||||||
|
def test_replace_denied(self):
|
||||||
|
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||||
|
self.assertNotEqual(self.new_state, response['state'])
|
||||||
|
|
||||||
|
response = self.patch_json(
|
||||||
|
'/audits/%s' % self.audit.uuid,
|
||||||
|
[{'path': '/state', 'value': self.new_state,
|
||||||
|
'op': 'replace'}],
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertEqual(400, response.status_code)
|
||||||
|
self.assertTrue(response.json['error_message'])
|
||||||
|
|
||||||
|
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||||
|
self.assertEqual(self.original_state, response['state'])
|
||||||
|
|
||||||
|
|
||||||
|
class TestPatchStateTransitionOk(api_base.FunctionalTest):
|
||||||
|
|
||||||
|
scenarios = [
|
||||||
|
(
|
||||||
|
"%s -> %s" % (transition["original_state"],
|
||||||
|
transition["new_state"]),
|
||||||
|
transition
|
||||||
|
)
|
||||||
|
for transition in ALLOWED_TRANSITIONS
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestPatchStateTransitionOk, self).setUp()
|
||||||
|
obj_utils.create_test_goal(self.context)
|
||||||
|
obj_utils.create_test_strategy(self.context)
|
||||||
|
obj_utils.create_test_audit_template(self.context)
|
||||||
|
self.audit = obj_utils.create_test_audit(self.context,
|
||||||
|
state=self.original_state)
|
||||||
|
p = mock.patch.object(db_api.BaseConnection, 'update_audit')
|
||||||
|
self.mock_audit_update = p.start()
|
||||||
|
self.mock_audit_update.side_effect = self._simulate_rpc_audit_update
|
||||||
|
self.addCleanup(p.stop)
|
||||||
|
|
||||||
|
def _simulate_rpc_audit_update(self, audit):
|
||||||
|
audit.save()
|
||||||
|
return audit
|
||||||
|
|
||||||
|
@mock.patch('oslo_utils.timeutils.utcnow')
|
||||||
|
def test_replace_ok(self, mock_utcnow):
|
||||||
|
test_time = datetime.datetime(2000, 1, 1, 0, 0)
|
||||||
|
mock_utcnow.return_value = test_time
|
||||||
|
|
||||||
|
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||||
|
self.assertNotEqual(self.new_state, response['state'])
|
||||||
|
|
||||||
|
response = self.patch_json(
|
||||||
|
'/audits/%s' % self.audit.uuid,
|
||||||
|
[{'path': '/state', 'value': self.new_state,
|
||||||
|
'op': 'replace'}])
|
||||||
|
self.assertEqual('application/json', response.content_type)
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
|
||||||
|
response = self.get_json('/audits/%s' % self.audit.uuid)
|
||||||
|
self.assertEqual(self.new_state, response['state'])
|
||||||
|
return_updated_at = timeutils.parse_isotime(
|
||||||
|
response['updated_at']).replace(tzinfo=None)
|
||||||
|
self.assertEqual(test_time, return_updated_at)
|
||||||
|
|
||||||
|
|
||||||
class TestPost(api_base.FunctionalTest):
|
class TestPost(api_base.FunctionalTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user