Compare commits
13 Commits
10.0.0.0rc
...
ocata-eol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d5e816eaa | ||
|
|
9c3736cb1b | ||
|
|
520cdb3c51 | ||
|
|
4d8d1cd356 | ||
|
|
6d4fb3c3eb | ||
|
|
1caf89686c | ||
|
|
ed3224835a | ||
|
|
8a7e316f73 | ||
|
|
112ac3bbdf | ||
|
|
d1ab697612 | ||
|
|
095ca0ffb2 | ||
|
|
e1131a65d8 | ||
|
|
5e507e56f4 |
@@ -1,4 +1,5 @@
|
|||||||
[gerrit]
|
[gerrit]
|
||||||
host=review.openstack.org
|
host=review.opendev.org
|
||||||
port=29418
|
port=29418
|
||||||
project=openstack/watcher.git
|
project=openstack/watcher.git
|
||||||
|
defaultbranch=stable/ocata
|
||||||
|
|||||||
9
.zuul.yaml
Normal file
9
.zuul.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
- project:
|
||||||
|
templates:
|
||||||
|
- openstack-python-jobs
|
||||||
|
- openstack-python35-jobs
|
||||||
|
- publish-openstack-sphinx-docs
|
||||||
|
- check-requirements
|
||||||
|
- release-notes-jobs
|
||||||
|
gate:
|
||||||
|
queue: watcher
|
||||||
@@ -35,7 +35,7 @@ VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP
|
|||||||
NOVA_INSTANCES_PATH=/opt/stack/data/instances
|
NOVA_INSTANCES_PATH=/opt/stack/data/instances
|
||||||
|
|
||||||
# Enable the Ceilometer plugin for the compute agent
|
# Enable the Ceilometer plugin for the compute agent
|
||||||
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
|
||||||
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
|
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
|
||||||
|
|
||||||
LOGFILE=$DEST/logs/stack.sh.log
|
LOGFILE=$DEST/logs/stack.sh.log
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ ENABLED_SERVICES+=,q-svc,q-dhcp,q-meta,q-agt,q-l3,neutron
|
|||||||
enable_service n-cauth
|
enable_service n-cauth
|
||||||
|
|
||||||
# Enable the Watcher Dashboard plugin
|
# Enable the Watcher Dashboard plugin
|
||||||
# enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
|
# enable_plugin watcher-dashboard https://git.openstack.org/openstack/watcher-dashboard
|
||||||
|
|
||||||
# Enable the Watcher plugin
|
# Enable the Watcher plugin
|
||||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
enable_plugin watcher https://git.openstack.org/openstack/watcher
|
||||||
|
|
||||||
# Enable the Ceilometer plugin
|
# Enable the Ceilometer plugin
|
||||||
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
|
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
|
||||||
|
|
||||||
# This is the controller node, so disable the ceilometer compute agent
|
# This is the controller node, so disable the ceilometer compute agent
|
||||||
disable_service ceilometer-acompute
|
disable_service ceilometer-acompute
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ You can easily generate and update a sample configuration file
|
|||||||
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
|
||||||
these following commands::
|
these following commands::
|
||||||
|
|
||||||
$ git clone git://git.openstack.org/openstack/watcher
|
$ git clone https://git.openstack.org/openstack/watcher
|
||||||
$ cd watcher/
|
$ cd watcher/
|
||||||
$ tox -e genconfig
|
$ tox -e genconfig
|
||||||
$ vi etc/watcher/watcher.conf.sample
|
$ vi etc/watcher/watcher.conf.sample
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ model. To enable the Watcher plugin with DevStack, add the following to the
|
|||||||
`[[local|localrc]]` section of your controller's `local.conf` to enable the
|
`[[local|localrc]]` section of your controller's `local.conf` to enable the
|
||||||
Watcher plugin::
|
Watcher plugin::
|
||||||
|
|
||||||
enable_plugin watcher git://git.openstack.org/openstack/watcher
|
enable_plugin watcher https://git.openstack.org/openstack/watcher
|
||||||
|
|
||||||
For more detailed instructions, see `Detailed DevStack Instructions`_. Check
|
For more detailed instructions, see `Detailed DevStack Instructions`_. Check
|
||||||
out the `DevStack documentation`_ for more information regarding DevStack.
|
out the `DevStack documentation`_ for more information regarding DevStack.
|
||||||
|
|||||||
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