1# Copyright (c) 2016 VMware, Inc.
2# All Rights Reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16import json
17from unittest import mock
18
19import ddt
20
21from osprofiler.drivers import loginsight
22from osprofiler import exc
23from osprofiler.tests import test
24
25
26@ddt.ddt
27class LogInsightDriverTestCase(test.TestCase):
28
29    BASE_ID = "8d28af1e-acc0-498c-9890-6908e33eff5f"
30
31    def setUp(self):
32        super(LogInsightDriverTestCase, self).setUp()
33        self._client = mock.Mock(spec=loginsight.LogInsightClient)
34        self._project = "cinder"
35        self._service = "osapi_volume"
36        self._host = "ubuntu"
37        with mock.patch.object(loginsight, "LogInsightClient",
38                               return_value=self._client):
39            self._driver = loginsight.LogInsightDriver(
40                "loginsight://username:password@host",
41                project=self._project,
42                service=self._service,
43                host=self._host)
44
45    @mock.patch.object(loginsight, "LogInsightClient")
46    def test_init(self, client_class):
47        client = mock.Mock()
48        client_class.return_value = client
49
50        loginsight.LogInsightDriver("loginsight://username:password@host")
51        client_class.assert_called_once_with("host", "username", "password")
52        client.login.assert_called_once_with()
53
54    @ddt.data("loginsight://username@host",
55              "loginsight://username:p@ssword@host",
56              "loginsight://us:rname:password@host")
57    def test_init_with_invalid_connection_string(self, conn_str):
58        self.assertRaises(ValueError, loginsight.LogInsightDriver, conn_str)
59
60    @mock.patch.object(loginsight, "LogInsightClient")
61    def test_init_with_special_chars_in_conn_str(self, client_class):
62        client = mock.Mock()
63        client_class.return_value = client
64
65        loginsight.LogInsightDriver("loginsight://username:p%40ssword@host")
66        client_class.assert_called_once_with("host", "username", "p@ssword")
67        client.login.assert_called_once_with()
68
69    def test_get_name(self):
70        self.assertEqual("loginsight", self._driver.get_name())
71
72    def _create_trace(self,
73                      name,
74                      timestamp,
75                      parent_id="8d28af1e-acc0-498c-9890-6908e33eff5f",
76                      base_id=BASE_ID,
77                      trace_id="e465db5c-9672-45a1-b90b-da918f30aef6"):
78        return {"parent_id": parent_id,
79                "name": name,
80                "base_id": base_id,
81                "trace_id": trace_id,
82                "timestamp": timestamp,
83                "info": {"host": self._host}}
84
85    def _create_start_trace(self):
86        return self._create_trace("wsgi-start", "2016-10-04t11:50:21.902303")
87
88    def _create_stop_trace(self):
89        return self._create_trace("wsgi-stop", "2016-10-04t11:50:30.123456")
90
91    @mock.patch("json.dumps")
92    def test_notify(self, dumps):
93        json_str = mock.sentinel.json_str
94        dumps.return_value = json_str
95
96        trace = self._create_stop_trace()
97        self._driver.notify(trace)
98
99        trace["project"] = self._project
100        trace["service"] = self._service
101        exp_event = {"text": "OSProfiler trace",
102                     "fields": [{"name": "base_id",
103                                 "content": trace["base_id"]},
104                                {"name": "trace_id",
105                                 "content": trace["trace_id"]},
106                                {"name": "project",
107                                 "content": trace["project"]},
108                                {"name": "service",
109                                 "content": trace["service"]},
110                                {"name": "name",
111                                 "content": trace["name"]},
112                                {"name": "trace",
113                                 "content": json_str}]
114                     }
115        self._client.send_event.assert_called_once_with(exp_event)
116
117    @mock.patch.object(loginsight.LogInsightDriver, "_append_results")
118    @mock.patch.object(loginsight.LogInsightDriver, "_parse_results")
119    def test_get_report(self, parse_results, append_results):
120        start_trace = self._create_start_trace()
121        start_trace["project"] = self._project
122        start_trace["service"] = self._service
123
124        stop_trace = self._create_stop_trace()
125        stop_trace["project"] = self._project
126        stop_trace["service"] = self._service
127
128        resp = {"events": [{"text": "OSProfiler trace",
129                            "fields": [{"name": "trace",
130                                        "content": json.dumps(start_trace)
131                                        }
132                                       ]
133                            },
134                           {"text": "OSProfiler trace",
135                            "fields": [{"name": "trace",
136                                        "content": json.dumps(stop_trace)
137                                        }
138                                       ]
139                            }
140                           ]
141                }
142        self._client.query_events = mock.Mock(return_value=resp)
143
144        self._driver.get_report(self.BASE_ID)
145        self._client.query_events.assert_called_once_with({"base_id":
146                                                           self.BASE_ID})
147        append_results.assert_has_calls(
148            [mock.call(start_trace["trace_id"], start_trace["parent_id"],
149                       start_trace["name"], start_trace["project"],
150                       start_trace["service"], start_trace["info"]["host"],
151                       start_trace["timestamp"], start_trace),
152             mock.call(stop_trace["trace_id"], stop_trace["parent_id"],
153                       stop_trace["name"], stop_trace["project"],
154                       stop_trace["service"], stop_trace["info"]["host"],
155                       stop_trace["timestamp"], stop_trace)
156             ])
157        parse_results.assert_called_once_with()
158
159
160class LogInsightClientTestCase(test.TestCase):
161
162    def setUp(self):
163        super(LogInsightClientTestCase, self).setUp()
164        self._host = "localhost"
165        self._username = "username"
166        self._password = "password"
167        self._client = loginsight.LogInsightClient(
168            self._host, self._username, self._password)
169        self._client._session_id = "4ff800d1-3175-4b49-9209-39714ea56416"
170
171    def test_check_response_login_timeout(self):
172        resp = mock.Mock(status_code=440)
173        self.assertRaises(
174            exc.LogInsightLoginTimeout, self._client._check_response, resp)
175
176    def test_check_response_api_error(self):
177        resp = mock.Mock(status_code=401, ok=False)
178        resp.text = json.dumps(
179            {"errorMessage": "Invalid username or password.",
180             "errorCode": "FIELD_ERROR"})
181        e = self.assertRaises(
182            exc.LogInsightAPIError, self._client._check_response, resp)
183        self.assertEqual("Invalid username or password.", str(e))
184
185    @mock.patch("requests.Request")
186    @mock.patch("json.dumps")
187    @mock.patch.object(loginsight.LogInsightClient, "_check_response")
188    def test_send_request(self, check_resp, json_dumps, request_class):
189        req = mock.Mock()
190        request_class.return_value = req
191        prep_req = mock.sentinel.prep_req
192        req.prepare = mock.Mock(return_value=prep_req)
193
194        data = mock.sentinel.data
195        json_dumps.return_value = data
196
197        self._client._session = mock.Mock()
198        resp = mock.Mock()
199        self._client._session.send = mock.Mock(return_value=resp)
200        resp_json = mock.sentinel.resp_json
201        resp.json = mock.Mock(return_value=resp_json)
202
203        header = {"X-LI-Session-Id": "foo"}
204        body = mock.sentinel.body
205        params = mock.sentinel.params
206        ret = self._client._send_request(
207            "get", "https", "api/v1/events", header, body, params)
208
209        self.assertEqual(resp_json, ret)
210        exp_headers = {"X-LI-Session-Id": "foo",
211                       "content-type": "application/json"}
212        request_class.assert_called_once_with(
213            "get", "https://localhost:9543/api/v1/events", headers=exp_headers,
214            data=data, params=mock.sentinel.params)
215        self._client._session.send.assert_called_once_with(prep_req,
216                                                           verify=False)
217        check_resp.assert_called_once_with(resp)
218
219    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
220    def test_is_current_session_active_with_active_session(self, send_request):
221        self.assertTrue(self._client._is_current_session_active())
222        exp_header = {"X-LI-Session-Id": self._client._session_id}
223        send_request.assert_called_once_with(
224            "get", "https", "api/v1/sessions/current", headers=exp_header)
225
226    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
227    def test_is_current_session_active_with_expired_session(self,
228                                                            send_request):
229        send_request.side_effect = exc.LogInsightLoginTimeout
230
231        self.assertFalse(self._client._is_current_session_active())
232        send_request.assert_called_once_with(
233            "get", "https", "api/v1/sessions/current",
234            headers={"X-LI-Session-Id": self._client._session_id})
235
236    @mock.patch.object(loginsight.LogInsightClient,
237                       "_is_current_session_active", return_value=True)
238    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
239    def test_login_with_current_session_active(self, send_request,
240                                               is_current_session_active):
241        self._client.login()
242        is_current_session_active.assert_called_once_with()
243        send_request.assert_not_called()
244
245    @mock.patch.object(loginsight.LogInsightClient,
246                       "_is_current_session_active", return_value=False)
247    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
248    def test_login(self, send_request, is_current_session_active):
249        new_session_id = "569a80aa-be5c-49e5-82c1-bb62392d2667"
250        resp = {"sessionId": new_session_id}
251        send_request.return_value = resp
252
253        self._client.login()
254        is_current_session_active.assert_called_once_with()
255        exp_body = {"username": self._username, "password": self._password}
256        send_request.assert_called_once_with(
257            "post", "https", "api/v1/sessions", body=exp_body)
258        self.assertEqual(new_session_id, self._client._session_id)
259
260    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
261    def test_send_event(self, send_request):
262        event = mock.sentinel.event
263        self._client.send_event(event)
264
265        exp_body = {"events": [event]}
266        exp_path = ("api/v1/events/ingest/%s" %
267                    self._client.LI_OSPROFILER_AGENT_ID)
268        send_request.assert_called_once_with(
269            "post", "http", exp_path, body=exp_body)
270
271    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
272    def test_query_events(self, send_request):
273        resp = mock.sentinel.response
274        send_request.return_value = resp
275
276        self.assertEqual(resp, self._client.query_events({"foo": "bar"}))
277        exp_header = {"X-LI-Session-Id": self._client._session_id}
278        exp_params = {"limit": 20000, "timeout": self._client._query_timeout}
279        send_request.assert_called_once_with(
280            "get", "https", "api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
281            headers=exp_header, params=exp_params)
282
283    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
284    @mock.patch.object(loginsight.LogInsightClient, "login")
285    def test_query_events_with_session_expiry(self, login, send_request):
286        resp = mock.sentinel.response
287        send_request.side_effect = [exc.LogInsightLoginTimeout, resp]
288
289        self.assertEqual(resp, self._client.query_events({"foo": "bar"}))
290        login.assert_called_once_with()
291        exp_header = {"X-LI-Session-Id": self._client._session_id}
292        exp_params = {"limit": 20000, "timeout": self._client._query_timeout}
293        exp_send_request_call = mock.call(
294            "get", "https", "api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
295            headers=exp_header, params=exp_params)
296        send_request.assert_has_calls([exp_send_request_call] * 2)
297