Compare commits

..

26 Commits

Author SHA1 Message Date
Thierry Carrez
e206bc474c Move queue declaration to project level
This moves the watcher queue declaration from the pipeline level
(where it is no longer valid) to the project level.

https: //lists.openstack.org/pipermail/openstack-discuss/2022-May/028603.html
Change-Id: I06923abb00f7eecd59587f44cd1f6a069e88a9fc
(cherry picked from commit 6003322711)
2023-08-19 07:31:12 +00:00
Ghanshyam Mann
ba394096fd Make greande jobs n-v for EM and oldest stable
As discussed in ML thread[1], we are going to
make grenade jobs as non voting for all EM stable and
oldest stable. grenade jobs are failing not and it might take
time to fix those if we are able to fix. Once it jobs are
working depends on project team, they can bring them back to
voting or keep non-voting.

If those jobs are failing consistently and no one is fixing them
then removing those n-v jobs in future also fine.

[1] http://lists.openstack.org/pipermail/openstack-discuss/2020-June/015499.html

StableOnly

Change-Id: Icf6403de9655e99e1bed44ded896b1bbb2a6a635
2020-06-22 21:11:29 -05:00
licanwei
306224f70c Don't throw exception when missing metrics
When querying data from datasource, it's possible to miss some data.
In this case if we throw an exception, Audit will failed because of
the exception. We should remove the exception and give the decision
to the strategy.

Change-Id: I1b0e6b78b3bba4df9ba16e093b3910aab1de922e
Closes-Bug: #1847434
can not cherry picke from master because of code refactoring
2019-10-18 16:41:29 +08:00
Sumit Jamgade
ec5780902f pass default_config_dirs variable for config initialization.
Currently default config files are being for initialization of CONF from
oslo_config. However default config dirs are not being passed as a
result watcher components (eg: decision-engine) are unable to load
files from default directories (eg: /etc/watcher/watcher.conf.d)
supported by oslo_config. This is a short-coming on watcher's side.
Also this forces user to have multiple config for each component.

Without this default set, oslo_config will search for conf with string
'python-watcher' in it, eg: /etc/python-watcher/.... Since there is a
because project=python-watcher a couple of lines below

This patch adds the option after evaluating using project as 'watcher'
which is similar to evaluation of default_config_files and also allows
it to be passed in as a function parameter.

Change-Id: I013f9d03978f8716847f8d1ee6888629faf5779b
(cherry picked from commit dce23d7eb4)
2019-09-13 22:44:18 +00:00
licanwei
78574f92e9 Move eventlet monkey patch code
Eventlet monkey patching is not recommended on top level __init__ [1],
because Apache WSGI module uses own concurrency model [2] and API
service under Apache should be runned without eventlet. This patch
moves eventlet monkey patching code to watcher.cmd module __init__
(like in nova).

[1] https://specs.openstack.org/openstack/openstack-specs/specs/eventlet-best-practices.html
[2] http://modwsgi.readthedocs.io/en/develop/user-guides/processes-and-threading.html

Change-Id: Ie5cf67429ea9ef8d00dd7348ce288437ea105c08
(cherry picked from commit ac3aa94599)
2019-08-30 15:13:29 +00:00
chenke
34324c95f9 Reduce the query time of the instances when call get_instance_list()
The problem is that watcher is passing limit=-1 to novaclient when
listing servers which will always make at least two API calls to be
sure it's done paging:

https://github.com/openstack/python-novaclient/blob/13.0.1/novaclient/v2/servers.py#L896

If we can determine before we list servers that there are only a
certain number where the number of servers is less than 1000. For
example: 4, we should just pass the limit=len(servers) to novaclient
and avoid the second call for paging which takes extra time and
yields no results.

Change-Id: I797ad934a0f8496dbcbf65798e28b0443f238137
Closes-Bug: #1834679
(cherry picked from commit 1e8b17ac46)
2019-07-09 09:39:46 -04:00
Matt Riedemann
0f90ad596c [stable-only] Stop running watcherclient-tempest-functional
The watcherclient-tempest-functional job is broken for stable
branches since python-watcherclient in stable/stein does not
support microversions but that is what the tempest plugin
is using when executing commands [1]. Since the job is non-voting
in stable anyway we are not losing anything really, and fixing
the job in stable would be complicated, so we just remove it here.

[1] I79fcaded18471d2df4d49a526ad3024e55488f96

Change-Id: Idff3f7cb3db1af1aacb680ab3611149355474841
2019-05-29 13:03:52 -04:00
Zuul
322cd786df Merge "Handle no nova CDM in notification code" into stable/stein 2019-05-29 08:13:30 +00:00
Zuul
7e2b6c75bb Merge "Optimize NovaClusterDataModelCollector.add_instance_node" into stable/stein 2019-05-29 08:13:29 +00:00
Zuul
fcbf256cbb Merge "Remove dead code from NovaClusterDataModelCollector" into stable/stein 2019-05-29 08:02:34 +00:00
Zuul
dbeca934f5 Merge "Update migration notification" into stable/stein 2019-05-29 07:49:55 +00:00
Zuul
59be8928d0 Merge "allow building docs without ceilometer client" into stable/stein 2019-05-28 01:36:30 +00:00
Matt Riedemann
fd9c5c85cb Optimize NovaClusterDataModelCollector.add_instance_node
This does two things:

1. Rather than make an API call per server on the host,
   get all of the servers in a single API call by
   filtering on the host. The os-hypervisors API results
   to use make this require a bit of refactoring since
   get_compute_node_by_name does not have the service
   entry in it and get_compute_node_by_id does not have the
   servers entry in it. A TODO is added to clean that up
   with a single call to os-hypervisors once we have the
   support in python-novaclient.

2. Pulls get_node_by_uuid() out of the loop.

A test is added for the nova_helper get_instance_list method
since one did not exist before.

The fake compute node mocks in test_nova_cdmc_execute are
also cleaned up since, as noted above, get_compute_node_by_name
and get_compute_node_by_id don't both return all the details.

Change-Id: Ifd9f83c2f399d4c1765b0c520f4d5a62ad0f5fbd
(cherry picked from commit fdea38fb06)
2019-05-27 10:07:20 +03:00
Matt Riedemann
2132063fd3 Remove dead code from NovaClusterDataModelCollector
The _add_virtual_layer and _add_virtual_servers methods
have not been used since Ic4659d1f18af181203439a8bf1b38805ff34c309
in Stein so this change removes them.

Change-Id: I8c05f29c3c03aa5897cb182bb492948771c42881
(cherry picked from commit 4cd8a2f46e)
2019-05-27 10:06:27 +03:00
Sumit Jamgade
42957fc912 allow building docs without ceilometer client
CeilometerClient has been deprecated and is no longer available for
master. Without ceilometer client installed docs fail to build with
an exception [1].

