Merge "Improve OpenStack clients API"

This commit is contained in:
Jenkins
2015-12-01 16:32:04 +00:00
committed by Gerrit Code Review
9 changed files with 266 additions and 86 deletions

View File

@@ -23,9 +23,9 @@ from oslo_config import cfg
from watcher.applier.api.primitive_command import PrimitiveCommand
from watcher.applier.api.promise import Promise
from watcher.applier.framework.command.wrapper.nova_wrapper import NovaWrapper
from watcher.common.keystone import Client
from watcher.decision_engine.model.hypervisor_state import \
HypervisorState
from watcher.common.keystone import KeystoneClient
from watcher.decision_engine.model.hypervisor_state import HypervisorState
CONF = cfg.CONF
@@ -36,7 +36,7 @@ class HypervisorStateCommand(PrimitiveCommand):
self.status = status
def nova_manage_service(self, status):
keystone = Client()
keystone = KeystoneClient()
wrapper = NovaWrapper(keystone.get_credentials(),
session=keystone.get_session())
if status is True:

View File

@@ -24,7 +24,8 @@ from oslo_config import cfg
from watcher.applier.api.primitive_command import PrimitiveCommand
from watcher.applier.api.promise import Promise
from watcher.applier.framework.command.wrapper.nova_wrapper import NovaWrapper
from watcher.common.keystone import Client
from watcher.common.keystone import KeystoneClient
from watcher.decision_engine.planner.default import Primitives
CONF = cfg.CONF
@@ -41,7 +42,7 @@ class MigrateCommand(PrimitiveCommand):
self.destination_hypervisor = destination_hypervisor
def migrate(self, destination):
keystone = Client()
keystone = KeystoneClient()
wrapper = NovaWrapper(keystone.get_credentials(),
session=keystone.get_session())
instance = wrapper.find_instance(self.instance_uuid)

View File

