diff --git a/aodh/api/controllers/v2/alarm_rules/gnocchi.py b/aodh/api/controllers/v2/alarm_rules/gnocchi.py index ade84b7ff..f06b93459 100644 --- a/aodh/api/controllers/v2/alarm_rules/gnocchi.py +++ b/aodh/api/controllers/v2/alarm_rules/gnocchi.py @@ -63,7 +63,7 @@ class AlarmGnocchiThresholdRule(base.AlarmRule): ks_client = keystone_client.get_client(pecan.request.cfg) gnocchi_url = pecan.request.cfg.gnocchi_url headers = {'Content-Type': "application/json", - 'X-Auth-Token': ks_client.auth_token} + 'X-Auth-Token': keystone_client.get_auth_token(ks_client)} try: r = requests.get("%s/v1/capabilities" % gnocchi_url, headers=headers) @@ -103,7 +103,7 @@ class MetricOfResourceRule(AlarmGnocchiThresholdRule): ks_client = keystone_client.get_client(pecan.request.cfg) gnocchi_url = pecan.request.cfg.gnocchi_url headers = {'Content-Type': "application/json", - 'X-Auth-Token': ks_client.auth_token} + 'X-Auth-Token': keystone_client.get_auth_token(ks_client)} try: r = requests.get("%s/v1/resource/%s/%s" % ( gnocchi_url, rule.resource_type, @@ -168,7 +168,8 @@ class AggregationMetricByResourcesLookupRule(AlarmGnocchiThresholdRule): rule.resource_type, rule.metric), 'headers': {'Content-Type': "application/json", - 'X-Auth-Token': ks_client.auth_token}, + 'X-Auth-Token': keystone_client.get_auth_token( + ks_client)}, 'params': {'aggregation': rule.aggregation_method, 'needed_overlap': 0}, 'data': rule.query, diff --git a/aodh/evaluator/gnocchi.py b/aodh/evaluator/gnocchi.py index 4e725e714..1815beece 100644 --- a/aodh/evaluator/gnocchi.py +++ b/aodh/evaluator/gnocchi.py @@ -20,6 +20,7 @@ import requests from aodh.evaluator import threshold from aodh.i18n import _ +from aodh import keystone_client LOG = log.getLogger(__name__) @@ -40,7 +41,7 @@ class GnocchiThresholdEvaluator(threshold.ThresholdEvaluator): def _get_headers(self, content_type="application/json"): return { 'Content-Type': content_type, - 'X-Auth-Token': self.ks_client.auth_token, + 'X-Auth-Token': keystone_client.get_auth_token(self.ks_client), } def _statistics(self, alarm, start, end): diff --git a/aodh/evaluator/threshold.py b/aodh/evaluator/threshold.py index a67e513d1..d1e53a739 100644 --- a/aodh/evaluator/threshold.py +++ b/aodh/evaluator/threshold.py @@ -25,6 +25,7 @@ from oslo_utils import timeutils from aodh import evaluator from aodh.evaluator import utils from aodh.i18n import _, _LW +from aodh import keystone_client LOG = log.getLogger(__name__) @@ -53,20 +54,13 @@ class ThresholdEvaluator(evaluator.Evaluator): if self._cm_client is None: auth_config = self.conf.service_credentials self._cm_client = ceiloclient.get_client( - 2, - os_auth_url=auth_config.os_auth_url.replace('/v2.0', '/'), - os_region_name=auth_config.os_region_name, - os_tenant_name=auth_config.os_tenant_name, - os_password=auth_config.os_password, - os_username=auth_config.os_username, - os_cacert=auth_config.os_cacert, - os_endpoint_type=auth_config.os_endpoint_type, - insecure=auth_config.insecure, - timeout=self.conf.http_timeout, - os_user_domain_id=auth_config.os_user_domain_id, - os_project_name=auth_config.os_project_name, - os_project_domain_id=auth_config.os_project_domain_id, + version=2, + session=keystone_client.get_session(self.conf), + # ceiloclient adapter options + region_name=auth_config.region_name, + interface=auth_config.interface, ) + return self._cm_client @classmethod diff --git a/aodh/keystone_client.py b/aodh/keystone_client.py index 56a0bb687..2c36e415f 100644 --- a/aodh/keystone_client.py +++ b/aodh/keystone_client.py @@ -13,64 +13,60 @@ # License for the specific language governing permissions and limitations # under the License. +import os -from keystoneclient import discover as ks_discover -from keystoneclient import exceptions as ks_exception -from keystoneclient import session as ks_session -from keystoneclient.v2_0 import client as ks_client + +from keystoneauth1 import exceptions as ka_exception +from keystoneauth1 import identity as ka_identity +from keystoneauth1 import loading as ka_loading from keystoneclient.v3 import client as ks_client_v3 +from oslo_config import cfg +from oslo_log import log + +LOG = log.getLogger(__name__) + +CFG_GROUP = "service_credentials" -def get_client(conf): - return ks_client.Client( - username=conf.service_credentials.os_username, - password=conf.service_credentials.os_password, - tenant_id=conf.service_credentials.os_tenant_id, - tenant_name=conf.service_credentials.os_tenant_name, - cacert=conf.service_credentials.os_cacert, - auth_url=conf.service_credentials.os_auth_url, - region_name=conf.service_credentials.os_region_name, - insecure=conf.service_credentials.insecure, - timeout=conf.http_timeout,) +def get_session(conf, requests_session=None): + """Get a aodh service credentials auth session.""" + auth_plugin = ka_loading.load_auth_from_conf_options(conf, CFG_GROUP) + session = ka_loading.load_session_from_conf_options( + conf, CFG_GROUP, auth=auth_plugin, session=requests_session + ) + return session -def get_v3_client(conf, trust_id=None): +def get_client(conf, trust_id=None, requests_session=None): """Return a client for keystone v3 endpoint, optionally using a trust.""" - auth_url = conf.service_credentials.os_auth_url - try: - auth_url_noneversion = auth_url.replace('/v2.0', '/') - discover = ks_discover.Discover(auth_url=auth_url_noneversion) - v3_auth_url = discover.url_for('3.0') - if v3_auth_url: - auth_url = v3_auth_url - else: - auth_url = auth_url - except Exception: - auth_url = auth_url.replace('/v2.0', '/v3') - return ks_client_v3.Client( - username=conf.service_credentials.os_username, - password=conf.service_credentials.os_password, - cacert=conf.service_credentials.os_cacert, - auth_url=auth_url, - region_name=conf.service_credentials.os_region_name, - insecure=conf.service_credentials.insecure, - timeout=conf.http_timeout, - trust_id=trust_id) + session = get_session(conf, requests_session=requests_session) + return ks_client_v3.Client(session=session, trust_id=trust_id) -def create_trust_id(conf, trustor_user_id, trustor_project_id, - roles, auth_plugin): +def get_service_catalog(client): + return client.session.auth.get_access(client.session).service_catalog + + +def get_auth_token(client): + return client.session.auth.get_access(client.session).auth_token + + +def get_client_on_behalf_user(conf, auth_plugin, trust_id=None, + requests_session=None): + """Return a client for keystone v3 endpoint, optionally using a trust.""" + session = ka_loading.load_session_from_conf_options( + conf, CFG_GROUP, auth=auth_plugin, session=requests_session + ) + return ks_client_v3.Client(session=session, trust_id=trust_id) + + +def create_trust_id(conf, trustor_user_id, trustor_project_id, roles, + auth_plugin): """Create a new trust using the aodh service user.""" - admin_client = get_v3_client(conf) - + admin_client = get_client(conf) trustee_user_id = admin_client.auth_ref.user_id - session = ks_session.Session.construct({ - 'cacert': conf.service_credentials.os_cacert, - 'insecure': conf.service_credentials.insecure}) - - client = ks_client_v3.Client(session=session, auth=auth_plugin) - + client = get_client_on_behalf_user(conf, auth_plugin=auth_plugin) trust = client.trusts.create(trustor_user=trustor_user_id, trustee_user=trustee_user_id, project=trustor_project_id, @@ -81,12 +77,94 @@ def create_trust_id(conf, trustor_user_id, trustor_project_id, def delete_trust_id(conf, trust_id, auth_plugin): """Delete a trust previously setup for the aodh user.""" - session = ks_session.Session.construct({ - 'cacert': conf.service_credentials.os_cacert, - 'insecure': conf.service_credentials.insecure}) - - client = ks_client_v3.Client(session=session, auth=auth_plugin) + client = get_client_on_behalf_user(conf, auth_plugin=auth_plugin) try: client.trusts.delete(trust_id) - except ks_exception.NotFound: + except ka_exception.NotFound: pass + + +OPTS = [ + cfg.StrOpt('region-name', + default=os.environ.get('OS_REGION_NAME'), + deprecated_name="os-region-name", + help='Region name to use for OpenStack service endpoints.'), + cfg.StrOpt('interface', + default=os.environ.get( + 'OS_INTERFACE', os.environ.get('OS_ENDPOINT_TYPE', + 'public')), + deprecated_name="os-endpoint-type", + choices=('public', 'internal', 'admin', 'auth', 'publicURL', + 'internalURL', 'adminURL'), + help='Type of endpoint in Identity service catalog to use for ' + 'communication with OpenStack services.'), +] + + +def register_keystoneauth_opts(conf): + ka_loading.register_auth_conf_options(conf, CFG_GROUP) + ka_loading.register_session_conf_options( + conf, CFG_GROUP, + deprecated_opts={'cacert': [ + cfg.DeprecatedOpt('os-cacert', group=CFG_GROUP), + cfg.DeprecatedOpt('os-cacert', group="DEFAULT")] + }) + conf.set_default("auth_type", default="password-aodh-legacy", + group=CFG_GROUP) + + +def setup_keystoneauth(conf): + if conf[CFG_GROUP].auth_type == "password-aodh-legacy": + LOG.warn("Value 'password-aodh-legacy' for '[%s]/auth_type' " + "is deprecated. And will be removed in Aodh 3.0. " + "Use 'password' instead.", + CFG_GROUP) + ka_loading.load_auth_from_conf_options(conf, CFG_GROUP) + + +class LegacyAodhKeystoneLoader(ka_loading.BaseLoader): + @property + def plugin_class(self): + return ka_identity.V2Password + + def get_options(self): + options = super(LegacyAodhKeystoneLoader, self).get_options() + options.extend([ + ka_loading.Opt( + 'os-username', + default=os.environ.get('OS_USERNAME', 'aodh'), + help='User name to use for OpenStack service access.'), + ka_loading.Opt( + 'os-password', + secret=True, + default=os.environ.get('OS_PASSWORD', 'admin'), + help='Password to use for OpenStack service access.'), + ka_loading.Opt( + 'os-tenant-id', + default=os.environ.get('OS_TENANT_ID', ''), + help='Tenant ID to use for OpenStack service access.'), + ka_loading.Opt( + 'os-tenant-name', + default=os.environ.get('OS_TENANT_NAME', 'admin'), + help='Tenant name to use for OpenStack service access.'), + ka_loading.Opt( + 'os-auth-url', + default=os.environ.get('OS_AUTH_URL', + 'http://localhost:5000/v2.0'), + help='Auth URL to use for OpenStack service access.'), + ]) + return options + + def load_from_options(self, **kwargs): + options_map = { + 'os_auth_url': 'auth_url', + 'os_username': 'username', + 'os_password': 'password', + 'os_tenant_name': 'tenant_name', + 'os_tenant_id': 'tenant_id', + } + identity_kwargs = dict((options_map[o.dest], + kwargs.get(o.dest) or o.default) + for o in self.get_options() + if o.dest in options_map) + return self.plugin_class(**identity_kwargs) diff --git a/aodh/notifier/trust.py b/aodh/notifier/trust.py index 040ab61db..879791763 100644 --- a/aodh/notifier/trust.py +++ b/aodh/notifier/trust.py @@ -34,7 +34,7 @@ class TrustRestAlarmNotifier(rest.RestAlarmNotifier): reason, reason_data): trust_id = action.username - client = keystone_client.get_v3_client(self.conf, trust_id) + client = keystone_client.get_client(self.conf, trust_id) # Remove the fake user netloc = action.netloc.split("@")[1] @@ -44,7 +44,7 @@ class TrustRestAlarmNotifier(rest.RestAlarmNotifier): action = parse.SplitResult(scheme, netloc, action.path, action.query, action.fragment) - headers = {'X-Auth-Token': client.auth_token} + headers = {'X-Auth-Token': keystone_client.get_auth_token(client)} super(TrustRestAlarmNotifier, self).notify( action, alarm_id, alarm_name, severity, previous, current, reason, reason_data, headers) diff --git a/aodh/opts.py b/aodh/opts.py index ded7a5c82..0683ab165 100644 --- a/aodh/opts.py +++ b/aodh/opts.py @@ -13,6 +13,7 @@ # under the License. import itertools +from keystoneauth1 import loading from oslo_config import cfg import aodh.api @@ -22,6 +23,7 @@ import aodh.evaluator import aodh.evaluator.event import aodh.evaluator.gnocchi import aodh.event +import aodh.keystone_client import aodh.notifier.rest import aodh.rpc import aodh.service @@ -64,5 +66,16 @@ def list_opts(): ])), ('coordination', aodh.coordination.OPTS), ('database', aodh.storage.OPTS), - ('service_credentials', aodh.service.CLI_OPTS), + ('service_credentials', aodh.keystone_client.OPTS), ] + + +def list_keystoneauth_opts(): + # NOTE(sileht): the configuration file contains only the options + # for the password plugin that handles keystone v2 and v3 API + # with discovery. But other options are possible. + # Also, the default loaded plugin is password-aodh-legacy for + # backward compatibily + return [('service_credentials', ( + loading.get_auth_common_conf_options() + + loading.get_auth_plugin_conf_options('password')))] diff --git a/aodh/service.py b/aodh/service.py index 56c744264..0d7d1279d 100644 --- a/aodh/service.py +++ b/aodh/service.py @@ -14,7 +14,6 @@ # 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 os import socket from oslo_config import cfg @@ -23,6 +22,7 @@ import oslo_i18n from oslo_log import log from oslo_policy import opts as policy_opts +from aodh import keystone_client from aodh import messaging @@ -47,50 +47,6 @@ OPTS = [ ] -CLI_OPTS = [ - cfg.StrOpt('os-username', - default=os.environ.get('OS_USERNAME', 'aodh'), - help='User name to use for OpenStack service access.'), - cfg.StrOpt('os-password', - secret=True, - default=os.environ.get('OS_PASSWORD', 'admin'), - help='Password to use for OpenStack service access.'), - cfg.StrOpt('os-tenant-id', - default=os.environ.get('OS_TENANT_ID', ''), - help='Tenant ID to use for OpenStack service access.'), - cfg.StrOpt('os-tenant-name', - default=os.environ.get('OS_TENANT_NAME', 'admin'), - help='Tenant name to use for OpenStack service access.'), - cfg.StrOpt('os-cacert', - default=os.environ.get('OS_CACERT'), - help='Certificate chain for SSL validation.'), - cfg.StrOpt('os-auth-url', - default=os.environ.get('OS_AUTH_URL', - 'http://localhost:5000/v2.0'), - help='Auth URL to use for OpenStack service access.'), - cfg.StrOpt('os-region-name', - default=os.environ.get('OS_REGION_NAME'), - help='Region name to use for OpenStack service endpoints.'), - cfg.StrOpt('os-endpoint-type', - default=os.environ.get('OS_ENDPOINT_TYPE', 'publicURL'), - help='Type of endpoint in Identity service catalog to use for ' - 'communication with OpenStack services.'), - cfg.BoolOpt('insecure', - default=False, - help='Disables X.509 certificate validation when an ' - 'SSL connection to Identity Service is established.'), - cfg.StrOpt('os-user-domain-id', - default=os.environ.get('OS_USER_DOMAIN_ID', 'default'), - help='The domain id of the user'), - cfg.StrOpt('os-project-domain-id', - default=os.environ.get('OS_PROJECT_DOMAIN_ID', 'default'), - help='The domain id of the user project'), - cfg.StrOpt('os-project-name', - default=os.environ.get('OS_PROJECT_NAME', 'admin'), - help='The user project name'), -] - - def prepare_service(argv=None, config_files=None): conf = cfg.ConfigOpts() oslo_i18n.enable_lazy() @@ -105,9 +61,12 @@ def prepare_service(argv=None, config_files=None): for group, options in opts.list_opts(): conf.register_opts(list(options), group=None if group == "DEFAULT" else group) + keystone_client.register_keystoneauth_opts(conf) conf(argv, project='aodh', validate_default_values=True, default_config_files=config_files) + + keystone_client.setup_keystoneauth(conf) log.setup(conf, 'aodh') messaging.setup() return conf diff --git a/aodh/tests/functional/api/v2/test_alarm_scenarios.py b/aodh/tests/functional/api/v2/test_alarm_scenarios.py index 42a8c52ba..5558dcc5e 100644 --- a/aodh/tests/functional/api/v2/test_alarm_scenarios.py +++ b/aodh/tests/functional/api/v2/test_alarm_scenarios.py @@ -1320,7 +1320,7 @@ class TestAlarms(TestAlarmsBase): } auth = mock.Mock() trust_client = mock.Mock() - with mock.patch('aodh.keystone_client.get_v3_client') as client: + with mock.patch('aodh.keystone_client.get_client') as client: client.return_value = mock.Mock( auth_ref=mock.Mock(user_id='my_user')) with mock.patch('keystoneclient.v3.client.Client') as sub_client: @@ -1345,7 +1345,7 @@ class TestAlarms(TestAlarmsBase): else: self.fail("Alarm not found") - with mock.patch('aodh.keystone_client.get_v3_client') as client: + with mock.patch('aodh.keystone_client.get_client') as client: client.return_value = mock.Mock( auth_ref=mock.Mock(user_id='my_user')) with mock.patch('keystoneclient.v3.client.Client') as sub_client: @@ -1571,7 +1571,7 @@ class TestAlarms(TestAlarmsBase): data = self._get_alarm('a') data.update({'ok_actions': ['trust+http://something/ok']}) trust_client = mock.Mock() - with mock.patch('aodh.keystone_client.get_v3_client') as client: + with mock.patch('aodh.keystone_client.get_client') as client: client.return_value = mock.Mock( auth_ref=mock.Mock(user_id='my_user')) with mock.patch('keystoneclient.v3.client.Client') as sub_client: @@ -1586,7 +1586,7 @@ class TestAlarms(TestAlarmsBase): data.update({'ok_actions': ['http://no-trust-something/ok']}) - with mock.patch('aodh.keystone_client.get_v3_client') as client: + with mock.patch('aodh.keystone_client.get_client') as client: client.return_value = mock.Mock( auth_ref=mock.Mock(user_id='my_user')) with mock.patch('keystoneclient.v3.client.Client') as sub_client: diff --git a/aodh/tests/unit/evaluator/test_gnocchi.py b/aodh/tests/unit/evaluator/test_gnocchi.py index dac856317..dad012cb4 100644 --- a/aodh/tests/unit/evaluator/test_gnocchi.py +++ b/aodh/tests/unit/evaluator/test_gnocchi.py @@ -198,7 +198,7 @@ class TestGnocchiThresholdEvaluate(base.TestEvaluatorBase): self.requests.post.side_effect = [avgs2] self._evaluate_all_alarms() - expected_headers = {'X-Auth-Token': 'fake_token', + expected_headers = {'X-Auth-Token': mock.ANY, 'Content-Type': 'application/json'} start_alarm1 = "2015-01-26T12:51:00" diff --git a/aodh/tests/unit/test_notifier.py b/aodh/tests/unit/test_notifier.py index 0c8b2f8a5..ab3219506 100644 --- a/aodh/tests/unit/test_notifier.py +++ b/aodh/tests/unit/test_notifier.py @@ -243,7 +243,7 @@ class TestAlarmNotifier(tests_base.BaseTestCase): url = 'http://host/action' client = mock.MagicMock() - client.auth_token = 'token_1234' + client.session.auth.get_access.return_value.auth_token = 'token_1234' headers = {'X-Auth-Token': 'token_1234'} headers.update(self.HTTP_HEADERS) diff --git a/etc/aodh/aodh-config-generator.conf b/etc/aodh/aodh-config-generator.conf index d2ea63a41..48155071a 100644 --- a/etc/aodh/aodh-config-generator.conf +++ b/etc/aodh/aodh-config-generator.conf @@ -2,6 +2,7 @@ output_file = etc/aodh/aodh.conf wrap_width = 79 namespace = aodh +namespace = aodh-auth namespace = oslo.db namespace = oslo.log namespace = oslo.messaging diff --git a/setup.cfg b/setup.cfg index aeccad37c..ba8d7e9ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -106,6 +106,10 @@ console_scripts = oslo.config.opts = aodh = aodh.opts:list_opts + aodh-auth = aodh.opts:list_keystoneauth_opts + +keystoneauth1.plugin = + password-aodh-legacy = aodh.keystone_client:LegacyAodhKeystoneLoader [build_sphinx] all_files = 1