1 |
|
#!/usr/bin/python3 |
2 |
|
# -*- coding: utf-8 -*- |
3 |
|
|
4 |
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
# you may not use this file except in compliance with the License. |
6 |
|
# You may obtain 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, |
12 |
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
13 |
|
# implied. |
14 |
|
# See the License for the specific language governing permissions and |
15 |
|
# limitations under the License. |
16 |
|
|
17 |
0 |
import cherrypy |
18 |
0 |
import time |
19 |
0 |
import json |
20 |
0 |
import yaml |
21 |
0 |
import osm_nbi.html_out as html |
22 |
0 |
import logging |
23 |
0 |
import logging.handlers |
24 |
0 |
import getopt |
25 |
0 |
import sys |
26 |
|
|
27 |
0 |
from osm_nbi.authconn import AuthException, AuthconnException |
28 |
0 |
from osm_nbi.auth import Authenticator |
29 |
0 |
from osm_nbi.engine import Engine, EngineException |
30 |
0 |
from osm_nbi.subscriptions import SubscriptionThread |
31 |
0 |
from osm_nbi.utils import cef_event, cef_event_builder |
32 |
0 |
from osm_nbi.validation import ValidationError |
33 |
0 |
from osm_common.dbbase import DbException |
34 |
0 |
from osm_common.fsbase import FsException |
35 |
0 |
from osm_common.msgbase import MsgException |
36 |
0 |
from http import HTTPStatus |
37 |
0 |
from codecs import getreader |
38 |
0 |
from os import environ, path |
39 |
0 |
from osm_nbi import version as nbi_version, version_date as nbi_version_date |
40 |
|
|
41 |
0 |
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>" |
42 |
|
|
43 |
0 |
__version__ = "0.1.3" # file version, not NBI version |
44 |
0 |
version_date = "Aug 2019" |
45 |
|
|
46 |
0 |
database_version = "1.2" |
47 |
0 |
auth_database_version = "1.0" |
48 |
0 |
nbi_server = None # instance of Server class |
49 |
0 |
subscription_thread = None # instance of SubscriptionThread class |
50 |
0 |
cef_logger = None |
51 |
|
|
52 |
0 |
""" |
53 |
|
North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented) |
54 |
|
URL: /osm GET POST PUT DELETE PATCH |
55 |
|
/nsd/v1 |
56 |
|
/ns_descriptors_content O O |
57 |
|
/<nsdInfoId> O O O O |
58 |
|
/ns_descriptors O5 O5 |
59 |
|
/<nsdInfoId> O5 O5 5 |
60 |
|
/nsd_content O5 O5 |
61 |
|
/nsd O |
62 |
|
/artifacts[/<artifactPath>] O |
63 |
|
/pnf_descriptors 5 5 |
64 |
|
/<pnfdInfoId> 5 5 5 |
65 |
|
/pnfd_content 5 5 |
66 |
|
/subscriptions 5 5 |
67 |
|
/<subscriptionId> 5 X |
68 |
|
|
69 |
|
/vnfpkgm/v1 |
70 |
|
/vnf_packages_content O O |
71 |
|
/<vnfPkgId> O O |
72 |
|
/vnf_packages O5 O5 |
73 |
|
/<vnfPkgId> O5 O5 5 |
74 |
|
/package_content O5 O5 |
75 |
|
/upload_from_uri X |
76 |
|
/vnfd O5 |
77 |
|
/artifacts[/<artifactPath>] O5 |
78 |
|
/subscriptions X X |
79 |
|
/<subscriptionId> X X |
80 |
|
|
81 |
|
/nslcm/v1 |
82 |
|
/ns_instances_content O O |
83 |
|
/<nsInstanceId> O O |
84 |
|
/ns_instances 5 5 |
85 |
|
/<nsInstanceId> O5 O5 |
86 |
|
instantiate O5 |
87 |
|
terminate O5 |
88 |
|
action O |
89 |
|
scale O5 |
90 |
|
migrate O |
91 |
|
update 05 |
92 |
|
heal O5 |
93 |
|
/ns_lcm_op_occs 5 5 |
94 |
|
/<nsLcmOpOccId> 5 5 5 |
95 |
|
cancel 05 |
96 |
|
/vnf_instances (also vnfrs for compatibility) O |
97 |
|
/<vnfInstanceId> O |
98 |
|
/subscriptions 5 5 |
99 |
|
/<subscriptionId> 5 X |
100 |
|
|
101 |
|
/pdu/v1 |
102 |
|
/pdu_descriptors O O |
103 |
|
/<id> O O O O |
104 |
|
|
105 |
|
/admin/v1 |
106 |
|
/tokens O O |
107 |
|
/<id> O O |
108 |
|
/users O O |
109 |
|
/<id> O O O O |
110 |
|
/projects O O |
111 |
|
/<id> O O |
112 |
|
/vim_accounts (also vims for compatibility) O O |
113 |
|
/<id> O O O |
114 |
|
/wim_accounts O O |
115 |
|
/<id> O O O |
116 |
|
/sdns O O |
117 |
|
/<id> O O O |
118 |
|
/k8sclusters O O |
119 |
|
/<id> O O O |
120 |
|
/k8srepos O O |
121 |
|
/<id> O O |
122 |
|
/osmrepos O O |
123 |
|
/<id> O O |
124 |
|
|
125 |
|
/nst/v1 O O |
126 |
|
/netslice_templates_content O O |
127 |
|
/<nstInfoId> O O O O |
128 |
|
/netslice_templates O O |
129 |
|
/<nstInfoId> O O O |
130 |
|
/nst_content O O |
131 |
|
/nst O |
132 |
|
/artifacts[/<artifactPath>] O |
133 |
|
/subscriptions X X |
134 |
|
/<subscriptionId> X X |
135 |
|
|
136 |
|
/nsilcm/v1 |
137 |
|
/netslice_instances_content O O |
138 |
|
/<SliceInstanceId> O O |
139 |
|
/netslice_instances O O |
140 |
|
/<SliceInstanceId> O O |
141 |
|
instantiate O |
142 |
|
terminate O |
143 |
|
action O |
144 |
|
/nsi_lcm_op_occs O O |
145 |
|
/<nsiLcmOpOccId> O O O |
146 |
|
/subscriptions X X |
147 |
|
/<subscriptionId> X X |
148 |
|
|
149 |
|
query string: |
150 |
|
Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force. |
151 |
|
simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]* |
152 |
|
filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]* |
153 |
|
op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont" |
154 |
|
attrName := string |
155 |
|
For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any |
156 |
|
item of the array, that is, pass if any item of the array pass the filter. |
157 |
|
It allows both ne and neq for not equal |
158 |
|
TODO: 4.3.3 Attribute selectors |
159 |
|
all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,... |
160 |
|
(none) … same as “exclude_default” |
161 |
|
all_fields … all attributes. |
162 |
|
fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not |
163 |
|
conditionally mandatory, and that are not provided in <list>. |
164 |
|
exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that |
165 |
|
are not conditionally mandatory, and that are provided in <list>. |
166 |
|
exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not |
167 |
|
conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for |
168 |
|
the particular resource |
169 |
|
exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality |
170 |
|
of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the |
171 |
|
present specification for the particular resource, but that are not part of <list> |
172 |
|
Additionally it admits some administrator values: |
173 |
|
FORCE: To force operations skipping dependency checkings |
174 |
|
ADMIN: To act as an administrator or a different project |
175 |
|
PUBLIC: To get public descriptors or set a descriptor as public |
176 |
|
SET_PROJECT: To make a descriptor available for other project |
177 |
|
|
178 |
|
Header field name Reference Example Descriptions |
179 |
|
Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response. |
180 |
|
This header field shall be present if the response is expected to have a non-empty message body. |
181 |
|
Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request. |
182 |
|
This header field shall be present if the request has a non-empty message body. |
183 |
|
Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request. |
184 |
|
Details are specified in clause 4.5.3. |
185 |
|
Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file |
186 |
|
Header field name Reference Example Descriptions |
187 |
|
Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response. |
188 |
|
This header field shall be present if the response has a non-empty message body. |
189 |
|
Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a |
190 |
|
new resource has been created. |
191 |
|
This header field shall be present if the response status code is 201 or 3xx. |
192 |
|
In the present document this header field is also used if the response status code is 202 and a new resource was |
193 |
|
created. |
194 |
|
WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not |
195 |
|
provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization |
196 |
|
token. |
197 |
|
Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for |
198 |
|
certain resources. |
199 |
|
Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the |
200 |
|
response, and the total length of the file. |
201 |
|
Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT |
202 |
|
""" |
203 |
|
|
204 |
0 |
valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC") |
205 |
|
# ^ Contains possible administrative query string words: |
206 |
|
# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project |
207 |
|
# (not owned by my session project). |
208 |
|
# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public |
209 |
|
# FORCE=True(by default)|False: Force edition/deletion operations |
210 |
|
# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio |
211 |
|
|
212 |
0 |
valid_url_methods = { |
213 |
|
# contains allowed URL and methods, and the role_permission name |
214 |
|
"admin": { |
215 |
|
"v1": { |
216 |
|
"tokens": { |
217 |
|
"METHODS": ("GET", "POST", "DELETE"), |
218 |
|
"ROLE_PERMISSION": "tokens:", |
219 |
|
"<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"}, |
220 |
|
}, |
221 |
|
"users": { |
222 |
|
"METHODS": ("GET", "POST"), |
223 |
|
"ROLE_PERMISSION": "users:", |
224 |
|
"<ID>": { |
225 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
226 |
|
"ROLE_PERMISSION": "users:id:", |
227 |
|
}, |
228 |
|
}, |
229 |
|
"projects": { |
230 |
|
"METHODS": ("GET", "POST"), |
231 |
|
"ROLE_PERMISSION": "projects:", |
232 |
|
"<ID>": { |
233 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
234 |
|
"ROLE_PERMISSION": "projects:id:", |
235 |
|
}, |
236 |
|
}, |
237 |
|
"roles": { |
238 |
|
"METHODS": ("GET", "POST"), |
239 |
|
"ROLE_PERMISSION": "roles:", |
240 |
|
"<ID>": { |
241 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
242 |
|
"ROLE_PERMISSION": "roles:id:", |
243 |
|
}, |
244 |
|
}, |
245 |
|
"vims": { |
246 |
|
"METHODS": ("GET", "POST"), |
247 |
|
"ROLE_PERMISSION": "vims:", |
248 |
|
"<ID>": { |
249 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
250 |
|
"ROLE_PERMISSION": "vims:id:", |
251 |
|
}, |
252 |
|
}, |
253 |
|
"vim_accounts": { |
254 |
|
"METHODS": ("GET", "POST"), |
255 |
|
"ROLE_PERMISSION": "vim_accounts:", |
256 |
|
"<ID>": { |
257 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
258 |
|
"ROLE_PERMISSION": "vim_accounts:id:", |
259 |
|
}, |
260 |
|
}, |
261 |
|
"wim_accounts": { |
262 |
|
"METHODS": ("GET", "POST"), |
263 |
|
"ROLE_PERMISSION": "wim_accounts:", |
264 |
|
"<ID>": { |
265 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
266 |
|
"ROLE_PERMISSION": "wim_accounts:id:", |
267 |
|
}, |
268 |
|
}, |
269 |
|
"sdns": { |
270 |
|
"METHODS": ("GET", "POST"), |
271 |
|
"ROLE_PERMISSION": "sdn_controllers:", |
272 |
|
"<ID>": { |
273 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
274 |
|
"ROLE_PERMISSION": "sdn_controllers:id:", |
275 |
|
}, |
276 |
|
}, |
277 |
|
"k8sclusters": { |
278 |
|
"METHODS": ("GET", "POST"), |
279 |
|
"ROLE_PERMISSION": "k8sclusters:", |
280 |
|
"<ID>": { |
281 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
282 |
|
"ROLE_PERMISSION": "k8sclusters:id:", |
283 |
|
}, |
284 |
|
}, |
285 |
|
"vca": { |
286 |
|
"METHODS": ("GET", "POST"), |
287 |
|
"ROLE_PERMISSION": "vca:", |
288 |
|
"<ID>": { |
289 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
290 |
|
"ROLE_PERMISSION": "vca:id:", |
291 |
|
}, |
292 |
|
}, |
293 |
|
"k8srepos": { |
294 |
|
"METHODS": ("GET", "POST"), |
295 |
|
"ROLE_PERMISSION": "k8srepos:", |
296 |
|
"<ID>": { |
297 |
|
"METHODS": ("GET", "DELETE"), |
298 |
|
"ROLE_PERMISSION": "k8srepos:id:", |
299 |
|
}, |
300 |
|
}, |
301 |
|
"osmrepos": { |
302 |
|
"METHODS": ("GET", "POST"), |
303 |
|
"ROLE_PERMISSION": "osmrepos:", |
304 |
|
"<ID>": { |
305 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
306 |
|
"ROLE_PERMISSION": "osmrepos:id:", |
307 |
|
}, |
308 |
|
}, |
309 |
|
"domains": { |
310 |
|
"METHODS": ("GET",), |
311 |
|
"ROLE_PERMISSION": "domains:", |
312 |
|
}, |
313 |
|
} |
314 |
|
}, |
315 |
|
"pdu": { |
316 |
|
"v1": { |
317 |
|
"pdu_descriptors": { |
318 |
|
"METHODS": ("GET", "POST"), |
319 |
|
"ROLE_PERMISSION": "pduds:", |
320 |
|
"<ID>": { |
321 |
|
"METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"), |
322 |
|
"ROLE_PERMISSION": "pduds:id:", |
323 |
|
}, |
324 |
|
}, |
325 |
|
} |
326 |
|
}, |
327 |
|
"nsd": { |
328 |
|
"v1": { |
329 |
|
"ns_descriptors_content": { |
330 |
|
"METHODS": ("GET", "POST"), |
331 |
|
"ROLE_PERMISSION": "nsds:", |
332 |
|
"<ID>": { |
333 |
|
"METHODS": ("GET", "PUT", "DELETE"), |
334 |
|
"ROLE_PERMISSION": "nsds:id:", |
335 |
|
}, |
336 |
|
}, |
337 |
|
"ns_descriptors": { |
338 |
|
"METHODS": ("GET", "POST"), |
339 |
|
"ROLE_PERMISSION": "nsds:", |
340 |
|
"<ID>": { |
341 |
|
"METHODS": ("GET", "DELETE", "PATCH"), |
342 |
|
"ROLE_PERMISSION": "nsds:id:", |
343 |
|
"nsd_content": { |
344 |
|
"METHODS": ("GET", "PUT"), |
345 |
|
"ROLE_PERMISSION": "nsds:id:content:", |
346 |
|
}, |
347 |
|
"nsd": { |
348 |
|
"METHODS": ("GET",), # descriptor inside package |
349 |
|
"ROLE_PERMISSION": "nsds:id:content:", |
350 |
|
}, |
351 |
|
"artifacts": { |
352 |
|
"METHODS": ("GET",), |
353 |
|
"ROLE_PERMISSION": "nsds:id:nsd_artifact:", |
354 |
|
"*": None, |
355 |
|
}, |
356 |
|
}, |
357 |
|
}, |
358 |
|
"pnf_descriptors": { |
359 |
|
"TODO": ("GET", "POST"), |
360 |
|
"<ID>": { |
361 |
|
"TODO": ("GET", "DELETE", "PATCH"), |
362 |
|
"pnfd_content": {"TODO": ("GET", "PUT")}, |
363 |
|
}, |
364 |
|
}, |
365 |
|
"subscriptions": { |
366 |
|
"TODO": ("GET", "POST"), |
367 |
|
"<ID>": {"TODO": ("GET", "DELETE")}, |
368 |
|
}, |
369 |
|
} |
370 |
|
}, |
371 |
|
"vnfpkgm": { |
372 |
|
"v1": { |
373 |
|
"vnf_packages_content": { |
374 |
|
"METHODS": ("GET", "POST"), |
375 |
|
"ROLE_PERMISSION": "vnfds:", |
376 |
|
"<ID>": { |
377 |
|
"METHODS": ("GET", "PUT", "DELETE"), |
378 |
|
"ROLE_PERMISSION": "vnfds:id:", |
379 |
|
}, |
380 |
|
}, |
381 |
|
"vnf_packages": { |
382 |
|
"METHODS": ("GET", "POST"), |
383 |
|
"ROLE_PERMISSION": "vnfds:", |
384 |
|
"<ID>": { |
385 |
|
"METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo |
386 |
|
"ROLE_PERMISSION": "vnfds:id:", |
387 |
|
"package_content": { |
388 |
|
"METHODS": ("GET", "PUT"), # package |
389 |
|
"ROLE_PERMISSION": "vnfds:id:", |
390 |
|
"upload_from_uri": { |
391 |
|
"METHODS": (), |
392 |
|
"TODO": ("POST",), |
393 |
|
"ROLE_PERMISSION": "vnfds:id:upload:", |
394 |
|
}, |
395 |
|
}, |
396 |
|
"vnfd": { |
397 |
|
"METHODS": ("GET",), # descriptor inside package |
398 |
|
"ROLE_PERMISSION": "vnfds:id:content:", |
399 |
|
}, |
400 |
|
"artifacts": { |
401 |
|
"METHODS": ("GET",), |
402 |
|
"ROLE_PERMISSION": "vnfds:id:vnfd_artifact:", |
403 |
|
"*": None, |
404 |
|
}, |
405 |
|
"action": { |
406 |
|
"METHODS": ("POST",), |
407 |
|
"ROLE_PERMISSION": "vnfds:id:action:", |
408 |
|
}, |
409 |
|
}, |
410 |
|
}, |
411 |
|
"subscriptions": { |
412 |
|
"TODO": ("GET", "POST"), |
413 |
|
"<ID>": {"TODO": ("GET", "DELETE")}, |
414 |
|
}, |
415 |
|
"vnfpkg_op_occs": { |
416 |
|
"METHODS": ("GET",), |
417 |
|
"ROLE_PERMISSION": "vnfds:vnfpkgops:", |
418 |
|
"<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"}, |
419 |
|
}, |
420 |
|
} |
421 |
|
}, |
422 |
|
"nslcm": { |
423 |
|
"v1": { |
424 |
|
"ns_instances_content": { |
425 |
|
"METHODS": ("GET", "POST"), |
426 |
|
"ROLE_PERMISSION": "ns_instances:", |
427 |
|
"<ID>": { |
428 |
|
"METHODS": ("GET", "DELETE"), |
429 |
|
"ROLE_PERMISSION": "ns_instances:id:", |
430 |
|
}, |
431 |
|
}, |
432 |
|
"ns_instances": { |
433 |
|
"METHODS": ("GET", "POST"), |
434 |
|
"ROLE_PERMISSION": "ns_instances:", |
435 |
|
"<ID>": { |
436 |
|
"METHODS": ("GET", "DELETE"), |
437 |
|
"ROLE_PERMISSION": "ns_instances:id:", |
438 |
|
"heal": { |
439 |
|
"METHODS": ("POST",), |
440 |
|
"ROLE_PERMISSION": "ns_instances:id:heal:", |
441 |
|
}, |
442 |
|
"scale": { |
443 |
|
"METHODS": ("POST",), |
444 |
|
"ROLE_PERMISSION": "ns_instances:id:scale:", |
445 |
|
}, |
446 |
|
"terminate": { |
447 |
|
"METHODS": ("POST",), |
448 |
|
"ROLE_PERMISSION": "ns_instances:id:terminate:", |
449 |
|
}, |
450 |
|
"instantiate": { |
451 |
|
"METHODS": ("POST",), |
452 |
|
"ROLE_PERMISSION": "ns_instances:id:instantiate:", |
453 |
|
}, |
454 |
|
"migrate": { |
455 |
|
"METHODS": ("POST",), |
456 |
|
"ROLE_PERMISSION": "ns_instances:id:migrate:", |
457 |
|
}, |
458 |
|
"action": { |
459 |
|
"METHODS": ("POST",), |
460 |
|
"ROLE_PERMISSION": "ns_instances:id:action:", |
461 |
|
}, |
462 |
|
"update": { |
463 |
|
"METHODS": ("POST",), |
464 |
|
"ROLE_PERMISSION": "ns_instances:id:update:", |
465 |
|
}, |
466 |
|
"verticalscale": { |
467 |
|
"METHODS": ("POST",), |
468 |
|
"ROLE_PERMISSION": "ns_instances:id:verticalscale:", |
469 |
|
}, |
470 |
|
}, |
471 |
|
}, |
472 |
|
"ns_lcm_op_occs": { |
473 |
|
"METHODS": ("GET",), |
474 |
|
"ROLE_PERMISSION": "ns_instances:opps:", |
475 |
|
"<ID>": { |
476 |
|
"METHODS": ("GET",), |
477 |
|
"ROLE_PERMISSION": "ns_instances:opps:id:", |
478 |
|
"cancel": { |
479 |
|
"METHODS": ("POST",), |
480 |
|
"ROLE_PERMISSION": "ns_instances:opps:cancel:", |
481 |
|
}, |
482 |
|
}, |
483 |
|
}, |
484 |
|
"vnfrs": { |
485 |
|
"METHODS": ("GET",), |
486 |
|
"ROLE_PERMISSION": "vnf_instances:", |
487 |
|
"<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"}, |
488 |
|
}, |
489 |
|
"vnf_instances": { |
490 |
|
"METHODS": ("GET",), |
491 |
|
"ROLE_PERMISSION": "vnf_instances:", |
492 |
|
"<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"}, |
493 |
|
}, |
494 |
|
"subscriptions": { |
495 |
|
"METHODS": ("GET", "POST"), |
496 |
|
"ROLE_PERMISSION": "ns_subscriptions:", |
497 |
|
"<ID>": { |
498 |
|
"METHODS": ("GET", "DELETE"), |
499 |
|
"ROLE_PERMISSION": "ns_subscriptions:id:", |
500 |
|
}, |
501 |
|
}, |
502 |
|
} |
503 |
|
}, |
504 |
|
"vnflcm": { |
505 |
|
"v1": { |
506 |
|
"vnf_instances": { |
507 |
|
"METHODS": ("GET", "POST"), |
508 |
|
"ROLE_PERMISSION": "vnflcm_instances:", |
509 |
|
"<ID>": { |
510 |
|
"METHODS": ("GET", "DELETE"), |
511 |
|
"ROLE_PERMISSION": "vnflcm_instances:id:", |
512 |
|
"scale": { |
513 |
|
"METHODS": ("POST",), |
514 |
|
"ROLE_PERMISSION": "vnflcm_instances:id:scale:", |
515 |
|
}, |
516 |
|
"terminate": { |
517 |
|
"METHODS": ("POST",), |
518 |
|
"ROLE_PERMISSION": "vnflcm_instances:id:terminate:", |
519 |
|
}, |
520 |
|
"instantiate": { |
521 |
|
"METHODS": ("POST",), |
522 |
|
"ROLE_PERMISSION": "vnflcm_instances:id:instantiate:", |
523 |
|
}, |
524 |
|
}, |
525 |
|
}, |
526 |
|
"vnf_lcm_op_occs": { |
527 |
|
"METHODS": ("GET",), |
528 |
|
"ROLE_PERMISSION": "vnf_instances:opps:", |
529 |
|
"<ID>": { |
530 |
|
"METHODS": ("GET",), |
531 |
|
"ROLE_PERMISSION": "vnf_instances:opps:id:", |
532 |
|
}, |
533 |
|
}, |
534 |
|
"subscriptions": { |
535 |
|
"METHODS": ("GET", "POST"), |
536 |
|
"ROLE_PERMISSION": "vnflcm_subscriptions:", |
537 |
|
"<ID>": { |
538 |
|
"METHODS": ("GET", "DELETE"), |
539 |
|
"ROLE_PERMISSION": "vnflcm_subscriptions:id:", |
540 |
|
}, |
541 |
|
}, |
542 |
|
} |
543 |
|
}, |
544 |
|
"nst": { |
545 |
|
"v1": { |
546 |
|
"netslice_templates_content": { |
547 |
|
"METHODS": ("GET", "POST"), |
548 |
|
"ROLE_PERMISSION": "slice_templates:", |
549 |
|
"<ID>": { |
550 |
|
"METHODS": ("GET", "PUT", "DELETE"), |
551 |
|
"ROLE_PERMISSION": "slice_templates:id:", |
552 |
|
}, |
553 |
|
}, |
554 |
|
"netslice_templates": { |
555 |
|
"METHODS": ("GET", "POST"), |
556 |
|
"ROLE_PERMISSION": "slice_templates:", |
557 |
|
"<ID>": { |
558 |
|
"METHODS": ("GET", "DELETE"), |
559 |
|
"TODO": ("PATCH",), |
560 |
|
"ROLE_PERMISSION": "slice_templates:id:", |
561 |
|
"nst_content": { |
562 |
|
"METHODS": ("GET", "PUT"), |
563 |
|
"ROLE_PERMISSION": "slice_templates:id:content:", |
564 |
|
}, |
565 |
|
"nst": { |
566 |
|
"METHODS": ("GET",), # descriptor inside package |
567 |
|
"ROLE_PERMISSION": "slice_templates:id:content:", |
568 |
|
}, |
569 |
|
"artifacts": { |
570 |
|
"METHODS": ("GET",), |
571 |
|
"ROLE_PERMISSION": "slice_templates:id:content:", |
572 |
|
"*": None, |
573 |
|
}, |
574 |
|
}, |
575 |
|
}, |
576 |
|
"subscriptions": { |
577 |
|
"TODO": ("GET", "POST"), |
578 |
|
"<ID>": {"TODO": ("GET", "DELETE")}, |
579 |
|
}, |
580 |
|
} |
581 |
|
}, |
582 |
|
"nsilcm": { |
583 |
|
"v1": { |
584 |
|
"netslice_instances_content": { |
585 |
|
"METHODS": ("GET", "POST"), |
586 |
|
"ROLE_PERMISSION": "slice_instances:", |
587 |
|
"<ID>": { |
588 |
|
"METHODS": ("GET", "DELETE"), |
589 |
|
"ROLE_PERMISSION": "slice_instances:id:", |
590 |
|
}, |
591 |
|
}, |
592 |
|
"netslice_instances": { |
593 |
|
"METHODS": ("GET", "POST"), |
594 |
|
"ROLE_PERMISSION": "slice_instances:", |
595 |
|
"<ID>": { |
596 |
|
"METHODS": ("GET", "DELETE"), |
597 |
|
"ROLE_PERMISSION": "slice_instances:id:", |
598 |
|
"terminate": { |
599 |
|
"METHODS": ("POST",), |
600 |
|
"ROLE_PERMISSION": "slice_instances:id:terminate:", |
601 |
|
}, |
602 |
|
"instantiate": { |
603 |
|
"METHODS": ("POST",), |
604 |
|
"ROLE_PERMISSION": "slice_instances:id:instantiate:", |
605 |
|
}, |
606 |
|
"action": { |
607 |
|
"METHODS": ("POST",), |
608 |
|
"ROLE_PERMISSION": "slice_instances:id:action:", |
609 |
|
}, |
610 |
|
}, |
611 |
|
}, |
612 |
|
"nsi_lcm_op_occs": { |
613 |
|
"METHODS": ("GET",), |
614 |
|
"ROLE_PERMISSION": "slice_instances:opps:", |
615 |
|
"<ID>": { |
616 |
|
"METHODS": ("GET",), |
617 |
|
"ROLE_PERMISSION": "slice_instances:opps:id:", |
618 |
|
}, |
619 |
|
}, |
620 |
|
} |
621 |
|
}, |
622 |
|
"nspm": { |
623 |
|
"v1": { |
624 |
|
"pm_jobs": { |
625 |
|
"<ID>": { |
626 |
|
"reports": { |
627 |
|
"<ID>": { |
628 |
|
"METHODS": ("GET",), |
629 |
|
"ROLE_PERMISSION": "reports:id:", |
630 |
|
} |
631 |
|
} |
632 |
|
}, |
633 |
|
}, |
634 |
|
}, |
635 |
|
}, |
636 |
|
"nsfm": { |
637 |
|
"v1": { |
638 |
|
"alarms": { |
639 |
|
"METHODS": ("GET", "PATCH"), |
640 |
|
"ROLE_PERMISSION": "alarms:", |
641 |
|
"<ID>": { |
642 |
|
"METHODS": ("GET", "PATCH"), |
643 |
|
"ROLE_PERMISSION": "alarms:id:", |
644 |
|
}, |
645 |
|
} |
646 |
|
}, |
647 |
|
}, |
648 |
|
} |
649 |
|
|
650 |
|
|
651 |
0 |
class NbiException(Exception): |
652 |
0 |
def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED): |
653 |
0 |
Exception.__init__(self, message) |
654 |
0 |
self.http_code = http_code |
655 |
|
|
656 |
|
|
657 |
0 |
class Server(object): |
658 |
0 |
instance = 0 |
659 |
|
# to decode bytes to str |
660 |
0 |
reader = getreader("utf-8") |
661 |
|
|
662 |
0 |
def __init__(self): |
663 |
0 |
self.instance += 1 |
664 |
0 |
self.authenticator = Authenticator(valid_url_methods, valid_query_string) |
665 |
0 |
self.engine = Engine(self.authenticator) |
666 |
|
|
667 |
0 |
def _format_in(self, kwargs): |
668 |
0 |
error_text = "" # error_text must be initialized outside try |
669 |
0 |
try: |
670 |
0 |
indata = None |
671 |
0 |
if cherrypy.request.body.length: |
672 |
0 |
error_text = "Invalid input format " |
673 |
|
|
674 |
0 |
if "Content-Type" in cherrypy.request.headers: |
675 |
0 |
if "application/json" in cherrypy.request.headers["Content-Type"]: |
676 |
0 |
error_text = "Invalid json format " |
677 |
0 |
indata = json.load(self.reader(cherrypy.request.body)) |
678 |
0 |
cherrypy.request.headers.pop("Content-File-MD5", None) |
679 |
0 |
elif "application/yaml" in cherrypy.request.headers["Content-Type"]: |
680 |
0 |
error_text = "Invalid yaml format " |
681 |
0 |
indata = yaml.safe_load(cherrypy.request.body) |
682 |
0 |
cherrypy.request.headers.pop("Content-File-MD5", None) |
683 |
0 |
elif ( |
684 |
|
"application/binary" in cherrypy.request.headers["Content-Type"] |
685 |
|
or "application/gzip" |
686 |
|
in cherrypy.request.headers["Content-Type"] |
687 |
|
or "application/zip" in cherrypy.request.headers["Content-Type"] |
688 |
|
or "text/plain" in cherrypy.request.headers["Content-Type"] |
689 |
|
): |
690 |
0 |
indata = cherrypy.request.body # .read() |
691 |
0 |
elif ( |
692 |
|
"multipart/form-data" |
693 |
|
in cherrypy.request.headers["Content-Type"] |
694 |
|
): |
695 |
0 |
if "descriptor_file" in kwargs: |
696 |
0 |
filecontent = kwargs.pop("descriptor_file") |
697 |
0 |
if not filecontent.file: |
698 |
0 |
raise NbiException( |
699 |
|
"empty file or content", HTTPStatus.BAD_REQUEST |
700 |
|
) |
701 |
0 |
indata = filecontent.file # .read() |
702 |
0 |
if filecontent.content_type.value: |
703 |
0 |
cherrypy.request.headers[ |
704 |
|
"Content-Type" |
705 |
|
] = filecontent.content_type.value |
706 |
|
else: |
707 |
|
# raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable, |
708 |
|
# "Only 'Content-Type' of type 'application/json' or |
709 |
|
# 'application/yaml' for input format are available") |
710 |
0 |
error_text = "Invalid yaml format " |
711 |
0 |
indata = yaml.safe_load(cherrypy.request.body) |
712 |
0 |
cherrypy.request.headers.pop("Content-File-MD5", None) |
713 |
|
else: |
714 |
0 |
error_text = "Invalid yaml format " |
715 |
0 |
indata = yaml.safe_load(cherrypy.request.body) |
716 |
0 |
cherrypy.request.headers.pop("Content-File-MD5", None) |
717 |
0 |
if not indata: |
718 |
0 |
indata = {} |
719 |
|
|
720 |
0 |
format_yaml = False |
721 |
0 |
if cherrypy.request.headers.get("Query-String-Format") == "yaml": |
722 |
0 |
format_yaml = True |
723 |
|
|
724 |
0 |
for k, v in kwargs.items(): |
725 |
0 |
if isinstance(v, str): |
726 |
0 |
if v == "": |
727 |
0 |
kwargs[k] = None |
728 |
0 |
elif format_yaml: |
729 |
0 |
try: |
730 |
0 |
kwargs[k] = yaml.safe_load(v) |
731 |
0 |
except Exception: |
732 |
0 |
pass |
733 |
0 |
elif ( |
734 |
|
k.endswith(".gt") |
735 |
|
or k.endswith(".lt") |
736 |
|
or k.endswith(".gte") |
737 |
|
or k.endswith(".lte") |
738 |
|
): |
739 |
0 |
try: |
740 |
0 |
kwargs[k] = int(v) |
741 |
0 |
except Exception: |
742 |
0 |
try: |
743 |
0 |
kwargs[k] = float(v) |
744 |
0 |
except Exception: |
745 |
0 |
pass |
746 |
0 |
elif v.find(",") > 0: |
747 |
0 |
kwargs[k] = v.split(",") |
748 |
0 |
elif isinstance(v, (list, tuple)): |
749 |
0 |
for index in range(0, len(v)): |
750 |
0 |
if v[index] == "": |
751 |
0 |
v[index] = None |
752 |
0 |
elif format_yaml: |
753 |
0 |
try: |
754 |
0 |
v[index] = yaml.safe_load(v[index]) |
755 |
0 |
except Exception: |
756 |
0 |
pass |
757 |
|
|
758 |
0 |
return indata |
759 |
0 |
except (ValueError, yaml.YAMLError) as exc: |
760 |
0 |
raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST) |
761 |
0 |
except KeyError as exc: |
762 |
0 |
raise NbiException( |
763 |
|
"Query string error: " + str(exc), HTTPStatus.BAD_REQUEST |
764 |
|
) |
765 |
0 |
except Exception as exc: |
766 |
0 |
raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST) |
767 |
|
|
768 |
0 |
@staticmethod |
769 |
0 |
def _format_out(data, token_info=None, _format=None): |
770 |
|
""" |
771 |
|
return string of dictionary data according to requested json, yaml, xml. By default json |
772 |
|
:param data: response to be sent. Can be a dict, text or file |
773 |
|
:param token_info: Contains among other username and project |
774 |
|
:param _format: The format to be set as Content-Type if data is a file |
775 |
|
:return: None |
776 |
|
""" |
777 |
0 |
accept = cherrypy.request.headers.get("Accept") |
778 |
0 |
if data is None: |
779 |
0 |
if accept and "text/html" in accept: |
780 |
0 |
return html.format( |
781 |
|
data, cherrypy.request, cherrypy.response, token_info |
782 |
|
) |
783 |
|
# cherrypy.response.status = HTTPStatus.NO_CONTENT.value |
784 |
0 |
return |
785 |
0 |
elif hasattr(data, "read"): # file object |
786 |
0 |
if _format: |
787 |
0 |
cherrypy.response.headers["Content-Type"] = _format |
788 |
0 |
elif "b" in data.mode: # binariy asssumig zip |
789 |
0 |
cherrypy.response.headers["Content-Type"] = "application/zip" |
790 |
|
else: |
791 |
0 |
cherrypy.response.headers["Content-Type"] = "text/plain" |
792 |
|
# TODO check that cherrypy close file. If not implement pending things to close per thread next |
793 |
0 |
return data |
794 |
0 |
if accept: |
795 |
0 |
if "text/html" in accept: |
796 |
0 |
return html.format( |
797 |
|
data, cherrypy.request, cherrypy.response, token_info |
798 |
|
) |
799 |
0 |
elif "application/yaml" in accept or "*/*" in accept: |
800 |
0 |
pass |
801 |
0 |
elif "application/json" in accept or ( |
802 |
|
cherrypy.response.status and cherrypy.response.status >= 300 |
803 |
|
): |
804 |
0 |
cherrypy.response.headers[ |
805 |
|
"Content-Type" |
806 |
|
] = "application/json; charset=utf-8" |
807 |
0 |
a = json.dumps(data, indent=4) + "\n" |
808 |
0 |
return a.encode("utf8") |
809 |
0 |
cherrypy.response.headers["Content-Type"] = "application/yaml" |
810 |
0 |
return yaml.safe_dump( |
811 |
|
data, |
812 |
|
explicit_start=True, |
813 |
|
indent=4, |
814 |
|
default_flow_style=False, |
815 |
|
tags=False, |
816 |
|
encoding="utf-8", |
817 |
|
allow_unicode=True, |
818 |
|
) # , canonical=True, default_style='"' |
819 |
|
|
820 |
0 |
@cherrypy.expose |
821 |
0 |
def index(self, *args, **kwargs): |
822 |
0 |
token_info = None |
823 |
0 |
try: |
824 |
0 |
if cherrypy.request.method == "GET": |
825 |
0 |
token_info = self.authenticator.authorize() |
826 |
0 |
outdata = token_info # Home page |
827 |
|
else: |
828 |
0 |
raise cherrypy.HTTPError( |
829 |
|
HTTPStatus.METHOD_NOT_ALLOWED.value, |
830 |
|
"Method {} not allowed for tokens".format(cherrypy.request.method), |
831 |
|
) |
832 |
|
|
833 |
0 |
return self._format_out(outdata, token_info) |
834 |
|
|
835 |
0 |
except (EngineException, AuthException) as e: |
836 |
|
# cherrypy.log("index Exception {}".format(e)) |
837 |
0 |
cherrypy.response.status = e.http_code.value |
838 |
0 |
return self._format_out("Welcome to OSM!", token_info) |
839 |
|
|
840 |
0 |
@cherrypy.expose |
841 |
0 |
def version(self, *args, **kwargs): |
842 |
|
# TODO consider to remove and provide version using the static version file |
843 |
0 |
try: |
844 |
0 |
if cherrypy.request.method != "GET": |
845 |
0 |
raise NbiException( |
846 |
|
"Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED |
847 |
|
) |
848 |
0 |
elif args or kwargs: |
849 |
0 |
raise NbiException( |
850 |
|
"Invalid URL or query string for version", |
851 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
852 |
|
) |
853 |
|
# TODO include version of other modules, pick up from some kafka admin message |
854 |
0 |
osm_nbi_version = {"version": nbi_version, "date": nbi_version_date} |
855 |
0 |
return self._format_out(osm_nbi_version) |
856 |
0 |
except NbiException as e: |
857 |
0 |
cherrypy.response.status = e.http_code.value |
858 |
0 |
problem_details = { |
859 |
|
"code": e.http_code.name, |
860 |
|
"status": e.http_code.value, |
861 |
|
"detail": str(e), |
862 |
|
} |
863 |
0 |
return self._format_out(problem_details, None) |
864 |
|
|
865 |
0 |
def domain(self): |
866 |
0 |
try: |
867 |
0 |
domains = { |
868 |
|
"user_domain_name": cherrypy.tree.apps["/osm"] |
869 |
|
.config["authentication"] |
870 |
|
.get("user_domain_name"), |
871 |
|
"project_domain_name": cherrypy.tree.apps["/osm"] |
872 |
|
.config["authentication"] |
873 |
|
.get("project_domain_name"), |
874 |
|
} |
875 |
0 |
return self._format_out(domains) |
876 |
0 |
except NbiException as e: |
877 |
0 |
cherrypy.response.status = e.http_code.value |
878 |
0 |
problem_details = { |
879 |
|
"code": e.http_code.name, |
880 |
|
"status": e.http_code.value, |
881 |
|
"detail": str(e), |
882 |
|
} |
883 |
0 |
return self._format_out(problem_details, None) |
884 |
|
|
885 |
0 |
@staticmethod |
886 |
0 |
def _format_login(token_info): |
887 |
|
""" |
888 |
|
Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will |
889 |
|
log this information |
890 |
|
:param token_info: Dictionary with token content |
891 |
|
:return: None |
892 |
|
""" |
893 |
0 |
cherrypy.request.login = token_info.get("username", "-") |
894 |
0 |
if token_info.get("project_name"): |
895 |
0 |
cherrypy.request.login += "/" + token_info["project_name"] |
896 |
0 |
if token_info.get("id"): |
897 |
0 |
cherrypy.request.login += ";session=" + token_info["id"][0:12] |
898 |
|
|
899 |
|
# NS Fault Management |
900 |
0 |
@cherrypy.expose |
901 |
0 |
def nsfm( |
902 |
|
self, |
903 |
|
version=None, |
904 |
|
topic=None, |
905 |
|
uuid=None, |
906 |
|
project_name=None, |
907 |
|
ns_id=None, |
908 |
|
*args, |
909 |
|
**kwargs |
910 |
|
): |
911 |
0 |
if topic == "alarms": |
912 |
0 |
try: |
913 |
0 |
method = cherrypy.request.method |
914 |
0 |
role_permission = self._check_valid_url_method( |
915 |
|
method, "nsfm", version, topic, None, None, *args |
916 |
|
) |
917 |
0 |
query_string_operations = self._extract_query_string_operations( |
918 |
|
kwargs, method |
919 |
|
) |
920 |
|
|
921 |
0 |
self.authenticator.authorize( |
922 |
|
role_permission, query_string_operations, None |
923 |
|
) |
924 |
|
|
925 |
|
# to handle get request |
926 |
0 |
if cherrypy.request.method == "GET": |
927 |
|
# if request is on basis of uuid |
928 |
0 |
if uuid and uuid != "None": |
929 |
0 |
try: |
930 |
0 |
alarm = self.engine.db.get_one("alarms", {"uuid": uuid}) |
931 |
0 |
alarm_action = self.engine.db.get_one( |
932 |
|
"alarms_action", {"uuid": uuid} |
933 |
|
) |
934 |
0 |
alarm.update(alarm_action) |
935 |
0 |
vnf = self.engine.db.get_one( |
936 |
|
"vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]} |
937 |
|
) |
938 |
0 |
alarm["vnf-id"] = vnf["_id"] |
939 |
0 |
return self._format_out(str(alarm)) |
940 |
0 |
except Exception: |
941 |
0 |
return self._format_out("Please provide valid alarm uuid") |
942 |
0 |
elif ns_id and ns_id != "None": |
943 |
|
# if request is on basis of ns_id |
944 |
0 |
try: |
945 |
0 |
alarms = self.engine.db.get_list( |
946 |
|
"alarms", {"tags.ns_id": ns_id} |
947 |
|
) |
948 |
0 |
for alarm in alarms: |
949 |
0 |
alarm_action = self.engine.db.get_one( |
950 |
|
"alarms_action", {"uuid": alarm["uuid"]} |
951 |
|
) |
952 |
0 |
alarm.update(alarm_action) |
953 |
0 |
return self._format_out(str(alarms)) |
954 |
0 |
except Exception: |
955 |
0 |
return self._format_out("Please provide valid ns id") |
956 |
|
else: |
957 |
|
# to return only alarm which are related to given project |
958 |
0 |
project = self.engine.db.get_one( |
959 |
|
"projects", {"name": project_name} |
960 |
|
) |
961 |
0 |
project_id = project.get("_id") |
962 |
0 |
ns_list = self.engine.db.get_list( |
963 |
|
"nsrs", {"_admin.projects_read": project_id} |
964 |
|
) |
965 |
0 |
ns_ids = [] |
966 |
0 |
for ns in ns_list: |
967 |
0 |
ns_ids.append(ns.get("_id")) |
968 |
0 |
alarms = self.engine.db.get_list("alarms") |
969 |
0 |
alarm_list = [ |
970 |
|
alarm |
971 |
|
for alarm in alarms |
972 |
|
if alarm["tags"]["ns_id"] in ns_ids |
973 |
|
] |
974 |
0 |
for alrm in alarm_list: |
975 |
0 |
action = self.engine.db.get_one( |
976 |
|
"alarms_action", {"uuid": alrm.get("uuid")} |
977 |
|
) |
978 |
0 |
alrm.update(action) |
979 |
0 |
return self._format_out(str(alarm_list)) |
980 |
|
# to handle patch request for alarm update |
981 |
0 |
elif cherrypy.request.method == "PATCH": |
982 |
0 |
data = yaml.safe_load(cherrypy.request.body) |
983 |
0 |
try: |
984 |
|
# check if uuid is valid |
985 |
0 |
self.engine.db.get_one("alarms", {"uuid": data.get("uuid")}) |
986 |
0 |
except Exception: |
987 |
0 |
return self._format_out("Please provide valid alarm uuid.") |
988 |
0 |
if data.get("is_enable") is not None: |
989 |
0 |
if data.get("is_enable"): |
990 |
0 |
alarm_status = "ok" |
991 |
|
else: |
992 |
0 |
alarm_status = "disabled" |
993 |
0 |
self.engine.db.set_one( |
994 |
|
"alarms", |
995 |
|
{"uuid": data.get("uuid")}, |
996 |
|
{"alarm_status": alarm_status}, |
997 |
|
) |
998 |
|
else: |
999 |
0 |
self.engine.db.set_one( |
1000 |
|
"alarms", |
1001 |
|
{"uuid": data.get("uuid")}, |
1002 |
|
{"threshold": data.get("threshold")}, |
1003 |
|
) |
1004 |
0 |
return self._format_out("Alarm updated") |
1005 |
0 |
except Exception as e: |
1006 |
0 |
if isinstance( |
1007 |
|
e, |
1008 |
|
( |
1009 |
|
NbiException, |
1010 |
|
EngineException, |
1011 |
|
DbException, |
1012 |
|
FsException, |
1013 |
|
MsgException, |
1014 |
|
AuthException, |
1015 |
|
ValidationError, |
1016 |
|
AuthconnException, |
1017 |
|
), |
1018 |
|
): |
1019 |
0 |
http_code_value = cherrypy.response.status = e.http_code.value |
1020 |
0 |
http_code_name = e.http_code.name |
1021 |
0 |
cherrypy.log("Exception {}".format(e)) |
1022 |
|
else: |
1023 |
0 |
http_code_value = ( |
1024 |
|
cherrypy.response.status |
1025 |
|
) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR |
1026 |
0 |
cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True) |
1027 |
0 |
http_code_name = HTTPStatus.BAD_REQUEST.name |
1028 |
0 |
problem_details = { |
1029 |
|
"code": http_code_name, |
1030 |
|
"status": http_code_value, |
1031 |
|
"detail": str(e), |
1032 |
|
} |
1033 |
0 |
return self._format_out(problem_details) |
1034 |
|
|
1035 |
0 |
@cherrypy.expose |
1036 |
0 |
def token(self, method, token_id=None, kwargs=None): |
1037 |
0 |
token_info = None |
1038 |
|
# self.engine.load_dbase(cherrypy.request.app.config) |
1039 |
0 |
indata = self._format_in(kwargs) |
1040 |
0 |
if not isinstance(indata, dict): |
1041 |
0 |
raise NbiException( |
1042 |
|
"Expected application/yaml or application/json Content-Type", |
1043 |
|
HTTPStatus.BAD_REQUEST, |
1044 |
|
) |
1045 |
|
|
1046 |
0 |
if method == "GET": |
1047 |
0 |
token_info = self.authenticator.authorize() |
1048 |
|
# for logging |
1049 |
0 |
self._format_login(token_info) |
1050 |
0 |
if token_id: |
1051 |
0 |
outdata = self.authenticator.get_token(token_info, token_id) |
1052 |
|
else: |
1053 |
0 |
outdata = self.authenticator.get_token_list(token_info) |
1054 |
0 |
elif method == "POST": |
1055 |
0 |
try: |
1056 |
0 |
token_info = self.authenticator.authorize() |
1057 |
0 |
except Exception: |
1058 |
0 |
token_info = None |
1059 |
0 |
if kwargs: |
1060 |
0 |
indata.update(kwargs) |
1061 |
|
# This is needed to log the user when authentication fails |
1062 |
0 |
cherrypy.request.login = "{}".format(indata.get("username", "-")) |
1063 |
0 |
outdata = token_info = self.authenticator.new_token( |
1064 |
|
token_info, indata, cherrypy.request.remote |
1065 |
|
) |
1066 |
0 |
cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101 |
1067 |
0 |
self._set_location_header("admin", "v1", "tokens", outdata["_id"]) |
1068 |
|
# for logging |
1069 |
0 |
self._format_login(token_info) |
1070 |
|
# password expiry check |
1071 |
0 |
if self.authenticator.check_password_expiry(outdata): |
1072 |
0 |
outdata = { |
1073 |
|
"id": outdata["id"], |
1074 |
|
"message": "change_password", |
1075 |
|
"user_id": outdata["user_id"], |
1076 |
|
} |
1077 |
|
# cherrypy.response.cookie["Authorization"] = outdata["id"] |
1078 |
|
# cherrypy.response.cookie["Authorization"]['expires'] = 3600 |
1079 |
0 |
cef_event( |
1080 |
|
cef_logger, |
1081 |
|
{ |
1082 |
|
"name": "User Login", |
1083 |
|
"sourceUserName": token_info.get("username"), |
1084 |
|
"message": "User Logged In, Project={} Outcome=Success".format( |
1085 |
|
token_info.get("project_name") |
1086 |
|
), |
1087 |
|
}, |
1088 |
|
) |
1089 |
0 |
cherrypy.log("{}".format(cef_logger)) |
1090 |
0 |
elif method == "DELETE": |
1091 |
0 |
if not token_id and "id" in kwargs: |
1092 |
0 |
token_id = kwargs["id"] |
1093 |
0 |
elif not token_id: |
1094 |
0 |
token_info = self.authenticator.authorize() |
1095 |
|
# for logging |
1096 |
0 |
self._format_login(token_info) |
1097 |
0 |
token_id = token_info["_id"] |
1098 |
0 |
if current_backend != "keystone": |
1099 |
0 |
token_details = self.engine.db.get_one("tokens", {"_id": token_id}) |
1100 |
0 |
current_user = token_details.get("username") |
1101 |
0 |
current_project = token_details.get("project_name") |
1102 |
|
else: |
1103 |
0 |
current_user = "keystone backend" |
1104 |
0 |
current_project = "keystone backend" |
1105 |
0 |
outdata = self.authenticator.del_token(token_id) |
1106 |
0 |
token_info = None |
1107 |
0 |
cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101 |
1108 |
0 |
cef_event( |
1109 |
|
cef_logger, |
1110 |
|
{ |
1111 |
|
"name": "User Logout", |
1112 |
|
"sourceUserName": current_user, |
1113 |
|
"message": "User Logged Out, Project={} Outcome=Success".format( |
1114 |
|
current_project |
1115 |
|
), |
1116 |
|
}, |
1117 |
|
) |
1118 |
0 |
cherrypy.log("{}".format(cef_logger)) |
1119 |
|
# cherrypy.response.cookie["Authorization"] = token_id |
1120 |
|
# cherrypy.response.cookie["Authorization"]['expires'] = 0 |
1121 |
|
else: |
1122 |
0 |
raise NbiException( |
1123 |
|
"Method {} not allowed for token".format(method), |
1124 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1125 |
|
) |
1126 |
0 |
return self._format_out(outdata, token_info) |
1127 |
|
|
1128 |
0 |
@cherrypy.expose |
1129 |
0 |
def test(self, *args, **kwargs): |
1130 |
0 |
if not cherrypy.config.get("server.enable_test") or ( |
1131 |
|
isinstance(cherrypy.config["server.enable_test"], str) |
1132 |
|
and cherrypy.config["server.enable_test"].lower() == "false" |
1133 |
|
): |
1134 |
0 |
cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value |
1135 |
0 |
return "test URL is disabled" |
1136 |
0 |
thread_info = None |
1137 |
0 |
if args and args[0] == "help": |
1138 |
0 |
return ( |
1139 |
|
"<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n" |
1140 |
|
"sleep/<time>\nmessage/topic\n</pre></html>" |
1141 |
|
) |
1142 |
|
|
1143 |
0 |
elif args and args[0] == "init": |
1144 |
0 |
try: |
1145 |
|
# self.engine.load_dbase(cherrypy.request.app.config) |
1146 |
0 |
pid = self.authenticator.create_admin_project() |
1147 |
0 |
self.authenticator.create_admin_user(pid) |
1148 |
0 |
return "Done. User 'admin', password 'admin' created" |
1149 |
0 |
except Exception: |
1150 |
0 |
cherrypy.response.status = HTTPStatus.FORBIDDEN.value |
1151 |
0 |
return self._format_out("Database already initialized") |
1152 |
0 |
elif args and args[0] == "file": |
1153 |
0 |
return cherrypy.lib.static.serve_file( |
1154 |
|
cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1], |
1155 |
|
"text/plain", |
1156 |
|
"attachment", |
1157 |
|
) |
1158 |
0 |
elif args and args[0] == "file2": |
1159 |
0 |
f_path = ( |
1160 |
|
cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1] |
1161 |
|
) |
1162 |
0 |
f = open(f_path, "r") |
1163 |
0 |
cherrypy.response.headers["Content-type"] = "text/plain" |
1164 |
0 |
return f |
1165 |
|
|
1166 |
0 |
elif len(args) == 2 and args[0] == "db-clear": |
1167 |
0 |
deleted_info = self.engine.db.del_list(args[1], kwargs) |
1168 |
0 |
return "{} {} deleted\n".format(deleted_info["deleted"], args[1]) |
1169 |
0 |
elif len(args) and args[0] == "fs-clear": |
1170 |
0 |
if len(args) >= 2: |
1171 |
0 |
folders = (args[1],) |
1172 |
|
else: |
1173 |
0 |
folders = self.engine.fs.dir_ls(".") |
1174 |
0 |
for folder in folders: |
1175 |
0 |
self.engine.fs.file_delete(folder) |
1176 |
0 |
return ",".join(folders) + " folders deleted\n" |
1177 |
0 |
elif args and args[0] == "login": |
1178 |
0 |
if not cherrypy.request.headers.get("Authorization"): |
1179 |
0 |
cherrypy.response.headers[ |
1180 |
|
"WWW-Authenticate" |
1181 |
|
] = 'Basic realm="Access to OSM site", charset="UTF-8"' |
1182 |
0 |
cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value |
1183 |
0 |
elif args and args[0] == "login2": |
1184 |
0 |
if not cherrypy.request.headers.get("Authorization"): |
1185 |
0 |
cherrypy.response.headers[ |
1186 |
|
"WWW-Authenticate" |
1187 |
|
] = 'Bearer realm="Access to OSM site"' |
1188 |
0 |
cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value |
1189 |
0 |
elif args and args[0] == "sleep": |
1190 |
0 |
sleep_time = 5 |
1191 |
0 |
try: |
1192 |
0 |
sleep_time = int(args[1]) |
1193 |
0 |
except Exception: |
1194 |
0 |
cherrypy.response.status = HTTPStatus.FORBIDDEN.value |
1195 |
0 |
return self._format_out("Database already initialized") |
1196 |
0 |
thread_info = cherrypy.thread_data |
1197 |
0 |
print(thread_info) |
1198 |
0 |
time.sleep(sleep_time) |
1199 |
|
# thread_info |
1200 |
0 |
elif len(args) >= 2 and args[0] == "message": |
1201 |
0 |
main_topic = args[1] |
1202 |
0 |
return_text = "<html><pre>{} ->\n".format(main_topic) |
1203 |
0 |
try: |
1204 |
0 |
if cherrypy.request.method == "POST": |
1205 |
0 |
to_send = yaml.safe_load(cherrypy.request.body) |
1206 |
0 |
for k, v in to_send.items(): |
1207 |
0 |
self.engine.msg.write(main_topic, k, v) |
1208 |
0 |
return_text += " {}: {}\n".format(k, v) |
1209 |
0 |
elif cherrypy.request.method == "GET": |
1210 |
0 |
for k, v in kwargs.items(): |
1211 |
0 |
v_dict = yaml.safe_load(v) |
1212 |
0 |
self.engine.msg.write(main_topic, k, v_dict) |
1213 |
0 |
return_text += " {}: {}\n".format(k, v_dict) |
1214 |
0 |
except Exception as e: |
1215 |
0 |
return_text += "Error: " + str(e) |
1216 |
0 |
return_text += "</pre></html>\n" |
1217 |
0 |
return return_text |
1218 |
|
|
1219 |
0 |
return_text = ( |
1220 |
|
"<html><pre>\nheaders:\n args: {}\n".format(args) |
1221 |
|
+ " kwargs: {}\n".format(kwargs) |
1222 |
|
+ " headers: {}\n".format(cherrypy.request.headers) |
1223 |
|
+ " path_info: {}\n".format(cherrypy.request.path_info) |
1224 |
|
+ " query_string: {}\n".format(cherrypy.request.query_string) |
1225 |
|
+ " session: {}\n".format(cherrypy.session) # pylint: disable=E1101 |
1226 |
|
+ " cookie: {}\n".format(cherrypy.request.cookie) |
1227 |
|
+ " method: {}\n".format(cherrypy.request.method) |
1228 |
|
+ " session: {}\n".format( |
1229 |
|
cherrypy.session.get("fieldname") # pylint: disable=E1101 |
1230 |
|
) |
1231 |
|
+ " body:\n" |
1232 |
|
) |
1233 |
0 |
return_text += " length: {}\n".format(cherrypy.request.body.length) |
1234 |
0 |
if cherrypy.request.body.length: |
1235 |
0 |
return_text += " content: {}\n".format( |
1236 |
|
str( |
1237 |
|
cherrypy.request.body.read( |
1238 |
|
int(cherrypy.request.headers.get("Content-Length", 0)) |
1239 |
|
) |
1240 |
|
) |
1241 |
|
) |
1242 |
0 |
if thread_info: |
1243 |
0 |
return_text += "thread: {}\n".format(thread_info) |
1244 |
0 |
return_text += "</pre></html>" |
1245 |
0 |
return return_text |
1246 |
|
|
1247 |
0 |
@staticmethod |
1248 |
0 |
def _check_valid_url_method(method, *args): |
1249 |
0 |
if len(args) < 3: |
1250 |
0 |
raise NbiException( |
1251 |
|
"URL must contain at least 'main_topic/version/topic'", |
1252 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1253 |
|
) |
1254 |
|
|
1255 |
0 |
reference = valid_url_methods |
1256 |
0 |
for arg in args: |
1257 |
0 |
if arg is None: |
1258 |
0 |
break |
1259 |
0 |
if not isinstance(reference, dict): |
1260 |
0 |
raise NbiException( |
1261 |
|
"URL contains unexpected extra items '{}'".format(arg), |
1262 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1263 |
|
) |
1264 |
|
|
1265 |
0 |
if arg in reference: |
1266 |
0 |
reference = reference[arg] |
1267 |
0 |
elif "<ID>" in reference: |
1268 |
0 |
reference = reference["<ID>"] |
1269 |
0 |
elif "*" in reference: |
1270 |
|
# if there is content |
1271 |
0 |
if reference["*"]: |
1272 |
0 |
reference = reference["*"] |
1273 |
0 |
break |
1274 |
|
else: |
1275 |
0 |
raise NbiException( |
1276 |
|
"Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED |
1277 |
|
) |
1278 |
0 |
if "TODO" in reference and method in reference["TODO"]: |
1279 |
0 |
raise NbiException( |
1280 |
|
"Method {} not supported yet for this URL".format(method), |
1281 |
|
HTTPStatus.NOT_IMPLEMENTED, |
1282 |
|
) |
1283 |
0 |
elif "METHODS" in reference and method not in reference["METHODS"]: |
1284 |
0 |
raise NbiException( |
1285 |
|
"Method {} not supported for this URL".format(method), |
1286 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1287 |
|
) |
1288 |
0 |
return reference["ROLE_PERMISSION"] + method.lower() |
1289 |
|
|
1290 |
0 |
@staticmethod |
1291 |
0 |
def _set_location_header(main_topic, version, topic, id): |
1292 |
|
""" |
1293 |
|
Insert response header Location with the URL of created item base on URL params |
1294 |
|
:param main_topic: |
1295 |
|
:param version: |
1296 |
|
:param topic: |
1297 |
|
:param id: |
1298 |
|
:return: None |
1299 |
|
""" |
1300 |
|
# Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT |
1301 |
0 |
cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format( |
1302 |
|
main_topic, version, topic, id |
1303 |
|
) |
1304 |
0 |
return |
1305 |
|
|
1306 |
0 |
@staticmethod |
1307 |
0 |
def _extract_query_string_operations(kwargs, method): |
1308 |
|
""" |
1309 |
|
|
1310 |
|
:param kwargs: |
1311 |
|
:return: |
1312 |
|
""" |
1313 |
0 |
query_string_operations = [] |
1314 |
0 |
if kwargs: |
1315 |
0 |
for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"): |
1316 |
0 |
if qs in kwargs and kwargs[qs].lower() != "false": |
1317 |
0 |
query_string_operations.append(qs.lower() + ":" + method.lower()) |
1318 |
0 |
return query_string_operations |
1319 |
|
|
1320 |
0 |
@staticmethod |
1321 |
0 |
def _manage_admin_query(token_info, kwargs, method, _id): |
1322 |
|
""" |
1323 |
|
Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT |
1324 |
|
Check that users has rights to use them and returs the admin_query |
1325 |
|
:param token_info: token_info rights obtained by token |
1326 |
|
:param kwargs: query string input. |
1327 |
|
:param method: http method: GET, POSST, PUT, ... |
1328 |
|
:param _id: |
1329 |
|
:return: admin_query dictionary with keys: |
1330 |
|
public: True, False or None |
1331 |
|
force: True or False |
1332 |
|
project_id: tuple with projects used for accessing an element |
1333 |
|
set_project: tuple with projects that a created element will belong to |
1334 |
|
method: show, list, delete, write |
1335 |
|
""" |
1336 |
0 |
admin_query = { |
1337 |
|
"force": False, |
1338 |
|
"project_id": (token_info["project_id"],), |
1339 |
|
"username": token_info["username"], |
1340 |
|
"admin": token_info["admin"], |
1341 |
|
"public": None, |
1342 |
|
"allow_show_user_project_role": token_info["allow_show_user_project_role"], |
1343 |
|
} |
1344 |
0 |
if kwargs: |
1345 |
|
# FORCE |
1346 |
0 |
if "FORCE" in kwargs: |
1347 |
0 |
if ( |
1348 |
|
kwargs["FORCE"].lower() != "false" |
1349 |
|
): # if None or True set force to True |
1350 |
0 |
admin_query["force"] = True |
1351 |
0 |
del kwargs["FORCE"] |
1352 |
|
# PUBLIC |
1353 |
0 |
if "PUBLIC" in kwargs: |
1354 |
0 |
if ( |
1355 |
|
kwargs["PUBLIC"].lower() != "false" |
1356 |
|
): # if None or True set public to True |
1357 |
0 |
admin_query["public"] = True |
1358 |
|
else: |
1359 |
0 |
admin_query["public"] = False |
1360 |
0 |
del kwargs["PUBLIC"] |
1361 |
|
# ADMIN |
1362 |
0 |
if "ADMIN" in kwargs: |
1363 |
0 |
behave_as = kwargs.pop("ADMIN") |
1364 |
0 |
if behave_as.lower() != "false": |
1365 |
0 |
if not token_info["admin"]: |
1366 |
0 |
raise NbiException( |
1367 |
|
"Only admin projects can use 'ADMIN' query string", |
1368 |
|
HTTPStatus.UNAUTHORIZED, |
1369 |
|
) |
1370 |
0 |
if ( |
1371 |
|
not behave_as or behave_as.lower() == "true" |
1372 |
|
): # convert True, None to empty list |
1373 |
0 |
admin_query["project_id"] = () |
1374 |
0 |
elif isinstance(behave_as, (list, tuple)): |
1375 |
0 |
admin_query["project_id"] = behave_as |
1376 |
|
else: # isinstance(behave_as, str) |
1377 |
0 |
admin_query["project_id"] = (behave_as,) |
1378 |
0 |
if "SET_PROJECT" in kwargs: |
1379 |
0 |
set_project = kwargs.pop("SET_PROJECT") |
1380 |
0 |
if not set_project: |
1381 |
0 |
admin_query["set_project"] = list(admin_query["project_id"]) |
1382 |
|
else: |
1383 |
0 |
if isinstance(set_project, str): |
1384 |
0 |
set_project = (set_project,) |
1385 |
0 |
if admin_query["project_id"]: |
1386 |
0 |
for p in set_project: |
1387 |
0 |
if p not in admin_query["project_id"]: |
1388 |
0 |
raise NbiException( |
1389 |
|
"Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or " |
1390 |
|
"'ADMIN='{p}'".format(p=p), |
1391 |
|
HTTPStatus.UNAUTHORIZED, |
1392 |
|
) |
1393 |
0 |
admin_query["set_project"] = set_project |
1394 |
|
|
1395 |
|
# PROJECT_READ |
1396 |
|
# if "PROJECT_READ" in kwargs: |
1397 |
|
# admin_query["project"] = kwargs.pop("project") |
1398 |
|
# if admin_query["project"] == token_info["project_id"]: |
1399 |
0 |
if method == "GET": |
1400 |
0 |
if _id: |
1401 |
0 |
admin_query["method"] = "show" |
1402 |
|
else: |
1403 |
0 |
admin_query["method"] = "list" |
1404 |
0 |
elif method == "DELETE": |
1405 |
0 |
admin_query["method"] = "delete" |
1406 |
|
else: |
1407 |
0 |
admin_query["method"] = "write" |
1408 |
0 |
return admin_query |
1409 |
|
|
1410 |
0 |
@cherrypy.expose |
1411 |
0 |
def default( |
1412 |
|
self, |
1413 |
|
main_topic=None, |
1414 |
|
version=None, |
1415 |
|
topic=None, |
1416 |
|
_id=None, |
1417 |
|
item=None, |
1418 |
|
*args, |
1419 |
|
**kwargs |
1420 |
|
): |
1421 |
0 |
token_info = None |
1422 |
0 |
outdata = {} |
1423 |
0 |
_format = None |
1424 |
0 |
method = "DONE" |
1425 |
0 |
engine_topic = None |
1426 |
0 |
rollback = [] |
1427 |
0 |
engine_session = None |
1428 |
0 |
url_id = "" |
1429 |
0 |
log_mapping = { |
1430 |
|
"POST": "Creating", |
1431 |
|
"GET": "Fetching", |
1432 |
|
"DELETE": "Deleting", |
1433 |
|
"PUT": "Updating", |
1434 |
|
"PATCH": "Updating", |
1435 |
|
} |
1436 |
0 |
try: |
1437 |
0 |
if not main_topic or not version or not topic: |
1438 |
0 |
raise NbiException( |
1439 |
|
"URL must contain at least 'main_topic/version/topic'", |
1440 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1441 |
|
) |
1442 |
0 |
if main_topic not in ( |
1443 |
|
"admin", |
1444 |
|
"vnfpkgm", |
1445 |
|
"nsd", |
1446 |
|
"nslcm", |
1447 |
|
"pdu", |
1448 |
|
"nst", |
1449 |
|
"nsilcm", |
1450 |
|
"nspm", |
1451 |
|
"vnflcm", |
1452 |
|
): |
1453 |
0 |
raise NbiException( |
1454 |
|
"URL main_topic '{}' not supported".format(main_topic), |
1455 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1456 |
|
) |
1457 |
0 |
if version != "v1": |
1458 |
0 |
raise NbiException( |
1459 |
|
"URL version '{}' not supported".format(version), |
1460 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1461 |
|
) |
1462 |
0 |
if _id is not None: |
1463 |
0 |
url_id = _id |
1464 |
|
|
1465 |
0 |
if ( |
1466 |
|
kwargs |
1467 |
|
and "METHOD" in kwargs |
1468 |
|
and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH") |
1469 |
|
): |
1470 |
0 |
method = kwargs.pop("METHOD") |
1471 |
|
else: |
1472 |
0 |
method = cherrypy.request.method |
1473 |
|
|
1474 |
0 |
role_permission = self._check_valid_url_method( |
1475 |
|
method, main_topic, version, topic, _id, item, *args |
1476 |
|
) |
1477 |
0 |
query_string_operations = self._extract_query_string_operations( |
1478 |
|
kwargs, method |
1479 |
|
) |
1480 |
0 |
if main_topic == "admin" and topic == "tokens": |
1481 |
0 |
return self.token(method, _id, kwargs) |
1482 |
0 |
token_info = self.authenticator.authorize( |
1483 |
|
role_permission, query_string_operations, _id |
1484 |
|
) |
1485 |
0 |
if main_topic == "admin" and topic == "domains": |
1486 |
0 |
return self.domain() |
1487 |
0 |
engine_session = self._manage_admin_query(token_info, kwargs, method, _id) |
1488 |
0 |
indata = self._format_in(kwargs) |
1489 |
0 |
engine_topic = topic |
1490 |
|
|
1491 |
0 |
if item and topic != "pm_jobs": |
1492 |
0 |
engine_topic = item |
1493 |
|
|
1494 |
0 |
if main_topic == "nsd": |
1495 |
0 |
engine_topic = "nsds" |
1496 |
0 |
elif main_topic == "vnfpkgm": |
1497 |
0 |
engine_topic = "vnfds" |
1498 |
0 |
if topic == "vnfpkg_op_occs": |
1499 |
0 |
engine_topic = "vnfpkgops" |
1500 |
0 |
if topic == "vnf_packages" and item == "action": |
1501 |
0 |
engine_topic = "vnfpkgops" |
1502 |
0 |
elif main_topic == "nslcm": |
1503 |
0 |
engine_topic = "nsrs" |
1504 |
0 |
if topic == "ns_lcm_op_occs": |
1505 |
0 |
engine_topic = "nslcmops" |
1506 |
0 |
if topic == "vnfrs" or topic == "vnf_instances": |
1507 |
0 |
engine_topic = "vnfrs" |
1508 |
0 |
elif main_topic == "vnflcm": |
1509 |
0 |
if topic == "vnf_lcm_op_occs": |
1510 |
0 |
engine_topic = "vnflcmops" |
1511 |
0 |
elif main_topic == "nst": |
1512 |
0 |
engine_topic = "nsts" |
1513 |
0 |
elif main_topic == "nsilcm": |
1514 |
0 |
engine_topic = "nsis" |
1515 |
0 |
if topic == "nsi_lcm_op_occs": |
1516 |
0 |
engine_topic = "nsilcmops" |
1517 |
0 |
elif main_topic == "pdu": |
1518 |
0 |
engine_topic = "pdus" |
1519 |
0 |
if ( |
1520 |
|
engine_topic == "vims" |
1521 |
|
): # TODO this is for backward compatibility, it will be removed in the future |
1522 |
0 |
engine_topic = "vim_accounts" |
1523 |
|
|
1524 |
0 |
if topic == "subscriptions": |
1525 |
0 |
engine_topic = main_topic + "_" + topic |
1526 |
|
|
1527 |
0 |
if method == "GET": |
1528 |
0 |
if item in ( |
1529 |
|
"nsd_content", |
1530 |
|
"package_content", |
1531 |
|
"artifacts", |
1532 |
|
"vnfd", |
1533 |
|
"nsd", |
1534 |
|
"nst", |
1535 |
|
"nst_content", |
1536 |
|
): |
1537 |
0 |
if item in ("vnfd", "nsd", "nst"): |
1538 |
0 |
path = "$DESCRIPTOR" |
1539 |
0 |
elif args: |
1540 |
0 |
path = args |
1541 |
0 |
elif item == "artifacts": |
1542 |
0 |
path = () |
1543 |
|
else: |
1544 |
0 |
path = None |
1545 |
0 |
file, _format = self.engine.get_file( |
1546 |
|
engine_session, |
1547 |
|
engine_topic, |
1548 |
|
_id, |
1549 |
|
path, |
1550 |
|
cherrypy.request.headers.get("Accept"), |
1551 |
|
) |
1552 |
0 |
outdata = file |
1553 |
0 |
elif not _id: |
1554 |
0 |
outdata = self.engine.get_item_list( |
1555 |
|
engine_session, engine_topic, kwargs, api_req=True |
1556 |
|
) |
1557 |
|
else: |
1558 |
0 |
if item == "reports": |
1559 |
|
# TODO check that project_id (_id in this context) has permissions |
1560 |
0 |
_id = args[0] |
1561 |
0 |
filter_q = None |
1562 |
0 |
if "vcaStatusRefresh" in kwargs: |
1563 |
0 |
filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]} |
1564 |
0 |
outdata = self.engine.get_item( |
1565 |
|
engine_session, engine_topic, _id, filter_q, True |
1566 |
|
) |
1567 |
|
|
1568 |
0 |
elif method == "POST": |
1569 |
0 |
cherrypy.response.status = HTTPStatus.CREATED.value |
1570 |
0 |
if topic in ( |
1571 |
|
"ns_descriptors_content", |
1572 |
|
"vnf_packages_content", |
1573 |
|
"netslice_templates_content", |
1574 |
|
): |
1575 |
0 |
_id = cherrypy.request.headers.get("Transaction-Id") |
1576 |
0 |
if not _id: |
1577 |
0 |
_id, _ = self.engine.new_item( |
1578 |
|
rollback, |
1579 |
|
engine_session, |
1580 |
|
engine_topic, |
1581 |
|
{}, |
1582 |
|
None, |
1583 |
|
cherrypy.request.headers, |
1584 |
|
) |
1585 |
0 |
completed = self.engine.upload_content( |
1586 |
|
engine_session, |
1587 |
|
engine_topic, |
1588 |
|
_id, |
1589 |
|
indata, |
1590 |
|
kwargs, |
1591 |
|
cherrypy.request.headers, |
1592 |
|
) |
1593 |
0 |
if completed: |
1594 |
0 |
self._set_location_header(main_topic, version, topic, _id) |
1595 |
|
else: |
1596 |
0 |
cherrypy.response.headers["Transaction-Id"] = _id |
1597 |
0 |
outdata = {"id": _id} |
1598 |
0 |
elif topic == "ns_instances_content": |
1599 |
|
# creates NSR |
1600 |
0 |
_id, _ = self.engine.new_item( |
1601 |
|
rollback, engine_session, engine_topic, indata, kwargs |
1602 |
|
) |
1603 |
|
# creates nslcmop |
1604 |
0 |
indata["lcmOperationType"] = "instantiate" |
1605 |
0 |
indata["nsInstanceId"] = _id |
1606 |
0 |
nslcmop_id, _ = self.engine.new_item( |
1607 |
|
rollback, engine_session, "nslcmops", indata, None |
1608 |
|
) |
1609 |
0 |
self._set_location_header(main_topic, version, topic, _id) |
1610 |
0 |
outdata = {"id": _id, "nslcmop_id": nslcmop_id} |
1611 |
0 |
elif topic == "ns_instances" and item: |
1612 |
0 |
indata["lcmOperationType"] = item |
1613 |
0 |
indata["nsInstanceId"] = _id |
1614 |
0 |
_id, _ = self.engine.new_item( |
1615 |
|
rollback, engine_session, "nslcmops", indata, kwargs |
1616 |
|
) |
1617 |
0 |
self._set_location_header( |
1618 |
|
main_topic, version, "ns_lcm_op_occs", _id |
1619 |
|
) |
1620 |
0 |
outdata = {"id": _id} |
1621 |
0 |
cherrypy.response.status = HTTPStatus.ACCEPTED.value |
1622 |
0 |
elif topic == "netslice_instances_content": |
1623 |
|
# creates NetSlice_Instance_record (NSIR) |
1624 |
0 |
_id, _ = self.engine.new_item( |
1625 |
|
rollback, engine_session, engine_topic, indata, kwargs |
1626 |
|
) |
1627 |
0 |
self._set_location_header(main_topic, version, topic, _id) |
1628 |
0 |
indata["lcmOperationType"] = "instantiate" |
1629 |
0 |
indata["netsliceInstanceId"] = _id |
1630 |
0 |
nsilcmop_id, _ = self.engine.new_item( |
1631 |
|
rollback, engine_session, "nsilcmops", indata, kwargs |
1632 |
|
) |
1633 |
0 |
outdata = {"id": _id, "nsilcmop_id": nsilcmop_id} |
1634 |
0 |
elif topic == "netslice_instances" and item: |
1635 |
0 |
indata["lcmOperationType"] = item |
1636 |
0 |
indata["netsliceInstanceId"] = _id |
1637 |
0 |
_id, _ = self.engine.new_item( |
1638 |
|
rollback, engine_session, "nsilcmops", indata, kwargs |
1639 |
|
) |
1640 |
0 |
self._set_location_header( |
1641 |
|
main_topic, version, "nsi_lcm_op_occs", _id |
1642 |
|
) |
1643 |
0 |
outdata = {"id": _id} |
1644 |
0 |
cherrypy.response.status = HTTPStatus.ACCEPTED.value |
1645 |
0 |
elif topic == "vnf_packages" and item == "action": |
1646 |
0 |
indata["lcmOperationType"] = item |
1647 |
0 |
indata["vnfPkgId"] = _id |
1648 |
0 |
_id, _ = self.engine.new_item( |
1649 |
|
rollback, engine_session, "vnfpkgops", indata, kwargs |
1650 |
|
) |
1651 |
0 |
self._set_location_header( |
1652 |
|
main_topic, version, "vnfpkg_op_occs", _id |
1653 |
|
) |
1654 |
0 |
outdata = {"id": _id} |
1655 |
0 |
cherrypy.response.status = HTTPStatus.ACCEPTED.value |
1656 |
0 |
elif topic == "subscriptions": |
1657 |
0 |
_id, _ = self.engine.new_item( |
1658 |
|
rollback, engine_session, engine_topic, indata, kwargs |
1659 |
|
) |
1660 |
0 |
self._set_location_header(main_topic, version, topic, _id) |
1661 |
0 |
link = {} |
1662 |
0 |
link["self"] = cherrypy.response.headers["Location"] |
1663 |
0 |
outdata = { |
1664 |
|
"id": _id, |
1665 |
|
"filter": indata["filter"], |
1666 |
|
"callbackUri": indata["CallbackUri"], |
1667 |
|
"_links": link, |
1668 |
|
} |
1669 |
0 |
cherrypy.response.status = HTTPStatus.CREATED.value |
1670 |
0 |
elif topic == "vnf_instances" and item: |
1671 |
0 |
indata["lcmOperationType"] = item |
1672 |
0 |
indata["vnfInstanceId"] = _id |
1673 |
0 |
_id, _ = self.engine.new_item( |
1674 |
|
rollback, engine_session, "vnflcmops", indata, kwargs |
1675 |
|
) |
1676 |
0 |
self._set_location_header( |
1677 |
|
main_topic, version, "vnf_lcm_op_occs", _id |
1678 |
|
) |
1679 |
0 |
outdata = {"id": _id} |
1680 |
0 |
cherrypy.response.status = HTTPStatus.ACCEPTED.value |
1681 |
0 |
elif topic == "ns_lcm_op_occs" and item == "cancel": |
1682 |
0 |
indata["nsLcmOpOccId"] = _id |
1683 |
0 |
self.engine.cancel_item( |
1684 |
|
rollback, engine_session, "nslcmops", indata, None |
1685 |
|
) |
1686 |
0 |
self._set_location_header(main_topic, version, topic, _id) |
1687 |
0 |
cherrypy.response.status = HTTPStatus.ACCEPTED.value |
1688 |
|
else: |
1689 |
0 |
_id, op_id = self.engine.new_item( |
1690 |
|
rollback, |
1691 |
|
engine_session, |
1692 |
|
engine_topic, |
1693 |
|
indata, |
1694 |
|
kwargs, |
1695 |
|
cherrypy.request.headers, |
1696 |
|
) |
1697 |
0 |
self._set_location_header(main_topic, version, topic, _id) |
1698 |
0 |
outdata = {"id": _id} |
1699 |
0 |
if op_id: |
1700 |
0 |
outdata["op_id"] = op_id |
1701 |
0 |
cherrypy.response.status = HTTPStatus.ACCEPTED.value |
1702 |
|
# TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages") |
1703 |
|
|
1704 |
0 |
elif method == "DELETE": |
1705 |
0 |
if not _id: |
1706 |
0 |
outdata = self.engine.del_item_list( |
1707 |
|
engine_session, engine_topic, kwargs |
1708 |
|
) |
1709 |
0 |
cherrypy.response.status = HTTPStatus.OK.value |
1710 |
|
else: # len(args) > 1 |
1711 |
|
# for NS NSI generate an operation |
1712 |
0 |
op_id = None |
1713 |
0 |
if topic == "ns_instances_content" and not engine_session["force"]: |
1714 |
0 |
nslcmop_desc = { |
1715 |
|
"lcmOperationType": "terminate", |
1716 |
|
"nsInstanceId": _id, |
1717 |
|
"autoremove": True, |
1718 |
|
} |
1719 |
0 |
op_id, _ = self.engine.new_item( |
1720 |
|
rollback, engine_session, "nslcmops", nslcmop_desc, kwargs |
1721 |
|
) |
1722 |
0 |
if op_id: |
1723 |
0 |
outdata = {"_id": op_id} |
1724 |
0 |
elif ( |
1725 |
|
topic == "netslice_instances_content" |
1726 |
|
and not engine_session["force"] |
1727 |
|
): |
1728 |
0 |
nsilcmop_desc = { |
1729 |
|
"lcmOperationType": "terminate", |
1730 |
|
"netsliceInstanceId": _id, |
1731 |
|
"autoremove": True, |
1732 |
|
} |
1733 |
0 |
op_id, _ = self.engine.new_item( |
1734 |
|
rollback, engine_session, "nsilcmops", nsilcmop_desc, None |
1735 |
|
) |
1736 |
0 |
if op_id: |
1737 |
0 |
outdata = {"_id": op_id} |
1738 |
|
# if there is not any deletion in process, delete |
1739 |
0 |
if not op_id: |
1740 |
0 |
op_id = self.engine.del_item(engine_session, engine_topic, _id) |
1741 |
0 |
if op_id: |
1742 |
0 |
outdata = {"op_id": op_id} |
1743 |
0 |
cherrypy.response.status = ( |
1744 |
|
HTTPStatus.ACCEPTED.value |
1745 |
|
if op_id |
1746 |
|
else HTTPStatus.NO_CONTENT.value |
1747 |
|
) |
1748 |
|
|
1749 |
0 |
elif method in ("PUT", "PATCH"): |
1750 |
0 |
op_id = None |
1751 |
0 |
if not indata and not kwargs and not engine_session.get("set_project"): |
1752 |
0 |
raise NbiException( |
1753 |
|
"Nothing to update. Provide payload and/or query string", |
1754 |
|
HTTPStatus.BAD_REQUEST, |
1755 |
|
) |
1756 |
0 |
if ( |
1757 |
|
item in ("nsd_content", "package_content", "nst_content") |
1758 |
|
and method == "PUT" |
1759 |
|
): |
1760 |
0 |
completed = self.engine.upload_content( |
1761 |
|
engine_session, |
1762 |
|
engine_topic, |
1763 |
|
_id, |
1764 |
|
indata, |
1765 |
|
kwargs, |
1766 |
|
cherrypy.request.headers, |
1767 |
|
) |
1768 |
0 |
if not completed: |
1769 |
0 |
cherrypy.response.headers["Transaction-Id"] = id |
1770 |
|
else: |
1771 |
0 |
op_id = self.engine.edit_item( |
1772 |
|
engine_session, engine_topic, _id, indata, kwargs |
1773 |
|
) |
1774 |
|
|
1775 |
0 |
if op_id: |
1776 |
0 |
cherrypy.response.status = HTTPStatus.ACCEPTED.value |
1777 |
0 |
outdata = {"op_id": op_id} |
1778 |
|
else: |
1779 |
0 |
cherrypy.response.status = HTTPStatus.NO_CONTENT.value |
1780 |
0 |
outdata = None |
1781 |
|
else: |
1782 |
0 |
raise NbiException( |
1783 |
|
"Method {} not allowed".format(method), |
1784 |
|
HTTPStatus.METHOD_NOT_ALLOWED, |
1785 |
|
) |
1786 |
|
|
1787 |
|
# if Role information changes, it is needed to reload the information of roles |
1788 |
0 |
if topic == "roles" and method != "GET": |
1789 |
0 |
self.authenticator.load_operation_to_allowed_roles() |
1790 |
|
|
1791 |
0 |
if ( |
1792 |
|
topic == "projects" |
1793 |
|
and method == "DELETE" |
1794 |
|
or topic in ["users", "roles"] |
1795 |
|
and method in ["PUT", "PATCH", "DELETE"] |
1796 |
|
): |
1797 |
0 |
self.authenticator.remove_token_from_cache() |
1798 |
|
|
1799 |
0 |
if item is not None: |
1800 |
0 |
cef_event( |
1801 |
|
cef_logger, |
1802 |
|
{ |
1803 |
|
"name": "User Operation", |
1804 |
|
"sourceUserName": token_info.get("username"), |
1805 |
|
"message": "Performing {} operation on {} {}, Project={} Outcome=Success".format( |
1806 |
|
item, |
1807 |
|
topic, |
1808 |
|
url_id, |
1809 |
|
token_info.get("project_name"), |
1810 |
|
), |
1811 |
|
}, |
1812 |
|
) |
1813 |
0 |
cherrypy.log("{}".format(cef_logger)) |
1814 |
|
else: |
1815 |
0 |
cef_event( |
1816 |
|
cef_logger, |
1817 |
|
{ |
1818 |
|
"name": "User Operation", |
1819 |
|
"sourceUserName": token_info.get("username"), |
1820 |
|
"message": "{} {} {}, Project={} Outcome=Success".format( |
1821 |
|
log_mapping[method], |
1822 |
|
topic, |
1823 |
|
url_id, |
1824 |
|
token_info.get("project_name"), |
1825 |
|
), |
1826 |
|
}, |
1827 |
|
) |
1828 |
0 |
cherrypy.log("{}".format(cef_logger)) |
1829 |
0 |
return self._format_out(outdata, token_info, _format) |
1830 |
0 |
except Exception as e: |
1831 |
0 |
if isinstance( |
1832 |
|
e, |
1833 |
|
( |
1834 |
|
NbiException, |
1835 |
|
EngineException, |
1836 |
|
DbException, |
1837 |
|
FsException, |
1838 |
|
MsgException, |
1839 |
|
AuthException, |
1840 |
|
ValidationError, |
1841 |
|
AuthconnException, |
1842 |
|
), |
1843 |
|
): |
1844 |
0 |
http_code_value = cherrypy.response.status = e.http_code.value |
1845 |
0 |
http_code_name = e.http_code.name |
1846 |
0 |
cherrypy.log("Exception {}".format(e)) |
1847 |
|
else: |
1848 |
0 |
http_code_value = ( |
1849 |
|
cherrypy.response.status |
1850 |
|
) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR |
1851 |
0 |
cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True) |
1852 |
0 |
http_code_name = HTTPStatus.BAD_REQUEST.name |
1853 |
0 |
if hasattr(outdata, "close"): # is an open file |
1854 |
0 |
outdata.close() |
1855 |
0 |
error_text = str(e) |
1856 |
0 |
rollback.reverse() |
1857 |
0 |
for rollback_item in rollback: |
1858 |
0 |
try: |
1859 |
0 |
if rollback_item.get("operation") == "set": |
1860 |
0 |
self.engine.db.set_one( |
1861 |
|
rollback_item["topic"], |
1862 |
|
{"_id": rollback_item["_id"]}, |
1863 |
|
rollback_item["content"], |
1864 |
|
fail_on_empty=False, |
1865 |
|
) |
1866 |
0 |
elif rollback_item.get("operation") == "del_list": |
1867 |
0 |
self.engine.db.del_list( |
1868 |
|
rollback_item["topic"], |
1869 |
|
rollback_item["filter"], |
1870 |
|
) |
1871 |
|
else: |
1872 |
0 |
self.engine.db.del_one( |
1873 |
|
rollback_item["topic"], |
1874 |
|
{"_id": rollback_item["_id"]}, |
1875 |
|
fail_on_empty=False, |
1876 |
|
) |
1877 |
0 |
except Exception as e2: |
1878 |
0 |
rollback_error_text = "Rollback Exception {}: {}".format( |
1879 |
|
rollback_item, e2 |
1880 |
|
) |
1881 |
0 |
cherrypy.log(rollback_error_text) |
1882 |
0 |
error_text += ". " + rollback_error_text |
1883 |
|
# if isinstance(e, MsgException): |
1884 |
|
# error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format( |
1885 |
|
# engine_topic[:-1], method, error_text) |
1886 |
0 |
problem_details = { |
1887 |
|
"code": http_code_name, |
1888 |
|
"status": http_code_value, |
1889 |
|
"detail": error_text, |
1890 |
|
} |
1891 |
0 |
if item is not None and token_info is not None: |
1892 |
0 |
cef_event( |
1893 |
|
cef_logger, |
1894 |
|
{ |
1895 |
|
"name": "User Operation", |
1896 |
|
"sourceUserName": token_info.get("username", None), |
1897 |
|
"message": "Performing {} operation on {} {}, Project={} Outcome=Failure".format( |
1898 |
|
item, |
1899 |
|
topic, |
1900 |
|
url_id, |
1901 |
|
token_info.get("project_name", None), |
1902 |
|
), |
1903 |
|
"severity": "2", |
1904 |
|
}, |
1905 |
|
) |
1906 |
0 |
cherrypy.log("{}".format(cef_logger)) |
1907 |
0 |
elif token_info is not None: |
1908 |
0 |
cef_event( |
1909 |
|
cef_logger, |
1910 |
|
{ |
1911 |
|
"name": "User Operation", |
1912 |
|
"sourceUserName": token_info.get("username", None), |
1913 |
|
"message": "{} {} {}, Project={} Outcome=Failure".format( |
1914 |
|
item, |
1915 |
|
topic, |
1916 |
|
url_id, |
1917 |
|
token_info.get("project_name", None), |
1918 |
|
), |
1919 |
|
"severity": "2", |
1920 |
|
}, |
1921 |
|
) |
1922 |
0 |
cherrypy.log("{}".format(cef_logger)) |
1923 |
0 |
return self._format_out(problem_details, token_info) |
1924 |
|
# raise cherrypy.HTTPError(e.http_code.value, str(e)) |
1925 |
|
finally: |
1926 |
0 |
if token_info: |
1927 |
0 |
self._format_login(token_info) |
1928 |
0 |
if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict): |
1929 |
0 |
for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"): |
1930 |
0 |
if outdata.get(logging_id): |
1931 |
0 |
cherrypy.request.login += ";{}={}".format( |
1932 |
|
logging_id, outdata[logging_id][:36] |
1933 |
|
) |
1934 |
|
|
1935 |
|
|
1936 |
0 |
def _start_service(): |
1937 |
|
""" |
1938 |
|
Callback function called when cherrypy.engine starts |
1939 |
|
Override configuration with env variables |
1940 |
|
Set database, storage, message configuration |
1941 |
|
Init database with admin/admin user password |
1942 |
|
""" |
1943 |
|
global nbi_server |
1944 |
|
global subscription_thread |
1945 |
|
global cef_logger |
1946 |
|
global current_backend |
1947 |
0 |
cherrypy.log.error("Starting osm_nbi") |
1948 |
|
# update general cherrypy configuration |
1949 |
0 |
update_dict = {} |
1950 |
|
|
1951 |
0 |
engine_config = cherrypy.tree.apps["/osm"].config |
1952 |
0 |
for k, v in environ.items(): |
1953 |
0 |
if k == "OSMNBI_USER_MANAGEMENT": |
1954 |
0 |
feature_state = eval(v.title()) |
1955 |
0 |
engine_config["authentication"]["user_management"] = feature_state |
1956 |
0 |
if not k.startswith("OSMNBI_"): |
1957 |
0 |
continue |
1958 |
0 |
k1, _, k2 = k[7:].lower().partition("_") |
1959 |
0 |
if not k2: |
1960 |
0 |
continue |
1961 |
0 |
try: |
1962 |
|
# update static configuration |
1963 |
0 |
if k == "OSMNBI_STATIC_DIR": |
1964 |
0 |
engine_config["/static"]["tools.staticdir.dir"] = v |
1965 |
0 |
engine_config["/static"]["tools.staticdir.on"] = True |
1966 |
0 |
elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT": |
1967 |
0 |
update_dict["server.socket_port"] = int(v) |
1968 |
0 |
elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST": |
1969 |
0 |
update_dict["server.socket_host"] = v |
1970 |
0 |
elif k1 in ("server", "test", "auth", "log"): |
1971 |
0 |
update_dict[k1 + "." + k2] = v |
1972 |
0 |
elif k1 in ("message", "database", "storage", "authentication"): |
1973 |
|
# k2 = k2.replace('_', '.') |
1974 |
0 |
if k2 in ("port", "db_port"): |
1975 |
0 |
engine_config[k1][k2] = int(v) |
1976 |
|
else: |
1977 |
0 |
engine_config[k1][k2] = v |
1978 |
|
|
1979 |
0 |
except ValueError as e: |
1980 |
0 |
cherrypy.log.error("Ignoring environ '{}': " + str(e)) |
1981 |
0 |
except Exception as e: |
1982 |
0 |
cherrypy.log( |
1983 |
|
"WARNING: skipping environ '{}' on exception '{}'".format(k, e) |
1984 |
|
) |
1985 |
|
|
1986 |
0 |
if update_dict: |
1987 |
0 |
cherrypy.config.update(update_dict) |
1988 |
0 |
engine_config["global"].update(update_dict) |
1989 |
|
|
1990 |
|
# logging cherrypy |
1991 |
0 |
log_format_simple = ( |
1992 |
|
"%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s" |
1993 |
|
) |
1994 |
0 |
log_formatter_simple = logging.Formatter( |
1995 |
|
log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S" |
1996 |
|
) |
1997 |
0 |
logger_server = logging.getLogger("cherrypy.error") |
1998 |
0 |
logger_access = logging.getLogger("cherrypy.access") |
1999 |
0 |
logger_cherry = logging.getLogger("cherrypy") |
2000 |
0 |
logger_nbi = logging.getLogger("nbi") |
2001 |
|
|
2002 |
0 |
if "log.file" in engine_config["global"]: |
2003 |
0 |
file_handler = logging.handlers.RotatingFileHandler( |
2004 |
|
engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0 |
2005 |
|
) |
2006 |
0 |
file_handler.setFormatter(log_formatter_simple) |
2007 |
0 |
logger_cherry.addHandler(file_handler) |
2008 |
0 |
logger_nbi.addHandler(file_handler) |
2009 |
|
# log always to standard output |
2010 |
0 |
for format_, logger in { |
2011 |
|
"nbi.server %(filename)s:%(lineno)s": logger_server, |
2012 |
|
"nbi.access %(filename)s:%(lineno)s": logger_access, |
2013 |
|
"%(name)s %(filename)s:%(lineno)s": logger_nbi, |
2014 |
|
}.items(): |
2015 |
0 |
log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_) |
2016 |
0 |
log_formatter_cherry = logging.Formatter( |
2017 |
|
log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S" |
2018 |
|
) |
2019 |
0 |
str_handler = logging.StreamHandler() |
2020 |
0 |
str_handler.setFormatter(log_formatter_cherry) |
2021 |
0 |
logger.addHandler(str_handler) |
2022 |
|
|
2023 |
0 |
if engine_config["global"].get("log.level"): |
2024 |
0 |
logger_cherry.setLevel(engine_config["global"]["log.level"]) |
2025 |
0 |
logger_nbi.setLevel(engine_config["global"]["log.level"]) |
2026 |
|
|
2027 |
|
# logging other modules |
2028 |
0 |
for k1, logname in { |
2029 |
|
"message": "nbi.msg", |
2030 |
|
"database": "nbi.db", |
2031 |
|
"storage": "nbi.fs", |
2032 |
|
}.items(): |
2033 |
0 |
engine_config[k1]["logger_name"] = logname |
2034 |
0 |
logger_module = logging.getLogger(logname) |
2035 |
0 |
if "logfile" in engine_config[k1]: |
2036 |
0 |
file_handler = logging.handlers.RotatingFileHandler( |
2037 |
|
engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0 |
2038 |
|
) |
2039 |
0 |
file_handler.setFormatter(log_formatter_simple) |
2040 |
0 |
logger_module.addHandler(file_handler) |
2041 |
0 |
if "loglevel" in engine_config[k1]: |
2042 |
0 |
logger_module.setLevel(engine_config[k1]["loglevel"]) |
2043 |
|
# TODO add more entries, e.g.: storage |
2044 |
0 |
cherrypy.tree.apps["/osm"].root.engine.start(engine_config) |
2045 |
0 |
cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config) |
2046 |
0 |
cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version) |
2047 |
0 |
cherrypy.tree.apps["/osm"].root.authenticator.init_db( |
2048 |
|
target_version=auth_database_version |
2049 |
|
) |
2050 |
|
|
2051 |
0 |
cef_logger = cef_event_builder(engine_config["authentication"]) |
2052 |
|
|
2053 |
|
# start subscriptions thread: |
2054 |
0 |
subscription_thread = SubscriptionThread( |
2055 |
|
config=engine_config, engine=nbi_server.engine |
2056 |
|
) |
2057 |
0 |
subscription_thread.start() |
2058 |
|
# Do not capture except SubscriptionException |
2059 |
|
|
2060 |
0 |
backend = engine_config["authentication"]["backend"] |
2061 |
0 |
current_backend = backend |
2062 |
0 |
cherrypy.log.error( |
2063 |
|
"Starting OSM NBI Version '{} {}' with '{}' authentication backend".format( |
2064 |
|
nbi_version, nbi_version_date, backend |
2065 |
|
) |
2066 |
|
) |
2067 |
|
|
2068 |
|
|
2069 |
0 |
def _stop_service(): |
2070 |
|
""" |
2071 |
|
Callback function called when cherrypy.engine stops |
2072 |
|
TODO: Ending database connections. |
2073 |
|
""" |
2074 |
|
global subscription_thread |
2075 |
0 |
if subscription_thread: |
2076 |
0 |
subscription_thread.terminate() |
2077 |
0 |
subscription_thread = None |
2078 |
0 |
cherrypy.tree.apps["/osm"].root.engine.stop() |
2079 |
0 |
cherrypy.log.error("Stopping osm_nbi") |
2080 |
|
|
2081 |
|
|
2082 |
0 |
def nbi(config_file): |
2083 |
|
global nbi_server |
2084 |
|
# conf = { |
2085 |
|
# '/': { |
2086 |
|
# #'request.dispatch': cherrypy.dispatch.MethodDispatcher(), |
2087 |
|
# 'tools.sessions.on': True, |
2088 |
|
# 'tools.response_headers.on': True, |
2089 |
|
# # 'tools.response_headers.headers': [('Content-Type', 'text/plain')], |
2090 |
|
# } |
2091 |
|
# } |
2092 |
|
# cherrypy.Server.ssl_module = 'builtin' |
2093 |
|
# cherrypy.Server.ssl_certificate = "http/cert.pem" |
2094 |
|
# cherrypy.Server.ssl_private_key = "http/privkey.pem" |
2095 |
|
# cherrypy.Server.thread_pool = 10 |
2096 |
|
# cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]}) |
2097 |
|
|
2098 |
|
# cherrypy.config.update({'tools.auth_basic.on': True, |
2099 |
|
# 'tools.auth_basic.realm': 'localhost', |
2100 |
|
# 'tools.auth_basic.checkpassword': validate_password}) |
2101 |
0 |
nbi_server = Server() |
2102 |
0 |
cherrypy.engine.subscribe("start", _start_service) |
2103 |
0 |
cherrypy.engine.subscribe("stop", _stop_service) |
2104 |
0 |
cherrypy.quickstart(nbi_server, "/osm", config_file) |
2105 |
|
|
2106 |
|
|
2107 |
0 |
def usage(): |
2108 |
0 |
print( |
2109 |
|
"""Usage: {} [options] |
2110 |
|
-c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg) |
2111 |
|
-h|--help: shows this help |
2112 |
|
""".format( |
2113 |
|
sys.argv[0] |
2114 |
|
) |
2115 |
|
) |
2116 |
|
# --log-socket-host HOST: send logs to this host") |
2117 |
|
# --log-socket-port PORT: send logs using this port (default: 9022)") |
2118 |
|
|
2119 |
|
|
2120 |
0 |
if __name__ == "__main__": |
2121 |
0 |
try: |
2122 |
|
# load parameters and configuration |
2123 |
0 |
opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"]) |
2124 |
|
# TODO add "log-socket-host=", "log-socket-port=", "log-file=" |
2125 |
0 |
config_file = None |
2126 |
0 |
for o, a in opts: |
2127 |
0 |
if o in ("-h", "--help"): |
2128 |
0 |
usage() |
2129 |
0 |
sys.exit() |
2130 |
0 |
elif o in ("-c", "--config"): |
2131 |
0 |
config_file = a |
2132 |
|
# elif o == "--log-socket-port": |
2133 |
|
# log_socket_port = a |
2134 |
|
# elif o == "--log-socket-host": |
2135 |
|
# log_socket_host = a |
2136 |
|
# elif o == "--log-file": |
2137 |
|
# log_file = a |
2138 |
|
else: |
2139 |
0 |
assert False, "Unhandled option" |
2140 |
0 |
if config_file: |
2141 |
0 |
if not path.isfile(config_file): |
2142 |
0 |
print( |
2143 |
|
"configuration file '{}' that not exist".format(config_file), |
2144 |
|
file=sys.stderr, |
2145 |
|
) |
2146 |
0 |
exit(1) |
2147 |
|
else: |
2148 |
0 |
for config_file in ( |
2149 |
|
__file__[: __file__.rfind(".")] + ".cfg", |
2150 |
|
"./nbi.cfg", |
2151 |
|
"/etc/osm/nbi.cfg", |
2152 |
|
): |
2153 |
0 |
if path.isfile(config_file): |
2154 |
0 |
break |
2155 |
|
else: |
2156 |
0 |
print( |
2157 |
|
"No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", |
2158 |
|
file=sys.stderr, |
2159 |
|
) |
2160 |
0 |
exit(1) |
2161 |
0 |
nbi(config_file) |
2162 |
0 |
except getopt.GetoptError as e: |
2163 |
0 |
print(str(e), file=sys.stderr) |
2164 |
|
# usage() |
2165 |
0 |
exit(1) |