This patch marks the import optional.

1 -
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/sphinx/config.py", line 368, in
eval_config_file
    execfile_(filename, namespace)
  File "/usr/lib/python2.7/site-packages/sphinx/util/pycompat.py", line
150, in execfile_
    exec_(code, _globals)
  File "/usr/lib/python2.7/site-packages/six.py", line 709, in exec_
    exec(""exec _code_ in _globs_, _locs_"")
  File "<string>", line 1, in <module>
  File
"/home/abuild/rpmbuild/BUILD/python-watcher-2.1.0.dev45/doc/source/conf.py",
line 20, in <module>
    objects.register_all()
  File
"/home/abuild/rpmbuild/BUILD/python-watcher-2.1.0.dev45/watcher/objects/__init__.py",
line 31, in register_all
    __import__('watcher.objects.action_plan')
  File
"/home/abuild/rpmbuild/BUILD/python-watcher-2.1.0.dev45/watcher/objects/action_plan.py",
line 78, in <module>
    from watcher import conf
  File
"/home/abuild/rpmbuild/BUILD/python-watcher-2.1.0.dev45/watcher/conf/__init__.py",
line 28, in <module>
    from watcher.conf import datasources
  File
"/home/abuild/rpmbuild/BUILD/python-watcher-2.1.0.dev45/watcher/conf/datasources.py",
line 21, in <module>
    from watcher.datasources import manager
  File
"/home/abuild/rpmbuild/BUILD/python-watcher-2.1.0.dev45/watcher/datasources/manager.py",
line 19, in <module>
    from watcher.datasources import ceilometer as ceil
  File
"/home/abuild/rpmbuild/BUILD/python-watcher-2.1.0.dev45/watcher/datasources/ceilometer.py",
line 21, in <module>
    from ceilometerclient import exc
ImportError: No module named ceilometerclient
)

Change-Id: Idcf582c2495aab39aacf691b687759405bb94dca
(cherry picked from commit: 241df0d5f4)
2019-05-27 02:46:08 +00:00
Matt Riedemann
3c75c13f80 Handle no nova CDM in notification code
As of change Ic4659d1f18af181203439a8bf1b38805ff34c309 the
nova CDM will not be built until an audit is performed.

Instances and services (compute hosts) can be created and
deleted before an audit is performed which will attempt
to use the notification callback function which relies
on the CDM being built already, and if not results in
an AttributeError.

This change side-steps that issue by checking to see that the
nova CDM exists before trying to call the notification
callback function.

An alternative to this is forcefully create the nova CDM when
notifications are received before an audit which is what happend
before change Ic4659d1f18af181203439a8bf1b38805ff34c309.

Change-Id: I16990afb82019821c443c9df26d3e515e52efa69
Closes-Bug: #1828582
(cherry picked from commit 8a206a6ae5)
2019-05-22 15:38:49 +00:00
licanwei
7fafedbb43 Update migration notification
_post_live_migration[1] runs on the source host and calls
post_live_migration_at_destination on the dest host which
emits the instance.live_migration_post_dest.end notification:[2]
But it's not the last notification for the live migration operation.
so we should use instance.live_migration_post.end instead of
instance.live_migration_post_dest.end notification.

[1]daa2ac2287/nova/compute/manager.py (L6907)
[2]daa2ac2287/nova/compute/manager.py (L7035)

Change-Id: Id1e2d98f56d5a95d49e32f98d2910660b9f48ce6
(cherry picked from commit 6d96512188)
2019-05-20 10:43:25 +00:00
chenke
750547bc33 Fix bandit and sphinx requirements for stable branches
This is a combination of 2 commits.

1st commit:
Update Sphinx requirement
Sphinx 2.0 no longer works on python 2.7, so we need to start capping
it there as well.

2nd commit:
Fix bandit runs with 1.6.0
The -x option for bandit changed in 1.6.0 and now
supports glob patterns so use that to correctly
exclude test code from bandit scans.

Change-Id: I588d3fb02ef61623affd82a43a54585aba0cb5f9
2019-05-20 12:40:30 +03:00
Dantali0n
36c2095254 Resolve problems with audit scope and add tests
This resolves problems with the audit scope such as the scope being
ignored, the scope not merging due to a type in .append, change update
into .add method when adding single elements to a set and making the
access of dict keys and values as lists work in python 3.7.

All these methods from the model builder now have tests to prevent
regressions.

Co-Authored-By: Canwei Li <li.canwei2@zte.com.cn>

Change-Id: I287763d5e426ff860aefabc4a1f3fe3f51accd76
(cherry picked from commit d84f8c50f5)
2019-05-08 01:44:46 +00:00
Tatiana Kholkina
a089183b52 Add hardware.cpu_util in workload_stabilization
Since commit I8df8921337ea3f4e751c0c822d823e64e3ca7e1c
the check for hardware.cpu.util was removed.
But it can be still used in workload stabilization.

Change-Id: I301487837aac2e1e63bce16a79d0f8136452c313
(cherry picked from commit 4db39c527d)
2019-04-26 08:21:36 +00:00
OpenDev Sysadmins
8989ed9357 OpenDev Migration Patch
This commit was bulk generated and pushed by the OpenDev sysadmins
as a part of the Git hosting and code review systems migration
detailed in these mailing list posts:

http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003603.html
http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004920.html

Attempts have been made to correct repository namespaces and
hostnames based on simple pattern matching, but it's possible some
were updated incorrectly or missed entirely. Please reach out to us
via the contact information listed at https://opendev.org/ with any
questions you may have.
2019-04-19 19:40:48 +00:00
Zuul
5b1610037c Merge "Replace openstack.org git:// URLs with https://" into stable/stein 2019-04-01 01:18:27 +00:00
ghanshyam
eb6771137d Migrate legacy jobs to Ubuntu Bionic
We have migrated the zuulv3 job to Bionic during Dec/Jan month.
 - http://lists.openstack.org/pipermail/openstack-discuss/2018-December/000837.html
 - https://etherpad.openstack.org/p/devstack-bionic
But that effort does not move all gate job to Bionic as there are
large amount of jobs are still legacy jobs. All the legacy jobs still
use Xenial as nodeset.

As per the decided runtime for Stein, we need to test everything on openstack
CI/CD on Bionic - https://governance.openstack.org/tc/reference/runtimes/stein.html

Below patch move the legacy base jobs to bionic which will move the derived jobs
automatically to bionic. These jobs are modified with branch variant so that they will use
Bionic node from stein onwards and xenial for all other stable branches
until stable/rocky.
- https://review.openstack.org/#/c/639096

This commit remove the overridden nodeset from legacy jobs
so that it will start using the nodeset defined in parent job.

More Details: 
- https://etherpad.openstack.org/p/legacy-job-bionic
- http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003614.html