@@ -16,34 +16,32 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from urlparse import urlparse
from ceilometerclient import client
from ceilometerclient.exc import HTTPUnauthorized
from watcher.common import keystone
class Client(object):
# todo olso conf: this must be sync with ceilometer
CEILOMETER_API_VERSION = '2'
class CeilometerClient(object):
def __init__(self, api_version='2'):
self._cmclient = None
self._api_version = api_version
def __init__(self):
ksclient = keystone.Client()
self.creds = ksclient.get_credentials()
self.creds['os_auth_token'] = ksclient.get_token()
self.creds['token'] = ksclient.get_token()
self.creds['ceilometer_url'] = "http://" + urlparse(
ksclient.get_endpoint(
service_type='metering',
endpoint_type='publicURL')).netloc
self.connect()
def connect(self):
@property
def cmclient(self):
"""Initialization of Ceilometer client."""
self.cmclient = client.get_client(self.CEILOMETER_API_VERSION,
**self.creds)
if not self._cmclient:
ksclient = keystone.KeystoneClient()
creds = ksclient.get_credentials()
endpoint = ksclient.get_endpoint(
service_type='metering',
endpoint_type='publicURL')
self._cmclient = client.get_client(self._api_version,
ceilometer_url=endpoint,
**creds)
return self._cmclient
def build_query(user_id=None, tenant_id=None, resource_id=None,
def build_query(self, user_id=None, tenant_id=None, resource_id=None,
user_ids=None, tenant_ids=None, resource_ids=None):
"""Returns query built from given parameters.
This query can be then used for querying resources, meters and
@@ -78,37 +76,32 @@ class Client(object):
return query
def query_sample(self, meter_name, query, limit=1):
def query_retry(self, f, *args, **kargs):
try:
samples = self.ceilometerclient().samples.list(
meter_name=meter_name,
limit=limit,
q=query)
return f(*args, **kargs)
except HTTPUnauthorized:
self.connect()
samples = self.ceilometerclient().samples.list(
meter_name=meter_name,
limit=limit,
q=query)
self.reset_client()
return f(*args, **kargs)
except Exception:
raise
return samples
def get_endpoint(self, service_type, endpoint_type=None):
ksclient = keystone.Client()
endpoint = ksclient.get_endpoint(service_type=service_type,
endpoint_type=endpoint_type)
return endpoint
def query_sample(self, meter_name, query, limit=1):
return self.query_retry(f=self.cmclient.samples.list,
meter_name=meter_name,
limit=limit,
q=query)
def statistic_list(self, meter_name, query=None, period=None):
"""List of statistics."""
statistics = self.ceilometerclient().statistics.list(
meter_name=meter_name, q=query, period=period)
statistics = self.cmclient.statistics.list(
meter_name=meter_name,
q=query,
period=period)
return statistics
def meter_list(self, query=None):
"""List the user's meters."""
meters = self.ceilometerclient().meters.list(query)
meters = self.query_retry(f=self.cmclient.meters.list, query=query)
return meters
def statistic_aggregation(self,
@@ -129,25 +122,14 @@ class Client(object):
"""Representing a statistic aggregate by operators"""
query = self.build_query(resource_id=resource_id)
try:
statistic = self.cmclient.statistics.list(
meter_name=meter_name,
q=query,
period=period,
aggregates=[
{'func': aggregate}],
groupby=['resource_id'])
except HTTPUnauthorized:
self.connect()
statistic = self.cmclient.statistics.list(
meter_name=meter_name,
q=query,
period=period,
aggregates=[
{'func': aggregate}],
groupby=['resource_id'])
except Exception:
raise
statistic = self.query_retry(f=self.cmclient.statistics.list,
meter_name=meter_name,
q=query,
period=period,
aggregates=[
{'func': aggregate}],
groupby=['resource_id'])
item_value = None
if statistic:
item_value = statistic[-1]._info.get('aggregate').get('avg')
@@ -171,3 +153,6 @@ class Client(object):
return samples[-1]._info['counter_volume']
else:
return False
def reset_client(self):
self._cmclient = None

View File

@@ -282,6 +282,10 @@ class NoDataFound(BaseException):
return self._desc
class KeystoneFailure(WatcherException):
message = _("'Keystone API endpoint is missing''")
class ClusterEmpty(WatcherException):
message = _("The list of hypervisor(s) in the cluster is empty.'")

View File

@@ -16,14 +16,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import datetime
from keystoneclient.auth.identity import v3
from keystoneclient import session
import keystoneclient.v3.client as ksclient
from oslo_config import cfg
from oslo_log import log
from urlparse import urljoin
from urlparse import urlparse
from keystoneclient.auth.identity import generic
from keystoneclient import session as keystone_session
from watcher.common.exception import KeystoneFailure
LOG = log.getLogger(__name__)
CONF = cfg.CONF
@@ -35,30 +40,66 @@ CONF.import_opt('admin_password', 'keystonemiddleware.auth_token',
group='keystone_authtoken')
CONF.import_opt('auth_uri', 'keystonemiddleware.auth_token',
group='keystone_authtoken')
CONF.import_opt('auth_version', 'keystonemiddleware.auth_token',
group='keystone_authtoken')
CONF.import_opt('insecure', 'keystonemiddleware.auth_token',
group='keystone_authtoken')
class Client(object):
class KeystoneClient(object):
def __init__(self):
ks_args = self.get_credentials()
self.ks_client = ksclient.Client(**ks_args)
self._ks_client = None
self._session = None
self._auth = None
self._token = None
def get_endpoint(self, **kwargs):
kc = self._get_ksclient()
if not kc.has_service_catalog():
raise KeystoneFailure('No Keystone service catalog '
'loaded')
attr = None
filter_value = None
if kwargs.get('region_name'):
attr = 'region'
filter_value = kwargs.get('region_name')
return self.ks_client.service_catalog.url_for(
return self._get_ksclient().service_catalog.url_for(
service_type=kwargs.get('service_type') or 'metering',
attr=attr,
filter_value=filter_value,
endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
def get_token(self):
return self.ks_client.auth_token
def _is_apiv3(self, auth_url, auth_version):
return auth_version == 'v3.0' or '/v3' in urlparse(auth_url).path
@staticmethod
def get_credentials():
def get_keystone_url(self, auth_url, auth_version):
"""Gives an http/https url to contact keystone.
"""
api_v3 = self._is_apiv3(auth_url, auth_version)
api_version = 'v3' if api_v3 else 'v2.0'
# NOTE(lucasagomes): Get rid of the trailing '/' otherwise urljoin()
# fails to override the version in the URL
return urljoin(auth_url.rstrip('/'), api_version)
def _get_ksclient(self):
"""Get an endpoint and auth token from Keystone.
"""
ks_args = self.get_credentials()
auth_version = CONF.keystone_authtoken.auth_version
auth_url = CONF.keystone_authtoken.auth_uri
api_version = self._is_apiv3(auth_url, auth_version)
if api_version:
from keystoneclient.v3 import client
else:
from keystoneclient.v2_0 import client
# generic
# ksclient = client.Client(version=api_version,
# session=session,**ks_args)
return client.Client(**ks_args)
def get_credentials(self):
creds = \
{'auth_url': CONF.keystone_authtoken.auth_uri,
'username': CONF.keystone_authtoken.admin_user,
@@ -71,10 +112,6 @@ class Client(object):
def get_session(self):
creds = self.get_credentials()
auth = v3.Password(**creds)
return session.Session(auth=auth)
def is_token_expired(self, token):
expires = datetime.datetime.strptime(token['expires'],
'%Y-%m-%dT%H:%M:%SZ')
return datetime.datetime.now() >= expires
self._auth = generic.Password(**creds)
session = keystone_session.Session(auth=self._auth)
return session

View File

@@ -20,7 +20,7 @@
from oslo_config import cfg
from oslo_log import log
from watcher.common.ceilometer import Client
from watcher.common.ceilometer import CeilometerClient
from watcher.metrics_engine.cluster_history.api import BaseClusterHistory
@@ -30,7 +30,7 @@ LOG = log.getLogger(__name__)
class CeilometerClusterHistory(BaseClusterHistory):
def __init__(self):
self.ceilometer = Client()
self.ceilometer = CeilometerClient()
def statistic_list(self, meter_name, query=None, period=None):
return self.ceilometer.statistic_list(meter_name, query, period)

View File

@@ -21,7 +21,7 @@ from oslo_config import cfg
from oslo_log import log
from watcher.applier.framework.command.wrapper.nova_wrapper import NovaWrapper
from watcher.common.keystone import Client
from watcher.common.keystone import KeystoneClient
from watcher.metrics_engine.cluster_model_collector.nova import \
NovaClusterModelCollector
@@ -31,7 +31,7 @@ CONF = cfg.CONF
class CollectorManager(object):
def get_cluster_model_collector(self):
keystone = Client()
keystone = KeystoneClient()
wrapper = NovaWrapper(keystone.get_credentials(),
session=keystone.get_session())
return NovaClusterModelCollector(wrapper=wrapper)

View File

@@ -0,0 +1,93 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>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 __future__ import absolute_import
from __future__ import unicode_literals
from mock import MagicMock
from mock import mock
from watcher.common.ceilometer import CeilometerClient
from watcher.tests.base import BaseTestCase
class TestCeilometer(BaseTestCase):
def setUp(self):
super(TestCeilometer, self).setUp()
self.cm = CeilometerClient()
def test_build_query(self):
expected = [{'field': 'user_id', 'op': 'eq', 'value': u'user_id'},
{'field': 'project_id', 'op': 'eq', 'value': u'tenant_id'},
{'field': 'resource_id', 'op': 'eq',
'value': u'resource_id'}]
query = self.cm.build_query(user_id="user_id",
tenant_id="tenant_id",
resource_id="resource_id",
user_ids=["user_ids"],
tenant_ids=["tenant_ids"],
resource_ids=["resource_ids"])
self.assertEqual(query, expected)
@mock.patch("watcher.common.keystone.Keystoneclient")
def test_get_ceilometer_v2(self, mock_keystone):
c = CeilometerClient(api_version='2')
from ceilometerclient.v2 import Client
self.assertIsInstance(c.cmclient, Client)
@mock.patch.object(CeilometerClient, "cmclient")
def test_statistic_aggregation(self, mock_keystone):
statistic = MagicMock()
expected_result = 100
statistic[-1]._info = {'aggregate': {'avg': expected_result}}
mock_keystone.statistics.list.return_value = statistic
val = self.cm.statistic_aggregation(
resource_id="VM_ID",
meter_name="cpu_util",
period="7300"
)
self.assertEqual(val, expected_result)
@mock.patch.object(CeilometerClient, "cmclient")
def test_get_last_sample(self, mock_keystone):
statistic = MagicMock()
expected_result = 100
statistic[-1]._info = {'counter_volume': expected_result}
mock_keystone.samples.list.return_value = statistic
val = self.cm.get_last_sample_value(
resource_id="id",
meter_name="compute.node.percent"
)
self.assertEqual(val, expected_result)
@mock.patch.object(CeilometerClient, "cmclient")
def test_get_last_sample_none(self, mock_keystone):
expected = False
mock_keystone.samples.list.return_value = None
val = self.cm.get_last_sample_values(
resource_id="id",
meter_name="compute.node.percent"
)
self.assertEqual(val, expected)
@mock.patch.object(CeilometerClient, "cmclient")
def test_statistic_list(self, mock_keystone):
expected_value = []
mock_keystone.statistics.list.return_value = expected_value
val = self.cm.statistic_list(meter_name="cpu_util")
self.assertEqual(val, expected_value)

View File

@@ -0,0 +1,60 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>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 __future__ import absolute_import
from __future__ import unicode_literals
from keystoneclient.auth.identity import Password
from keystoneclient.session import Session
from mock import mock
from watcher.common.keystone import KeystoneClient
from watcher.tests.base import BaseTestCase
class TestKeyStone(BaseTestCase):
def setUp(self):
super(TestKeyStone, self).setUp()
self.ckeystone = KeystoneClient()
@mock.patch('keystoneclient.client.Client')
def test_get_endpoint(self, keystone):
expected_endpoint = "http://IP:PORT"
ks = mock.Mock()
ks.service_catalog.url_for.return_value = expected_endpoint
keystone.return_value = ks
ep = self.ckeystone.get_endpoint(service_type='metering',
endpoint_type='publicURL',
region_name='RegionOne')
self.assertEqual(ep, expected_endpoint)
def test_get_session(self):
k = KeystoneClient()
session = k.get_session()
self.assertIsInstance(session.auth, Password)
self.assertIsInstance(session, Session)
def test_get_credentials(self):
expected_creds = {'auth_url': None,
'password': None,
'project_domain_name': 'default',
'project_name': 'admin',
'user_domain_name': 'default',
'username': None}
creds = self.ckeystone.get_credentials()
self.assertEqual(creds, expected_creds)