Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3640fc4a0b | ||
|
|
15511203d8 | ||
|
|
f390f3526f | ||
|
|
0ce454f3b3 | ||
|
|
5b4729dcbe | ||
|
|
a36b42afef | ||
|
|
9699fc5f70 | ||
|
|
51a3b3fe3f | ||
|
|
b30fad9965 |
@@ -6,12 +6,12 @@ Client for resource optimization service for OpenStack.
|
||||
|
||||
OpenStack Watcher provides a flexible and scalable resource optimization
|
||||
service for multi-tenant OpenStack-based clouds.
|
||||
Watcher provides a complete optimization loop—including everything from a
|
||||
Watcher provides a complete optimization loop-including everything from a
|
||||
metrics receiver, complex event processor and profiler, optimization processor
|
||||
and an action plan applier. This provides a robust framework to realize a wide
|
||||
range of cloud optimization goals, including the reduction of data center
|
||||
operating costs, increased system performance via intelligent virtual machine
|
||||
migration, increased energy efficiency—and more!
|
||||
migration, increased energy efficiency-and more!
|
||||
|
||||
* Free software: Apache license
|
||||
* Wiki: http://wiki.openstack.org/wiki/Watcher
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
Babel>=1.3
|
||||
oslo.i18n>=1.5.0 # Apache-2.0
|
||||
oslo.utils>=2.0.0,!=2.6.0 # Apache-2.0
|
||||
pbr>=1.6
|
||||
python-keystoneclient>=1.6.0,!=1.8.0
|
||||
six>=1.9.0
|
||||
Babel>=1.3 # BSD
|
||||
oslo.i18n>=2.1.0 # Apache-2.0
|
||||
oslo.utils>=3.5.0 # Apache-2.0
|
||||
pbr>=1.6 # Apache-2.0
|
||||
python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
|
||||
3
setup.py
Executable file → Normal file
3
setup.py
Executable file → Normal file
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -26,5 +25,5 @@ except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
coverage>=3.6
|
||||
discover
|
||||
hacking<0.11,>=0.10
|
||||
httpretty>=0.8.4,<0.8.7
|
||||
mock>=1.2
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
python-subunit>=0.0.18
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=1.4.0
|
||||
coverage>=3.6 # Apache-2.0
|
||||
discover # BSD
|
||||
hacking<0.11,>=0.10.2
|
||||
mock>=1.2 # BSD
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
||||
# Needed for pypi packaging
|
||||
wheel
|
||||
twine
|
||||
wheel # MIT
|
||||
|
||||
5
tox.ini
5
tox.ini
@@ -34,11 +34,6 @@ ignore = E123,E125
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
||||
|
||||
[testenv:pypi]
|
||||
commands =
|
||||
python setup.py sdist bdist_wheel
|
||||
twine upload --config-file .pypirc {posargs} dist/*
|
||||
|
||||
[testenv:wheel]
|
||||
commands = python setup.py bdist_wheel
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ class HTTPClient(object):
|
||||
self.log_http_response(resp)
|
||||
|
||||
if 400 <= resp.status < 600:
|
||||
LOG.warn(_LW("Request returned failure status."))
|
||||
LOG.warning(_LW("Request returned failure status."))
|
||||
error_json = _extract_error_json(body_str)
|
||||
raise exc.from_response(
|
||||
resp, error_json.get('faultstring'),
|
||||
|
||||
@@ -12,24 +12,17 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import fixtures
|
||||
import httpretty
|
||||
from keystoneclient import exceptions as keystone_exc
|
||||
from keystoneclient.fixture import v2 as ks_v2_fixture
|
||||
from keystoneclient.fixture import v3 as ks_v3_fixture
|
||||
import mock
|
||||
import six
|
||||
import testtools
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient import exceptions as exc
|
||||
from watcherclient import shell as watcher_shell
|
||||
from watcherclient.tests import keystone_client_fixtures
|
||||
from watcherclient.tests import utils
|
||||
|
||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
@@ -37,22 +30,6 @@ FAKE_ENV = {'OS_USERNAME': 'username',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0/'}
|
||||
|
||||
FAKE_ENV_KEYSTONE_V2 = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
|
||||
}
|
||||
|
||||
FAKE_ENV_KEYSTONE_V3 = {
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': keystone_client_fixtures.BASE_URL,
|
||||
'OS_USER_DOMAIN_ID': 'default',
|
||||
'OS_PROJECT_DOMAIN_ID': 'default',
|
||||
}
|
||||
|
||||
|
||||
class ShellTest(utils.BaseTestCase):
|
||||
re_options = re.DOTALL | re.MULTILINE
|
||||
@@ -175,132 +152,3 @@ class ShellTest(utils.BaseTestCase):
|
||||
for r in required:
|
||||
self.assertThat(stdout,
|
||||
matchers.MatchesRegex(r, self.re_options))
|
||||
|
||||
|
||||
class TestCase(testtools.TestCase):
|
||||
|
||||
tokenid = keystone_client_fixtures.TOKENID
|
||||
|
||||
def set_fake_env(self, fake_env):
|
||||
client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_TENANT_ID',
|
||||
'OS_TENANT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME',
|
||||
'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE',
|
||||
'OS_ENDPOINT_TYPE')
|
||||
|
||||
for key in client_env:
|
||||
self.useFixture(
|
||||
fixtures.EnvironmentVariable(key, fake_env.get(key)))
|
||||
|
||||
# required for testing with Python 2.6
|
||||
def assertRegexpMatches(self, text, expected_regexp, msg=None):
|
||||
"""Fail the test unless the text matches the regular expression."""
|
||||
if isinstance(expected_regexp, six.string_types):
|
||||
expected_regexp = re.compile(expected_regexp)
|
||||
if not expected_regexp.search(text):
|
||||
msg = msg or "Regexp didn't match"
|
||||
msg = '%s: %r not found in %r' % (
|
||||
msg, expected_regexp.pattern, text)
|
||||
raise self.failureException(msg)
|
||||
|
||||
def register_keystone_v2_token_fixture(self):
|
||||
v2_token = ks_v2_fixture.Token(token_id=self.tokenid)
|
||||
service = v2_token.add_service('baremetal')
|
||||
service.add_endpoint('http://watcher.example.com', region='RegionOne')
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
'%s/tokens' % (keystone_client_fixtures.V2_URL),
|
||||
body=json.dumps(v2_token))
|
||||
|
||||
def register_keystone_v3_token_fixture(self):
|
||||
v3_token = ks_v3_fixture.Token()
|
||||
service = v3_token.add_service('baremetal')
|
||||
service.add_standard_endpoints(public='http://watcher.example.com')
|
||||
httpretty.register_uri(
|
||||
httpretty.POST,
|
||||
'%s/auth/tokens' % (keystone_client_fixtures.V3_URL),
|
||||
body=json.dumps(v3_token),
|
||||
adding_headers={'X-Subject-Token': self.tokenid})
|
||||
|
||||
def register_keystone_auth_fixture(self):
|
||||
self.register_keystone_v2_token_fixture()
|
||||
self.register_keystone_v3_token_fixture()
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
keystone_client_fixtures.BASE_URL,
|
||||
body=keystone_client_fixtures.keystone_request_callback)
|
||||
|
||||
|
||||
class ShellTestNoMox(TestCase):
|
||||
def setUp(self):
|
||||
super(ShellTestNoMox, self).setUp()
|
||||
# httpretty doesn't work as expected if http proxy environment
|
||||
# variable is set.
|
||||
os.environ = dict((k, v) for (k, v) in os.environ.items()
|
||||
if k.lower() not in ('http_proxy', 'https_proxy'))
|
||||
self.set_fake_env(FAKE_ENV_KEYSTONE_V2)
|
||||
|
||||
def shell(self, argstr):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = six.StringIO()
|
||||
_shell = watcher_shell.WatcherShell()
|
||||
_shell.main(argstr.split())
|
||||
self.subcommands = _shell.subcommands.keys()
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
self.assertEqual(0, exc_value.code)
|
||||
finally:
|
||||
out = sys.stdout.getvalue()
|
||||
sys.stdout.close()
|
||||
sys.stdout = orig
|
||||
|
||||
return out
|
||||
|
||||
# @httpretty.activate
|
||||
# def test_action_list(self):
|
||||
# self.register_keystone_auth_fixture()
|
||||
# resp_dict = {"dummies": [
|
||||
# {"instance_uuid": "null",
|
||||
# "uuid": "351a82d6-9f04-4c36-b79a-a38b9e98ff71",
|
||||
# "links": [{"href": "http://watcher.example.com:6385/"
|
||||
# "v1/dummies/foo",
|
||||
# "rel": "self"},
|
||||
# {"href": "http://watcher.example.com:6385/"
|
||||
# "dummies/foo",
|
||||
# "rel": "bookmark"}],
|
||||
# "maintenance": "false",
|
||||
# "provision_state": "null",
|
||||
# "power_state": "power off"},
|
||||
# {"instance_uuid": "null",
|
||||
# "uuid": "66fbba13-29e8-4b8a-9e80-c655096a40d3",
|
||||
# "links": [{"href": "http://watcher.example.com:6385/"
|
||||
# "v1/dummies/foo2",
|
||||
# "rel": "self"},
|
||||
# {"href": "http://watcher.example.com:6385/"
|
||||
# "dummies/foo2",
|
||||
# "rel": "bookmark"}],
|
||||
# "maintenance": "false",
|
||||
# "provision_state": "null",
|
||||
# "power_state": "power off"}]}
|
||||
# httpretty.register_uri(
|
||||
# httpretty.GET,
|
||||
# 'http://watcher.example.com/v1/dummies',
|
||||
# status=200,
|
||||
# content_type='application/json; charset=UTF-8',
|
||||
# body=json.dumps(resp_dict))
|
||||
|
||||
# event_list_text = self.shell('action-list')
|
||||
|
||||
# required = [
|
||||
# '351a82d6-9f04-4c36-b79a-a38b9e98ff71',
|
||||
# '66fbba13-29e8-4b8a-9e80-c655096a40d3',
|
||||
# ]
|
||||
|
||||
# for r in required:
|
||||
# self.assertRegexpMatches(event_list_text, r)
|
||||
|
||||
|
||||
class ShellTestNoMoxV3(ShellTestNoMox):
|
||||
|
||||
def _set_fake_env(self):
|
||||
self.set_fake_env(FAKE_ENV_KEYSTONE_V3)
|
||||
|
||||
@@ -38,7 +38,7 @@ ACTION_PLAN2 = {
|
||||
}
|
||||
|
||||
UPDATED_ACTION_PLAN = copy.deepcopy(ACTION_PLAN1)
|
||||
NEW_STATE = 'TRIGGERED'
|
||||
NEW_STATE = 'PENDING'
|
||||
UPDATED_ACTION_PLAN['state'] = NEW_STATE
|
||||
|
||||
fake_responses = {
|
||||
|
||||
@@ -137,7 +137,7 @@ class ActionPlanShellTest(utils.BaseTestCase):
|
||||
|
||||
ap_shell.do_action_plan_start(client_mock, args)
|
||||
patch = commonutils.args_array_to_patch(
|
||||
'replace', ['state=TRIGGERED'])
|
||||
'replace', ['state=PENDING'])
|
||||
client_mock.action_plan.update.assert_called_once_with(
|
||||
'a5199d0e-0702-4613-9234-5ae2af8dafea', patch)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import copy
|
||||
|
||||
from six.moves.urllib import parse as urlparse
|
||||
import testtools
|
||||
from testtools.matchers import HasLength
|
||||
from testtools import matchers
|
||||
|
||||
from watcherclient.tests import utils
|
||||
import watcherclient.v1.audit_template
|
||||
@@ -41,7 +41,7 @@ AUDIT_TMPL2 = {
|
||||
'description': 'Audit Template 2 description',
|
||||
'host_aggregate': 8,
|
||||
'extra': {'automatic': True},
|
||||
'goal': 'SERVERS_CONSOLIDATION'
|
||||
'goal': 'BASIC_CONSOLIDATION'
|
||||
}
|
||||
|
||||
AUDIT_TMPL3 = {
|
||||
@@ -176,6 +176,16 @@ fake_responses_sorting = {
|
||||
},
|
||||
}
|
||||
|
||||
fake_responses_filter_by_goal = {
|
||||
'/v1/audit_templates/?goal=BASIC_CONSOLIDATION':
|
||||
{
|
||||
'GET': (
|
||||
{},
|
||||
{"audit_templates": [AUDIT_TMPL2]}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class AuditTemplateManagerTest(testtools.TestCase):
|
||||
|
||||
@@ -202,6 +212,18 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_by_goal(self):
|
||||
self.api = utils.FakeAPI(fake_responses_filter_by_goal)
|
||||
self.mgr = watcherclient.v1.audit_template.AuditTemplateManager(
|
||||
self.api)
|
||||
audit_templates = self.mgr.list(goal="BASIC_CONSOLIDATION")
|
||||
expect = [
|
||||
('GET', '/v1/audit_templates/?goal=%s' % AUDIT_TMPL2['goal'],
|
||||
{}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(1, len(audit_templates))
|
||||
|
||||
def test_audit_templates_list_detail(self):
|
||||
audit_templates = self.mgr.list(detail=True)
|
||||
expect = [
|
||||
@@ -230,7 +252,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audit_templates/?limit=1', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audit_templates, HasLength(1))
|
||||
self.assertThat(audit_templates, matchers.HasLength(1))
|
||||
|
||||
def test_audit_templates_list_pagination_no_limit(self):
|
||||
self.api = utils.FakeAPI(fake_responses_pagination)
|
||||
@@ -242,7 +264,7 @@ class AuditTemplateManagerTest(testtools.TestCase):
|
||||
('GET', '/v1/audit_templates/?limit=1', {}, None)
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(audit_templates, HasLength(2))
|
||||
self.assertThat(audit_templates, matchers.HasLength(2))
|
||||
|
||||
def test_audit_templates_list_sort_key(self):
|
||||
self.api = utils.FakeAPI(fake_responses_sorting)
|
||||
|
||||
@@ -145,7 +145,7 @@ def do_action_plan_start(cc, args):
|
||||
action_plan_uuid = getattr(args, 'action-plan')
|
||||
if uuidutils.is_uuid_like(action_plan_uuid):
|
||||
args.op = 'replace'
|
||||
args.attributes = [['state=TRIGGERED']]
|
||||
args.attributes = [['state=PENDING']]
|
||||
|
||||
patch = utils.args_array_to_patch(
|
||||
args.op,
|
||||
|
||||
@@ -34,7 +34,7 @@ class AuditTemplateManager(base.Manager):
|
||||
def _path(id=None):
|
||||
return '/v1/audit_templates/%s' % id if id else '/v1/audit_templates'
|
||||
|
||||
def list(self, name=None, limit=None, sort_key=None,
|
||||
def list(self, name=None, goal=None, limit=None, sort_key=None,
|
||||
sort_dir=None, detail=False):
|
||||
"""Retrieve a list of audit template.
|
||||
|
||||
@@ -65,6 +65,8 @@ class AuditTemplateManager(base.Manager):
|
||||
filters = utils.common_filters(limit, sort_key, sort_dir)
|
||||
if name is not None:
|
||||
filters.append('name=%s' % name)
|
||||
if goal is not None:
|
||||
filters.append("goal=%s" % goal)
|
||||
|
||||
path = ''
|
||||
if detail:
|
||||
|
||||
@@ -45,10 +45,6 @@ def do_audit_template_show(cc, args):
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Show detailed information about audit templates.")
|
||||
@cliutils.arg(
|
||||
'--name',
|
||||
metavar='<name>',
|
||||
help='Only show information for the audit template with this name.')
|
||||
@cliutils.arg(
|
||||
'--goal',
|
||||
metavar='<goal>',
|
||||
@@ -73,8 +69,6 @@ def do_audit_template_list(cc, args):
|
||||
"""List the audit templates."""
|
||||
params = {}
|
||||
|
||||
if args.name is not None:
|
||||
params['name'] = args.name
|
||||
if args.goal is not None:
|
||||
params['goal'] = args.goal
|
||||
if args.detail:
|
||||
|
||||
Reference in New Issue
Block a user