Compare commits

..

47 Commits

Author SHA1 Message Date
Zuul
595cd1d435 Merge "Use jsonschema to validate efficacy indicators" 2018-07-26 12:06:57 +00:00
Zuul
df8419949b Merge "Rescheduling continuous audits from FAILED nodes" 2018-07-26 11:49:29 +00:00
Zuul
05a8f0ba3e Merge "Add HA support" 2018-07-26 11:49:28 +00:00
Alexander Chadin
20ffb5945f Rescheduling continuous audits from FAILED nodes
This patch set adds background job that reschedules CONTINUOUS
audits from FAILED to ACTIVE decision engine nodes using round-robin
algorithm. It also contains fix for main[1] HA PS about filtering audits.

[1]: https://review.openstack.org/#/c/578102/

Partially-Implements: blueprint support-watcher-ha-active-active-mode
Change-Id: Ib248a6cd3adbd3927c47db6bb819300361492411
2018-07-26 09:57:35 +00:00
Alexander Chadin
e426a015ee Add HA support
This patch set adds hostname field to Audit and Action Plan
objects to track services which execute these objects.

Change-Id: I786e419952925c380c969b12cc60f9a1004af96b
Partially-Implements: blueprint support-watcher-ha-active-active-mode
2018-07-26 12:54:11 +03:00
Zuul
f21f3dbb8b Merge "Add actionplan list detail api ref" 2018-07-26 08:57:21 +00:00
Zuul
996fc85081 Merge "Sync CDM among Decision Engines by using notification pool" 2018-07-26 07:44:52 +00:00
Zuul
758b1fab59 Merge "Add noisy neighbor description" 2018-07-26 01:53:39 +00:00
Zuul
fad85443b6 Merge "Fix service task interval" 2018-07-26 01:48:42 +00:00
Zuul
66723e97be Merge "Check job before removing it" 2018-07-26 01:48:42 +00:00
Zuul
ac6a471d2f Merge "remove LOG definitions that have not been used" 2018-07-26 01:38:56 +00:00
Zuul
550c306063 Merge "Update host_maintenance doc" 2018-07-25 14:35:30 +00:00
licanwei
b54647e6c0 Fix service task interval
Change-Id: Id6912c8d9c34b0aa3c0b5455586149747b07d491
2018-07-25 00:13:05 -07:00
licanwei
6a31f2c343 Add noisy neighbor description
Change-Id: Ibe0feb389e75c6ef52294413011e4fe250b42d0b
2018-07-25 02:45:51 +00:00
chenke
ea5252dd29 remove LOG definitions that have not been used
Change-Id: I6d1b942390a592e00755c5974995d0810085ace2
2018-07-25 10:31:03 +08:00
Zuul
1272ca579e Merge "trivial: fix strategy name" 2018-07-24 11:03:19 +00:00
licanwei
e76b27c0fe trivial: fix strategy name
Change-Id: Ic3df4233a544670ad4f736a0046725ea2797e337
2018-07-23 20:32:04 -07:00
licanwei
8377603f3c update Ubuntu version from 14.04 to 16.04
Change-Id: Ia05e7bd45f0bf5d300d238396b52b313949e3bef
2018-07-23 18:35:42 -07:00
licanwei
9630e2c4e2 Update host_maintenance doc
add change_nova_service_state action help

Change-Id: I68c41a4342fc1a215d697ef4bb0d333794e6a077
2018-07-23 01:43:46 -07:00
licanwei
4022714f5d Check job before removing it
Change-Id: Ibbd4da25fac6016a0d76c8f810ac567f6fd075f1
Closes-Bug: #1782731
2018-07-20 19:20:26 -07:00
licanwei
6fcdb5e74b update monascaclient version
TimingSession is removed and replaced by keystone session (see [1]).
monasca client fixed it in [2].
Watcher need to update monascaclient version
[1] https://review.openstack.org/#/c/579139/
[2] https://review.openstack.org/#/c/582882

Closes-Bug: #1782276
Change-Id: I567b7727cbfe645c4d16e06a8cbb8eb8e33eb872
Co-Authored-By: zhurong <aaronzhu1121@gmail.com>
2018-07-20 14:34:10 +03:00
Zuul
31a1a2e7d7 Merge "Remove help message about ZeroMQ driver" 2018-07-20 08:48:31 +00:00
Zuul
3c817fe0a0 Merge "Switch to stestr" 2018-07-20 08:48:30 +00:00
Zuul
f95b755c09 Merge "Switch to oslo_messaging.ConfFixture.transport_url" 2018-07-20 08:23:28 +00:00
Alexander Chadin
d62c4967bd Sync CDM among Decision Engines by using notification pool
This commit allows to consume notifications via notifications pools[1].
Listeners in notification pools recieves a copy of notification. It
will let Watcher to sync Data Models of Decision Engines.

[1]: https://docs.openstack.org/oslo.messaging/ocata/notification_listener.html

Change-Id: Ie37528263181924f84510500fc1277b0237c1df8
Partially-Implements: blueprint support-watcher-ha-active-active-mode
2018-07-20 10:54:19 +03:00
licanwei
486d08bc5e Add actionplan list detail api ref
Change-Id: If8bdea3b29049870b222b69ede10669183a3d952
2018-07-19 23:48:06 -07:00
Zuul
5bedb43d69 Merge "fix the rule name" 2018-07-20 03:20:17 +00:00
zhang.lei
b3e84fa2dc Remove help message about ZeroMQ driver
ZeroMQ driver is deprecated, as per the Dublin 2018 PTG decision:
http://lists.openstack.org/pipermail/openstack-dev/2018-March/128055.html

Change-Id: Ia3d5fd1d2ccd57bf3b9f97172c09994bf5d44022
2018-07-12 01:48:00 +00:00
Vu Cong Tuan
dc3531fa10 Switch to stestr
According to Openstack summit session [1],
stestr is maintained project to which all Openstack projects should migrate.
Let's switch to stestr as other projects have already moved to it.

[1] https://etherpad.openstack.org/p/YVR-python-pti

Change-Id: I8f70f7d8a3d18301559c0eb47e6a64c8b5100d39
2018-07-10 15:38:50 +07:00
Zuul
fb7be7984c Merge "Triggers the api-ref-jobs to publish wather api reference" 2018-07-05 08:13:48 +00:00
Andreas Jaeger
c9e8886631 Remove non-voting jobs from gate queue
Non-voting jobs should not be in gate queue at all, remove them.
They are wasting needlessly are resources.

To make clearer which jobs are non-voting, move voting:false
from base job to individual jobs.

Make watcherclient-tempest-functional as voting job and explicitly set
it non-voting in project stanza so that it can be easier to make it
voting in other repos.

Change-Id: Id94e49347006bca850f72a0400794da8c2a67144
2018-07-04 11:50:02 +02:00
Clark Boylan
7373588673 Remove undefined job
The legacy-rally-dsvm-watcher-rally job does not exist but it is listed
in the .zuul.yaml config. This is a zuul configuration error. Remove
this job which does not exist to fix zuul.

Change-Id: I1bbfd373ad12b98696ab2ddb78e56e6503cc4c4d
2018-07-04 09:28:27 +00:00
chenxing
c783a29047 Triggers the api-ref-jobs to publish wather api reference
Change-Id: I842a4e4bbe0bb713c8206df32952b4297b21d15f
2018-07-03 16:56:43 +08:00
Hidekazu Nakamura
393d68f658 Fix unit test error
This patch fixes watcher.tests.common.test_clients.TestClients.
test_clients_ironic unit test error due to python-ironicclient 2.4.0.

Change-Id: I0aaa2047cc8fcf09dee8bc7168e35f7a58c10125
2018-07-03 13:23:51 +09:00
Yumeng_Bao
0540cd22d6 Use jsonschema to validate efficacy indicators
This patch replaces voluptuous with JSON-schema to validate
efficacy indicator since in watcher we want to remove voluptuous
and use JSONSchema as our only JSON validation tool to keep consistency.

Change-Id: Iaa77566f1cdfdac03ce8e7d5a75406274c7d5298
Implements: blueprint replace-voplutuous-with-jsonschema
2018-07-02 05:43:22 +00:00
lvxianguo
281a5e6998 fix the rule name
Change-Id: Ica3ab522a816d7d31e8a72d26953e961ec4d9919
2018-06-22 16:26:16 +08:00
Zuul
0c4231e422 Merge "Amend the spelling error of a word" 2018-06-20 08:37:04 +00:00
Zuul
0d5df127f5 Merge "fix tox python3 overrides" 2018-06-20 00:39:25 +00:00
Zuul
6ed8fc0c15 Merge "Add API Reference for Watcher" 2018-06-19 00:37:59 +00:00
Zuul
855baacb5c Merge "replace windows line endings with unix line endings" 2018-06-18 10:59:22 +00:00
Zuul
02b72f4a38 Merge "Correcting url in action_plan policy" 2018-06-18 10:12:27 +00:00
rajat29
5f93e96b7a Correcting url in action_plan policy
url in action_plan policy file is:
'path': '/v1/action_plans/{action_plan_uuid}/action'
whereas it shouls be :
'path': '/v1/action_plans/{action_plan_uuid}/start'

Related-Bug: #1756274

Change-Id: Ic15fed9af739b59efb2777b70514697747b2af7f
2018-06-18 04:46:56 +00:00
Doug Hellmann
88ff5e1b1f fix tox python3 overrides
We want to default to running all tox environments under python 3, so
set the basepython value in each environment.

We do not want to specify a minor version number, because we do not
want to have to update the file every time we upgrade python.

We do not want to set the override once in testenv, because that
breaks the more specific versions used in default environments like
py35 and py36.

Change-Id: Ia481330b9a889b113b585fca0d4ddb86df9f74d3
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-06-13 14:34:39 -04:00
Doug Hellmann
ae0918488d replace windows line endings with unix line endings
The python 3 version of the linter does not allow Windows-style line
endings (\r\n) so replace them with UNIX-style endings (\n).

Change-Id: Ifb97491323d3df92bb1520e373552aeb5e1919a4
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-06-13 14:34:39 -04:00
Steve Kowalik
b683d2c6bd Switch to oslo_messaging.ConfFixture.transport_url
oslo_messaging's rpc_backend setting, which is set by
ConfFixture.transport_driver has been deprecated since Newton. To allow
oslo_messaging to remove it, switch to setting transport_url instead.

Change-Id: Ie37b20d1be6c177f2f1c26cc473b632d7d934c05
Partial-Bug: #1712399
2018-06-12 12:56:54 +10:00
Alexander Chadin
533c0a4114 Add API Reference for Watcher
This patch set adds API Reference along with some
fixes to documentation. It partially fixes bug #1757423.

Change-Id: I107b4fd5daf40aad63fc13864debbbbc82a9826c
2018-06-07 12:57:11 +03:00
chengebj5238
a8079ba0f1 Amend the spelling error of a word
Change-Id: I2b67774cf923d14a5735fd174061c87adaaaf63f
2018-06-07 09:50:21 +08:00
109 changed files with 4191 additions and 716 deletions

2
.gitignore vendored
View File

@@ -23,9 +23,7 @@ pip-log.txt
# Unit test / coverage reports
.coverage*
.tox
nosetests.xml
.stestr/
.testrepository
.venv
.idea

View File

@@ -1,4 +1,4 @@
[DEFAULT]
test_path=${OS_TEST_PATH:-./watcher/tests}
test_path=./watcher/tests
top_dir=./

View File

@@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \
${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./watcher/tests} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@@ -1,53 +1,51 @@
- project:
check:
jobs:
- watcher-tempest-functional
- watcher-tempest-functional:
voting: false
- watcher-tempest-dummy_optim
- watcher-tempest-actuator
- watcher-tempest-basic_optim
- watcher-tempest-workload_balancing
- watcherclient-tempest-functional
- legacy-rally-dsvm-watcher-rally
- watcherclient-tempest-functional:
voting: false
- openstack-tox-lower-constraints
gate:
jobs:
- watcher-tempest-functional
- watcher-tempest-dummy_optim
- watcher-tempest-actuator
- watcher-tempest-basic_optim
- watcher-tempest-workload_balancing
- watcherclient-tempest-functional
- legacy-rally-dsvm-watcher-rally
# - watcher-tempest-functional
- openstack-tox-lower-constraints
- job:
name: watcher-tempest-dummy_optim
parent: watcher-tempest-multinode
voting: false
vars:
tempest_test_regex: 'watcher_tempest_plugin.tests.scenario.test_execute_dummy_optim'
- job:
name: watcher-tempest-actuator
parent: watcher-tempest-multinode
voting: false
vars:
tempest_test_regex: 'watcher_tempest_plugin.tests.scenario.test_execute_actuator'
- job:
name: watcher-tempest-basic_optim
parent: watcher-tempest-multinode
voting: false
vars:
tempest_test_regex: 'watcher_tempest_plugin.tests.scenario.test_execute_basic_optim'
- job:
name: watcher-tempest-workload_balancing
parent: watcher-tempest-multinode
voting: false
vars:
tempest_test_regex: 'watcher_tempest_plugin.tests.scenario.test_execute_workload_balancing'
- job:
name: watcher-tempest-multinode
parent: watcher-tempest-functional
voting: false
nodeset: openstack-two-node
pre-run: playbooks/pre.yaml
run: playbooks/orchestrate-tempest.yaml
@@ -62,7 +60,7 @@
live_migration_uri: 'qemu+ssh://root@%s/system'
devstack_services:
watcher-api: false
watcher-decision-engine: false
watcher-decision-engine: true
watcher-applier: false
# We need to add TLS support for watcher plugin
tls-proxy: false
@@ -130,7 +128,6 @@
# This job is used in python-watcherclient repo
name: watcherclient-tempest-functional
parent: watcher-tempest-functional
voting: false
timeout: 4200
vars:
tempest_concurrency: 1

90
api-ref/source/conf.py Normal file
View File

@@ -0,0 +1,90 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# nova documentation build configuration file, created by
# sphinx-quickstart on Sat May 1 15:17:47 2010.
#
# This file is execfile()d with the current directory set to
# its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
from watcher import version as watcher_version
extensions = [
'openstackdocstheme',
'os_api_ref',
]
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Infrastructure Optimization API Reference'
copyright = u'2010-present, OpenStack Foundation'
# openstackdocstheme options
repository_name = 'openstack/watcher'
bug_project = 'watcher'
bug_tag = ''
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The full version, including alpha/beta/rc tags.
release = watcher_version.version_info.release_string()
# The short X.Y version.
version = watcher_version.version_string
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'openstackdocs'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
"sidebar_mode": "toc",
}
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# -- Options for LaTeX output -------------------------------------------------
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index', 'Watcher.tex', u'Infrastructure Optimization API Reference',
u'OpenStack Foundation', 'manual'),
]

16
api-ref/source/index.rst Normal file
View File

@@ -0,0 +1,16 @@
:tocdepth: 2
===========
Watcher API
===========
.. rest_expand_all::
.. include:: watcher-api-v1-audittemplates.inc
.. include:: watcher-api-v1-audits.inc
.. include:: watcher-api-v1-actionplans.inc
.. include:: watcher-api-v1-actions.inc
.. include:: watcher-api-v1-goals.inc
.. include:: watcher-api-v1-strategies.inc
.. include:: watcher-api-v1-services.inc
.. include:: watcher-api-v1-scoring_engines.inc

View File

