Add CaaS Logging REST API and CLI plugins
[ta/caas-logging.git] / src / caas_logging / rest-plugin / caashandler.py
1 # Copyright 2019 Nokia
2
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import json
16
17 from cmframework.apis import cmclient
18
19 from yarf.restresource import RestResource
20
21 MISSING_LOG_STORE_ENTRY_INDEX = 'Missing log store entry index parameter'
22 PATTERN_SHOULD_BE_ONE_OF = '{} should be one of: {}'
23
24 ID = 'id'
25 NAMESPACE = 'namespace'
26 SUPPORTED_BACKENDS = ('elasticsearch', 'remote_syslog')
27 SUPPORTED_STREAMS = ('stdout', 'stderr', 'both')
28
29
30 class CaasHandler(RestResource):
31     KEY_CAAS = 'cloud.caas'
32
33     def _get_caas_config(self):
34         api = cmclient.CMClient()
35         return json.loads(api.get_property(self.KEY_CAAS))
36
37     def _update_caas_config(self, updated):
38         api = cmclient.CMClient()
39         api.set_property(self.KEY_CAAS, json.dumps(updated))
40
41     @staticmethod
42     def _response(return_code, data='', error_desc=''):
43         return {"code": return_code, "description": error_desc, "data": data}
44
45     def _result(self, data=None):
46         return self._response(0, data=data)
47
48     def _error(self, exc):
49         error_desc = str(exc).replace('Internal error, ', '')
50         return self._response(1, error_desc=error_desc)
51
52     def _modify_caas_config(self, modifier_method):
53         try:
54             caas = self._get_caas_config()
55             modifier_method(self, caas, self.get_args())
56             self._update_caas_config(caas)
57             return self._result('')
58         except Exception as exc:
59             return self._error(exc)
60
61
62 class AppLogStoreHandler(CaasHandler):
63     KEY_NAME = 'log_forwarding'
64
65     parser_arguments = [ID, NAMESPACE, 'plugin', 'target_url', 'stream']
66     endpoints = ['log/apps']
67
68     def post(self):
69         """
70         .. :quickref: CaaS - Log forwarding;Add the log forwarding configuration entry
71
72         **Add the new containerized application log forwarding entry to configuration**:
73
74         **Example request**:
75
76         .. sourcecode:: http
77
78             POST /caas/v1/log/apps HTTP/1.1
79             Host: haproxyvip:61200
80             Content-Type: application/json
81             Accept: application/json
82
83             {
84                 "namespace": "app1",
85                 "plugin": "elasticsearch",
86                 "target_url": "http://elasticsearch.elk.svc.nokia.net:9200",
87                 "stream": "stderr"
88             }
89
90         :<json string namespace: Apply forwarding for containers deployed in this namespace
91         :<json string plugin: Plugin that handles log forwarding, one of ``elasticsearch``, ``remote_syslog``
92         :<json string target_url: Log storage service URL (eg. ``protocol://ip-or-fqdn:port``)
93         :<json string stream: Output stream source of gathered logs, one of ``stdout``, ``stderr``, ``both``
94
95         **Example response**:
96
97         .. sourcecode:: http
98
99             HTTP/1.1 200 OK
100             Content-Type: application/json
101
102             {
103                 "code": 0,
104                 "description": ""
105                 "data": ""
106             }
107
108         :>json int code: The status code, 0 when OK other when error
109         :>json string description: The error description, present if code is non zero
110         :>json string data: Empty string in all cases
111         :statuscode 200: no error
112         :statuscode 401: on authentication error
113
114         """
115         def func(self, caas, args):
116             entries = caas[self.KEY_NAME]
117             args.pop(ID)
118             if args['plugin'] not in SUPPORTED_BACKENDS:
119                 raise ValueError(PATTERN_SHOULD_BE_ONE_OF.format('plugin', ', '.join(SUPPORTED_BACKENDS)))
120             if args['stream'] not in SUPPORTED_STREAMS:
121                 raise ValueError(PATTERN_SHOULD_BE_ONE_OF.format('stream', ', '.join(SUPPORTED_STREAMS)))
122             if entries.count(args):
123                 raise ValueError('Already exists')
124             entries.append(args)
125
126         return self._modify_caas_config(func)
127
128     def get(self):
129         """
130         .. :quickref: CaaS - Log forwarding;Get the existing log forwarding configuration
131
132         **Get the existing containerized application log forwarding configuration**:
133
134         Optionally request could filter entries by namespace.
135
136         **Example request**:
137
138         .. sourcecode:: http
139
140             GET /caas/v1/apps HTTP/1.1
141             Host: haproxyvip:61200
142             Content-Type: application/json
143             Accept: application/json
144
145             {
146                 "namespace": "app1"
147             }
148
149         :<json string namespace: (optional) The IP address of the server where logs are forwarded to
150
151         **Example response**:
152
153         .. sourcecode:: http
154
155             HTTP/1.1 200 OK
156             Content-Type: application/json
157
158             {
159                 "code": 0,
160                 "description": ""
161                 "data":
162                 {
163                     "1":
164                     {
165                         "id": 1,
166                         "namespace": "app1",
167                         "plugin": "remote_syslog",
168                         "target_url": "tcp://http://rsyslog.log.svc.nokia.net:1234",
169                         "stream": "stderr"
170                     }
171                 }
172             }
173
174         :>json int code: The status code, 0 when OK other when error
175         :>json string description: The error description, present if code is non zero
176         :>json string data: Contains the log forwarding configuration
177         :>json string id: The identifier of the log forwarding entry
178         :>json string namespace: Apply forwarding for containers deployed in this namespace
179         :>json string plugin: Plugin that handles log forwarding, one of ``elasticsearch``, ``remote_syslog``
180         :>json string target_url: Log storage service URL (eg. ``protocol://ip-or-fqdn:port``)
181         :>json string stream: Output stream source of gathered logs, one of ``stdout``, ``stderr``, ``both``
182         :statuscode 200: no error
183         :statuscode 401: on authentication error
184
185         """
186         try:
187             caas = self._get_caas_config()
188             value = caas[self.KEY_NAME]
189             args = self.get_args()
190             data = {}
191             for idx, v in enumerate(value, start=1):
192                 v[ID] = idx
193                 data[idx] = v
194             if args[ID] is not None:
195                 return self._result({args[ID]: value[int(args[ID]) - 1]})
196             elif args[NAMESPACE] is not None:
197                 return self._result({k: v for (k, v) in data.items() if v[NAMESPACE] == args[NAMESPACE]})
198             else:
199                 return self._result(data)
200
201         except Exception as exc:
202             return self._error(exc)
203
204     def put(self):
205         """
206         .. :quickref: CaaS - Log forwarding;Change the existing log forwarding entry configuration
207
208         **Change the existing containerized application log forwarding entry configuration**:
209
210         **Example request**:
211
212         .. sourcecode:: http
213
214             PUT /caas/v1/apps HTTP/1.1
215             Host: haproxyvip:61200
216             Content-Type: application/json
217             Accept: application/json
218
219             {
220                 "id": 1,
221                 "namespace": "app2"
222             }
223
224         :<json string id: (mandatory) The index of the entry to be modified
225         :<json string namespace: (optional) Apply forwarding for containers deployed in this namespace
226         :<json string plugin: (optional) Plugin that handles log forwarding, one of ``elasticsearch``, ``remote_syslog``
227         :<json string target_url: (optional) Log storage service URL (eg. ``protocol://ip-or-fqdn:port``)
228         :<json string stream: (optional) Output stream source of gathered logs, one of ``stdout``, ``stderr``, ``both``
229
230         **Example response**:
231
232         .. sourcecode:: http
233
234             HTTP/1.1 200 OK
235             Content-Type: application/json
236
237             {
238                 "code": 0,
239                 "description": "",
240                 "data": ""
241             }
242
243         :>json int code: The status code, 0 when OK other when error
244         :>json string description: The error description, present if code is non zero
245         :>json string data: Empty string in all cases
246         :statuscode 200: no error
247         :statuscode 401: on authentication error
248
249         """
250         def func(self, caas, args):
251             if args[ID] is None:
252                 raise ValueError(MISSING_LOG_STORE_ENTRY_INDEX)
253             else:
254                 idx = int(args[ID]) - 1
255                 for k, v in args.items():
256                     if not k == ID and v is not None:
257                         caas[self.KEY_NAME][idx][k] = v
258
259         return self._modify_caas_config(func)
260
261     def delete(self):
262         """
263         .. :quickref: CaaS - Log forwarding;Delete the entry from log forwarding configuration
264
265         **Delete the entry from containerized application log forwarding configuration**:
266
267         **Example request**:
268
269         .. sourcecode:: http
270
271             DELETE /caas/v1/apps HTTP/1.1
272             Host: haproxyvip:61200
273             Content-Type: application/json
274             Accept: application/json
275
276             {
277                 "id": "1"
278             }
279
280         :<json string id: (mandatory) The index of the entry to be deleted
281
282         **Example response**:
283
284         .. sourcecode:: http
285
286             HTTP/1.1 200 OK
287             Content-Type: application/json
288
289             {
290                 "code": 0,
291                 "description": ""
292                 "data": ""
293             }
294
295         :>json int code: The status code, 0 when OK other when error
296         :>json string description: The error description, present if code is non zero
297         :>json string data: Empty string in all cases
298         :statuscode 200: no error
299         :statuscode 401: on authentication error
300
301         """
302         def func(self, caas, args):
303             if args[ID] is None:
304                 raise ValueError(MISSING_LOG_STORE_ENTRY_INDEX)
305             else:
306                 caas[self.KEY_NAME].pop(int(args[ID]) - 1)
307
308         return self._modify_caas_config(func)