1#   Licensed under the Apache License, Version 2.0 (the "License"); you may
2#   not use this file except in compliance with the License. You may obtain
3#   a copy of the License at
4#
5#       http://www.apache.org/licenses/LICENSE-2.0
6#
7#   Unless required by applicable law or agreed to in writing, software
8#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10#   License for the specific language governing permissions and limitations
11#   under the License.
12
13import types
14
15from keystoneauth1 import exceptions as ks_exc
16from keystoneauth1.identity import v2 as v2_auth
17from keystoneauth1.identity import v3 as v3_auth
18from keystoneauth1 import session as ks_session
19import mock
20import requests
21
22from ceilometerclient.apiclient import exceptions
23from ceilometerclient import client
24from ceilometerclient import exc
25from ceilometerclient.tests.unit import utils
26from ceilometerclient.v2 import client as v2client
27
28FAKE_ENV = {
29    'username': 'username',
30    'password': 'password',
31    'tenant_name': 'tenant_name',
32    'auth_url': 'http://no.where',
33    'auth_plugin': mock.Mock(),
34    'ceilometer_url': 'http://no.where',
35    'token': '1234',
36    'user_domain_name': 'default',
37    'project_domain_name': 'default',
38}
39
40
41class ClientTest(utils.BaseTestCase):
42    @staticmethod
43    def create_client(env, api_version=2, endpoint=None, exclude=[]):
44        env = dict((k, v) for k, v in env.items()
45                   if k not in exclude)
46        with mock.patch(
47                'ceilometerclient.v2.client.Client._get_redirect_client',
48                return_value=None):
49            return client.get_client(api_version, **env)
50
51    def test_client_v2_with_session(self):
52        resp = mock.Mock(status_code=200, text=b'')
53        resp.json.return_value = []
54        session = mock.Mock()
55        session.request.return_value = resp
56        c = client.get_client(2, session=session)
57        c.resources.list()
58        self.assertTrue(session.request.called)
59        self.assertTrue(resp.json.called)
60
61    def test_client_version(self):
62        c2 = self.create_client(env=FAKE_ENV, api_version=2)
63        self.assertIsInstance(c2, v2client.Client)
64
65    def test_client_auth_lambda(self):
66        env = FAKE_ENV.copy()
67        env['token'] = lambda: env['token']
68        self.assertIsInstance(env['token'],
69                              types.FunctionType)
70        c2 = self.create_client(env)
71        self.assertIsInstance(c2, v2client.Client)
72
73    def test_client_auth_non_lambda(self):
74        env = FAKE_ENV.copy()
75        env['token'] = "1234"
76        self.assertIsInstance(env['token'], str)
77        c2 = self.create_client(env)
78        self.assertIsInstance(c2, v2client.Client)
79
80    def test_client_without_auth_plugin(self):
81        env = FAKE_ENV.copy()
82        del env['auth_plugin']
83        c = self.create_client(env, api_version=2, endpoint='fake_endpoint')
84        self.assertIsInstance(c.auth_plugin, client.AuthPlugin)
85
86    def test_client_without_auth_plugin_keystone_v3(self):
87        env = FAKE_ENV.copy()
88        del env['auth_plugin']
89        expected = {
90            'username': 'username',
91            'endpoint': 'http://no.where',
92            'tenant_name': 'tenant_name',
93            'service_type': None,
94            'token': '1234',
95            'endpoint_type': None,
96            'region_name': None,
97            'auth_url': 'http://no.where',
98            'tenant_id': None,
99            'insecure': None,
100            'cacert': None,
101            'password': 'password',
102            'user_domain_name': 'default',
103            'user_domain_id': None,
104            'project_domain_name': 'default',
105            'project_domain_id': None,
106        }
107        with mock.patch('ceilometerclient.client.AuthPlugin') as auth_plugin:
108            self.create_client(env, api_version=2, endpoint='http://no.where')
109            self.assertEqual(mock.call(**expected),
110                             auth_plugin.mock_calls[0])
111
112    def test_v2_client_timeout_invalid_value(self):
113        env = FAKE_ENV.copy()
114        env['timeout'] = 'abc'
115        self.assertRaises(ValueError, self.create_client, env)
116        env['timeout'] = '1.5'
117        self.assertRaises(ValueError, self.create_client, env)
118
119    def _test_v2_client_timeout_integer(self, timeout, expected_value):
120        env = FAKE_ENV.copy()
121        env['timeout'] = timeout
122        expected = {
123            'auth_plugin': mock.ANY,
124            'timeout': expected_value,
125            'original_ip': None,
126            'http': None,
127            'region_name': None,
128            'verify': True,
129            'timings': None,
130            'keyring_saver': None,
131            'cert': None,
132            'endpoint_type': None,
133            'user_agent': None,
134            'debug': None,
135        }
136        cls = 'ceilometerclient.apiclient.client.HTTPClient'
137        with mock.patch(cls) as mocked:
138            self.create_client(env)
139            mocked.assert_called_with(**expected)
140
141    def test_v2_client_timeout_zero(self):
142        self._test_v2_client_timeout_integer(0, None)
143
144    def test_v2_client_timeout_valid_value(self):
145        self._test_v2_client_timeout_integer(30, 30)
146
147    @mock.patch.object(ks_session, 'Session')
148    def test_v2_client_timeout_keystone_session(self, mocked_session):
149        mocked_session.side_effect = RuntimeError('Stop!')
150        env = FAKE_ENV.copy()
151        env['timeout'] = 5
152        del env['auth_plugin']
153        del env['token']
154        client = self.create_client(env)
155        self.assertRaises(RuntimeError, client.alarms.list)
156        args, kwargs = mocked_session.call_args
157        self.assertEqual(5, kwargs['timeout'])
158
159    def test_v2_client_cacert_in_verify(self):
160        env = FAKE_ENV.copy()
161        env['cacert'] = '/path/to/cacert'
162        client = self.create_client(env)
163        self.assertEqual('/path/to/cacert',
164                         client.http_client.http_client.verify)
165
166    def test_v2_client_certfile_and_keyfile(self):
167        env = FAKE_ENV.copy()
168        env['cert_file'] = '/path/to/cert'
169        env['key_file'] = '/path/to/keycert'
170        client = self.create_client(env)
171        self.assertEqual(('/path/to/cert', '/path/to/keycert'),
172                         client.http_client.http_client.cert)
173
174    def test_v2_client_insecure(self):
175        env = FAKE_ENV.copy()
176        env.pop('auth_plugin')
177        env['os_insecure'] = 'True'
178        client = self.create_client(env)
179        self.assertIn('insecure', client.auth_plugin.opts)
180        self.assertEqual('True', client.auth_plugin.opts['insecure'])
181
182
183class ClientTest2(ClientTest):
184    @staticmethod
185    def create_client(env, api_version=2, endpoint=None, exclude=[]):
186        env = dict((k, v) for k, v in env.items()
187                   if k not in exclude)
188        with mock.patch(
189                'ceilometerclient.v2.client.Client._get_redirect_client',
190                return_value=None):
191            return client.Client(api_version, endpoint, **env)
192
193
194class ClientTestWithAodh(ClientTest):
195    @staticmethod
196    def create_client(env, api_version=2, endpoint=None, exclude=[]):
197        env = dict((k, v) for k, v in env.items()
198                   if k not in exclude)
199        with mock.patch('ceilometerclient.apiclient.client.'
200                        'HTTPClient.client_request',
201                        return_value=mock.MagicMock()):
202            return client.get_client(api_version, **env)
203
204    def test_client_without_auth_plugin(self):
205        env = FAKE_ENV.copy()
206        del env['auth_plugin']
207        c = self.create_client(env, api_version=2, endpoint='fake_endpoint')
208        self.assertIsInstance(c.alarm_client.http_client.auth_plugin,
209                              client.AuthPlugin)
210
211    def test_v2_client_insecure(self):
212        env = FAKE_ENV.copy()
213        env.pop('auth_plugin')
214        env['insecure'] = 'True'
215        client = self.create_client(env)
216        self.assertIn('insecure',
217                      client.alarm_client.http_client.auth_plugin.opts)
218        self.assertEqual('True', (client.alarm_client.http_client.
219                                  auth_plugin.opts['insecure']))
220
221    def test_ceilometerclient_available_without_aodh_services_running(self):
222        env = FAKE_ENV.copy()
223        env.pop('auth_plugin', None)
224        with mock.patch('ceilometerclient.apiclient.client.'
225                        'HTTPClient.client_request') as mocked_request:
226            mocked_request.side_effect = requests.exceptions.ConnectionError
227            ceiloclient = client.get_client(2, **env)
228            self.assertIsInstance(ceiloclient, v2client.Client)
229
230    @mock.patch('ceilometerclient.client.SessionClient')
231    def test_http_client_with_session_and_aodh(self, mock_sc):
232        session = mock.Mock()
233        kwargs = {"session": session,
234                  "service_type": "metering",
235                  "user_agent": "python-ceilometerclient"}
236        expected = {
237            "auth": None,
238            "interface": 'publicURL',
239            "region_name": None,
240            "timings": None,
241            "session": session,
242            "service_type": "metering",
243            "user_agent": "python-ceilometerclient"}
244        kwargs['aodh_endpoint'] = 'http://aodh.where'
245        client._construct_http_client(**kwargs)
246        mock_sc.assert_called_with(**expected)
247
248
249class ClientAuthTest(utils.BaseTestCase):
250
251    @staticmethod
252    def create_client(env, api_version=2, endpoint=None, exclude=[]):
253        env = dict((k, v) for k, v in env.items()
254                   if k not in exclude)
255        with mock.patch('ceilometerclient.apiclient.client.'
256                        'HTTPClient.client_request',
257                        return_value=mock.MagicMock()):
258            return client.get_client(api_version, **env)
259
260    @mock.patch('keystoneauth1.discover.Discover')
261    @mock.patch('keystoneauth1.session.Session')
262    def test_discover_auth_versions(self, session, discover_mock):
263        env = FAKE_ENV.copy()
264        env.pop('auth_plugin', None)
265
266        mock_session_instance = mock.MagicMock()
267        session.return_value = mock_session_instance
268
269        client = self.create_client(env)
270        client.auth_plugin.opts.pop('token', None)
271        client.auth_plugin._do_authenticate(mock.MagicMock())
272
273        self.assertEqual([mock.call(url='http://no.where',
274                                    session=mock_session_instance)],
275                         discover_mock.call_args_list)
276        self.assertIsInstance(mock_session_instance.auth, v3_auth.Password)
277
278    @mock.patch('keystoneauth1.discover.Discover')
279    @mock.patch('keystoneauth1.session.Session')
280    def test_discover_auth_versions_v2_only(self, session, discover):
281        env = FAKE_ENV.copy()
282        env.pop('auth_plugin', None)
283        env.pop('user_domain_name', None)
284        env.pop('user_domain_id', None)
285        env.pop('project_domain_name', None)
286        env.pop('project_domain_id', None)
287
288        session_instance_mock = mock.MagicMock()
289        session.return_value = session_instance_mock
290
291        discover_instance_mock = mock.MagicMock()
292        discover_instance_mock.url_for.side_effect = (lambda v: v
293                                                      if v == '2.0' else None)
294        discover.return_value = discover_instance_mock
295
296        client = self.create_client(env)
297        client.auth_plugin.opts.pop('token', None)
298        client.auth_plugin._do_authenticate(mock.MagicMock())
299        self.assertEqual([mock.call(url='http://no.where',
300                                    session=session_instance_mock)],
301                         discover.call_args_list)
302
303        self.assertIsInstance(session_instance_mock.auth, v2_auth.Password)
304
305    @mock.patch('keystoneauth1.discover.Discover')
306    @mock.patch('keystoneauth1.session.Session')
307    def test_discover_auth_versions_raise_discovery_failure(self,
308                                                            session,
309                                                            discover):
310        env = FAKE_ENV.copy()
311        env.pop('auth_plugin', None)
312        env.pop('token', None)
313
314        session_instance_mock = mock.MagicMock()
315        session.return_value = session_instance_mock
316
317        discover_instance_mock = mock.MagicMock()
318        discover_instance_mock.url_for.side_effect = (lambda v: v
319                                                      if v == '2.0' else None)
320        discover.side_effect = ks_exc.DiscoveryFailure
321        client = self.create_client(env)
322        self.assertRaises(ks_exc.DiscoveryFailure,
323                          client.auth_plugin._do_authenticate,
324                          mock.Mock())
325        discover.side_effect = mock.MagicMock()
326        client = self.create_client(env)
327        discover.side_effect = ks_exc.DiscoveryFailure
328        client.auth_plugin.opts.pop('token', None)
329
330        self.assertRaises(ks_exc.DiscoveryFailure,
331                          client.auth_plugin._do_authenticate,
332                          mock.Mock())
333        self.assertEqual([mock.call(url='http://no.where',
334                                    session=session_instance_mock),
335                          mock.call(url='http://no.where',
336                                    session=session_instance_mock)],
337                         discover.call_args_list)
338
339    @mock.patch('ceilometerclient.client._get_keystone_session')
340    def test_get_endpoint(self, session):
341        env = FAKE_ENV.copy()
342        env.pop('auth_plugin', None)
343        env.pop('endpoint', None)
344
345        session_instance_mock = mock.MagicMock()
346        session.return_value = session_instance_mock
347
348        client = self.create_client(env)
349        client.auth_plugin.opts.pop('endpoint')
350        client.auth_plugin.opts.pop('token', None)
351        alarm_auth_plugin = client.alarm_client.http_client.auth_plugin
352        alarm_auth_plugin.opts.pop('endpoint')
353        alarm_auth_plugin.opts.pop('token', None)
354
355        self.assertNotEqual(client.auth_plugin, alarm_auth_plugin)
356
357        client.auth_plugin._do_authenticate(mock.MagicMock())
358        alarm_auth_plugin._do_authenticate(mock.MagicMock())
359
360        self.assertEqual([
361            mock.call(interface='publicURL', region_name=None,
362                      service_type='metering'),
363            mock.call(interface='publicURL', region_name=None,
364                      service_type='alarming'),
365        ], session_instance_mock.get_endpoint.mock_calls)
366
367    def test_http_client_with_session(self):
368        session = mock.Mock()
369        session.request.return_value = mock.Mock(status_code=404,
370                                                 text=b'')
371        env = {"session": session,
372               "service_type": "metering",
373               "user_agent": "python-ceilometerclient"}
374        c = client.SessionClient(**env)
375        self.assertRaises(exc.HTTPException, c.get, "/")
376
377    def test_get_aodh_endpoint_without_auth_url(self):
378        env = FAKE_ENV.copy()
379        env.pop('auth_plugin', None)
380        env.pop('endpoint', None)
381        env.pop('auth_url', None)
382        client = self.create_client(env, endpoint='fake_endpoint')
383        self.assertEqual(client.alarm_client.http_client.auth_plugin.opts,
384                         client.auth_plugin.opts)
385
386    @mock.patch('ceilometerclient.client._get_keystone_session')
387    def test_get_different_endpoint_type(self, session):
388        env = FAKE_ENV.copy()
389        env.pop('auth_plugin', None)
390        env.pop('endpoint', None)
391        env['endpoint_type'] = 'internal'
392
393        session_instance_mock = mock.MagicMock()
394        session.return_value = session_instance_mock
395
396        client = self.create_client(env)
397        client.auth_plugin.opts.pop('endpoint')
398        client.auth_plugin.opts.pop('token', None)
399        alarm_auth_plugin = client.alarm_client.http_client.auth_plugin
400        alarm_auth_plugin.opts.pop('endpoint')
401        alarm_auth_plugin.opts.pop('token', None)
402
403        self.assertNotEqual(client.auth_plugin, alarm_auth_plugin)
404
405        client.auth_plugin._do_authenticate(mock.MagicMock())
406        alarm_auth_plugin._do_authenticate(mock.MagicMock())
407
408        self.assertEqual([
409            mock.call(interface='internal', region_name=None,
410                      service_type='metering'),
411            mock.call(interface='internal', region_name=None,
412                      service_type='alarming'),
413        ], session_instance_mock.get_endpoint.mock_calls)
414
415    @mock.patch('ceilometerclient.client._get_keystone_session')
416    def test_get_sufficient_options_missing(self, session):
417        env = FAKE_ENV.copy()
418        env.pop('auth_plugin', None)
419        env.pop('password', None)
420        env.pop('endpoint', None)
421        env.pop('auth_token', None)
422        env.pop('tenant_name', None)
423        env.pop('username', None)
424
425        session_instance_mock = mock.MagicMock()
426        session.return_value = session_instance_mock
427        client = self.create_client(env)
428        client.auth_plugin.opts.pop('token', None)
429        self.assertRaises(exceptions.AuthPluginOptionsMissing,
430                          client.auth_plugin.sufficient_options)
431