@@ -0,0 +1,433 @@
# Path
action_ident:
description: |
The UUID of the Action.
in: path
required: true
type: string
actionplan_ident:
description: |
The UUID of the Action Plan.
in: path
required: true
type: string
audit_ident:
description: |
The UUID or name of the Audit.
in: path
required: true
type: string
audittemplate_ident:
description: |
The UUID or name of the Audit Template.
in: path
required: true
type: string
goal_ident:
description: |
The UUID or name of the Goal.
in: path
required: true
type: string
scoring_engine_ident:
description: |
The UUID or name of the Scoring Engine.
in: path
required: true
type: string
service_ident:
description: |
The ID or name of the Service.
in: path
required: true
type: string
strategy_ident:
description: |
The UUID or name of the Strategy.
in: path
required: true
type: string
# Query body
limit:
description: |
Requests a page size of items. Returns a number of items up to a ``limit``
value. Use the limit parameter to make an initial limited request and use
the ID of the last-seen item from the response as the ``marker`` parameter
value in a subsequent limited request.
in: query
required: false
type: integer
marker:
description: |
The ID of the last-seen item. Use the ``limit`` parameter to make an
initial limited request and use the ID of the last-seen item from the
response as the ``marker`` parameter value in a subsequent limited request.
in: query
required: false
type: string
r_action_plan:
description: |
UUID of the action plan used for filtering.
in: query
required: false
type: string
r_audit:
description: |
Optional UUID of an audit, to get only actions for that audit.
in: query
required: false
type: string
r_goal:
description: |
The UUID or name of the Goal.
in: query
required: false
type: string
r_strategy:
description: |
The UUID or name of the Strategy.
in: query
required: false
type: string
sort_dir:
description: |
Sorts the response by the requested sort direction.
A valid value is ``asc`` (ascending) or ``desc`` (descending).
Default is ``asc``.
in: query
required: false
type: string
sort_key:
description: |
Sorts the response by the this attribute value. Default is ``id``.
in: query
required: false
type: string
# variables in the API response body
# Action
action_action_plan_uuid:
description: |
The action plan this action belongs to.
in: body
required: true
type: string
action_description:
description: |
Action description.
in: body
required: true
type: string
action_input_parameters:
description: |
Input parameters which are used by appropriate action type. For example,
``migration`` action takes into account such parameters as
``migration_type``, ``destination_node``, ``resource_id`` and
``source_node``. To see a list of supported action types and their input
parameters visit `Action plugins page <https://docs.openstack.org/watcher/latest/contributor/plugin/plugins.html#actions>`_.
in: body
required: true
type: JSON
action_parents:
description: |
UUIDs of parent actions.
in: body
required: true
type: array
action_state:
description: |
State of Action.
in: body
required: true
type: string
action_type:
description: |
Action type based on specific API action. Actions in Watcher are
pluggable, to see a list of supported action types visit
`Action plugins page <https://docs.openstack.org/watcher/latest/contributor/plugin/plugins.html#actions>`_.
in: body
required: true
type: string
# Action Plan
actionplan_audit_uuid:
description: |
The UUID of the audit this acton plan belongs to.
in: body
required: false
type: string
actionplan_efficacy_indicators:
description: |
The list of efficacy indicators associated to this action plan.
in: body
required: false
type: array
actionplan_global_efficacy:
description: |
The global efficacy of this action plan.
in: body
required: false
type: array
actionplan_state:
description: |
State of this action plan. To get more information about states and
action plan's lifecycle, visit `Action Plan State Machine page <https://docs.openstack.org/watcher/latest/architecture.html#action-plan-state-machine>`_.
in: body
required: false
type: string
# Audit
audit_autotrigger:
description: |
Autoexecute action plan once audit is succeeded.
in: body
required: false
type: boolean
audit_goal:
description: |
The UUID or name of the Goal.
in: body
required: false
type: string
audit_interval:
description: |
Time interval between audit's execution.
Can be set either in seconds or cron syntax.
Should be defined only for CONTINUOUS audits.
in: body
required: false
type: string
audit_name:
description: |
Name of this audit.
in: body
required: false
type: string
audit_next_run_time:
description: |
The next time audit launch. Defined only for CONTINUOUS audits.
in: body
required: false
type: string
audit_parameters:
description: |
The strategy parameters for this audit.
in: body
required: false
type: JSON
audit_state:
description: |
State of this audit. To get more information about states and
audit's lifecycle, visit `Audit State Machine page <https://docs.openstack.org/watcher/latest/architecture.html#audit-state-machine>`_.
in: body
required: true
type: string
audit_strategy:
description: |
The UUID or name of the Strategy.
in: body
required: false
type: string
audit_type:
description: |
Type of this audit. Can be either ONESHOT or CONTINUOUS.
in: body
required: true
type: string
# Audit Template
audittemplate_description:
description: |
Short description of the Audit Template.
in: body
required: false
type: string
audittemplate_goal:
description: |
The UUID or name of the Goal.
in: body
required: true
type: string
audittemplate_name:
description: |
The name of the Audit template.
in: body
required: true
type: string
audittemplate_scope:
description: |
Audit Scope.
in: body
required: false
type: JSON
audittemplate_strategy:
description: |
The UUID or name of the Strategy.
in: body
required: false
type: string
created_at:
description: |
The date and time when the resource was created. The date and time
stamp format is `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_
in: body
required: true
type: string
deleted_at:
description: |
The date and time when the resource was deleted. The date and time
stamp format is `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_
in: body
required: true
type: string
# Goal
goal_display_name:
description: |
Localized name of the goal.
in: body
required: true
type: string
goal_efficacy_specification:
description: |
Efficacy specifications as result of stategy's execution.
in: body
required: true
type: array
goal_name:
description: |
Name of the goal.
in: body
required: true
type: string
goal_uuid:
description: |
Unique UUID for this goal.
in: body
required: true
type: string
links:
description: |
A list of relative links. Includes the self and bookmark links.
in: body
required: true
type: array
# Scoring Engine
scoring_engine_description:
description: |
A human readable description of the Scoring Engine.
in: body
required: true
type: string
scoring_engine_metainfo:
description: |
A metadata associated with the scoring engine
in: body
required: true
type: string
scoring_engine_name:
description: |
The name of the scoring engine.
in: body
required: true
type: string
# Service
service_host:
description: |
Name of host where service is placed on.
in: body
required: true
type: string
service_id:
description: |
ID of service.
in: body
required: true
type: integer
service_last_seen_up:
description: |
Time when Watcher service sent latest heartbeat.
in: body
required: true
type: string
service_name:
description: |
Name of service like ``watcher-applier``.
in: body
required: true
type: string
service_status:
description: |
State of service. It can be either in ACTIVE or FAILED state.
in: body
required: true
type: string
# Strategy
strategy_check_comment:
description: |
Requirement comment.
in: body
required: true
type: string
strategy_check_mandatory:
description: |
Whether this requirement mandatory or not.
in: body
required: true
type: boolean
strategy_check_state:
description: |
State of requirement for Strategy.
in: body
required: true
type: string or JSON
strategy_check_type:
description: |
Type of requirement for Strategy.
in: body
required: true
type: string
strategy_display_name:
description: |
Localized name of the strategy.
in: body
required: true
type: string
strategy_name:
description: |
Name of the strategy.
in: body
required: true
type: string
strategy_parameters_spec:
description: |
Parameters specifications for this strategy.
in: body
required: true
type: JSON
strategy_uuid:
description: |
Unique UUID for this strategy.
in: body
required: true
type: string
updated_at:
description: |
The date and time when the resource was updated. The date and time
stamp format is `ISO 8601 <https://en.wikipedia.org/wiki/ISO_8601>`_
in: body
required: true
type: string
uuid:
description: |
The UUID for the resource.
in: body
required: true
type: string

View File

@@ -0,0 +1,7 @@
[
{
"op": "replace",
"value": "CANCELLING",
"path": "/state"
}
]

View File

@@ -0,0 +1,7 @@
[
{
"op": "replace",
"value": "CANCELLED",
"path": "/state"
}
]

View File

@@ -0,0 +1,26 @@
{
"action_plans": [
{
"state": "ONGOING",
"efficacy_indicators": [],
"strategy_uuid": "7dae0eea-9df7-42b8-bb3e-313958ff2242",
"global_efficacy": [],
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
},
{
"rel": "bookmark",
"href": "http://controller:9322/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
}
],
"updated_at": "2018-04-10T11:59:52.640067+00:00",
"strategy_name": "dummy_with_resize",
"deleted_at": null,
"uuid": "4cbc4ede-0d25-481b-b86e-998dbbd4f8bf",
"audit_uuid": "7d100b05-0a86-491f-98a7-f93da19b272a",
"created_at": "2018-04-10T11:59:52.640067+00:00"
}
]
}

View File

@@ -0,0 +1,24 @@
{
"action_plans": [
{
"state": "ONGOING",
"efficacy_indicators": [],
"strategy_uuid": "7dae0eea-9df7-42b8-bb3e-313958ff2242",
"global_efficacy": [],
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
},
{
"rel": "bookmark",
"href": "http://controller:9322/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
}
],
"updated_at": "2018-04-10T11:59:52.640067+00:00",
"strategy_name": "dummy_with_resize",
"uuid": "4cbc4ede-0d25-481b-b86e-998dbbd4f8bf",
"audit_uuid": "7d100b05-0a86-491f-98a7-f93da19b272a"
}
]
}

View File

@@ -0,0 +1,20 @@
{
"state": "ONGOING",
"efficacy_indicators": [],
"strategy_uuid": "7dae0eea-9df7-42b8-bb3e-313958ff2242",
"global_efficacy": [],
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
},
{
"rel": "bookmark",
"href": "http://controller:9322/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
}
],
"updated_at": "2018-04-10T11:59:52.640067+00:00",
"strategy_name": "dummy_with_resize",
"uuid": "4cbc4ede-0d25-481b-b86e-998dbbd4f8bf",
"audit_uuid": "7d100b05-0a86-491f-98a7-f93da19b272a"
}

View File

@@ -0,0 +1,22 @@
{
"state": "PENDING",
"efficacy_indicators": [],
"strategy_uuid": "7dae0eea-9df7-42b8-bb3e-313958ff2242",
"global_efficacy": [],
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
},
{
"rel": "bookmark",
"href": "http://controller:9322/action_plans/4cbc4ede-0d25-481b-b86e-998dbbd4f8bf"
}
],
"updated_at": "2018-04-10T11:59:41.602430+00:00",
"strategy_name": "dummy_with_resize",
"uuid": "4cbc4ede-0d25-481b-b86e-998dbbd4f8bf",
"audit_uuid": "7d100b05-0a86-491f-98a7-f93da19b272a",
"created_at": "2018-04-10T11:59:12.592729+00:00",
"deleted_at": null
}

View File

@@ -0,0 +1,30 @@
{
"actions": [
{
"state": "PENDING",
"description": "Wait for a given interval in seconds.",
"parents": [
"8119d16e-b419-4729-b015-fc04c4e45783"
],
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/actions/7182a988-e6c4-4152-a0d6-067119475c83"
},
{
"rel": "bookmark",
"href": "http://controller:9322/actions/7182a988-e6c4-4152-a0d6-067119475c83"
}
],
"action_plan_uuid": "c6bba9ed-a7eb-4370-9993-d873e5e22cba",
"uuid": "7182a988-e6c4-4152-a0d6-067119475c83",
"deleted_at": null,
"updated_at": null,
"input_parameters": {
"duration": 3.2
},
"action_type": "sleep",
"created_at": "2018-03-26T11:56:08.235226+00:00"
}
]
}

View File

@@ -0,0 +1,23 @@
{
"actions": [
{
"state": "PENDING",
"parents": [
"8119d16e-b419-4729-b015-fc04c4e45783"
],
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/actions/7182a988-e6c4-4152-a0d6-067119475c83"
},
{
"rel": "bookmark",
"href": "http://controller:9322/actions/7182a988-e6c4-4152-a0d6-067119475c83"
}
],
"action_plan_uuid": "c6bba9ed-a7eb-4370-9993-d873e5e22cba",
"uuid": "7182a988-e6c4-4152-a0d6-067119475c83",
"action_type": "sleep"
}
]
}

View File

@@ -0,0 +1,26 @@
{
"state": "SUCCEEDED",
"description": "Logging a NOP message",
"parents": [
"b4529294-1de6-4302-b57a-9b5d5dc363c6"
],
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/actions/54acc7a0-91b0-46ea-a5f7-4ae2b9df0b0a"
},
{
"rel": "bookmark",
"href": "http://controller:9322/actions/54acc7a0-91b0-46ea-a5f7-4ae2b9df0b0a"
}
],
"action_plan_uuid": "4cbc4ede-0d25-481b-b86e-998dbbd4f8bf",
"uuid": "54acc7a0-91b0-46ea-a5f7-4ae2b9df0b0a",
"deleted_at": null,
"updated_at": "2018-04-10T11:59:44.026973+00:00",
"input_parameters": {
"message": "Welcome"
},
"action_type": "nop",
"created_at": "2018-04-10T11:59:12.725147+00:00"
}

View File

@@ -0,0 +1,7 @@
[
{
"op": "replace",
"value": "CANCELLED",
"path": "/state"
}
]

View File

@@ -0,0 +1,51 @@
{
"interval": "*/2 * * * *",
"strategy_uuid": "6b3b3902-8508-4cb0-bb85-67f32866b086",
"goal_uuid": "e1a5a45b-f251-47cf-9c5f-fa1e66e1286a",
"name": "audit1",
"parameters": {
"host_choice": "retry",
"instance_metrics": {
"cpu_util": "compute.node.cpu.percent",
"memory.resident": "hardware.memory.used"
},
"granularity": 300,
"weights": {
"cpu_util_weight": 1.0,
"memory.resident_weight": 1.0
},
"retry_count": 1,
"metrics": [
"cpu_util"
],
"periods": {
"instance": 720,
"node": 600
},
"thresholds": {
"cpu_util": 0.2,
"memory.resident": 0.2
}
},
"auto_trigger": false,
"uuid": "65a5da84-5819-4aea-8278-a28d2b489028",
"goal_name": "workload_balancing",
"scope": [],
"created_at": "2018-04-06T07:27:27.820460+00:00",
"deleted_at": null,
"state": "CANCELLED",
"audit_type": "CONTINUOUS",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audits/65a5da84-5819-4aea-8278-a28d2b489028"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audits/65a5da84-5819-4aea-8278-a28d2b489028"
}
],
"strategy_name": "workload_stabilization",
"next_run_time": "2018-04-06T11:56:00",
"updated_at": "2018-04-06T11:54:01.266447+00:00"
}

View File

@@ -0,0 +1,12 @@
{
"auto_trigger": false,
"audit_template_uuid": "76fddfee-a9c4-40b0-8da0-c19ad6904f09",
"name": "test_audit",
"parameters": {
"metrics": [
"cpu_util"
]
},
"audit_type": "CONTINUOUS",
"interval": "*/2 * * * *"
}

View File

@@ -0,0 +1,5 @@
{
"audit_type": "ONESHOT",
"auto_trigger": false,
"audit_template_uuid": "5e70a156-ced7-4012-b1c6-88fcb02ee0c1"
}

View File

@@ -0,0 +1,51 @@
{
"interval": "*/2 * * * *",
"strategy_uuid": "6b3b3902-8508-4cb0-bb85-67f32866b086",
"goal_uuid": "e1a5a45b-f251-47cf-9c5f-fa1e66e1286a",
"name": "test_audit",
"parameters": {
"host_choice": "retry",
"granularity": 300,
"thresholds": {
"cpu_util": 0.2,
"memory.resident": 0.2
},
"periods": {
"node": 600,
"instance": 720
},
"retry_count": 1,
"metrics": [
"cpu_util"
],
"weights": {
"cpu_util_weight": 1.0,
"memory.resident_weight": 1.0
},
"instance_metrics": {
"cpu_util": "compute.node.cpu.percent",
"memory.resident": "hardware.memory.used"
}
},
"auto_trigger": false,
"uuid": "65a5da84-5819-4aea-8278-a28d2b489028",
"goal_name": "workload_balancing",
"scope": [],
"created_at": "2018-04-06T07:27:27.820460+00:00",
"deleted_at": null,
"state": "PENDING",
"audit_type": "CONTINUOUS",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audits/65a5da84-5819-4aea-8278-a28d2b489028"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audits/65a5da84-5819-4aea-8278-a28d2b489028"
}
],
"strategy_name": "workload_stabilization",
"next_run_time": null,
"updated_at": null
}

View File