Depends-On: https://review.openstack.org/#/c/639096
Change-Id: I99646904d3d0fb26f4e45df1be841a67c4c2477b
(cherry picked from commit a4865b64f6)
2019-03-25 03:18:55 +00:00
Ian Wienand
ca1773ffb6 Replace openstack.org git:// URLs with https://
This is a mechanically generated change to replace openstack.org
git:// URLs with https:// equivalents.

This is in aid of a planned future move of the git hosting
infrastructure to a self-hosted instance of gitea (https://gitea.io),
which does not support the git wire protocol at this stage.

This update should result in no functional change.

For more information see the thread at

 http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003825.html

Change-Id: Id6575b342e75058c00fd41041549277bbc1894aa
2019-03-24 20:36:26 +00:00
OpenStack Release Bot
9227b12efc Update UPPER_CONSTRAINTS_FILE for stable/stein
Update the URL to the upper-constraints file to point to the redirect
rule on releases.openstack.org so that anyone working on this branch
will switch to the correct upper-constraints list automatically when
the requirements repository branches.

Until the requirements repository has as stable/stein branch, tests will
continue to use the upper-constraints list on master.

Change-Id: I75ae6cefd75816c22bb163676010ed381b4a5123
2019-03-21 10:36:15 +00:00
OpenStack Release Bot
4acb764c68 Update .gitreview for stable/stein
Change-Id: I6c83e6b43d9e190a003491ecca5a036180138d53
2019-03-21 10:36:12 +00:00
26 changed files with 417 additions and 302 deletions

View File

@@ -1,4 +1,5 @@
[gerrit]
host=review.openstack.org
host=review.opendev.org
port=29418
project=openstack/watcher.git
defaultbranch=stable/stein

View File

@@ -1,4 +1,5 @@
- project:
queue: watcher
templates:
- check-requirements
- openstack-cover-jobs
@@ -11,19 +12,18 @@
check:
jobs:
- watcher-tempest-functional
- watcher-grenade
- watcher-grenade:
voting: false
- watcher-tempest-dummy_optim
- watcher-tempest-actuator
- watcher-tempest-basic_optim
- watcher-tempest-vm_workload_consolidation
- watcher-tempest-workload_balancing
- watcherclient-tempest-functional
- watcher-tempest-zone_migration
- watcher-tempest-host_maintenance
- watcher-tempest-storage_balance
- watcher-tls-test
gate:
queue: watcher
jobs:
- watcher-tempest-functional
@@ -109,7 +109,7 @@
- job:
name: watcher-tempest-multinode
parent: watcher-tempest-functional
nodeset: openstack-two-node
nodeset: openstack-two-node-bionic
roles:
- zuul: openstack/tempest
group-vars:
@@ -156,7 +156,7 @@
live_migration: true
block_migration_for_live_migration: true
devstack_plugins:
ceilometer: https://git.openstack.org/openstack/ceilometer
ceilometer: https://opendev.org/openstack/ceilometer
- job:
name: watcher-tempest-functional
@@ -164,7 +164,7 @@
timeout: 7200
required-projects:
- openstack/ceilometer
- openstack-infra/devstack-gate
- openstack/devstack-gate
- openstack/python-openstackclient
- openstack/python-watcherclient
- openstack/watcher
@@ -172,7 +172,7 @@
- openstack/tempest
vars:
devstack_plugins:
watcher: https://git.openstack.org/openstack/watcher
watcher: https://opendev.org/openstack/watcher
devstack_services:
tls-proxy: false
watcher-api: true
@@ -211,8 +211,8 @@
- ^tools/.*$
- ^tox.ini$
required-projects:
- openstack-dev/grenade
- openstack-infra/devstack-gate
- openstack/grenade
- openstack/devstack-gate
- openstack/watcher
- openstack/python-watcherclient
- openstack/watcher-tempest-plugin

View File

@@ -35,7 +35,7 @@ VNCSERVER_PROXYCLIENT_ADDRESS=$HOST_IP
NOVA_INSTANCES_PATH=/opt/stack/data/instances
# Enable the Ceilometer plugin for the compute agent
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
disable_service ceilometer-acentral,ceilometer-collector,ceilometer-api
LOGFILE=$DEST/logs/stack.sh.log

View File

@@ -25,13 +25,13 @@ MULTI_HOST=1
disable_service n-cpu
# Enable the Watcher Dashboard plugin
enable_plugin watcher-dashboard git://git.openstack.org/openstack/watcher-dashboard
enable_plugin watcher-dashboard https://git.openstack.org/openstack/watcher-dashboard
# Enable the Watcher plugin
enable_plugin watcher git://git.openstack.org/openstack/watcher
enable_plugin watcher https://git.openstack.org/openstack/watcher
# Enable the Ceilometer plugin
enable_plugin ceilometer git://git.openstack.org/openstack/ceilometer
enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer
# This is the controller node, so disable the ceilometer compute agent
disable_service ceilometer-acompute

View File

@@ -2,7 +2,8 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
openstackdocstheme>=1.20.0 # Apache-2.0
sphinx>=1.6.5,!=1.6.6,!=1.6.7 # BSD
sphinx>=1.6.5,!=1.6.6,!=1.6.7,<2.0.0;python_version=='2.7' # BSD
sphinx>=1.6.5,!=1.6.6,!=1.6.7;python_version>='3.4' # BSD
sphinxcontrib-pecanwsme>=0.8.0 # Apache-2.0
reno>=2.7.0 # Apache-2.0
sphinxcontrib-apidoc>=0.2.0 # BSD

View File

@@ -178,7 +178,7 @@ You can easily generate and update a sample configuration file
named :ref:`watcher.conf.sample <watcher_sample_configuration_files>` by using
these following commands::
$ git clone git://git.openstack.org/openstack/watcher
$ git clone https://git.openstack.org/openstack/watcher
$ cd watcher/
$ tox -e genconfig
$ vi etc/watcher/watcher.conf.sample

View File

@@ -19,7 +19,7 @@ model. To enable the Watcher plugin with DevStack, add the following to the
`[[local|localrc]]` section of your controller's `local.conf` to enable the
Watcher plugin::
enable_plugin watcher git://git.openstack.org/openstack/watcher
enable_plugin watcher https://git.openstack.org/openstack/watcher
For more detailed instructions, see `Detailed DevStack Instructions`_. Check
out the `DevStack documentation`_ for more information regarding DevStack.

View File

@@ -13,12 +13,12 @@
set -x
cat > clonemap.yaml << EOF
clonemap:
- name: openstack-infra/devstack-gate
- name: openstack/devstack-gate
dest: devstack-gate
EOF
/usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
git://git.openstack.org \
openstack-infra/devstack-gate
https://opendev.org \
openstack/devstack-gate
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
@@ -29,13 +29,13 @@
set -x
export PYTHONUNBUFFERED=true
export PROJECTS="openstack-dev/grenade $PROJECTS"
export PROJECTS="openstack/grenade $PROJECTS"
export PROJECTS="openstack/watcher $PROJECTS"
export PROJECTS="openstack/watcher-tempest-plugin $PROJECTS"
export PROJECTS="openstack/python-watcherclient $PROJECTS"
export DEVSTACK_PROJECT_FROM_GIT="python-watcherclient $DEVSTACK_PROJECT_FROM_GIT"
export GRENADE_PLUGINRC="enable_grenade_plugin watcher https://git.openstack.org/openstack/watcher"
export GRENADE_PLUGINRC="enable_grenade_plugin watcher https://opendev.org/openstack/watcher"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"export TEMPEST_PLUGINS='/opt/stack/new/watcher-tempest-plugin'"
export DEVSTACK_GATE_TEMPEST_NOTESTS=1

View File

@@ -45,5 +45,7 @@ stevedore>=1.28.0 # Apache-2.0
taskflow>=3.1.0 # Apache-2.0
WebOb>=1.7.4 # MIT
WSME>=0.9.2 # MIT
networkx>=1.11 # BSD
# NOTE(fdegir): NetworkX 2.3 dropped support for Python 2
networkx>=1.11,<2.3;python_version<'3.0' # BSD
networkx>=1.11;python_version>='3.4' # BSD
microversion_parse>=0.2.1 # Apache-2.0

View File

@@ -13,4 +13,4 @@ testscenarios>=0.5.0 # Apache-2.0/BSD
testtools>=2.3.0 # MIT
stestr>=2.0.0 # Apache-2.0
os-api-ref>=1.4.0 # Apache-2.0
bandit>=1.1.0 # Apache-2.0
bandit>=1.6.0 # Apache-2.0

View File

@@ -7,7 +7,7 @@ skipsdist = True
usedevelop = True
whitelist_externals = find
rm
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/stein} {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/test-requirements.txt
@@ -22,7 +22,7 @@ basepython = python3
commands =
doc8 doc/source/ CONTRIBUTING.rst HACKING.rst README.rst
flake8
bandit -r watcher -x tests -n5 -ll -s B320
bandit -r watcher -x watcher/tests/* -n5 -ll -s B320
[testenv:venv]
basepython = python3
@@ -98,7 +98,7 @@ commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasen
[testenv:bandit]
basepython = python3
deps = -r{toxinidir}/test-requirements.txt
commands = bandit -r watcher -x tests -n5 -ll -s B320
commands = bandit -r watcher -x watcher/tests/* -n5 -ll -s B320
[testenv:lower-constraints]
basepython = python3

View File

@@ -0,0 +1,20 @@
#
# 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.
# NOTE(licanwei): Do eventlet monkey patching here, instead of in
# common/service.py. This allows the API service to run without monkey
# patching under Apache (which uses its own concurrency model). Mixing
# concurrency models can cause undefined behavior and potentially API timeouts.
import eventlet
eventlet.monkey_patch()

View File

@@ -21,12 +21,15 @@ from watcher.common import rpc
from watcher import version
def parse_args(argv, default_config_files=None):
def parse_args(argv, default_config_files=None, default_config_dirs=None):
default_config_files = (default_config_files or
cfg.find_config_files(project='watcher'))
default_config_dirs = (default_config_dirs or
cfg.find_config_dirs(project='watcher'))
rpc.set_defaults(control_exchange='watcher')
cfg.CONF(argv[1:],
project='python-watcher',
version=version.version_info.release_string(),
default_config_dirs=default_config_dirs,
default_config_files=default_config_files)
rpc.init(cfg.CONF)

View File

@@ -76,9 +76,25 @@ class NovaHelper(object):
LOG.exception(exc)
raise exception.ComputeNodeNotFound(name=node_hostname)
def get_instance_list(self):
return self.nova.servers.list(search_opts={'all_tenants': True},
limit=-1)
def get_instance_list(self, filters=None, limit=-1):
"""List servers for all tenants with details.
This always gets servers with the all_tenants=True filter.
:param filters: dict of additional filters (optional).
:param limit: Maximum number of servers to return (optional).
If limit == -1, all servers will be returned,
note that limit == -1 will have a performance
penalty. For details, please see:
https://bugs.launchpad.net/watcher/+bug/1834679
:returns: list of novaclient Server objects
"""
search_opts = {'all_tenants': True}
if filters:
search_opts.update(filters)
# TODO(chenker) Add marker param to list Server objects.
return self.nova.servers.list(search_opts=search_opts,
limit=limit)
def get_flavor_list(self):
return self.nova.flavors.list(**{'is_public': None})

View File

@@ -17,7 +17,6 @@
import datetime
import socket
import eventlet
from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import _options
@@ -42,12 +41,6 @@ from watcher.objects import base
from watcher.objects import fields as wfields
from watcher import version
# NOTE:
# Ubuntu 14.04 forces librabbitmq when kombu is used
# Unfortunately it forces a version that has a crash
# bug. Calling eventlet.monkey_patch() tells kombu
# to use libamqp instead.
eventlet.monkey_patch()
NOTIFICATION_OPTS = [
cfg.StrOpt('notification_level',

View File

@@ -18,7 +18,6 @@
import datetime
from ceilometerclient import exc
from oslo_log import log
from oslo_utils import timeutils
@@ -31,6 +30,13 @@ from watcher.datasource import base
LOG = log.getLogger(__name__)
try:
from ceilometerclient import exc
HAS_CEILCLIENT = True
except ImportError:
HAS_CEILCLIENT = False
class CeilometerHelper(base.DataSourceBase):
NAME = 'ceilometer'
@@ -130,15 +136,15 @@ class CeilometerHelper(base.DataSourceBase):
self.osc.reset_clients()
self.ceilometer = self.osc.ceilometer()
return f(*args, **kargs)
except Exception:
raise
except Exception as e:
LOG.exception(e)
def check_availability(self):
try:
self.query_retry(self.ceilometer.resources.list)
except Exception:
status = self.query_retry(self.ceilometer.resources.list)
if status:
return 'available'
else:
return 'not available'
return 'available'
def query_sample(self, meter_name, query, limit=1):
return self.query_retry(f=self.ceilometer.samples.list,
@@ -156,9 +162,8 @@ class CeilometerHelper(base.DataSourceBase):
def list_metrics(self):
"""List the user's meters."""
try:
meters = self.query_retry(f=self.ceilometer.meters.list)
except Exception:
meters = self.query_retry(f=self.ceilometer.meters.list)
if not meters:
return set()
else:
return meters

View File

@@ -24,7 +24,6 @@ from oslo_config import cfg
from oslo_log import log
from watcher.common import clients
from watcher.common import exception
from watcher.common import utils as common_utils
from watcher.datasource import base
@@ -60,20 +59,18 @@ class GnocchiHelper(base.DataSourceBase):
except Exception as e:
LOG.exception(e)
time.sleep(CONF.gnocchi_client.query_timeout)
raise exception.DataSourceNotAvailable(datasource='gnocchi')
def check_availability(self):
try:
self.query_retry(self.gnocchi.status.get)
except Exception:
status = self.query_retry(self.gnocchi.status.get)
if status:
return 'available'
else:
return 'not available'
return 'available'
def list_metrics(self):
"""List the user's meters."""
try:
response = self.query_retry(f=self.gnocchi.metric.list)
except Exception:
response = self.query_retry(f=self.gnocchi.metric.list)
if not response:
return set()
else:
return set([metric['name'] for metric in response])
@@ -106,8 +103,9 @@ class GnocchiHelper(base.DataSourceBase):
f=self.gnocchi.resource.search, **kwargs)
if not resources:
raise exception.ResourceNotFound(name='gnocchi',
id=resource_id)
LOG.warning("The {0} resource {1} could not be "
"found".format(self.NAME, resource_id))
return
resource_id = resources[0]['id']

View File

@@ -19,11 +19,14 @@
import datetime
from monascaclient import exc
from oslo_log import log
from watcher.common import clients
from watcher.common import exception
from watcher.datasource import base
LOG = log.getLogger(__name__)
class MonascaHelper(base.DataSourceBase):
@@ -53,8 +56,8 @@ class MonascaHelper(base.DataSourceBase):
self.osc.reset_clients()
self.monasca = self.osc.monasca()
return f(*args, **kwargs)
except Exception:
raise
except Exception as e:
LOG.exception(e)
def _format_time_params(self, start_time, end_time, period):
"""Format time-related params to the correct Monasca format
@@ -78,11 +81,11 @@ class MonascaHelper(base.DataSourceBase):
return start_timestamp, end_timestamp, period
def check_availability(self):
try:
self.query_retry(self.monasca.metrics.list)
except Exception:
status = self.query_retry(self.monasca.metrics.list)
if status:
return 'available'
else:
return 'not available'
return 'available'
def list_metrics(self):
# TODO(alexchadin): this method should be implemented in accordance to

View File

@@ -15,7 +15,6 @@
from oslo_log import log
from watcher.common import exception
from watcher.common import nova_helper
from watcher.decision_engine.model.collector import base
from watcher.decision_engine.model import element
@@ -208,10 +207,9 @@ class ModelBuilder(object):
self.osc = osc
self.model = None
self.model_scope = dict()
self.no_model_scope_flag = False
self.nova = osc.nova()
self.nova_helper = nova_helper.NovaHelper(osc=self.osc)
# self.neutron = osc.neutron()
# self.cinder = osc.cinder()
def _collect_aggregates(self, host_aggregates, _nodes):
aggregate_list = self.nova_helper.get_aggregate_list()
@@ -237,7 +235,7 @@ class ModelBuilder(object):
include_all_nodes = True
for service in service_list:
if service.zone in zone_names or include_all_nodes:
_nodes.update(service.host)
_nodes.add(service.host)
def _add_physical_layer(self):
"""Add the physical layer of the graph.
@@ -254,22 +252,31 @@ class ModelBuilder(object):
self._collect_zones(availability_zones, compute_nodes)
if not compute_nodes:
self.no_model_scope_flag = True
all_nodes = self.nova_helper.get_compute_node_list()
compute_nodes = set(
[node.hypervisor_hostname for node in all_nodes])
LOG.debug("compute nodes: %s", compute_nodes)
for node_name in compute_nodes:
# TODO(mriedem): Change this to list hypervisors with details
# so we don't have to call get_compute_node_by_id. It requires
# changes to python-novaclient.
cnode = self.nova_helper.get_compute_node_by_name(node_name,
servers=True)
if cnode:
self.add_compute_node(cnode[0])
self.add_instance_node(cnode[0])
# Get the node details (like the service.host).
node_info = self.nova_helper.get_compute_node_by_id(
cnode[0].id)
self.add_compute_node(node_info)
# node.servers is a list of server objects
# New in nova version 2.53
instances = getattr(cnode[0], "servers", None)
self.add_instance_node(node_info, instances)
def add_compute_node(self, node):
# Build and add base node.
node_info = self.nova_helper.get_compute_node_by_id(node.id)
LOG.debug("node info: %s", node_info)
compute_node = self.build_compute_node(node_info)
LOG.debug("node info: %s", node)
compute_node = self.build_compute_node(node)
self.model.add_node(compute_node)
# NOTE(v-francoise): we can encapsulate capabilities of the node
@@ -315,99 +322,28 @@ class ModelBuilder(object):
# node_attributes)
return compute_node
# def _build_network_compute_node(self, base_node):
# attributes = {}
# net_node = self._build_node("physical", "network", "NIC", attributes)
# net_id = "{}_network".format(base_node)
# return net_id, net_node
# def build_disk_compute_node(self, base_node, compute):
# # Build disk node attributes.
# disk_attributes = {
# "size_gb": compute.local_gb,
# "used_gb": compute.local_gb_used,
# "available_gb": compute.free_disk_gb}
# disk_node = self._build_node("physical", "storage", "disk",
# disk_attributes)
# disk_id = "{}_disk".format(base_node)
# return disk_id, disk_node
# def build_memory_compute_node(self, base_node, compute):
# # Build memory node attributes.
# memory_attrs = {"size_mb": compute.memory_mb,
# "used_mb": compute.memory_mb_used,
# "available_mb": compute.free_ram_mb}
# memory_node = self._build_node("physical", "memory", "memory",
# memory_attrs)
# memory_id = "{}_memory".format(base_node)
# return memory_id, memory_node
# def build_cpu_compute_node(self, base_node, compute):
# # Build memory node attributes.
# cpu_attributes = {"vcpus": compute.vcpus,
# "vcpus_used": compute.vcpus_used,
# "info": jsonutils.loads(compute.cpu_info)}
# cpu_node = self._build_node("physical", "cpu", "cpu", cpu_attributes)
# cpu_id = "{}_cpu".format(base_node)
# return cpu_id, cpu_node
# @staticmethod
# def _build_node(layer, category, node_type, attributes):
# return {"layer": layer, "category": category, "type": node_type,
# "attributes": attributes}
def _add_virtual_layer(self):
"""Add the virtual layer to the graph.
This layer is the virtual components of the infrastructure,
such as vms.
"""
self._add_virtual_servers()
# self._add_virtual_network()
# self._add_virtual_storage()
def _add_virtual_servers(self):
all_instances = self.nova_helper.get_instance_list()
for inst in all_instances:
# Add Node
instance = self._build_instance_node(inst)
self.model.add_instance(instance)
# Get the cnode_name uuid.
cnode_uuid = getattr(inst, "OS-EXT-SRV-ATTR:host")
if cnode_uuid is None:
# The instance is not attached to any Compute node
continue
try:
# Nova compute node
# cnode = self.nova_helper.get_compute_node_by_hostname(
# cnode_uuid)
compute_node = self.model.get_node_by_uuid(
cnode_uuid)
# Connect the instance to its compute node
self.model.map_instance(instance, compute_node)
except exception.ComputeNodeNotFound:
continue
def add_instance_node(self, node):
# node.servers is a list of server objects
# New in nova version 2.53
instances = getattr(node, "servers", None)
def add_instance_node(self, node, instances):
if instances is None:
# no instances on this node
return
instance_uuids = [s['uuid'] for s in instances]
for uuid in instance_uuids:
try:
inst = self.nova_helper.find_instance(uuid)
except Exception as exc:
LOG.exception(exc)
continue
host = node.service["host"]
compute_node = self.model.get_node_by_uuid(host)
filters = {'host': host}
limit = len(instances) if len(instances) <= 1000 else -1
# Get all servers on this compute host.
# Note that the advantage of passing the limit parameter is
# that it can speed up the call time of novaclient. 1000 is
# the default maximum number of return servers provided by
# compute API. If we need to request more than 1000 servers,
# we can set limit=-1. For details, please see:
# https://bugs.launchpad.net/watcher/+bug/1834679
instances = self.nova_helper.get_instance_list(
filters=filters,
limit=limit)
for inst in instances:
# Add Node
instance = self._build_instance_node(inst)
self.model.add_instance(instance)
cnode_uuid = getattr(inst, "OS-EXT-SRV-ATTR:host")
compute_node = self.model.get_node_by_uuid(
cnode_uuid)
# Connect the instance to its compute node
self.model.map_instance(instance, compute_node)
@@ -439,137 +375,23 @@ class ModelBuilder(object):
# node_attributes["attributes"] = instance_attributes
return element.Instance(**instance_attributes)
# def _add_virtual_storage(self):
# try:
# volumes = self.cinder.volumes.list()
# except Exception:
# return
# for volume in volumes:
# volume_id, volume_node = self._build_storage_node(volume)
# self.add_node(volume_id, volume_node)
# host = self._get_volume_host_id(volume_node)
# self.add_edge(volume_id, host)
# # Add connections to an instance.
# if volume_node['attributes']['attachments']:
# for attachment in volume_node['attributes']['attachments']:
# self.add_edge(volume_id, attachment['server_id'],
# label='ATTACHED_TO')
# volume_node['attributes'].pop('attachments')
# def _add_virtual_network(self):
# try:
# routers = self.neutron.list_routers()
# except Exception:
# return
# for network in self.neutron.list_networks()['networks']:
# self.add_node(*self._build_network(network))
# for router in routers['routers']:
# self.add_node(*self._build_router(router))
# router_interfaces, _, compute_ports = self._group_ports()
# for router_interface in router_interfaces:
# interface = self._build_router_interface(router_interface)
# router_interface_id = interface[0]
# router_interface_node = interface[1]
# router_id = interface[2]
# self.add_node(router_interface_id, router_interface_node)
# self.add_edge(router_id, router_interface_id)
# network_id = router_interface_node['attributes']['network_id']
# self.add_edge(router_interface_id, network_id)
# for compute_port in compute_ports:
# cp_id, cp_node, instance_id = self._build_compute_port_node(
# compute_port)
# self.add_node(cp_id, cp_node)
# self.add_edge(cp_id, vm_id)
# net_id = cp_node['attributes']['network_id']
# self.add_edge(net_id, cp_id)
# # Connect port to physical node
# phys_net_node = "{}_network".format(cp_node['attributes']
# ['binding:host_id'])
# self.add_edge(cp_id, phys_net_node)
# def _get_volume_host_id(self, volume_node):
# host = volume_node['attributes']['os-vol-host-attr:host']
# if host.find('@') != -1:
# host = host.split('@')[0]
# elif host.find('#') != -1:
# host = host.split('#')[0]
# return "{}_disk".format(host)
# def _build_storage_node(self, volume_obj):
# volume = volume_obj.__dict__
# volume["name"] = volume["id"]
# volume.pop("id")
# volume.pop("manager")
# node = self._build_node("virtual", "storage", 'volume', volume)
# return volume["name"], node
# def _build_compute_port_node(self, compute_port):
# compute_port["name"] = compute_port["id"]
# compute_port.pop("id")
# nde_type = "{}_port".format(
# compute_port["device_owner"].split(":")[0])
# compute_port.pop("device_owner")
# device_id = compute_port["device_id"]
# compute_port.pop("device_id")
# node = self._build_node("virtual", "network", nde_type, compute_port)
# return compute_port["name"], node, device_id
# def _group_ports(self):
# router_interfaces = []
# floating_ips = []
# compute_ports = []
# interface_types = ["network:router_interface",
# 'network:router_gateway']
# for port in self.neutron.list_ports()['ports']:
# if port['device_owner'] in interface_types:
# router_interfaces.append(port)
# elif port['device_owner'].startswith('compute:'):
# compute_ports.append(port)
# elif port['device_owner'] == 'network:floatingip':
# floating_ips.append(port)
# return router_interfaces, floating_ips, compute_ports
# def _build_router_interface(self, interface):
# interface["name"] = interface["id"]
# interface.pop("id")
# node_type = interface["device_owner"].split(":")[1]
# node = self._build_node("virtual", "network", node_type, interface)
# return interface["name"], node, interface["device_id"]
# def _build_router(self, router):
# router_attrs = {"uuid": router['id'],
# "name": router['name'],
# "state": router['status']}
# node = self._build_node('virtual', 'network', 'router', router_attrs)
# return str(router['id']), node
# def _build_network(self, network):
# node = self._build_node('virtual', 'network', 'network', network)
# return network['id'], node
def _merge_compute_scope(self, compute_scope):
model_keys = self.model_scope.keys()
update_flag = False
role_keys = ("host_aggregates", "availability_zones")
for role in compute_scope:
role_key = role.keys()[0]
role_key = list(role.keys())[0]
if role_key not in role_keys:
continue
role_values = role.values()[0]
role_values = list(role.values())[0]
if role_key in model_keys:
for value in role_values:
if value not in self.model_scope[role_key]:
self.model_scope[role_key].sppend(value)
self.model_scope[role_key].append(value)
update_flag = True
else:
self.self.model_scope[role_key] = role_values
self.model_scope[role_key] = role_values
update_flag = True
return update_flag
@@ -581,10 +403,9 @@ class ModelBuilder(object):
compute_scope = _scope['compute']
break
if self.model_scope:
if model_scope:
if compute_scope:
update_flag = self._merge_compute_scope(compute_scope)
if self.no_model_scope_flag is False:
if compute_scope:
update_flag = self._merge_compute_scope(compute_scope)
else:
self.model_scope = dict()
update_flag = True

View File

@@ -256,7 +256,7 @@ class VersionedNotification(NovaNotification):
'instance.rescue.end': instance_updated,
'instance.update': instance_updated,
'instance.live_migration_force_complete.end': instance_updated,
'instance.live_migration_post_dest.end': instance_updated,
'instance.live_migration_post.end': instance_updated,
'instance.delete.end': instance_deleted,
'instance.soft_delete.end': instance_deleted,
'service.create': service_updated,
@@ -279,5 +279,10 @@ class VersionedNotification(NovaNotification):
metadata=metadata))
func = self.notification_mapping.get(event_type)
if func:
LOG.debug(payload)
func(self, payload)
# The nova CDM is not built until an audit is performed.
if self.cluster_data_model:
LOG.debug(payload)
func(self, payload)
else:
LOG.debug('Nova CDM has not yet been built; ignoring '
'notifications until an audit is performed.')

View File

@@ -271,6 +271,7 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
"for %(metric_name)s", dict(
resource_id=instance.uuid, metric_name=meter))
return
# cpu_util has been deprecated since Stein.
if meter == 'cpu_util':
avg_meter /= float(100)
LOG.debug('Load of %(metric)s for %(instance)s is %(value)s',
@@ -324,6 +325,9 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
avg_meter /= oslo_utils.units.Ki
if meter_name == 'compute.node.cpu.percent':
avg_meter /= 100
# hardware.cpu.util has been deprecated since Stein.
if meter_name == 'hardware.cpu.util':
avg_meter /= 100
LOG.debug('Load of %(metric)s for %(node)s is %(value)s',
{'metric': metric,
'node': node_id,

View File

@@ -146,6 +146,22 @@ class TestNovaHelper(base.TestCase):
nova_util.get_compute_node_by_hostname,
hypervisor_name)
def test_get_instance_list(self, *args):
nova_util = nova_helper.NovaHelper()
# Call it once with no filters.
with mock.patch.object(nova_util, 'nova') as nova_mock:
result = nova_util.get_instance_list()
nova_mock.servers.list.assert_called_once_with(
search_opts={'all_tenants': True}, limit=-1)
self.assertIs(result, nova_mock.servers.list.return_value)
# Call it again with filters.
with mock.patch.object(nova_util, 'nova') as nova_mock:
result = nova_util.get_instance_list(filters={'host': 'fake-host'})
nova_mock.servers.list.assert_called_once_with(
search_opts={'all_tenants': True, 'host': 'fake-host'},
limit=-1)
self.assertIs(result, nova_mock.servers.list.return_value)
@mock.patch.object(time, 'sleep', mock.Mock())
def test_stop_instance(self, mock_glance, mock_cinder, mock_neutron,
mock_nova):

View File

@@ -0,0 +1,175 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2019 European Organization for Nuclear Research (CERN)
#
# Authors: Corne Lukken <info@dantalion.nl>
#
# 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_log import log
from watcher.common import nova_helper
from watcher.decision_engine.model.collector import nova
from watcher.tests import base
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class TestModelBuilder(base.BaseTestCase):
"""Test the collector ModelBuilder
Objects under test are preceded with t_ and mocked objects are preceded
with m_ , additionally, patched objects are preceded with p_ no object
under test should be created in setUp this can influence the results.
"""
def setUp(self):
super(TestModelBuilder, self).setUp()
def test_check_model(self):
"""Initialize collector ModelBuilder and test check model"""
m_scope = [{"compute": [
{"host_aggregates": [{"id": 5}]},
{"availability_zones": [{"name": "av_a"}]}
]}]
t_nova_cluster = nova.ModelBuilder(mock.Mock())
self.assertTrue(t_nova_cluster._check_model_scope(m_scope))
def test_check_model_update_false(self):
"""Initialize check model with multiple identical scopes
The seconds check_model should return false as the models are the same
"""
m_scope = [{"compute": [
{"host_aggregates": [{"id": 5}]},
{"availability_zones": [{"name": "av_a"}]}
]}]
t_nova_cluster = nova.ModelBuilder(mock.Mock())
self.assertTrue(t_nova_cluster._check_model_scope(m_scope))
self.assertFalse(t_nova_cluster._check_model_scope(m_scope))
def test_check_model_update_true(self):
"""Initialize check model with multiple different scopes
Since the models differ both should return True for the update flag
"""
m_scope_one = [{"compute": [
{"host_aggregates": [{"id": 5}]},
{"availability_zones": [{"name": "av_a"}]}
]}]
m_scope_two = [{"compute": [
{"host_aggregates": [{"id": 2}]},
{"availability_zones": [{"name": "av_b"}]}
]}]
t_nova_cluster = nova.ModelBuilder(mock.Mock())
self.assertTrue(t_nova_cluster._check_model_scope(m_scope_one))
self.assertTrue(t_nova_cluster._check_model_scope(m_scope_two))
def test_merge_compute_scope(self):
""""""
m_scope_one = [
{"host_aggregates": [{"id": 5}]},
{"availability_zones": [{"name": "av_a"}]}
]
m_scope_two = [
{"host_aggregates": [{"id": 4}]},
{"availability_zones": [{"name": "av_b"}]}
]
reference = {'availability_zones':
[{'name': 'av_a'}, {'name': 'av_b'}],
'host_aggregates':
[{'id': 5}, {'id': 4}]}
t_nova_cluster = nova.ModelBuilder(mock.Mock())
t_nova_cluster._merge_compute_scope(m_scope_one)
t_nova_cluster._merge_compute_scope(m_scope_two)
self.assertEqual(reference, t_nova_cluster.model_scope)
@mock.patch.object(nova_helper, 'NovaHelper')
def test_collect_aggregates(self, m_nova):
""""""
m_nova.return_value.get_aggregate_list.return_value = \
[mock.Mock(id=1, name='example'),
mock.Mock(id=5, name='example', hosts=['hostone', 'hosttwo'])]
m_nova.return_value.get_compute_node_by_name.return_value = False
m_scope = [{'id': 5}]
t_nova_cluster = nova.ModelBuilder(mock.Mock())
result = set()
t_nova_cluster._collect_aggregates(m_scope, result)
self.assertEqual(set(['hostone', 'hosttwo']), result)
@mock.patch.object(nova_helper, 'NovaHelper')
def test_collect_zones(self, m_nova):
""""""
m_nova.return_value.get_service_list.return_value = \
[mock.Mock(zone='av_b'),
mock.Mock(zone='av_a', host='hostone')]
m_nova.return_value.get_compute_node_by_name.return_value = False
m_scope = [{'name': 'av_a'}]
t_nova_cluster = nova.ModelBuilder(mock.Mock())
result = set()
t_nova_cluster._collect_zones(m_scope, result)
self.assertEqual(set(['hostone']), result)
@mock.patch.object(nova_helper, 'NovaHelper')
def test_add_physical_layer(self, m_nova):
""""""
m_nova.return_value.get_aggregate_list.return_value = \
[mock.Mock(id=1, name='example'),
mock.Mock(id=5, name='example', hosts=['hostone', 'hosttwo'])]
m_nova.return_value.get_service_list.return_value = \
[mock.Mock(zone='av_b', host='hostthree'),
mock.Mock(zone='av_a', host='hostone')]
m_nova.return_value.get_compute_node_by_name.return_value = False
m_scope = [{"compute": [
{"host_aggregates": [{"id": 5}]},
{"availability_zones": [{"name": "av_a"}]}
]}]
t_nova_cluster = nova.ModelBuilder(mock.Mock())
t_nova_cluster.execute(m_scope)
m_nova.return_value.get_compute_node_by_name.assert_any_call(
'hostone', servers=True)
m_nova.return_value.get_compute_node_by_name.assert_any_call(
'hosttwo', servers=True)
self.assertEqual(
m_nova.return_value.get_compute_node_by_name.call_count, 2)

View File

@@ -43,23 +43,30 @@ class TestNovaClusterDataModelCollector(base.TestCase):
state='up',
disabled_reason='',
)
fake_compute_node = mock.Mock(
minimal_node = dict(
id=1337,
hypervisor_hostname='test_hostname',
state='TEST_STATE',
status='TEST_STATUS',
)
minimal_node_with_servers = dict(
servers=[
{'name': 'fake_instance',
'uuid': 'ef500f7e-dac8-470f-960c-169486fce71b'}
],
**minimal_node
)
fake_compute_node = mock.Mock(
service={'id': 123, 'host': 'test_hostname',
'disabled_reason': ''},
hypervisor_hostname='test_hostname',
memory_mb=333,
free_disk_gb=222,
local_gb=111,
vcpus=4,
state='TEST_STATE',
status='TEST_STATUS',
servers=[
{'name': 'fake_instance',
'uuid': 'ef500f7e-dac8-470f-960c-169486fce71b'}
]
servers=None, # Don't let the mock return a value for servers.
**minimal_node
)
fake_compute_node_with_servers = mock.Mock(**minimal_node_with_servers)
fake_instance = mock.Mock(
id='ef500f7e-dac8-470f-960c-169486fce71b',
human_id='fake_instance',
@@ -68,12 +75,14 @@ class TestNovaClusterDataModelCollector(base.TestCase):
tenant_id='ff560f7e-dbc8-771f-960c-164482fce21b',
)
setattr(fake_instance, 'OS-EXT-STS:vm_state', 'VM_STATE')
setattr(fake_instance, 'OS-EXT-SRV-ATTR:host', 'test_hostname')
# Returns the hypervisors with details (service) but no servers.
m_nova_helper.get_compute_node_list.return_value = [fake_compute_node]
# Returns the hypervisor with servers but no details (service).
m_nova_helper.get_compute_node_by_name.return_value = [
fake_compute_node]
fake_compute_node_with_servers]
# Returns the hypervisor with details (service) but no servers.
m_nova_helper.get_compute_node_by_id.return_value = fake_compute_node
m_nova_helper.find_instance.return_value = fake_instance
m_nova_helper.get_instance_list.return_value = [fake_instance]
m_config = mock.Mock()
m_osc = mock.Mock()
@@ -95,3 +104,28 @@ class TestNovaClusterDataModelCollector(base.TestCase):
self.assertEqual(node.uuid, 'test_hostname')
self.assertEqual(instance.uuid, 'ef500f7e-dac8-470f-960c-169486fce71b')
m_nova_helper.get_instance_list.assert_called_once_with(
filters={'host': fake_compute_node.service['host']}, limit=1)
class TestModelBuilder(base.TestCase):
@mock.patch.object(nova_helper, 'NovaHelper', mock.MagicMock())
def test_add_instance_node(self):
model_builder = nova.ModelBuilder(osc=mock.MagicMock())
model_builder.model = mock.MagicMock()
mock_node = mock.MagicMock()
mock_host = mock_node.service["host"]
mock_instances = [mock.MagicMock()]
model_builder.add_instance_node(mock_node, mock_instances)
# verify that when len(instances) <= 1000, limit == len(instance).
model_builder.nova_helper.get_instance_list.assert_called_once_with(
filters={'host': mock_host}, limit=1)
# verify that when len(instances) > 1000, limit == -1.
mock_instance = mock.Mock()
mock_instances = [mock_instance] * 1001
model_builder.add_instance_node(mock_node, mock_instances)
model_builder.nova_helper.get_instance_list.assert_called_with(
filters={'host': mock_host}, limit=-1)

View File

@@ -1,5 +1,5 @@
{
"event_type": "instance.live_migration_post_dest.end",
"event_type": "instance.live_migration_post.end",
"payload": {
"nova_object.data": {
"action_initiator_project": "6f70656e737461636b20342065766572",

View File

@@ -69,8 +69,8 @@ class TestReceiveNovaNotifications(NotificationTestCase):
'instance.update': 'instance-update.json',
'instance.live_migration_force_complete.end':
'instance-live_migration_force_complete-end.json',
'instance.live_migration_post_dest.end':
'instance-live_migration_post_dest-end.json',
'instance.live_migration_post.end':
'instance-live_migration_post-end.json',
'instance.delete.end': 'instance-delete-end.json',
'instance.soft_delete.end': 'instance-soft_delete-end.json',
'service.create': 'service-create.json',
@@ -437,7 +437,7 @@ class TestNovaNotifications(NotificationTestCase):
node = compute_model.get_node_by_instance_uuid(instance0_uuid)
self.assertEqual('Node_0', node.uuid)
message = self.load_message(
'instance-live_migration_post_dest-end.json')
'instance-live_migration_post-end.json')
handler.info(
ctxt=self.context,
publisher_id=message['publisher_id'],
@@ -743,3 +743,21 @@ class TestNovaNotifications(NotificationTestCase):
self.assertEqual(
element.InstanceState.SUSPENDED.value, instance0.state)
def test_info_no_cdm(self):
# Tests that a notification is received before an audit has been
# performed which would create the nova CDM.
mock_collector = mock.Mock(cluster_data_model=None)
handler = novanotification.VersionedNotification(mock_collector)
payload = {
'nova_object.data': {
'uuid': '9966d6bd-a45c-4e1c-9d57-3054899a3ec7',
'host': None
}
}
with mock.patch.object(handler, 'update_instance') as update_instance:
handler.info(mock.sentinel.ctxt, 'publisher_id', 'instance.update',
payload, metadata={})
# update_instance should not be called since we did not add an
# Instance object to the CDM since the CDM does not exist yet.
update_instance.assert_not_called()