@@ -0,0 +1,55 @@
{
"audits": [
{
"interval": "*/2 * * * *",
"strategy_uuid": "6b3b3902-8508-4cb0-bb85-67f32866b086",
"goal_uuid": "e1a5a45b-f251-47cf-9c5f-fa1e66e1286a",
"name": "test_audit",
"parameters": {
"host_choice": "retry",
"instance_metrics": {
"cpu_util": "compute.node.cpu.percent",
"memory.resident": "hardware.memory.used"
},
"granularity": 300,
"weights": {
"cpu_util_weight": 1.0,
"memory.resident_weight": 1.0
},
"retry_count": 1,
"metrics": [
"cpu_util"
],
"periods": {
"instance": 720,
"node": 600
},
"thresholds": {
"cpu_util": 0.2,
"memory.resident": 0.2
}
},
"auto_trigger": false,
"uuid": "65a5da84-5819-4aea-8278-a28d2b489028",
"goal_name": "workload_balancing",
"scope": [],
"created_at": "2018-04-06T07:27:27.820460+00:00",
"deleted_at": null,
"state": "ONGOING",
"audit_type": "CONTINUOUS",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audits/65a5da84-5819-4aea-8278-a28d2b489028"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audits/65a5da84-5819-4aea-8278-a28d2b489028"
}
],
"strategy_name": "workload_stabilization",
"next_run_time": "2018-04-06T09:46:00",
"updated_at": "2018-04-06T09:44:01.604146+00:00"
}
]
}

View File

@@ -0,0 +1,28 @@
{
"audits": [
{
"interval": null,
"strategy_uuid": "e311727b-b9b3-43ef-a5f7-8bd7ea80df25",
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "dummy-2018-03-26T11:56:07.950400",
"auto_trigger": false,
"uuid": "ccc69a5f-114e-46f4-b15e-a77eaa337b01",
"goal_name": "dummy",
"scope": [],
"state": "SUCCEEDED",
"audit_type": "ONESHOT",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audits/ccc69a5f-114e-46f4-b15e-a77eaa337b01"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audits/ccc69a5f-114e-46f4-b15e-a77eaa337b01"
}
],
"strategy_name": "dummy",
"next_run_time": null
}
]
}

View File

@@ -0,0 +1,51 @@
{
"interval": "*/2 * * * *",
"strategy_uuid": "6b3b3902-8508-4cb0-bb85-67f32866b086",
"goal_uuid": "e1a5a45b-f251-47cf-9c5f-fa1e66e1286a",
"name": "test_audit",
"parameters": {
"host_choice": "retry",
"instance_metrics": {
"cpu_util": "compute.node.cpu.percent",
"memory.resident": "hardware.memory.used"
},
"granularity": 300,
"weights": {
"cpu_util_weight": 1.0,
"memory.resident_weight": 1.0
},
"retry_count": 1,
"metrics": [
"cpu_util"
],
"periods": {
"instance": 720,
"node": 600
},
"thresholds": {
"cpu_util": 0.2,
"memory.resident": 0.2
}
},
"auto_trigger": false,
"uuid": "65a5da84-5819-4aea-8278-a28d2b489028",
"goal_name": "workload_balancing",
"scope": [],
"created_at": "2018-04-06T07:27:27.820460+00:00",
"deleted_at": null,
"state": "ONGOING",
"audit_type": "CONTINUOUS",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audits/65a5da84-5819-4aea-8278-a28d2b489028"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audits/65a5da84-5819-4aea-8278-a28d2b489028"
}
],
"strategy_name": "workload_stabilization",
"next_run_time": "2018-04-06T11:56:00",
"updated_at": "2018-04-06T11:54:01.266447+00:00"
}

View File

@@ -0,0 +1,12 @@
[
{
"value": "CANCELLED",
"path": "/state",
"op": "replace"
},
{
"value": "audit1",
"path": "/name",
"op": "replace"
}
]

View File

@@ -0,0 +1,51 @@
{
"interval": "*/2 * * * *",
"strategy_uuid": "6b3b3902-8508-4cb0-bb85-67f32866b086",
"goal_uuid": "e1a5a45b-f251-47cf-9c5f-fa1e66e1286a",
"name": "audit1",
"parameters": {
"host_choice": "retry",
"instance_metrics": {
"cpu_util": "compute.node.cpu.percent",
"memory.resident": "hardware.memory.used"
},
"granularity": 300,
"weights": {
"cpu_util_weight": 1.0,
"memory.resident_weight": 1.0
},
"retry_count": 1,
"metrics": [
"cpu_util"
],
"periods": {
"instance": 720,
"node": 600
},
"thresholds": {
"cpu_util": 0.2,
"memory.resident": 0.2
}
},
"auto_trigger": false,
"uuid": "65a5da84-5819-4aea-8278-a28d2b489028",
"goal_name": "workload_balancing",
"scope": [],
"created_at": "2018-04-06T07:27:27.820460+00:00",
"deleted_at": null,
"state": "CANCELLED",
"audit_type": "CONTINUOUS",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audits/65a5da84-5819-4aea-8278-a28d2b489028"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audits/65a5da84-5819-4aea-8278-a28d2b489028"
}
],
"strategy_name": "workload_stabilization",
"next_run_time": "2018-04-06T11:56:00",
"updated_at": "2018-04-06T11:54:01.266447+00:00"
}

View File

@@ -0,0 +1,7 @@
{
"name": "at2",
"goal": "dummy",
"strategy": "dummy",
"description": "the second audit template",
"scope": []
}

View File

@@ -0,0 +1,4 @@
{
"name": "at2",
"goal": "dummy"
}

View File

@@ -0,0 +1,23 @@
{
"description": null,
"strategy_uuid": null,
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "at3",
"uuid": "b4041d8c-85d7-4224-851d-649fe48b7196",
"goal_name": "dummy",
"scope": [],
"created_at": "2018-04-04T08:38:33.110432+00:00",
"deleted_at": null,
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audit_templates/b4041d8c-85d7-4224-851d-649fe48b7196"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audit_templates/b4041d8c-85d7-4224-851d-649fe48b7196"
}
],
"strategy_name": null,
"updated_at": null
}

View File

@@ -0,0 +1,23 @@
{
"audit_templates":[
{
"strategy_uuid": null,
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "at3",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audit_templates/b4041d8c-85d7-4224-851d-649fe48b7196"
},
{
"rel": "bookmark", "href":
"http://controller:9322/audit_templates/b4041d8c-85d7-4224-851d-649fe48b7196"
}
],
"strategy_name": null,
"uuid": "b4041d8c-85d7-4224-851d-649fe48b7196",
"goal_name": "dummy", "scope": [],
"description": null
}
]
}

View File

@@ -0,0 +1,22 @@
{
"audit_templates":[
{
"strategy_uuid": null,
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "at3",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audit_templates/b4041d8c-85d7-4224-851d-649fe48b7196"
},
{
"rel": "bookmark", "href":
"http://controller:9322/audit_templates/b4041d8c-85d7-4224-851d-649fe48b7196"
}
],
"strategy_name": null,
"uuid": "b4041d8c-85d7-4224-851d-649fe48b7196",
"goal_name": "dummy", "scope": []
}
]
}

View File

@@ -0,0 +1,23 @@
{
"description": "test 1",
"strategy_uuid": null,
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "at1",
"uuid": "0d100c27-14af-4962-86fb-f6079287c9c6",
"goal_name": "dummy",
"scope": [],
"created_at": "2018-04-04T07:48:36.175472+00:00",
"deleted_at": null,
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audit_templates/0d100c27-14af-4962-86fb-f6079287c9c6"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audit_templates/0d100c27-14af-4962-86fb-f6079287c9c6"
}
],
"strategy_name": null,
"updated_at": "2018-04-05T07:57:55.803650+00:00"
}

View File

@@ -0,0 +1,7 @@
[
{
"op": "replace",
"value": "PENDING",
"path": "/state"
}
]

View File

@@ -0,0 +1,23 @@
{
"description": "test 1",
"strategy_uuid": null,
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "at11",
"uuid": "0d100c27-14af-4962-86fb-f6079287c9c6",
"goal_name": "dummy",
"scope": [],
"created_at": "2018-04-04T07:48:36.175472+00:00",
"deleted_at": null,
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/audit_templates/0d100c27-14af-4962-86fb-f6079287c9c6"
},
{
"rel": "bookmark",
"href": "http://controller:9322/audit_templates/0d100c27-14af-4962-86fb-f6079287c9c6"
}
],
"strategy_name": null,
"updated_at": "2018-04-05T07:57:42.139127+00:00"
}

View File

@@ -0,0 +1,55 @@
{
"goals": [
{
"efficacy_specification": [],
"uuid": "e1a5a45b-f251-47cf-9c5f-fa1e66e1286a",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/goals/e1a5a45b-f251-47cf-9c5f-fa1e66e1286a"
},
{
"rel": "bookmark",
"href": "http://controller:9322/goals/e1a5a45b-f251-47cf-9c5f-fa1e66e1286a"
}
],
"name": "workload_balancing",
"display_name": "Workload Balancing"
},
{
"efficacy_specification": [
{
"description": "The total number of enabled compute nodes.",
"schema": "Range(min=0, max=None, min_included=True, max_included=True, msg=None)",
"name": "compute_nodes_count",
"unit": null
},
{
"description": "The number of compute nodes to be released.",
"schema": "Range(min=0, max=None, min_included=True, max_included=True, msg=None)",
"name": "released_compute_nodes_count",
"unit": null
},
{
"description": "The number of VM migrations to be performed.",
"schema": "Range(min=0, max=None, min_included=True, max_included=True, msg=None)",
"name": "instance_migrations_count",
"unit": null
}
],
"uuid": "cb9afa5e-aec7-4a8c-9261-c15c33f2262b",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/goals/cb9afa5e-aec7-4a8c-9261-c15c33f2262b"
},
{
"rel": "bookmark",
"href": "http://controller:9322/goals/cb9afa5e-aec7-4a8c-9261-c15c33f2262b"
}
],
"name": "server_consolidation",
"display_name": "Server Consolidation"
}
]
}

View File

@@ -0,0 +1,19 @@
{
"efficacy_specification": [],
"name": "saving_energy",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/goals/6f52889a-9dd4-4dbb-8e70-39b56c4836cc"
},
{
"rel": "bookmark",
"href": "http://controller:9322/goals/6f52889a-9dd4-4dbb-8e70-39b56c4836cc"
}
],
"uuid": "6f52889a-9dd4-4dbb-8e70-39b56c4836cc",
"updated_at": null,
"display_name": "Saving Energy",
"created_at": "2018-03-26T11:55:24.365584+00:00",
"deleted_at": null
}

View File

@@ -0,0 +1,20 @@
{
"scoring_engines": [
{
"description": "Dummy Scorer calculating the average value",
"uuid": "5a44f007-55b1-423c-809f-6a274a9bd93b",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/scoring_engines/5a44f007-55b1-423c-809f-6a274a9bd93b"
},
{
"rel": "bookmark",
"href": "http://controller:9322/scoring_engines/5a44f007-55b1-423c-809f-6a274a9bd93b"
}
],
"name": "dummy_avg_scorer",
"metainfo": ""
}
]
}

View File

@@ -0,0 +1,19 @@
{
"scoring_engines": [
{
"description": "Dummy Scorer calculating the average value",
"uuid": "5a44f007-55b1-423c-809f-6a274a9bd93b",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/scoring_engines/5a44f007-55b1-423c-809f-6a274a9bd93b"
},
{
"rel": "bookmark",
"href": "http://controller:9322/scoring_engines/5a44f007-55b1-423c-809f-6a274a9bd93b"
}
],
"name": "dummy_avg_scorer"
}
]
}

View File

@@ -0,0 +1,16 @@
{
"description": "Dummy Scorer calculating the maximum value",
"uuid": "1ac42282-4e77-473e-898b-62ea007f1deb",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/scoring_engines/1ac42282-4e77-473e-898b-62ea007f1deb"
},
{
"rel": "bookmark",
"href": "http://controller:9322/scoring_engines/1ac42282-4e77-473e-898b-62ea007f1deb"
}
],
"name": "dummy_max_scorer",
"metainfo": ""
}

View File

@@ -0,0 +1,24 @@
{
"services": [
{
"status": "ACTIVE",
"name": "watcher-applier",
"host": "controller",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/services/1"
},
{
"rel": "bookmark",
"href": "http://controller:9322/services/1"
}
],
"id": 1,
"deleted_at": null,
"updated_at": "2018-04-26T08:52:37.652895+00:00",
"last_seen_up": "2018-04-26T08:52:37.648572",
"created_at": "2018-03-26T11:55:24.075093+00:00"
}
]
}

View File

@@ -0,0 +1,36 @@
{
"services": [
{
"id": 1,
"status": "ACTIVE",
"name": "watcher-applier",
"host": "controller",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/services/1"
},
{
"rel": "bookmark",
"href": "http://controller:9322/services/1"
}
]
},
{
"id": 2,
"status": "ACTIVE",
"name": "watcher-decision-engine",
"host": "controller",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/services/2"
},
{
"rel": "bookmark",
"href": "http://controller:9322/services/2"
}
]
}
]
}

View File

@@ -0,0 +1,20 @@
{
"status": "ACTIVE",
"name": "watcher-applier",
"host": "controller",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/services/1"
},
{
"rel": "bookmark",
"href": "http://controller:9322/services/1"
}
],
"id": 1,
"deleted_at": null,
"updated_at": "2018-04-26T09:45:37.653061+00:00",
"last_seen_up": "2018-04-26T09:45:37.649314",
"created_at": "2018-03-26T11:55:24.075093+00:00"
}

View File

@@ -0,0 +1,35 @@
{
"strategies": [
{
"goal_uuid": "cb9afa5e-aec7-4a8c-9261-c15c33f2262b",
"name": "vm_workload_consolidation",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/strategies/6382b2d7-259e-487d-88db-78c852ffea54"
},
{
"rel": "bookmark",
"href": "http://controller:9322/strategies/6382b2d7-259e-487d-88db-78c852ffea54"
}
],
"parameters_spec": {
"properties": {
"granularity": {
"default": 300,
"type": "number",
"description": "The time between two measures in an aggregated timeseries of a metric."
},
"period": {
"default": 3600,
"type": "number",
"description": "The time interval in seconds for getting statistic aggregation"
}
}
},
"uuid": "6382b2d7-259e-487d-88db-78c852ffea54",
"goal_name": "server_consolidation",
"display_name": "VM Workload Consolidation Strategy"
}
]
}

View File

@@ -0,0 +1,21 @@
{
"strategies": [
{
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "dummy",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/strategies/e311727b-b9b3-43ef-a5f7-8bd7ea80df25"
},
{
"rel": "bookmark",
"href": "http://controller:9322/strategies/e311727b-b9b3-43ef-a5f7-8bd7ea80df25"
}
],
"uuid": "e311727b-b9b3-43ef-a5f7-8bd7ea80df25",
"goal_name": "dummy",
"display_name": "Dummy strategy"
}
]
}

View File

@@ -0,0 +1,33 @@
{
"goal_uuid": "4690f8ba-18ff-45c1-99e9-159556d23810",
"name": "dummy",
"links": [
{
"rel": "self",
"href": "http://controller:9322/v1/strategies/e311727b-b9b3-43ef-a5f7-8bd7ea80df25"
},
{
"rel": "bookmark",
"href": "http://controller:9322/strategies/e311727b-b9b3-43ef-a5f7-8bd7ea80df25"
}
],
"parameters_spec": {
"properties": {
"para2": {
"default": "hello",
"type": "string",
"description": "string parameter example"
},
"para1": {
"maximum": 10.2,
"type": "number",
"minimum": 1.0,
"description": "number parameter example",
"default": 3.2
}
}
},
"uuid": "e311727b-b9b3-43ef-a5f7-8bd7ea80df25",
"goal_name": "dummy",
"display_name": "Dummy strategy"
}

View File

@@ -0,0 +1,49 @@
[
{
"state": "gnocchi: available",
"comment": "",
"mandatory": true,
"type": "Datasource"
},
{
"state": [
{
"compute.node.cpu.percent": "available"
},
{
"cpu_util": "available"
},
{
"memory.resident": "available"
},
{
"hardware.memory.used": "available"
}
],
"comment": "",
"mandatory": false,
"type": "Metrics"
},
{
"state": [
{
"compute_model": "available"
},
{
"storage_model": "not available"
},
{
"baremetal_model": "not available"
}
],
"comment": "",
"mandatory": true,
"type": "CDM"
},
{
"state": "workload_stabilization",
"mandatory": "",
"comment": "",
"type": "Name"
}
]

View File

@@ -0,0 +1,254 @@
.. -*- rst -*-
============
Action Plans
============
An ``Action Plan`` specifies a flow of ``Actions`` that should be executed
in order to satisfy a given ``Goal``. It also contains an estimated
``global efficacy`` alongside a set of ``efficacy indicators``.
An ``Action Plan`` is generated by Watcher when an ``Audit`` is successful
which implies that the ``Strategy`` which was used has found a ``Solution``
to achieve the ``Goal`` of this ``Audit``.
In the default implementation of Watcher, an action plan is composed of
a graph of linked ``Actions``. Each action may have parent actions, which
should be executed prior to child action.
Start Action Plan
=================
.. rest_method:: POST /v1/action_plans/{actionplan_ident}/start
Starts a created Action Plan resource.
Normal response codes: 200
Error codes: 400,404
Request
-------
.. rest_parameters:: parameters.yaml
- actionplan_ident: actionplan_ident
Response
--------
The list and example below are representative of the response as of API
version 1:
.. rest_parameters:: parameters.yaml
- uuid: uuid
- state: actionplan_state
- audit_uuid: actionplan_audit_uuid
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- efficacy_indicators: actionplan_efficacy_indicators
- global_efficacy: actionplan_global_efficacy
- links: links
**Example JSON representation of an Action Plan:**
.. literalinclude:: samples/actionplan-start-response.json
:language: javascript
List Action Plan
================
.. rest_method:: GET /v1/action_plans
Returns a list of Action Plan resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- audit_uuid: r_audit
- strategy: r_strategy
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- state: actionplan_state
- audit_uuid: actionplan_audit_uuid
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- efficacy_indicators: actionplan_efficacy_indicators
- global_efficacy: actionplan_global_efficacy
- links: links
**Example JSON representation of an Action Plan:**
.. literalinclude:: samples/actionplan-list-response.json
:language: javascript
List Action Plan detailed
=========================
.. rest_method:: GET /v1/action_plans/detail
Returns a list of Action Plan resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- audit_uuid: r_audit
- strategy: r_strategy
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- deleted_at: deleted_at
- updated_at: updated_at
- created_at: created_at
- uuid: uuid
- state: actionplan_state
- audit_uuid: actionplan_audit_uuid
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- efficacy_indicators: actionplan_efficacy_indicators
- global_efficacy: actionplan_global_efficacy
- links: links
**Example JSON representation of an Action Plan:**
.. literalinclude:: samples/actionplan-list-detailed-response.json
:language: javascript
Show Action Plan
================
.. rest_method:: GET /v1/action_plans/{actionplan_ident}
Shows details for an Action Plan.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- actionplan_ident: actionplan_ident
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- state: actionplan_state
- audit_uuid: actionplan_audit_uuid
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- efficacy_indicators: actionplan_efficacy_indicators
- global_efficacy: actionplan_global_efficacy
- links: links
**Example JSON representation of an Audit:**
.. literalinclude:: samples/actionplan-show-response.json
:language: javascript
Cancel Action Plan
==================
.. rest_method:: PATCH /v1/action_plans/{actionplan_ident}
Cancels a created Action Plan resource.
.. note:
If Action Plan is in ONGOING state, then ``state`` attribute should be
replaced with ``CANCELLING`` value. Otherwise, ``CANCELLED`` is to be
used.
Normal response codes: 200
Error codes: 400,404
Request
-------
.. rest_parameters:: parameters.yaml
- actionplan_ident: actionplan_ident
**Example Action Plan ONGOING cancelling request:**
.. literalinclude:: samples/actionplan-cancel-request-cancelling.json
:language: javascript
**Example Action Plan PENDING cancelling request:**
.. literalinclude:: samples/actionplan-cancel-request-pending.json
:language: javascript
Response
--------
The list and example below are representative of the response as of API
version 1:
.. rest_parameters:: parameters.yaml
- uuid: uuid
- state: actionplan_state
- audit_uuid: actionplan_audit_uuid
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- efficacy_indicators: actionplan_efficacy_indicators
- global_efficacy: actionplan_global_efficacy
- links: links
**Example JSON representation of an Action Plan:**
.. literalinclude:: samples/actionplan-start-response.json
:language: javascript
Delete Action Plan
==================
.. rest_method:: DELETE /v1/action_plans/{actionplan_ident}
Deletes an Action Plan. Action Plan can be deleted only from SUCCEEDED, RECOMMENDED, FAILED, SUPERSEDED, CANCELLED states.
Normal response codes: 204
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- actionplan_ident: actionplan_ident

View File

@@ -0,0 +1,155 @@
.. -*- rst -*-
=======
Actions
=======
An ``Action`` is what enables Watcher to transform the current state of a
``Cluster`` after an ``Audit``.
An ``Action`` is an atomic task which changes the current state of a target
Managed resource of the OpenStack ``Cluster`` such as:
- Live migration of an instance from one compute node to another compute
node with Nova
- Changing the power level of a compute node (ACPI level, ...)
- Changing the current state of a compute node (enable or disable) with Nova
In most cases, an ``Action`` triggers some concrete commands on an existing
OpenStack module (Nova, Neutron, Cinder, Ironic, etc.).
An ``Action`` has a life-cycle and its current state may be one of the
following:
- **PENDING** : the ``Action`` has not been executed yet by the
``Watcher Applier``.
- **ONGOING** : the ``Action`` is currently being processed by the
``Watcher Applier``.
- **SUCCEEDED** : the ``Action`` has been executed successfully
- **FAILED** : an error occurred while trying to execute the ``Action``.
- **DELETED** : the ``Action`` is still stored in the ``Watcher database``
but is not returned any more through the Watcher APIs.
- **CANCELLED** : the ``Action`` was in **PENDING** or **ONGOING** state and
was cancelled by the ``Administrator``
``Actions`` are created by ``Watcher Planner`` as result of Audit's execution.
``Action`` can't be created, modified or deleted by user.
List Action
===========
.. rest_method:: GET /v1/actions
Returns a list of Action resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- action_plan_uuid: r_action_plan
- audit_uuid: r_audit
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- action_type: action_type
- state: action_state
- action_plan_uuid: action_action_plan_uuid
- parents: action_parents
- links: links
**Example JSON representation of an Action:**
.. literalinclude:: samples/actions-list-response.json
:language: javascript
List Action Detailed
====================
.. rest_method:: GET /v1/actions/detail
Returns a list of Action resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- action_plan_uuid: r_action_plan
- audit_uuid: r_audit
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- action_type: action_type
- state: action_state
- action_plan_uuid: action_action_plan_uuid
- parents: action_parents
- description: action_description
- input_parameters: action_input_parameters
- links: links
**Example JSON representation of an Action:**
.. literalinclude:: samples/actions-list-detailed-response.json
:language: javascript
Show Action
===========
.. rest_method:: GET /v1/actions/{action_ident}
Shows details for an Action.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- action_ident: action_ident
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- action_type: action_type
- state: action_state
- action_plan_uuid: action_action_plan_uuid
- parents: action_parents
- description: action_description
- input_parameters: action_input_parameters
- links: links
**Example JSON representation of an Action:**
.. literalinclude:: samples/actions-show-response.json
:language: javascript

View File

@@ -0,0 +1,349 @@
.. -*- rst -*-
======
Audits
======
There are creating, listing, updating and deleting methods of Watcher Audit
resources which are implemented via the ``/v1/audits`` resource.
In the Watcher system, an ``Audit`` is a request for optimizing a ``Cluster``.
The optimization is done in order to satisfy one ``Goal`` on a given
``Cluster``.
For each ``Audit``, the Watcher system generates an ``Action Plan``.
Create Audit
============
.. rest_method:: POST /v1/audits
Creates a new Audit resource.
Mandatory attribute to be supplied: ``audit_type``.
``Audit`` can be created either based on existed ``Audit Template`` or by
itself. In the first case, there also should be supplied
``audit_template_uuid``. If ``Audit`` is created without ``Audit Template``,
``goal`` should be provided.
.. warning::
**Only ``audit_template_uuid`` can be used to create audit so far.**
It should be fixed during the ``Rocky`` cycle.
Normal response codes: 201
Error codes: 400,404
Request
-------
.. rest_parameters:: parameters.yaml
- audit_template_uuid: audittemplate_name
- audit_type: audit_type
- name: audit_name
- goal: audit_goal
- strategy: audit_strategy
- parameters: audit_parameters
- interval: audit_interval
- scope: audittemplate_scope
- auto_trigger: audit_autotrigger
**Example ONESHOT Audit creation request:**
.. literalinclude:: samples/audit-create-request-oneshot.json
:language: javascript
**Example CONTINUOUS Audit creation request with a specified strategy:**
.. literalinclude:: samples/audit-create-request-continuous.json
:language: javascript
Response
--------
The list and example below are representative of the response as of API
version 1:
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audit_name
- audit_type: audit_type
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- interval: audit_interval
- next_run_time: audit_next_run_time
- parameters: audit_parameters
- auto_trigger: audit_autotrigger
- state: audit_state
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit:**
.. literalinclude:: samples/audit-create-response.json
:language: javascript
List Audit
==========
.. rest_method:: GET /v1/audits
Returns a list of Audit resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- goal: r_goal
- strategy: r_strategy
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audit_name
- audit_type: audit_type
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- interval: audit_interval
- next_run_time: audit_next_run_time
- auto_trigger: audit_autotrigger
- state: audit_state
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit:**
.. literalinclude:: samples/audit-list-response.json
:language: javascript
List Audit Detailed
===================
.. rest_method:: GET /v1/audits/detail
Returns a list of Audit resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- goal: r_goal
- strategy: r_strategy
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audit_name
- audit_type: audit_type
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- interval: audit_interval
- next_run_time: audit_next_run_time
- parameters: audit_parameters
- auto_trigger: audit_autotrigger
- state: audit_state
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit:**
.. literalinclude:: samples/audit-list-detailed-response.json
:language: javascript
Show Audit
==========
.. rest_method:: GET /v1/audits/{audit_ident}
Shows details for an Audit.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- audit_ident: audit_ident
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audit_name
- audit_type: audit_type
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- interval: audit_interval
- next_run_time: audit_next_run_time
- parameters: audit_parameters
- auto_trigger: audit_autotrigger
- state: audit_state
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit:**
.. literalinclude:: samples/audit-show-response.json
:language: javascript
Cancel Audit
============
.. rest_method:: PATCH /v1/audits/{audit_ident}
Cancels an ONGOING Audit resource.
Normal response codes: 200
Error codes: 400,404
Request
-------
.. rest_parameters:: parameters.yaml
- audit_ident: audit_ident
**Example Audit cancelling request:**
.. literalinclude:: samples/audit-cancel-request.json
:language: javascript
Response
--------
The list and example below are representative of the response as of API
version 1:
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audit_name
- audit_type: audit_type
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- interval: audit_interval
- next_run_time: audit_next_run_time
- parameters: audit_parameters
- auto_trigger: audit_autotrigger
- state: audit_state
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit:**
.. literalinclude:: samples/audit-cancel-response.json
:language: javascript
Update Audit
============
.. rest_method:: PATCH /v1/audits/{audit_ident}
Updates an Audit with the given information.
.. note:
``audit_type`` shouldn't be changed by PATCH method.
Normal response codes: 200
Error codes: 400,404
Request
-------
.. rest_parameters:: parameters.yaml
- audit_ident: audit_ident
**Example PATCH document updating Audit:**
.. literalinclude:: samples/audit-update-request.json
:language: javascript
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audit_name
- audit_type: audit_type
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- interval: audit_interval
- next_run_time: audit_next_run_time
- parameters: audit_parameters
- auto_trigger: audit_autotrigger
- state: audit_state
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit:**
.. literalinclude:: samples/audit-update-response.json
:language: javascript
Delete Audit
============
.. rest_method:: DELETE /v1/audits/{audit_ident}
Deletes an Audit. Audit can be deleted only from FAILED, SUCCEEDED, CANCELLED,
SUSPENDED states.
Normal response codes: 204
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- audit_ident: audit_ident

View File

@@ -0,0 +1,257 @@
.. -*- rst -*-
===============
Audit Templates
===============
There are creating, listing, updating and deleting methods of Watcher Audit
Template resources which are implemented via the ``/v1/audit_templates``
resource.
An Audit may be launched several times with the same settings
(Goal, thresholds, ...). Therefore it makes sense to save those settings in
some sort of Audit preset object, which is known as an Audit Template.
An Audit Template contains at least the Goal of the Audit.
Create Audit Template
=====================
.. rest_method:: POST /v1/audit_templates
Creates a new Audit Template resource.
It requires ``name`` and ``goal`` attributes to be supplied in the request
body.
Normal response codes: 201
Error codes: 400,404,409
Request
-------
.. rest_parameters:: parameters.yaml
- name: audittemplate_name
- goal: audittemplate_goal
- strategy: audittemplate_strategy
- description: audittemplate_description
- scope: audittemplate_scope
**Example Audit Template creation request without a specified strategy:**
.. literalinclude:: samples/audittemplate-create-request-minimal.json
:language: javascript
**Example Audit Template creation request with a specified strategy:**
.. literalinclude:: samples/audittemplate-create-request-full.json
:language: javascript
Response
--------
The list and example below are representative of the response as of API
version 1:
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audittemplate_name
- description: audittemplate_description
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit Template:**
.. literalinclude:: samples/audittemplate-create-response.json
:language: javascript
List Audit Template
===================
.. rest_method:: GET /v1/audit_templates
Returns a list of Audit Template resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- goal: r_goal
- strategy: r_strategy
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audittemplate_name
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- scope: audittemplate_scope
- links: links
**Example JSON representation of an Audit Template:**
.. literalinclude:: samples/audittemplate-list-response.json
:language: javascript
List Audit Template Detailed
============================
.. rest_method:: GET /v1/audit_templates/detail
Returns a list of Audit Template resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- goal: r_goal
- strategy: r_strategy
- limit: limit
- marker: marker
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audittemplate_name
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- scope: audittemplate_scope
- links: links
- description: audittemplate_description
**Example JSON representation of an Audit Template:**
.. literalinclude:: samples/audittemplate-list-detailed-response.json
:language: javascript
Show Audit Template
===================
.. rest_method:: GET /v1/audit_templates/{audittemplate_ident}
Shows details for an Audit Template.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- audittemplate_ident: audittemplate_ident
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audittemplate_name
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- scope: audittemplate_scope
- links: links
- description: audittemplate_description
**Example JSON representation of an Audit Template:**
.. literalinclude:: samples/audittemplate-show-response.json
:language: javascript
Update Audit Template
=====================
.. rest_method:: PATCH /v1/audit_templates/{audittemplate_ident}
Updates an Audit Template with the given information.
Normal response codes: 200
Error codes: 400,404
Request
-------
.. rest_parameters:: parameters.yaml
- audittemplate_ident: audittemplate_ident
**Example PATCH document updating Audit Template:**
.. literalinclude:: samples/audittemplate-update-request.json
:language: javascript
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: audittemplate_name
- strategy_uuid: strategy_uuid
- strategy_name: strategy_name
- goal_uuid: goal_uuid
- goal_name: goal_name
- scope: audittemplate_scope
- links: links
- description: audittemplate_description
**Example JSON representation of an Audit Template:**
.. literalinclude:: samples/audittemplate-update-response.json
:language: javascript
Delete Audit Template
=====================
.. rest_method:: DELETE /v1/audit_templates/{audittemplate_ident}
Deletes an Audit Template.
Normal response codes: 204
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- audittemplate_ident: audittemplate_ident

View File

@@ -0,0 +1,126 @@
.. -*- rst -*-
=====
Goals
=====
A ``Goal`` is a human readable, observable and measurable end result having
one objective to be achieved.
Here are some examples of ``Goals``:
- minimize the energy consumption
- minimize the number of compute nodes (consolidation)
- balance the workload among compute nodes
- minimize the license cost (some softwares have a licensing model which is
based on the number of sockets or cores where the software is deployed)
- find the most appropriate moment for a planned maintenance on a
given group of host (which may be an entire availability zone):
power supply replacement, cooling system replacement, hardware
modification, ...
List Goal
=========
.. rest_method:: GET /v1/goals
Returns a list of Goal resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- efficacy_specification: goal_efficacy_specification
- name: goal_name
- display_name: goal_display_name
- links: links
**Example JSON representation of a Goal:**
.. literalinclude:: samples/goal-list-response.json
:language: javascript
List Goal Detailed
==================
.. rest_method:: GET /v1/goals/detail
Returns a list of Goal resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- efficacy_specification: goal_efficacy_specification
- name: goal_name
- display_name: goal_display_name
- links: links
**Example JSON representation of a Goal:**
.. literalinclude:: samples/goal-list-response.json
:language: javascript
Show Goal
=========
.. rest_method:: GET /v1/goals/{goal_ident}
Shows details for an Goal.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- goal_ident: goal_ident
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- efficacy_specification: goal_efficacy_specification
- name: goal_name
- display_name: goal_display_name
- links: links
**Example JSON representation of a Goal:**
.. literalinclude:: samples/goal-show-response.json
:language: javascript

View File

@@ -0,0 +1,120 @@
.. -*- rst -*-
===============
Scoring Engines
===============
A ``Scoring Engine`` is an executable that has a well-defined input, a
well-defined output, and performs a purely mathematical task. That is,
the calculation does not depend on the environment in which it is running - it
would produce the same result anywhere.
Because there might be multiple algorithms used to build a particular data
model (and therefore a scoring engine), the usage of scoring engine might
vary. A metainfo field is supposed to contain any information which might
be needed by the user of a given scoring engine.
List Scoring Engine
===================
.. rest_method:: GET /v1/scoring_engines
Returns a list of Scoring Engine resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: scoring_engine_name
- description: scoring_engine_description
- links: links
**Example JSON representation of a Scoring Engine:**
.. literalinclude:: samples/scoring_engine-list-response.json
:language: javascript
List Scoring Engine Detailed
============================
.. rest_method:: GET /v1/scoring_engines/detail
Returns a list of Scoring Engine resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: scoring_engine_name
- description: scoring_engine_description
- metainfo: scoring_engine_metainfo
- links: links
**Example JSON representation of a Scoring Engine:**
.. literalinclude:: samples/scoring_engine-list-detailed-response.json
:language: javascript
Show Scoring Engine
===================
.. rest_method:: GET /v1/scoring_engines/{scoring_engine_ident}
Shows details for a Scoring Engine resource.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- scoring_engine_ident: scoring_engine_ident
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: scoring_engine_name
- description: scoring_engine_description
- metainfo: scoring_engine_metainfo
- links: links
**Example JSON representation of a Scoring Engine:**
.. literalinclude:: samples/scoring_engine-show-response.json
:language: javascript

View File

@@ -0,0 +1,116 @@
.. -*- rst -*-
========
Services
========
This resource represents Watcher services, their states and hosts they are
placed on.
List Service
============
.. rest_method:: GET /v1/services
Returns a list of Service resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- id: service_id
- name: service_name
- host: service_host
- status: service_status
- links: links
**Example JSON representation of a Service:**
.. literalinclude:: samples/service-list-response.json
:language: javascript
List Service Detailed
=====================
.. rest_method:: GET /v1/services/detail
Returns a list of Service resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- id: service_id
- name: service_name
- host: service_host
- status: service_status
- last_seen_up: service_last_seen_up
- links: links
**Example JSON representation of a Service:**
.. literalinclude:: samples/service-list-detailed-response.json
:language: javascript
Show Service
============
.. rest_method:: GET /v1/services/{service_ident}
Shows details for a Service resource.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- service_ident: service_ident
Response
--------
.. rest_parameters:: parameters.yaml
- id: service_id
- name: service_name
- host: service_host
- status: service_status
- last_seen_up: service_last_seen_up
- links: links
**Example JSON representation of a Service:**
.. literalinclude:: samples/service-show-response.json
:language: javascript

View File

@@ -0,0 +1,164 @@
.. -*- rst -*-
==========
Strategies
==========
A ``Strategy`` is an algorithm implementation which is able to find a
``Solution`` for a given ``Goal``. To get more information about strategies
that are shipped along with Watcher, visit `strategies page`_.
There may be several potential strategies which are able to achieve the same
``Goal``. This is why it is possible to configure which specific ``Strategy``
should be used for each goal.
Some strategies may provide better optimization results but may take more time
to find an optimal ``Solution``.
.. _`strategies page`: https://docs.openstack.org/watcher/latest/strategies/index.html
List Strategy
=============
.. rest_method:: GET /v1/strategies
Returns a list of Strategy resources.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- goal: r_goal
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: strategy_name
- display_name: strategy_display_name
- goal_name: goal_name
- goal_uuid: goal_uuid
- links: links
**Example JSON representation of a Strategy:**
.. literalinclude:: samples/strategy-list-response.json
:language: javascript
List Strategy Detailed
======================
.. rest_method:: GET /v1/strategies/detail
Returns a list of Strategy resources with complete details.
Normal response codes: 200
Error codes: 400,401
Request
-------
.. rest_parameters:: parameters.yaml
- goal: r_goal
- limit: limit
- sort_dir: sort_dir
- sort_key: sort_key
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: strategy_name
- display_name: strategy_display_name
- parameters_spec: strategy_parameters_spec
- goal_name: goal_name
- goal_uuid: goal_uuid
- links: links
**Example JSON representation of a Strategy:**
.. literalinclude:: samples/strategy-list-detailed-response.json
:language: javascript
Show Strategy
=============
.. rest_method:: GET /v1/strategies/{strategy_ident}
Shows details for a Strategy resource.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- strategy_ident: strategy_ident
Response
--------
.. rest_parameters:: parameters.yaml
- uuid: uuid
- name: strategy_name
- display_name: strategy_display_name
- parameters_spec: strategy_parameters_spec
- goal_name: goal_name
- goal_uuid: goal_uuid
- links: links
**Example JSON representation of a Strategy:**
.. literalinclude:: samples/strategy-show-response.json
:language: javascript
Show Strategy State
===================
.. rest_method:: GET /v1/strategies/{strategy_ident}/state
Retrieve an information about strategy requirements.
Normal response codes: 200
Error codes: 404
Request
-------
.. rest_parameters:: parameters.yaml
- strategy_ident: strategy_ident
Response
--------
.. rest_parameters:: parameters.yaml
- state: strategy_check_state
- comment: strategy_check_comment
- mandatory: strategy_check_mandatory
- type: strategy_check_type
**Example JSON representation of a Strategy:**
.. literalinclude:: samples/strategy-state-response.json
:language: javascript

View File

@@ -131,6 +131,16 @@ The actions that may have a rule enforced on them are:
* ``GET /v1/actions/<ACTION_UUID>``
* ``service:get_all``, ``service:detail`` - List available Watcher services
* ``GET /v1/services``
* ``GET /v1/services/detail``
* ``service:get`` - Retrieve a specific Watcher service entity
* ``GET /v1/services/<SERVICE_ID>``
To limit an action to a particular role or roles, you list the roles like so ::

View File

@@ -30,7 +30,7 @@ and git_ available.
Your system shall also have some additional system libraries:
On Ubuntu (tested on 14.04LTS):
On Ubuntu (tested on 16.04LTS):
.. code-block:: bash

View File

@@ -19,11 +19,17 @@ optimize your IaaS platform. The Watcher service may, depending upon
configuration, interact with several other OpenStack services. This includes:
- the OpenStack Identity service (`keystone`_) for request authentication and
to locate other OpenStack services
- the OpenStack Telemetry service (`ceilometer`_) for consuming the resources
metrics
to locate other OpenStack services.
- the OpenStack Telemetry service (`ceilometer`_) for collecting the resources
metrics.
- the time series database (`gnocchi`_) for consuming the resources
metrics.
- the OpenStack Compute service (`nova`_) works with the Watcher service and
acts as a user-facing API for instance migration.
- the OpenStack Bare Metal service (`ironic`_) works with the Watcher service
and allows to manage power state of nodes.
- the OpenStack Block Storage service (`cinder`_) works with the Watcher
service and as an API for volume node migration.
The Watcher service includes the following components:
@@ -53,6 +59,9 @@ additional functionality:
.. _`keystone`: https://github.com/openstack/keystone
.. _`ceilometer`: https://github.com/openstack/ceilometer
.. _`nova`: https://github.com/openstack/nova
.. _`gnocchi`: https://github.com/gnocchixyz/gnocchi
.. _`ironic`: https://github.com/openstack/ironic
.. _`cinder`: https://github.com/openstack/cinder
.. _`python-watcherclient`: https://github.com/openstack/python-watcherclient
.. _`watcher-dashboard`: https://github.com/openstack/watcher-dashboard
.. _`watcher metering`: https://github.com/b-com/watcher-metering

View File

@@ -193,6 +193,8 @@ still need to configure the following sections:
:ref:`Identity service <identity-service_configuration>` i.e. Keystone
- The ``[watcher_messaging]`` section to configure the OpenStack AMQP-based
message bus
- The ``watcher_clients_auth`` section to configure Keystone client to access
related OpenStack projects
So if you need some more details on how to configure one or more of these
sections, please do have a look at :doc:`../configuration/configuring` before

View File

@@ -54,22 +54,6 @@ Getting Started
contributor/index
API References
--------------
.. toctree::
:maxdepth: 1
api/index
Plugins
-------
.. toctree::
:maxdepth: 1
contributor/plugin/index
Installation
============
.. toctree::
@@ -77,14 +61,6 @@ Installation
install/index
Watcher Configuration Options
=============================
.. toctree::
:maxdepth: 2
configuration/index
Admin Guide
===========
@@ -101,6 +77,30 @@ User Guide
user/index
API References
--------------
.. toctree::
:maxdepth: 1
api/index
Plugins
-------
.. toctree::
:maxdepth: 1
contributor/plugin/index
Watcher Configuration Options
=============================
.. toctree::
:maxdepth: 2
configuration/index
Watcher Manual Pages
====================

View File

@@ -41,6 +41,8 @@ Default Watcher's actions:
- description
* - ``migration``
- .. watcher-term:: watcher.applier.actions.migration.Migrate
* - ``change_nova_service_state``
- .. watcher-term:: watcher.applier.actions.change_nova_service_state.ChangeNovaServiceState
Planner
*******

View File

@@ -67,7 +67,7 @@ None
Algorithm
---------
For more information on the zone migration strategy please refer to:
For more information on the storage capacity balance strategy please refer to:
http://specs.openstack.org/openstack/watcher-specs/specs/queens/implemented/storage-capacity-balance.html
How to use it ?

View File

@@ -114,7 +114,7 @@ python-glanceclient==2.9.1
python-ironicclient==2.3.0
python-keystoneclient==3.15.0
python-mimeparse==1.6.0
python-monascaclient==1.10.0
python-monascaclient==1.12.0
python-neutronclient==6.7.0
python-novaclient==10.1.0
python-openstackclient==3.14.0
@@ -146,7 +146,6 @@ stevedore==1.28.0
taskflow==3.1.0
Tempita==0.5.2
tenacity==4.9.0
testrepository==0.0.20
testresources==2.0.1
testscenarios==0.5.0
testtools==2.3.0

View File

@@ -0,0 +1,6 @@
---
features:
- Watcher services can be launched in HA mode. From now on Watcher Decision
Engine and Watcher Applier services may be deployed on different nodes to
run in active-active or active-passive mode. Any ONGOING Audits or Action Plans
will be CANCELLED if service they are executed on is restarted.

View File

@@ -34,7 +34,7 @@ python-ceilometerclient>=2.9.0 # Apache-2.0
python-cinderclient>=3.5.0 # Apache-2.0
python-glanceclient>=2.9.1 # Apache-2.0
python-keystoneclient>=3.15.0 # Apache-2.0
python-monascaclient>=1.10.0 # Apache-2.0
python-monascaclient>=1.12.0 # Apache-2.0
python-neutronclient>=6.7.0 # Apache-2.0
python-novaclient>=10.1.0 # Apache-2.0
python-openstackclient>=3.14.0 # Apache-2.0

View File

@@ -2,27 +2,27 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
coverage!=4.4 # Apache-2.0
doc8 # Apache-2.0
freezegun # Apache-2.0
coverage>=4.5.1 # Apache-2.0
doc8>=0.8.0 # Apache-2.0
freezegun>=0.3.10 # Apache-2.0
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
mock # BSD
oslotest # Apache-2.0
os-testr # Apache-2.0
testrepository # Apache-2.0/BSD
testscenarios # Apache-2.0/BSD
testtools # MIT
mock>=2.0.0 # BSD
oslotest>=3.3.0 # Apache-2.0
os-testr>=1.0.0 # Apache-2.0
testscenarios>=0.5.0 # Apache-2.0/BSD
testtools>=2.3.0 # MIT
stestr>=2.0.0 # Apache-2.0
# Doc requirements
openstackdocstheme # Apache-2.0
sphinx!=1.6.6,!=1.6.7 # BSD
sphinxcontrib-pecanwsme # Apache-2.0
openstackdocstheme>=1.20.0 # Apache-2.0
sphinx>=1.6.5,!=1.6.6,!=1.6.7 # BSD
sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0
# api-ref
os-api-ref # Apache-2.0
os-api-ref>=1.4.0 # Apache-2.0
# releasenotes
reno # Apache-2.0
reno>=2.7.0 # Apache-2.0
# bandit
bandit>=1.1.0 # Apache-2.0

25
tox.ini
View File

@@ -18,35 +18,55 @@ commands =
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
[testenv:pep8]
basepython = python3
commands =
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
flake8
bandit -r watcher -x tests -n5 -ll -s B320
[testenv:venv]
basepython = python3
setenv = PYTHONHASHSEED=0
commands = {posargs}
[testenv:cover]
basepython = python3
setenv =
PYTHON=coverage run --source watcher --parallel-mode
commands =
python setup.py testr --coverage --testr-args='{posargs}'
stestr run '{posargs}'
coverage combine
coverage html -d cover
coverage xml -o cover/coverage.xml
coverage report
[testenv:docs]
basepython = python3
setenv = PYTHONHASHSEED=0
commands =
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
python setup.py build_sphinx
[testenv:api-ref]
# This environment is called from CI scripts to test and publish
# the API Ref to developer.openstack.org.
whitelist_externals = bash
commands =
bash -c 'rm -rf api-ref/build'
sphinx-build -W -b html -d api-ref/build/doctrees api-ref/source api-ref/build/html
[testenv:debug]
basepython = python3
commands = oslo_debug_helper -t watcher/tests {posargs}
[testenv:genconfig]
basepython = python3
sitepackages = False
commands =
oslo-config-generator --config-file etc/watcher/oslo-config-generator/watcher.conf
[testenv:genpolicy]
basepython = python3
commands =
oslopolicy-sample-generator --config-file etc/watcher/oslo-policy-generator/watcher-policy-generator.conf
@@ -59,6 +79,7 @@ enable-extensions = H106,H203,H904
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,*sqlalchemy/alembic/versions/*,demo/,releasenotes
[testenv:wheel]
basepython = python3
commands = python setup.py bdist_wheel
[hacking]
@@ -71,9 +92,11 @@ extension=.rst
ignore-path=doc/source/image_src,doc/source/man,doc/source/api
[testenv:releasenotes]
basepython = python3
commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
[testenv:bandit]
basepython = python3
deps = -r{toxinidir}/test-requirements.txt
commands = bandit -r watcher -x tests -n5 -ll -s B320

View File

@@ -230,6 +230,9 @@ class ActionPlan(base.APIBase):
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated action links"""
hostname = wsme.wsattr(wtypes.text, mandatory=False)
"""Hostname the actionplan is running on"""
def __init__(self, **kwargs):
super(ActionPlan, self).__init__()
self.fields = []

View File

@@ -77,6 +77,8 @@ class AuditPostType(wtypes.Base):
auto_trigger = wtypes.wsattr(bool, mandatory=False)
hostname = wtypes.wsattr(wtypes.text, readonly=True, mandatory=False)
def as_audit(self, context):
audit_type_values = [val.value for val in objects.audit.AuditType]
if self.audit_type not in audit_type_values:
@@ -305,6 +307,9 @@ class Audit(base.APIBase):
next_run_time = wsme.wsattr(datetime.datetime, mandatory=False)
"""The next time audit launch"""
hostname = wsme.wsattr(wtypes.text, mandatory=False)
"""Hostname the audit is running on"""
def __init__(self, **kwargs):
self.fields = []
fields = list(objects.Audit.fields)

View File

@@ -51,8 +51,6 @@ import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from oslo_log import log
from watcher._i18n import _
from watcher.api.controllers import base
from watcher.api.controllers import link
@@ -66,8 +64,6 @@ from watcher.common import utils as common_utils
from watcher.decision_engine.loading import default as default_loading
from watcher import objects
LOG = log.getLogger(__name__)
class AuditTemplatePostType(wtypes.Base):
_ctx = context_utils.make_context()

View File

@@ -83,7 +83,7 @@ class ScoringEngine(base.APIBase):
def _convert_with_links(se, url, expand=True):
if not expand:
se.unset_fields_except(
['uuid', 'name', 'description', 'metainfo'])
['uuid', 'name', 'description'])
se.links = [link.Link.make_link('self', url,
'scoring_engines', se.uuid),

View File

@@ -292,7 +292,7 @@ class StrategiesController(rest.RestController):
@wsme_pecan.wsexpose(wtypes.text, wtypes.text)
def state(self, strategy):
"""Retrieve a inforamation about strategy requirements.
"""Retrieve an information about strategy requirements.
:param strategy: name of the strategy.
"""

View File

@@ -16,6 +16,7 @@
import datetime
import itertools
from oslo_config import cfg
from oslo_log import log
from oslo_utils import timeutils
@@ -40,6 +41,8 @@ class APISchedulingService(scheduling.BackgroundSchedulerService):
def get_services_status(self, context):
services = objects.service.Service.list(context)
active_s = objects.service.ServiceStatus.ACTIVE
failed_s = objects.service.ServiceStatus.FAILED
for service in services:
result = self.get_service_status(context, service.id)
if service.id not in self.services_status:
@@ -49,6 +52,32 @@ class APISchedulingService(scheduling.BackgroundSchedulerService):
self.services_status[service.id] = result
notifications.service.send_service_update(context, service,
state=result)
if result == failed_s:
audit_filters = {
'audit_type': objects.audit.AuditType.CONTINUOUS.value,
'state': objects.audit.State.ONGOING,
'hostname': service.host
}
ongoing_audits = objects.Audit.list(
context,
filters=audit_filters,
eager=True)
alive_services = [
s.host for s in services
if (self.services_status[s.id] == active_s and
s.name == 'watcher-decision-engine')]
round_robin = itertools.cycle(alive_services)
for audit in ongoing_audits:
audit.hostname = round_robin.__next__()
audit.save()
LOG.info('Audit %(audit)s has been migrated to '
'%(host)s since %(failed_host)s is in'
' %(state)s',
{'audit': audit.uuid,
'host': audit.hostname,
'failed_host': service.host,
'state': failed_s})
def get_service_status(self, context, service_id):
service = objects.Service.get(context, service_id)
@@ -80,7 +109,8 @@ class APISchedulingService(scheduling.BackgroundSchedulerService):
context = watcher_context.make_context(is_admin=True)
self.add_job(self.get_services_status, name='service_status',
trigger='interval', jobstore='default', args=[context],
next_run_time=datetime.datetime.now(), seconds=60)
next_run_time=datetime.datetime.now(),
seconds=CONF.periodic_interval)
super(APISchedulingService, self).start()
def stop(self):

View File

@@ -16,6 +16,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from oslo_config import cfg
from oslo_log import log
from watcher.applier.action_plan import base
@@ -25,6 +26,7 @@ from watcher import notifications
from watcher import objects
from watcher.objects import fields
CONF = cfg.CONF
LOG = log.getLogger(__name__)
@@ -43,6 +45,7 @@ class DefaultActionPlanHandler(base.BaseActionPlanHandler):
if action_plan.state == objects.action_plan.State.CANCELLED:
self._update_action_from_pending_to_cancelled()
return
action_plan.hostname = CONF.host
action_plan.state = objects.action_plan.State.ONGOING
action_plan.save()
notifications.action_plan.send_action_notification(

View File

@@ -126,7 +126,7 @@ class BaseAction(loadable.Loadable):
:returns: A schema declaring the input parameters this action should be
provided along with their respective constraints
:rtype: :py:class:`voluptuous.Schema` instance
:rtype: :py:class:`jsonschema.Schema` instance
"""
raise NotImplementedError()

View File

@@ -15,12 +15,19 @@
# limitations under the License.
#
from oslo_config import cfg
from oslo_log import log
from watcher.applier.loading import default
from watcher.common import context
from watcher.common import exception
from watcher import objects
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class Syncer(object):
"""Syncs all available actions with the Watcher DB"""
@@ -42,3 +49,27 @@ class Syncer(object):
obj_action_desc.action_type = action_type
obj_action_desc.description = load_description
obj_action_desc.create()
self._cancel_ongoing_actionplans(ctx)
def _cancel_ongoing_actionplans(self, context):
actions_plans = objects.ActionPlan.list(
context,
filters={'state': objects.action_plan.State.ONGOING,
'hostname': CONF.host},
eager=True)
for ap in actions_plans:
ap.state = objects.action_plan.State.CANCELLED
ap.save()
filters = {'action_plan_uuid': ap.uuid,
'state__in': (objects.action.State.PENDING,
objects.action.State.ONGOING)}
actions = objects.Action.list(context, filters=filters, eager=True)
for a in actions:
a.state = objects.action.State.CANCELLED
a.save()
LOG.info("Action Plan %(uuid)s along with appropriate Actions "
"has been cancelled because it was in %(state)s state "
"when Applier had been stopped on %(hostname)s host.",
{'uuid': ap.uuid,
'state': objects.action_plan.State.ONGOING,
'hostname': ap.hostname})

View File

@@ -78,7 +78,7 @@ rules = [
description='Start an action plans.',
operations=[
{
'path': '/v1/action_plans/{action_plan_uuid}/action',
'path': '/v1/action_plans/{action_plan_uuid}/start',
'method': 'POST'
}
]

View File

@@ -251,11 +251,18 @@ class Service(service.ServiceBase):
def build_notification_handler(self, topic_names, endpoints=()):
serializer = rpc.RequestContextSerializer(rpc.JsonPayloadSerializer())
targets = [om.Target(topic=topic_name) for topic_name in topic_names]
targets = []
for topic in topic_names:
kwargs = {}
if '.' in topic:
exchange, topic = topic.split('.')
kwargs['exchange'] = exchange
kwargs['topic'] = topic
targets.append(om.Target(**kwargs))
return om.get_notification_listener(
self.notification_transport, targets, endpoints,
executor='eventlet', serializer=serializer,
allow_requeue=False)
allow_requeue=False, pool=CONF.host)
def start(self):
LOG.debug("Connecting to '%s' (%s)",

View File

@@ -30,9 +30,12 @@ WATCHER_DECISION_ENGINE_OPTS = [
'control events, this topic '
'used for RPC calls'),
cfg.ListOpt('notification_topics',
default=['versioned_notifications', 'watcher_notifications'],
help='The topic names from which notification events '
'will be listened to'),
default=['nova.versioned_notifications',
'watcher.watcher_notifications'],
help='The exchange and topic names from which '
'notification events will be listened to. '
'The exchange should be specified to get '
'an ability to use pools.'),
cfg.StrOpt('publisher_id',
default='watcher.decision.api',
help='The identifier used by the Watcher '

View File

@@ -32,8 +32,7 @@ SERVICE_OPTS = [
help=_('Name of this node. This can be an opaque '
'identifier. It is not necessarily a hostname, '
'FQDN, or IP address. However, the node name '
'must be valid within an AMQP key, and if using '
'ZeroMQ, a valid hostname, FQDN, or IP address.')
'must be valid within an AMQP key.')
),
cfg.IntOpt('service_down_time',
default=90,

View File

@@ -13,16 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log
from watcher.common import exception
from watcher.datasource import base
from watcher.datasource import ceilometer as ceil
from watcher.datasource import gnocchi as gnoc
from watcher.datasource import monasca as mon
LOG = log.getLogger(__name__)
class DataSourceManager(object):

View File

@@ -0,0 +1,26 @@
"""Add hostname field to both Audit and Action Plan models
Revision ID: 52804f2498c4
Revises: a86240e89a29
Create Date: 2018-06-26 13:06:45.530387
"""
# revision identifiers, used by Alembic.
revision = '52804f2498c4'
down_revision = 'a86240e89a29'
from alembic import op
import sqlalchemy as sa
def upgrade():
for table in ('audits', 'action_plans'):
op.add_column(
table,
sa.Column('hostname', sa.String(length=255), nullable=True))
def downgrade():
for table in ('audits', 'action_plans'):
op.drop_column(table, 'hostname')

View File

@@ -19,9 +19,15 @@ def upgrade():
connection = op.get_bind()
session = sessionmaker()
s = session(bind=connection)
for audit in s.query(models.Audit).filter(models.Audit.name is None).all():
strategy_name = s.query(models.Strategy).filter_by(id=audit.strategy_id).one().name
audit.update({'name': strategy_name + '-' + str(audit.created_at)})
audits = s.query(
models.Audit.strategy_id.label('strategy_id'),
models.Audit.created_at.label('created_at')).filter(
models.Audit.name is None).all()
for audit in audits:
strategy_name = s.query(models.Strategy).filter_by(
id=audit.strategy_id).one().name
s.query().filter(models.Audit.name is None).update(
{'name': strategy_name + '-' + str(audit.created_at)})
s.commit()
@@ -29,6 +35,11 @@ def downgrade():
connection = op.get_bind()
session = sessionmaker()
s = session(bind=connection)
for audit in s.query(models.Audit).filter(models.Audit.name is not None).all():
audit.update({'name': None})
audits = s.query(
models.Audit.strategy_id.label('strategy_id'),
models.Audit.created_at.label('created_at')).filter(
models.Audit.name is not None).all()
for audit in audits:
s.query().filter(models.Audit.name is not None).update(
{'name': None})
s.commit()

View File

@@ -375,7 +375,7 @@ class Connection(api.BaseConnection):
filters = {}
plain_fields = ['uuid', 'audit_type', 'state', 'goal_id',
'strategy_id']
'strategy_id', 'hostname']
join_fieldmap = {
'goal_uuid': ("uuid", models.Goal),
'goal_name': ("name", models.Goal),

View File

@@ -181,6 +181,7 @@ class Audit(Base):
scope = Column(JSONEncodedList, nullable=True)
auto_trigger = Column(Boolean, nullable=False)
next_run_time = Column(DateTime, nullable=True)
hostname = Column(String(255), nullable=True)
goal = orm.relationship(Goal, foreign_keys=goal_id, lazy=None)
strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None)
@@ -200,6 +201,7 @@ class ActionPlan(Base):
strategy_id = Column(Integer, ForeignKey('strategies.id'), nullable=False)
state = Column(String(20), nullable=True)
global_efficacy = Column(JSONEncodedList, nullable=True)
hostname = Column(String(255), nullable=True)
audit = orm.relationship(Audit, foreign_keys=audit_id, lazy=None)
strategy = orm.relationship(Strategy, foreign_keys=strategy_id, lazy=None)

View File

@@ -20,6 +20,7 @@
import abc
import six
from oslo_config import cfg
from oslo_log import log
from watcher.applier import rpcapi
@@ -31,6 +32,7 @@ from watcher import notifications
from watcher import objects
from watcher.objects import fields
CONF = cfg.CONF
LOG = log.getLogger(__name__)
@@ -120,6 +122,8 @@ class AuditHandler(BaseAuditHandler):
def pre_execute(self, audit, request_context):
LOG.debug("Trigger audit %s", audit.uuid)
self.check_ongoing_action_plans(request_context)
# Write hostname that will execute this audit.
audit.hostname = CONF.host
# change state of the audit to ONGOING
self.update_audit_state(audit, objects.audit.State.ONGOING)

View File

@@ -59,13 +59,15 @@ class ContinuousAuditHandler(base.AuditHandler):
def _is_audit_inactive(self, audit):
audit = objects.Audit.get_by_uuid(
self.context_show_deleted, audit.uuid)
if objects.audit.AuditStateTransitionManager().is_inactive(audit):
if (objects.audit.AuditStateTransitionManager().is_inactive(audit) or
audit.hostname != CONF.host):
# if audit isn't in active states, audit's job must be removed to
# prevent using of inactive audit in future.
if self.scheduler.get_jobs():
[job for job in self.scheduler.get_jobs()
if job.name == 'execute_audit' and
job.args[0].uuid == audit.uuid][0].remove()
jobs = [job for job in self.scheduler.get_jobs()
if job.name == 'execute_audit' and
job.args[0].uuid == audit.uuid]
if jobs:
jobs[0].remove()
return True
return False
@@ -123,19 +125,32 @@ class ContinuousAuditHandler(base.AuditHandler):
'audit_type': objects.audit.AuditType.CONTINUOUS.value,
'state__in': (objects.audit.State.PENDING,
objects.audit.State.ONGOING,
objects.audit.State.SUCCEEDED)
objects.audit.State.SUCCEEDED),
}
audits = objects.Audit.list(
audit_filters['hostname'] = None
unscheduled_audits = objects.Audit.list(
audit_context, filters=audit_filters, eager=True)
for audit in unscheduled_audits:
# If continuous audit doesn't have a hostname yet,
# Watcher will set current CONF.host value.
# TODO(alexchadin): Add scheduling of new continuous audits.
audit.hostname = CONF.host
audit.save()
scheduler_job_args = [
(job.args[0].uuid, job) for job
in self.scheduler.get_jobs()
if job.name == 'execute_audit']
scheduler_jobs = dict(scheduler_job_args)
# if audit isn't in active states, audit's job should be removed
jobs_to_remove = []
for job in scheduler_jobs.values():
if self._is_audit_inactive(job.args[0]):
scheduler_jobs.pop(job.args[0].uuid)
jobs_to_remove.append(job.args[0].uuid)
for audit_uuid in jobs_to_remove:
scheduler_jobs.pop(audit_uuid)
audit_filters['hostname'] = CONF.host
audits = objects.Audit.list(
audit_context, filters=audit_filters, eager=True)
for audit in audits:
existing_job = scheduler_jobs.get(audit.uuid, None)
# if audit is not presented in scheduled audits yet,
@@ -172,6 +187,7 @@ class ContinuousAuditHandler(base.AuditHandler):
audit.next_run_time = self._next_cron_time(audit)
self._add_job('date', audit, audit_context,
run_date=audit.next_run_time)
audit.hostname = CONF.host
audit.save()
def start(self):

View File

@@ -24,10 +24,10 @@ calculating its :ref:`global efficacy <efficacy_definition>`.
"""
import abc
import jsonschema
from oslo_serialization import jsonutils
import six
import voluptuous
@six.add_metaclass(abc.ABCMeta)
@@ -65,17 +65,21 @@ class EfficacySpecification(object):
@property
def schema(self):
"""Combined schema from the schema of the indicators"""
schema = voluptuous.Schema({}, required=True)
schema = {
"type": "object",
"properties": {},
"required": []
}
for indicator in self.indicators_specs:
key_constraint = (voluptuous.Required
if indicator.required else voluptuous.Optional)
schema = schema.extend(
{key_constraint(indicator.name): indicator.schema.schema})
schema["properties"][indicator.name] = indicator.schema
schema["required"].append(indicator.name)
return schema
def validate_efficacy_indicators(self, indicators_map):
return self.schema(indicators_map)
if indicators_map:
jsonschema.validate(indicators_map, self.schema)
else:
True
def get_indicators_specs_dicts(self):
return [indicator.to_dict()

View File

@@ -15,10 +15,13 @@
# limitations under the License.
import abc
import jsonschema
from jsonschema import SchemaError
from jsonschema import ValidationError
import six
from oslo_log import log
import voluptuous
from oslo_serialization import jsonutils
from watcher._i18n import _
from watcher.common import exception
@@ -37,10 +40,9 @@ class IndicatorSpecification(object):
@abc.abstractproperty
def schema(self):
"""Schema used to validate the indicator value
"""JsonSchema used to validate the indicator value
:return: A Voplutuous Schema
:rtype: :py:class:`.voluptuous.Schema` instance
:return: A Schema
"""
raise NotImplementedError()
@@ -54,7 +56,10 @@ class IndicatorSpecification(object):
value = None
try:
value = getattr(solution, indicator.name)
indicator.schema(value)
jsonschema.validate(value, cls.schema)
except (SchemaError, ValidationError) as exc:
LOG.exception(exc)
raise exc
except Exception as exc:
LOG.exception(exc)
raise exception.InvalidIndicatorValue(
@@ -65,7 +70,7 @@ class IndicatorSpecification(object):
"name": self.name,
"description": self.description,
"unit": self.unit,
"schema": str(self.schema.schema) if self.schema else None,
"schema": jsonutils.dumps(self.schema) if self.schema else None,
}
def __str__(self):
@@ -82,8 +87,10 @@ class ComputeNodesCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class ReleasedComputeNodesCount(IndicatorSpecification):
@@ -96,8 +103,10 @@ class ReleasedComputeNodesCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class InstanceMigrationsCount(IndicatorSpecification):
@@ -110,8 +119,10 @@ class InstanceMigrationsCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class LiveInstanceMigrateCount(IndicatorSpecification):
@@ -124,8 +135,10 @@ class LiveInstanceMigrateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class PlannedLiveInstanceMigrateCount(IndicatorSpecification):
@@ -138,8 +151,10 @@ class PlannedLiveInstanceMigrateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class ColdInstanceMigrateCount(IndicatorSpecification):
@@ -152,8 +167,10 @@ class ColdInstanceMigrateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class PlannedColdInstanceMigrateCount(IndicatorSpecification):
@@ -166,8 +183,10 @@ class PlannedColdInstanceMigrateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class VolumeMigrateCount(IndicatorSpecification):
@@ -180,8 +199,10 @@ class VolumeMigrateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class PlannedVolumeMigrateCount(IndicatorSpecification):
@@ -195,8 +216,10 @@ class PlannedVolumeMigrateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class VolumeUpdateCount(IndicatorSpecification):
@@ -210,8 +233,10 @@ class VolumeUpdateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}
class PlannedVolumeUpdateCount(IndicatorSpecification):
@@ -225,5 +250,7 @@ class PlannedVolumeUpdateCount(IndicatorSpecification):
@property
def schema(self):
return voluptuous.Schema(
voluptuous.Range(min=0), required=True)
return {
"type": "integer",
"minimum": 0
}

View File

@@ -88,10 +88,31 @@ class DecisionEngineSchedulingService(scheduling.BackgroundSchedulerService):
seconds=interval,
next_run_time=datetime.datetime.now())
def cancel_ongoing_audits(self):
audit_filters = {
'audit_type': objects.audit.AuditType.ONESHOT.value,
'state': objects.audit.State.ONGOING,
'hostname': CONF.host
}
local_context = context.make_context()
ongoing_audits = objects.Audit.list(
local_context,
filters=audit_filters)
for audit in ongoing_audits:
audit.state = objects.audit.State.CANCELLED
audit.save()
LOG.info("Audit %(uuid)s has been cancelled because it was in "
"%(state)s state when Decision Engine had been stopped "
"on %(hostname)s host.",
{'uuid': audit.uuid,
'state': objects.audit.State.ONGOING,
'hostname': audit.hostname})
def start(self):
"""Start service."""
self.add_sync_jobs()
self.add_checkstate_job()
self.cancel_ongoing_audits()
super(DecisionEngineSchedulingService, self).start()
def stop(self):

View File

@@ -14,14 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log
from watcher.decision_engine.scope import base
LOG = log.getLogger(__name__)
class BaremetalScope(base.BaseScope):
"""Baremetal Audit Scope Handler"""

View File

@@ -11,16 +11,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log
from watcher.common import cinder_helper
from watcher.common import exception
from watcher.decision_engine.scope import base
LOG = log.getLogger(__name__)
class StorageScope(base.BaseScope):
"""Storage Audit Scope Handler"""

View File

@@ -69,7 +69,7 @@ class DummyWithScorer(base.DummyBaseStrategy):
self._avg_scorer = (scoring_factory
.get_scoring_engine('dummy_avg_scorer'))
# Get metainfo from Workload Scorer for result intepretation
# Get metainfo from Workload Scorer for result interpretation
metainfo = jsonutils.loads(self._workload_scorer.get_metainfo())
self._workloads = {index: workload
for index, workload in enumerate(

View File

@@ -1,331 +1,331 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 chinac.com
#
# Authors: suzhengwei<suzhengwei@chinac.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from oslo_log import log
import six
from watcher._i18n import _
from watcher.common import exception as wexc
from watcher.decision_engine.model import element
from watcher.decision_engine.strategy.strategies import base
LOG = log.getLogger(__name__)
class HostMaintenance(base.HostMaintenanceBaseStrategy):
"""[PoC]Host Maintenance
*Description*
It is a migration strategy for one compute node maintenance,
without having the user's application been interruptted.
If given one backup node, the strategy will firstly
migrate all instances from the maintenance node to
the backup node. If the backup node is not provided,
it will migrate all instances, relying on nova-scheduler.
*Requirements*
* You must have at least 2 physical compute nodes to run this strategy.
*Limitations*
- This is a proof of concept that is not meant to be used in production
- It migrates all instances from one host to other hosts. It's better to
execute such strategy when load is not heavy, and use this algorithm
with `ONESHOT` audit.
- It assume that cold and live migrations are possible
"""
INSTANCE_MIGRATION = "migrate"
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
REASON_FOR_DISABLE = 'watcher_disabled'
def __init__(self, config, osc=None):
super(HostMaintenance, self).__init__(config, osc)
@classmethod
def get_name(cls):
return "host_maintenance"
@classmethod
def get_display_name(cls):
return _("Host Maintenance Strategy")
@classmethod
def get_translatable_display_name(cls):
return "Host Maintenance Strategy"
@classmethod
def get_schema(cls):
return {
"properties": {
"maintenance_node": {
"description": "The name of the compute node which "
"need maintenance",
"type": "string",
},
"backup_node": {
"description": "The name of the compute node which "
"will backup the maintenance node.",
"type": "string",
},
},
"required": ["maintenance_node"],
}
def get_disabled_compute_nodes_with_reason(self, reason=None):
return {uuid: cn for uuid, cn in
self.compute_model.get_all_compute_nodes().items()
if cn.state == element.ServiceState.ONLINE.value and
cn.status == element.ServiceState.DISABLED.value and
cn.disabled_reason == reason}
def get_disabled_compute_nodes(self):
return self.get_disabled_compute_nodes_with_reason(
self.REASON_FOR_DISABLE)
def get_instance_state_str(self, instance):
"""Get instance state in string format"""
if isinstance(instance.state, six.string_types):
return instance.state
elif isinstance(instance.state, element.InstanceState):
return instance.state.value
else:
LOG.error('Unexpected instance state type, '
'state=%(state)s, state_type=%(st)s.',
dict(state=instance.state,
st=type(instance.state)))
raise wexc.WatcherException
def get_node_status_str(self, node):
"""Get node status in string format"""
if isinstance(node.status, six.string_types):
return node.status
elif isinstance(node.status, element.ServiceState):
return node.status.value
else:
LOG.error('Unexpected node status type, '
'status=%(status)s, status_type=%(st)s.',
dict(status=node.status,
st=type(node.status)))
raise wexc.WatcherException
def get_node_capacity(self, node):
"""Collect cpu, ram and disk capacity of a node.
:param node: node object
:return: dict(cpu(cores), ram(MB), disk(B))
"""
return dict(cpu=node.vcpus,
ram=node.memory,
disk=node.disk_capacity)
def get_node_used(self, node):
"""Collect cpu, ram and disk used of a node.
:param node: node object
:return: dict(cpu(cores), ram(MB), disk(B))
"""
vcpus_used = 0
memory_used = 0
disk_used = 0
for instance in self.compute_model.get_node_instances(node):
vcpus_used += instance.vcpus
memory_used += instance.memory
disk_used += instance.disk
return dict(cpu=vcpus_used,
ram=memory_used,
disk=disk_used)
def get_node_free(self, node):
"""Collect cpu, ram and disk free of a node.
:param node: node object
:return: dict(cpu(cores), ram(MB), disk(B))
"""
node_capacity = self.get_node_capacity(node)
node_used = self.get_node_used(node)
return dict(cpu=node_capacity['cpu']-node_used['cpu'],
ram=node_capacity['ram']-node_used['ram'],
disk=node_capacity['disk']-node_used['disk'],
)
def host_fits(self, source_node, destination_node):
"""check host fits
return True if VMs could intensively migrate
from source_node to destination_node.
"""
source_node_used = self.get_node_used(source_node)
destination_node_free = self.get_node_free(destination_node)
metrics = ['cpu', 'ram']
for m in metrics:
if source_node_used[m] > destination_node_free[m]:
return False
return True
def add_action_enable_compute_node(self, node):
"""Add an action for node enabler into the solution."""
params = {'state': element.ServiceState.ENABLED.value}
self.solution.add_action(
action_type=self.CHANGE_NOVA_SERVICE_STATE,
resource_id=node.uuid,
input_parameters=params)
def add_action_maintain_compute_node(self, node):
"""Add an action for node maintenance into the solution."""
params = {'state': element.ServiceState.DISABLED.value,
'disabled_reason': self.REASON_FOR_MAINTAINING}
self.solution.add_action(
action_type=self.CHANGE_NOVA_SERVICE_STATE,
resource_id=node.uuid,
input_parameters=params)
def enable_compute_node_if_disabled(self, node):
node_status_str = self.get_node_status_str(node)
if node_status_str != element.ServiceState.ENABLED.value:
self.add_action_enable_compute_node(node)
def instance_migration(self, instance, src_node, des_node=None):
"""Add an action for instance migration into the solution.
:param instance: instance object
:param src_node: node object
:param des_node: node object. if None, the instance will be
migrated relying on nova-scheduler
:return: None
"""
instance_state_str = self.get_instance_state_str(instance)
if instance_state_str == element.InstanceState.ACTIVE.value:
migration_type = 'live'
else:
migration_type = 'cold'
params = {'migration_type': migration_type,
'source_node': src_node.uuid}
if des_node:
params['destination_node'] = des_node.uuid
self.solution.add_action(action_type=self.INSTANCE_MIGRATION,
resource_id=instance.uuid,
input_parameters=params)
def host_migration(self, source_node, destination_node):
"""host migration
Migrate all instances from source_node to destination_node.
Active instances use "live-migrate",
and other instances use "cold-migrate"
"""
instances = self.compute_model.get_node_instances(source_node)
for instance in instances:
self.instance_migration(instance, source_node, destination_node)
def safe_maintain(self, maintenance_node, backup_node=None):
"""safe maintain one compute node
Migrate all instances of the maintenance_node intensively to the
backup host. If users didn't give the backup host, it will select
one unused node to backup the maintaining node.
It calculate the resource both of the backup node and maintaining
node to evaluate the migrations from maintaining node to backup node.
If all instances of the maintaining node can migrated to
the backup node, it will set the maintaining node in
'watcher_maintaining' status., and add the migrations to solution.
"""
# If user gives a backup node with required capacity, then migrate
# all instances from the maintaining node to the backup node.
if backup_node:
if self.host_fits(maintenance_node, backup_node):
self.enable_compute_node_if_disabled(backup_node)
self.add_action_maintain_compute_node(maintenance_node)
self.host_migration(maintenance_node, backup_node)
return True
# If uses didn't give the backup host, select one unused node
# with required capacity, then migrate all instances
# from maintaining node to it.
nodes = sorted(
self.get_disabled_compute_nodes().values(),
key=lambda x: self.get_node_capacity(x)['cpu'])
if maintenance_node in nodes:
nodes.remove(maintenance_node)
for node in nodes:
if self.host_fits(maintenance_node, node):
self.enable_compute_node_if_disabled(node)
self.add_action_maintain_compute_node(maintenance_node)
self.host_migration(maintenance_node, node)
return True
return False
def try_maintain(self, maintenance_node):
"""try to maintain one compute node
It firstly set the maintenance_node in 'watcher_maintaining' status.
Then try to migrate all instances of the maintenance node, rely
on nova-scheduler.
"""
self.add_action_maintain_compute_node(maintenance_node)
instances = self.compute_model.get_node_instances(maintenance_node)
for instance in instances:
self.instance_migration(instance, maintenance_node)
def pre_execute(self):
LOG.debug(self.compute_model.to_string())
if not self.compute_model:
raise wexc.ClusterStateNotDefined()
if self.compute_model.stale:
raise wexc.ClusterStateStale()
def do_execute(self):
LOG.info(_('Executing Host Maintenance Migration Strategy'))
maintenance_node = self.input_parameters.get('maintenance_node')
backup_node = self.input_parameters.get('backup_node')
# if no VMs in the maintenance_node, just maintain the compute node
src_node = self.compute_model.get_node_by_uuid(maintenance_node)
if len(self.compute_model.get_node_instances(src_node)) == 0:
if (src_node.disabled_reason !=
self.REASON_FOR_MAINTAINING):
self.add_action_maintain_compute_node(src_node)
return
if backup_node:
des_node = self.compute_model.get_node_by_uuid(backup_node)
else:
des_node = None
if not self.safe_maintain(src_node, des_node):
self.try_maintain(src_node)
def post_execute(self):
"""Post-execution phase
This can be used to compute the global efficacy
"""
LOG.debug(self.solution.actions)
LOG.debug(self.compute_model.to_string())
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 chinac.com
#
# Authors: suzhengwei<suzhengwei@chinac.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from oslo_log import log
import six
from watcher._i18n import _
from watcher.common import exception as wexc
from watcher.decision_engine.model import element
from watcher.decision_engine.strategy.strategies import base
LOG = log.getLogger(__name__)
class HostMaintenance(base.HostMaintenanceBaseStrategy):
"""[PoC]Host Maintenance
*Description*
It is a migration strategy for one compute node maintenance,
without having the user's application been interruptted.
If given one backup node, the strategy will firstly
migrate all instances from the maintenance node to
the backup node. If the backup node is not provided,
it will migrate all instances, relying on nova-scheduler.
*Requirements*
* You must have at least 2 physical compute nodes to run this strategy.
*Limitations*
- This is a proof of concept that is not meant to be used in production
- It migrates all instances from one host to other hosts. It's better to
execute such strategy when load is not heavy, and use this algorithm
with `ONESHOT` audit.
- It assume that cold and live migrations are possible
"""
INSTANCE_MIGRATION = "migrate"
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
REASON_FOR_DISABLE = 'watcher_disabled'
def __init__(self, config, osc=None):
super(HostMaintenance, self).__init__(config, osc)
@classmethod
def get_name(cls):
return "host_maintenance"
@classmethod
def get_display_name(cls):
return _("Host Maintenance Strategy")
@classmethod
def get_translatable_display_name(cls):
return "Host Maintenance Strategy"
@classmethod
def get_schema(cls):
return {
"properties": {
"maintenance_node": {
"description": "The name of the compute node which "
"need maintenance",
"type": "string",
},
"backup_node": {
"description": "The name of the compute node which "
"will backup the maintenance node.",
"type": "string",
},
},
"required": ["maintenance_node"],
}
def get_disabled_compute_nodes_with_reason(self, reason=None):
return {uuid: cn for uuid, cn in
self.compute_model.get_all_compute_nodes().items()
if cn.state == element.ServiceState.ONLINE.value and
cn.status == element.ServiceState.DISABLED.value and
cn.disabled_reason == reason}
def get_disabled_compute_nodes(self):
return self.get_disabled_compute_nodes_with_reason(
self.REASON_FOR_DISABLE)
def get_instance_state_str(self, instance):
"""Get instance state in string format"""
if isinstance(instance.state, six.string_types):
return instance.state
elif isinstance(instance.state, element.InstanceState):
return instance.state.value
else:
LOG.error('Unexpected instance state type, '
'state=%(state)s, state_type=%(st)s.',
dict(state=instance.state,
st=type(instance.state)))
raise wexc.WatcherException
def get_node_status_str(self, node):
"""Get node status in string format"""
if isinstance(node.status, six.string_types):
return node.status
elif isinstance(node.status, element.ServiceState):
return node.status.value
else:
LOG.error('Unexpected node status type, '
'status=%(status)s, status_type=%(st)s.',
dict(status=node.status,
st=type(node.status)))
raise wexc.WatcherException
def get_node_capacity(self, node):
"""Collect cpu, ram and disk capacity of a node.
:param node: node object
:return: dict(cpu(cores), ram(MB), disk(B))
"""
return dict(cpu=node.vcpus,
ram=node.memory,
disk=node.disk_capacity)
def get_node_used(self, node):
"""Collect cpu, ram and disk used of a node.
:param node: node object
:return: dict(cpu(cores), ram(MB), disk(B))
"""
vcpus_used = 0
memory_used = 0
disk_used = 0
for instance in self.compute_model.get_node_instances(node):
vcpus_used += instance.vcpus
memory_used += instance.memory
disk_used += instance.disk
return dict(cpu=vcpus_used,
ram=memory_used,
disk=disk_used)
def get_node_free(self, node):
"""Collect cpu, ram and disk free of a node.
:param node: node object
:return: dict(cpu(cores), ram(MB), disk(B))
"""
node_capacity = self.get_node_capacity(node)
node_used = self.get_node_used(node)
return dict(cpu=node_capacity['cpu']-node_used['cpu'],
ram=node_capacity['ram']-node_used['ram'],
disk=node_capacity['disk']-node_used['disk'],
)
def host_fits(self, source_node, destination_node):
"""check host fits
return True if VMs could intensively migrate
from source_node to destination_node.
"""
source_node_used = self.get_node_used(source_node)
destination_node_free = self.get_node_free(destination_node)
metrics = ['cpu', 'ram']
for m in metrics:
if source_node_used[m] > destination_node_free[m]:
return False
return True
def add_action_enable_compute_node(self, node):
"""Add an action for node enabler into the solution."""
params = {'state': element.ServiceState.ENABLED.value}
self.solution.add_action(
action_type=self.CHANGE_NOVA_SERVICE_STATE,
resource_id=node.uuid,
input_parameters=params)
def add_action_maintain_compute_node(self, node):
"""Add an action for node maintenance into the solution."""
params = {'state': element.ServiceState.DISABLED.value,
'disabled_reason': self.REASON_FOR_MAINTAINING}
self.solution.add_action(
action_type=self.CHANGE_NOVA_SERVICE_STATE,
resource_id=node.uuid,
input_parameters=params)
def enable_compute_node_if_disabled(self, node):
node_status_str = self.get_node_status_str(node)
if node_status_str != element.ServiceState.ENABLED.value:
self.add_action_enable_compute_node(node)
def instance_migration(self, instance, src_node, des_node=None):
"""Add an action for instance migration into the solution.
:param instance: instance object
:param src_node: node object
:param des_node: node object. if None, the instance will be
migrated relying on nova-scheduler
:return: None
"""
instance_state_str = self.get_instance_state_str(instance)
if instance_state_str == element.InstanceState.ACTIVE.value:
migration_type = 'live'
else:
migration_type = 'cold'
params = {'migration_type': migration_type,
'source_node': src_node.uuid}
if des_node:
params['destination_node'] = des_node.uuid
self.solution.add_action(action_type=self.INSTANCE_MIGRATION,
resource_id=instance.uuid,
input_parameters=params)
def host_migration(self, source_node, destination_node):
"""host migration
Migrate all instances from source_node to destination_node.
Active instances use "live-migrate",
and other instances use "cold-migrate"
"""
instances = self.compute_model.get_node_instances(source_node)
for instance in instances:
self.instance_migration(instance, source_node, destination_node)
def safe_maintain(self, maintenance_node, backup_node=None):
"""safe maintain one compute node
Migrate all instances of the maintenance_node intensively to the
backup host. If users didn't give the backup host, it will select
one unused node to backup the maintaining node.
It calculate the resource both of the backup node and maintaining
node to evaluate the migrations from maintaining node to backup node.
If all instances of the maintaining node can migrated to
the backup node, it will set the maintaining node in
'watcher_maintaining' status., and add the migrations to solution.
"""
# If user gives a backup node with required capacity, then migrate
# all instances from the maintaining node to the backup node.
if backup_node:
if self.host_fits(maintenance_node, backup_node):
self.enable_compute_node_if_disabled(backup_node)
self.add_action_maintain_compute_node(maintenance_node)
self.host_migration(maintenance_node, backup_node)
return True
# If uses didn't give the backup host, select one unused node
# with required capacity, then migrate all instances
# from maintaining node to it.
nodes = sorted(
self.get_disabled_compute_nodes().values(),
key=lambda x: self.get_node_capacity(x)['cpu'])
if maintenance_node in nodes:
nodes.remove(maintenance_node)
for node in nodes:
if self.host_fits(maintenance_node, node):
self.enable_compute_node_if_disabled(node)
self.add_action_maintain_compute_node(maintenance_node)
self.host_migration(maintenance_node, node)
return True
return False
def try_maintain(self, maintenance_node):
"""try to maintain one compute node
It firstly set the maintenance_node in 'watcher_maintaining' status.
Then try to migrate all instances of the maintenance node, rely
on nova-scheduler.
"""
self.add_action_maintain_compute_node(maintenance_node)
instances = self.compute_model.get_node_instances(maintenance_node)
for instance in instances:
self.instance_migration(instance, maintenance_node)
def pre_execute(self):
LOG.debug(self.compute_model.to_string())
if not self.compute_model:
raise wexc.ClusterStateNotDefined()
if self.compute_model.stale:
raise wexc.ClusterStateStale()
def do_execute(self):
LOG.info(_('Executing Host Maintenance Migration Strategy'))
maintenance_node = self.input_parameters.get('maintenance_node')
backup_node = self.input_parameters.get('backup_node')
# if no VMs in the maintenance_node, just maintain the compute node
src_node = self.compute_model.get_node_by_uuid(maintenance_node)
if len(self.compute_model.get_node_instances(src_node)) == 0:
if (src_node.disabled_reason !=
self.REASON_FOR_MAINTAINING):
self.add_action_maintain_compute_node(src_node)
return
if backup_node:
des_node = self.compute_model.get_node_by_uuid(backup_node)
else:
des_node = None
if not self.safe_maintain(src_node, des_node):
self.try_maintain(src_node)
def post_execute(self):
"""Post-execution phase
This can be used to compute the global efficacy
"""
LOG.debug(self.solution.actions)
LOG.debug(self.compute_model.to_string())

View File

@@ -28,6 +28,27 @@ CONF = cfg.CONF
class NoisyNeighbor(base.NoisyNeighborBaseStrategy):
"""Noisy Neighbor strategy using live migration
*Description*
This strategy can identify and migrate a Noisy Neighbor -
a low priority VM that negatively affects performance of
a high priority VM in terms of IPC by over utilizing
Last Level Cache.
*Requirements*
To enable LLC metric, latest Intel server with CMT support is required.
*Limitations*
This is a proof of concept that is not meant to be used in production
*Spec URL*
http://specs.openstack.org/openstack/watcher-specs/specs/pike/implemented/noisy_neighbor_strategy.html
"""
MIGRATION = "migrate"
@@ -38,14 +59,6 @@ class NoisyNeighbor(base.NoisyNeighborBaseStrategy):
DEFAULT_WATCHER_PRIORITY = 5
def __init__(self, config, osc=None):
"""Noisy Neighbor strategy using live migration
:param config: A mapping containing the configuration of this strategy
:type config: dict
:param osc: an OpenStackClients object, defaults to None
:type osc: :py:class:`~.OpenStackClients` instance, optional
"""
super(NoisyNeighbor, self).__init__(config, osc)
self.meter_name = self.METER_NAME_L3

View File

@@ -106,7 +106,8 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
# Version 1.2: audit_id is not nullable anymore
# Version 2.0: Removed 'first_action_id' object field
# Version 2.1: Changed global_efficacy type
VERSION = '2.1'
# Version 2.2: Added 'hostname' field
VERSION = '2.2'
dbapi = db_api.get_instance()
@@ -117,6 +118,7 @@ class ActionPlan(base.WatcherPersistentObject, base.WatcherObject,
'strategy_id': wfields.IntegerField(),
'state': wfields.StringField(nullable=True),
'global_efficacy': wfields.FlexibleListOfDictField(nullable=True),
'hostname': wfields.StringField(nullable=True),
'audit': wfields.ObjectField('Audit', nullable=True),
'strategy': wfields.ObjectField('Strategy', nullable=True),

View File

@@ -87,7 +87,8 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
# Version 1.3: Added 'next_run_time' DateTime field,
# 'interval' type has been changed from Integer to String
# Version 1.4: Added 'name' string field
VERSION = '1.4'
# Version 1.5: Added 'hostname' field
VERSION = '1.5'
dbapi = db_api.get_instance()
@@ -105,6 +106,7 @@ class Audit(base.WatcherPersistentObject, base.WatcherObject,
'auto_trigger': wfields.BooleanField(),
'next_run_time': wfields.DateTimeField(nullable=True,
tzinfo_aware=False),
'hostname': wfields.StringField(nullable=True),
'goal': wfields.ObjectField('Goal', nullable=True),
'strategy': wfields.ObjectField('Strategy', nullable=True),

View File

@@ -497,6 +497,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
@@ -540,6 +541,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
# Make the audit template UUID some garbage value
audit_dict['audit_template_uuid'] = (
'01234567-8910-1112-1314-151617181920')
@@ -563,6 +565,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
with mock.patch.object(self.dbapi, 'create_audit',
wraps=self.dbapi.create_audit) as cn_mock:
response = self.post_json('/audits', audit_dict)
@@ -581,6 +584,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict)
self.assertEqual('application/json', response.content_type)
@@ -598,6 +602,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = '1200'
@@ -619,6 +624,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = '* * * * *'
@@ -640,6 +646,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['audit_type'] = objects.audit.AuditType.CONTINUOUS.value
audit_dict['interval'] = 'zxc'
@@ -662,6 +669,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int)
@@ -681,6 +689,7 @@ class TestPost(api_base.FunctionalTest):
audit_dict['audit_type'] = objects.audit.AuditType.ONESHOT.value
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual(400, response.status_int)
@@ -698,6 +707,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict)
de_mock.assert_called_once_with(mock.ANY, response.json['uuid'])
@@ -722,6 +732,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
@@ -744,6 +755,7 @@ class TestPost(api_base.FunctionalTest):
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
response = self.post_json('/audits', audit_dict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
@@ -766,7 +778,7 @@ class TestPost(api_base.FunctionalTest):
audit_dict['audit_template_uuid'] = audit_template['uuid']
del_keys = ['uuid', 'goal_id', 'strategy_id', 'state', 'interval',
'scope', 'next_run_time']
'scope', 'next_run_time', 'hostname']
for k in del_keys:
del audit_dict[k]
@@ -822,12 +834,13 @@ class TestPost(api_base.FunctionalTest):
audit_dict = post_get_test_audit()
normal_name = 'this audit name is just for test'
# long_name length exceeds 63 characters
long_name = normal_name+audit_dict['uuid']
long_name = normal_name + audit_dict['uuid']
del audit_dict['uuid']
del audit_dict['state']
del audit_dict['interval']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
audit_dict['name'] = normal_name
response = self.post_json('/audits', audit_dict)
@@ -954,6 +967,7 @@ class TestAuditPolicyEnforcement(api_base.FunctionalTest):
del audit_dict['state']
del audit_dict['scope']
del audit_dict['next_run_time']
del audit_dict['hostname']
self._common_policy_check(
"audit:create", self.post_json, '/audits', audit_dict,
expect_errors=True)

View File

@@ -148,7 +148,7 @@ class TestGoalPolicyEnforcement(api_base.FunctionalTest):
self.policy.set_rules({
"admin_api": "(role:admin or role:administrator)",
"default": "rule:admin_api",
rule: "rule:defaut"})
rule: "rule:default"})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)

View File

@@ -21,7 +21,7 @@ from watcher.tests.objects import utils as obj_utils
class TestListScoringEngine(api_base.FunctionalTest):
def _assert_scoring_engine_fields(self, scoring_engine):
scoring_engine_fields = ['uuid', 'name', 'description', 'metainfo']
scoring_engine_fields = ['uuid', 'name', 'description']
for field in scoring_engine_fields:
self.assertIn(field, scoring_engine)
@@ -140,7 +140,7 @@ class TestScoringEnginePolicyEnforcement(api_base.FunctionalTest):
self.policy.set_rules({
"admin_api": "(role:admin or role:administrator)",
"default": "rule:admin_api",
rule: "rule:defaut"})
rule: "rule:default"})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)

View File

@@ -0,0 +1,86 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2018 SBCloud
#
# Authors: Alexander Chadin <aschadin@sbcloud.ru>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
from oslo_config import cfg
from oslo_utils import uuidutils
from watcher.applier import sync
from watcher.decision_engine.strategy.strategies import dummy_strategy
from watcher.tests.db import base as db_base
from watcher import notifications
from watcher import objects
from watcher.tests.objects import utils as obj_utils
class TestCancelOngoingActionPlans(db_base.DbTestCase):
def setUp(self):
super(TestCancelOngoingActionPlans, self).setUp()
p_audit_notifications = mock.patch.object(
notifications, 'audit', autospec=True)
self.m_audit_notifications = p_audit_notifications.start()
self.addCleanup(p_audit_notifications.stop)
self.goal = obj_utils.create_test_goal(
self.context, id=1, name=dummy_strategy.DummyStrategy.get_name())
self.strategy = obj_utils.create_test_strategy(
self.context, name=dummy_strategy.DummyStrategy.get_name(),
goal_id=self.goal.id)
audit_template = obj_utils.create_test_audit_template(
self.context, strategy_id=self.strategy.id)
self.audit = obj_utils.create_test_audit(
self.context,
id=999,
name='My Audit 999',
uuid=uuidutils.generate_uuid(),
audit_template_id=audit_template.id,
goal_id=self.goal.id,
audit_type=objects.audit.AuditType.ONESHOT.value,
goal=self.goal,
hostname='hostname1',
state=objects.audit.State.ONGOING)
self.actionplan = obj_utils.create_test_action_plan(
self.context,
state=objects.action_plan.State.ONGOING,
audit_id=999,
hostname='hostname1')
self.action = obj_utils.create_test_action(
self.context,
action_plan_id=1,
state=objects.action.State.PENDING)
cfg.CONF.set_override("host", "hostname1")
@mock.patch.object(objects.action.Action, 'save')
@mock.patch.object(objects.action_plan.ActionPlan, 'save')
@mock.patch.object(objects.action.Action, 'list')
@mock.patch.object(objects.action_plan.ActionPlan, 'list')
def test_cancel_ongoing_actionplans(self, m_plan_list, m_action_list,
m_plan_save, m_action_save):
m_plan_list.return_value = [self.actionplan]
m_action_list.return_value = [self.action]
syncer = sync.Syncer()
syncer._cancel_ongoing_actionplans(self.context)
m_plan_list.assert_called()
m_action_list.assert_called()
m_plan_save.assert_called()
m_action_save.assert_called()
self.assertEqual(self.action.state, objects.audit.State.CANCELLED)

Some files were not shown because too many files have changed in this diff Show More