blob: d5a81ce46f8b8993bdec1daaf92bfda96f43c21d [file] [log] [blame]
tiernoc94c3df2018-02-09 15:38:54 +01001#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
tiernod125caf2018-11-22 16:05:54 +00004# 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
tiernoc94c3df2018-02-09 15:38:54 +010017import cherrypy
18import time
19import json
20import yaml
tierno9c630112019-08-29 14:21:41 +000021import osm_nbi.html_out as html
tiernoc94c3df2018-02-09 15:38:54 +010022import logging
tiernof5298be2018-05-16 14:43:57 +020023import logging.handlers
24import getopt
25import sys
Eduardo Sousa2f988212018-07-26 01:04:11 +010026
tierno9c630112019-08-29 14:21:41 +000027from osm_nbi.authconn import AuthException, AuthconnException
28from osm_nbi.auth import Authenticator
29from osm_nbi.engine import Engine, EngineException
30from osm_nbi.subscriptions import SubscriptionThread
31from osm_nbi.validation import ValidationError
tiernoa8d63632018-05-10 13:12:32 +020032from osm_common.dbbase import DbException
33from osm_common.fsbase import FsException
34from osm_common.msgbase import MsgException
tiernoc94c3df2018-02-09 15:38:54 +010035from http import HTTPStatus
tiernoc94c3df2018-02-09 15:38:54 +010036from codecs import getreader
tiernof5298be2018-05-16 14:43:57 +020037from os import environ, path
tiernob2e48bd2020-02-04 15:47:18 +000038from osm_nbi import version as nbi_version, version_date as nbi_version_date
tiernoc94c3df2018-02-09 15:38:54 +010039
40__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
tiernodfe09572018-04-24 10:41:10 +020041
garciadeblas4568a372021-03-24 09:19:48 +010042__version__ = "0.1.3" # file version, not NBI version
tierno9c630112019-08-29 14:21:41 +000043version_date = "Aug 2019"
44
garciadeblas4568a372021-03-24 09:19:48 +010045database_version = "1.2"
46auth_database_version = "1.0"
47nbi_server = None # instance of Server class
tierno932499c2019-01-28 17:28:10 +000048subscription_thread = None # instance of SubscriptionThread class
tiernoc94c3df2018-02-09 15:38:54 +010049
50"""
tiernof27c79b2018-03-12 17:08:42 +010051North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
tiernoc94c3df2018-02-09 15:38:54 +010052URL: /osm GET POST PUT DELETE PATCH
garciadeblas9750c5a2018-10-15 16:20:35 +020053 /nsd/v1
tierno2236d202018-05-16 19:05:16 +020054 /ns_descriptors_content O O
55 /<nsdInfoId> O O O O
tiernoc94c3df2018-02-09 15:38:54 +010056 /ns_descriptors O5 O5
57 /<nsdInfoId> O5 O5 5
58 /nsd_content O5 O5
tiernof27c79b2018-03-12 17:08:42 +010059 /nsd O
60 /artifacts[/<artifactPath>] O
tiernoc94c3df2018-02-09 15:38:54 +010061 /pnf_descriptors 5 5
62 /<pnfdInfoId> 5 5 5
63 /pnfd_content 5 5
tiernof27c79b2018-03-12 17:08:42 +010064 /subscriptions 5 5
65 /<subscriptionId> 5 X
tiernoc94c3df2018-02-09 15:38:54 +010066
67 /vnfpkgm/v1
tierno55945e72018-04-06 16:40:27 +020068 /vnf_packages_content O O
tierno2236d202018-05-16 19:05:16 +020069 /<vnfPkgId> O O
tiernoc94c3df2018-02-09 15:38:54 +010070 /vnf_packages O5 O5
71 /<vnfPkgId> O5 O5 5
tiernoc94c3df2018-02-09 15:38:54 +010072 /package_content O5 O5
73 /upload_from_uri X
tiernof27c79b2018-03-12 17:08:42 +010074 /vnfd O5
75 /artifacts[/<artifactPath>] O5
76 /subscriptions X X
77 /<subscriptionId> X X
tiernoc94c3df2018-02-09 15:38:54 +010078
79 /nslcm/v1
tiernof27c79b2018-03-12 17:08:42 +010080 /ns_instances_content O O
tierno2236d202018-05-16 19:05:16 +020081 /<nsInstanceId> O O
tiernof27c79b2018-03-12 17:08:42 +010082 /ns_instances 5 5
tierno95692442018-05-24 18:05:28 +020083 /<nsInstanceId> O5 O5
tierno65acb4d2018-04-06 16:42:40 +020084 instantiate O5
85 terminate O5
86 action O
87 scale O5
88 heal 5
tiernoc94c3df2018-02-09 15:38:54 +010089 /ns_lcm_op_occs 5 5
90 /<nsLcmOpOccId> 5 5 5
91 TO BE COMPLETED 5 5
tiernof759d822018-06-11 18:54:54 +020092 /vnf_instances (also vnfrs for compatibility) O
93 /<vnfInstanceId> O
tiernof27c79b2018-03-12 17:08:42 +010094 /subscriptions 5 5
95 /<subscriptionId> 5 X
garciadeblas9750c5a2018-10-15 16:20:35 +020096
tiernocb83c942018-09-24 17:28:13 +020097 /pdu/v1
tierno032916c2019-03-22 13:27:12 +000098 /pdu_descriptors O O
tiernocb83c942018-09-24 17:28:13 +020099 /<id> O O O O
garciadeblas9750c5a2018-10-15 16:20:35 +0200100
tiernof27c79b2018-03-12 17:08:42 +0100101 /admin/v1
102 /tokens O O
tierno2236d202018-05-16 19:05:16 +0200103 /<id> O O
tiernof27c79b2018-03-12 17:08:42 +0100104 /users O O
tiernocd54a4a2018-09-12 16:40:35 +0200105 /<id> O O O O
tiernof27c79b2018-03-12 17:08:42 +0100106 /projects O O
tierno2236d202018-05-16 19:05:16 +0200107 /<id> O O
tierno55ba2e62018-12-11 17:22:22 +0000108 /vim_accounts (also vims for compatibility) O O
109 /<id> O O O
110 /wim_accounts O O
tierno2236d202018-05-16 19:05:16 +0200111 /<id> O O O
tierno0f98af52018-03-19 10:28:22 +0100112 /sdns O O
tierno2236d202018-05-16 19:05:16 +0200113 /<id> O O O
delacruzramofe598fe2019-10-23 18:25:11 +0200114 /k8sclusters O O
115 /<id> O O O
116 /k8srepos O O
117 /<id> O O
Felipe Vicensb66b0412020-05-06 10:11:00 +0200118 /osmrepos O O
119 /<id> O O
tiernoc94c3df2018-02-09 15:38:54 +0100120
garciadeblas9750c5a2018-10-15 16:20:35 +0200121 /nst/v1 O O
122 /netslice_templates_content O O
123 /<nstInfoId> O O O O
124 /netslice_templates O O
125 /<nstInfoId> O O O
126 /nst_content O O
127 /nst O
128 /artifacts[/<artifactPath>] O
129 /subscriptions X X
130 /<subscriptionId> X X
131
132 /nsilcm/v1
133 /netslice_instances_content O O
134 /<SliceInstanceId> O O
135 /netslice_instances O O
136 /<SliceInstanceId> O O
137 instantiate O
138 terminate O
139 action O
140 /nsi_lcm_op_occs O O
141 /<nsiLcmOpOccId> O O O
142 /subscriptions X X
143 /<subscriptionId> X X
144
tierno2236d202018-05-16 19:05:16 +0200145query string:
146 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force.
tierno36ec8602018-11-02 17:27:11 +0100147 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]*
148 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]*
149 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont"
150 attrName := string
tierno2236d202018-05-16 19:05:16 +0200151 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any
152 item of the array, that is, pass if any item of the array pass the filter.
153 It allows both ne and neq for not equal
154 TODO: 4.3.3 Attribute selectors
155 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,...
tiernoc94c3df2018-02-09 15:38:54 +0100156 (none) … same as “exclude_default”
157 all_fields … all attributes.
tierno2236d202018-05-16 19:05:16 +0200158 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not
159 conditionally mandatory, and that are not provided in <list>.
160 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that
161 are not conditionally mandatory, and that are provided in <list>.
162 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not
163 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for
164 the particular resource
165 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality
166 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the
167 present specification for the particular resource, but that are not part of <list>
tierno65ca36d2019-02-12 19:27:52 +0100168 Additionally it admits some administrator values:
169 FORCE: To force operations skipping dependency checkings
170 ADMIN: To act as an administrator or a different project
171 PUBLIC: To get public descriptors or set a descriptor as public
172 SET_PROJECT: To make a descriptor available for other project
173
tiernoc94c3df2018-02-09 15:38:54 +0100174Header field name Reference Example Descriptions
175 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response.
176 This header field shall be present if the response is expected to have a non-empty message body.
177 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request.
178 This header field shall be present if the request has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200179 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request.
180 Details are specified in clause 4.5.3.
tiernoc94c3df2018-02-09 15:38:54 +0100181 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file
182Header field name Reference Example Descriptions
183 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response.
184 This header field shall be present if the response has a non-empty message body.
tierno2236d202018-05-16 19:05:16 +0200185 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a
186 new resource has been created.
tiernoc94c3df2018-02-09 15:38:54 +0100187 This header field shall be present if the response status code is 201 or 3xx.
tierno2236d202018-05-16 19:05:16 +0200188 In the present document this header field is also used if the response status code is 202 and a new resource was
189 created.
190 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not
191 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization
192 token.
193 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for
194 certain resources.
195 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the
196 response, and the total length of the file.
tiernoc94c3df2018-02-09 15:38:54 +0100197 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT
tiernoc94c3df2018-02-09 15:38:54 +0100198"""
199
tierno701018c2019-06-25 11:13:14 +0000200valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC")
201# ^ Contains possible administrative query string words:
202# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project
203# (not owned by my session project).
204# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public
205# FORCE=True(by default)|False: Force edition/deletion operations
206# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio
207
208valid_url_methods = {
209 # contains allowed URL and methods, and the role_permission name
210 "admin": {
211 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100212 "tokens": {
213 "METHODS": ("GET", "POST", "DELETE"),
214 "ROLE_PERMISSION": "tokens:",
215 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"},
216 },
217 "users": {
218 "METHODS": ("GET", "POST"),
219 "ROLE_PERMISSION": "users:",
220 "<ID>": {
221 "METHODS": ("GET", "DELETE", "PATCH"),
222 "ROLE_PERMISSION": "users:id:",
223 },
224 },
225 "projects": {
226 "METHODS": ("GET", "POST"),
227 "ROLE_PERMISSION": "projects:",
228 "<ID>": {
229 "METHODS": ("GET", "DELETE", "PATCH"),
230 "ROLE_PERMISSION": "projects:id:",
231 },
232 },
233 "roles": {
234 "METHODS": ("GET", "POST"),
235 "ROLE_PERMISSION": "roles:",
236 "<ID>": {
237 "METHODS": ("GET", "DELETE", "PATCH"),
238 "ROLE_PERMISSION": "roles:id:",
239 },
240 },
241 "vims": {
242 "METHODS": ("GET", "POST"),
243 "ROLE_PERMISSION": "vims:",
244 "<ID>": {
245 "METHODS": ("GET", "DELETE", "PATCH"),
246 "ROLE_PERMISSION": "vims:id:",
247 },
248 },
249 "vim_accounts": {
250 "METHODS": ("GET", "POST"),
251 "ROLE_PERMISSION": "vim_accounts:",
252 "<ID>": {
253 "METHODS": ("GET", "DELETE", "PATCH"),
254 "ROLE_PERMISSION": "vim_accounts:id:",
255 },
256 },
257 "wim_accounts": {
258 "METHODS": ("GET", "POST"),
259 "ROLE_PERMISSION": "wim_accounts:",
260 "<ID>": {
261 "METHODS": ("GET", "DELETE", "PATCH"),
262 "ROLE_PERMISSION": "wim_accounts:id:",
263 },
264 },
265 "sdns": {
266 "METHODS": ("GET", "POST"),
267 "ROLE_PERMISSION": "sdn_controllers:",
268 "<ID>": {
269 "METHODS": ("GET", "DELETE", "PATCH"),
270 "ROLE_PERMISSION": "sdn_controllers:id:",
271 },
272 },
273 "k8sclusters": {
274 "METHODS": ("GET", "POST"),
275 "ROLE_PERMISSION": "k8sclusters:",
276 "<ID>": {
277 "METHODS": ("GET", "DELETE", "PATCH"),
278 "ROLE_PERMISSION": "k8sclusters:id:",
279 },
280 },
281 "vca": {
282 "METHODS": ("GET", "POST"),
283 "ROLE_PERMISSION": "vca:",
284 "<ID>": {
285 "METHODS": ("GET", "DELETE", "PATCH"),
286 "ROLE_PERMISSION": "vca:id:",
287 },
288 },
289 "k8srepos": {
290 "METHODS": ("GET", "POST"),
291 "ROLE_PERMISSION": "k8srepos:",
292 "<ID>": {
293 "METHODS": ("GET", "DELETE"),
294 "ROLE_PERMISSION": "k8srepos:id:",
295 },
296 },
297 "osmrepos": {
298 "METHODS": ("GET", "POST"),
299 "ROLE_PERMISSION": "osmrepos:",
300 "<ID>": {
301 "METHODS": ("GET", "DELETE", "PATCH"),
302 "ROLE_PERMISSION": "osmrepos:id:",
303 },
304 },
305 "domains": {
306 "METHODS": ("GET",),
307 "ROLE_PERMISSION": "domains:",
308 },
tierno701018c2019-06-25 11:13:14 +0000309 }
310 },
311 "pdu": {
312 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100313 "pdu_descriptors": {
314 "METHODS": ("GET", "POST"),
315 "ROLE_PERMISSION": "pduds:",
316 "<ID>": {
317 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"),
318 "ROLE_PERMISSION": "pduds:id:",
319 },
320 },
tierno701018c2019-06-25 11:13:14 +0000321 }
322 },
323 "nsd": {
324 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100325 "ns_descriptors_content": {
326 "METHODS": ("GET", "POST"),
327 "ROLE_PERMISSION": "nsds:",
328 "<ID>": {
329 "METHODS": ("GET", "PUT", "DELETE"),
330 "ROLE_PERMISSION": "nsds:id:",
331 },
332 },
333 "ns_descriptors": {
334 "METHODS": ("GET", "POST"),
335 "ROLE_PERMISSION": "nsds:",
336 "<ID>": {
337 "METHODS": ("GET", "DELETE", "PATCH"),
338 "ROLE_PERMISSION": "nsds:id:",
339 "nsd_content": {
340 "METHODS": ("GET", "PUT"),
341 "ROLE_PERMISSION": "nsds:id:content:",
342 },
343 "nsd": {
344 "METHODS": ("GET",), # descriptor inside package
345 "ROLE_PERMISSION": "nsds:id:content:",
346 },
347 "artifacts": {
348 "METHODS": ("GET",),
349 "ROLE_PERMISSION": "nsds:id:nsd_artifact:",
350 "*": None,
351 },
352 },
353 },
354 "pnf_descriptors": {
355 "TODO": ("GET", "POST"),
356 "<ID>": {
357 "TODO": ("GET", "DELETE", "PATCH"),
358 "pnfd_content": {"TODO": ("GET", "PUT")},
359 },
360 },
361 "subscriptions": {
362 "TODO": ("GET", "POST"),
363 "<ID>": {"TODO": ("GET", "DELETE")},
364 },
tierno701018c2019-06-25 11:13:14 +0000365 }
366 },
367 "vnfpkgm": {
368 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100369 "vnf_packages_content": {
370 "METHODS": ("GET", "POST"),
371 "ROLE_PERMISSION": "vnfds:",
372 "<ID>": {
373 "METHODS": ("GET", "PUT", "DELETE"),
374 "ROLE_PERMISSION": "vnfds:id:",
375 },
376 },
377 "vnf_packages": {
378 "METHODS": ("GET", "POST"),
379 "ROLE_PERMISSION": "vnfds:",
380 "<ID>": {
381 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo
382 "ROLE_PERMISSION": "vnfds:id:",
383 "package_content": {
384 "METHODS": ("GET", "PUT"), # package
385 "ROLE_PERMISSION": "vnfds:id:",
386 "upload_from_uri": {
387 "METHODS": (),
388 "TODO": ("POST",),
389 "ROLE_PERMISSION": "vnfds:id:upload:",
390 },
391 },
392 "vnfd": {
393 "METHODS": ("GET",), # descriptor inside package
394 "ROLE_PERMISSION": "vnfds:id:content:",
395 },
396 "artifacts": {
397 "METHODS": ("GET",),
398 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:",
399 "*": None,
400 },
401 "action": {
402 "METHODS": ("POST",),
403 "ROLE_PERMISSION": "vnfds:id:action:",
404 },
405 },
406 },
407 "subscriptions": {
408 "TODO": ("GET", "POST"),
409 "<ID>": {"TODO": ("GET", "DELETE")},
410 },
411 "vnfpkg_op_occs": {
412 "METHODS": ("GET",),
413 "ROLE_PERMISSION": "vnfds:vnfpkgops:",
414 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"},
415 },
tierno701018c2019-06-25 11:13:14 +0000416 }
417 },
418 "nslcm": {
419 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100420 "ns_instances_content": {
421 "METHODS": ("GET", "POST"),
422 "ROLE_PERMISSION": "ns_instances:",
423 "<ID>": {
424 "METHODS": ("GET", "DELETE"),
425 "ROLE_PERMISSION": "ns_instances:id:",
426 },
427 },
428 "ns_instances": {
429 "METHODS": ("GET", "POST"),
430 "ROLE_PERMISSION": "ns_instances:",
431 "<ID>": {
432 "METHODS": ("GET", "DELETE"),
433 "ROLE_PERMISSION": "ns_instances:id:",
434 "scale": {
435 "METHODS": ("POST",),
436 "ROLE_PERMISSION": "ns_instances:id:scale:",
437 },
438 "terminate": {
439 "METHODS": ("POST",),
440 "ROLE_PERMISSION": "ns_instances:id:terminate:",
441 },
442 "instantiate": {
443 "METHODS": ("POST",),
444 "ROLE_PERMISSION": "ns_instances:id:instantiate:",
445 },
446 "action": {
447 "METHODS": ("POST",),
448 "ROLE_PERMISSION": "ns_instances:id:action:",
449 },
450 },
451 },
452 "ns_lcm_op_occs": {
453 "METHODS": ("GET",),
454 "ROLE_PERMISSION": "ns_instances:opps:",
455 "<ID>": {
456 "METHODS": ("GET",),
457 "ROLE_PERMISSION": "ns_instances:opps:id:",
458 },
459 },
460 "vnfrs": {
461 "METHODS": ("GET",),
462 "ROLE_PERMISSION": "vnf_instances:",
463 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
464 },
465 "vnf_instances": {
466 "METHODS": ("GET",),
467 "ROLE_PERMISSION": "vnf_instances:",
468 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"},
469 },
470 "subscriptions": {
471 "METHODS": ("GET", "POST"),
472 "ROLE_PERMISSION": "ns_subscriptions:",
473 "<ID>": {
474 "METHODS": ("GET", "DELETE"),
475 "ROLE_PERMISSION": "ns_subscriptions:id:",
476 },
477 },
tierno701018c2019-06-25 11:13:14 +0000478 }
479 },
480 "nst": {
481 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100482 "netslice_templates_content": {
483 "METHODS": ("GET", "POST"),
484 "ROLE_PERMISSION": "slice_templates:",
485 "<ID>": {
486 "METHODS": ("GET", "PUT", "DELETE"),
487 "ROLE_PERMISSION": "slice_templates:id:",
488 },
489 },
490 "netslice_templates": {
491 "METHODS": ("GET", "POST"),
492 "ROLE_PERMISSION": "slice_templates:",
493 "<ID>": {
494 "METHODS": ("GET", "DELETE"),
495 "TODO": ("PATCH",),
496 "ROLE_PERMISSION": "slice_templates:id:",
497 "nst_content": {
498 "METHODS": ("GET", "PUT"),
499 "ROLE_PERMISSION": "slice_templates:id:content:",
500 },
501 "nst": {
502 "METHODS": ("GET",), # descriptor inside package
503 "ROLE_PERMISSION": "slice_templates:id:content:",
504 },
505 "artifacts": {
506 "METHODS": ("GET",),
507 "ROLE_PERMISSION": "slice_templates:id:content:",
508 "*": None,
509 },
510 },
511 },
512 "subscriptions": {
513 "TODO": ("GET", "POST"),
514 "<ID>": {"TODO": ("GET", "DELETE")},
515 },
tierno701018c2019-06-25 11:13:14 +0000516 }
517 },
518 "nsilcm": {
519 "v1": {
garciadeblas4568a372021-03-24 09:19:48 +0100520 "netslice_instances_content": {
521 "METHODS": ("GET", "POST"),
522 "ROLE_PERMISSION": "slice_instances:",
523 "<ID>": {
524 "METHODS": ("GET", "DELETE"),
525 "ROLE_PERMISSION": "slice_instances:id:",
526 },
527 },
528 "netslice_instances": {
529 "METHODS": ("GET", "POST"),
530 "ROLE_PERMISSION": "slice_instances:",
531 "<ID>": {
532 "METHODS": ("GET", "DELETE"),
533 "ROLE_PERMISSION": "slice_instances:id:",
534 "terminate": {
535 "METHODS": ("POST",),
536 "ROLE_PERMISSION": "slice_instances:id:terminate:",
537 },
538 "instantiate": {
539 "METHODS": ("POST",),
540 "ROLE_PERMISSION": "slice_instances:id:instantiate:",
541 },
542 "action": {
543 "METHODS": ("POST",),
544 "ROLE_PERMISSION": "slice_instances:id:action:",
545 },
546 },
547 },
548 "nsi_lcm_op_occs": {
549 "METHODS": ("GET",),
550 "ROLE_PERMISSION": "slice_instances:opps:",
551 "<ID>": {
552 "METHODS": ("GET",),
553 "ROLE_PERMISSION": "slice_instances:opps:id:",
554 },
555 },
tierno701018c2019-06-25 11:13:14 +0000556 }
557 },
558 "nspm": {
559 "v1": {
560 "pm_jobs": {
561 "<ID>": {
562 "reports": {
garciadeblas4568a372021-03-24 09:19:48 +0100563 "<ID>": {
564 "METHODS": ("GET",),
565 "ROLE_PERMISSION": "reports:id:",
566 }
tierno701018c2019-06-25 11:13:14 +0000567 }
568 },
569 },
570 },
571 },
572}
573
tiernoc94c3df2018-02-09 15:38:54 +0100574
575class NbiException(Exception):
tiernoc94c3df2018-02-09 15:38:54 +0100576 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED):
577 Exception.__init__(self, message)
578 self.http_code = http_code
579
580
581class Server(object):
582 instance = 0
583 # to decode bytes to str
584 reader = getreader("utf-8")
585
586 def __init__(self):
587 self.instance += 1
tierno701018c2019-06-25 11:13:14 +0000588 self.authenticator = Authenticator(valid_url_methods, valid_query_string)
delacruzramoad682a52019-12-10 16:26:34 +0100589 self.engine = Engine(self.authenticator)
tiernoc94c3df2018-02-09 15:38:54 +0100590
tiernoc94c3df2018-02-09 15:38:54 +0100591 def _format_in(self, kwargs):
592 try:
593 indata = None
594 if cherrypy.request.body.length:
595 error_text = "Invalid input format "
596
597 if "Content-Type" in cherrypy.request.headers:
598 if "application/json" in cherrypy.request.headers["Content-Type"]:
599 error_text = "Invalid json format "
600 indata = json.load(self.reader(cherrypy.request.body))
gcalvinode4adfe2018-10-30 11:46:09 +0100601 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100602 elif "application/yaml" in cherrypy.request.headers["Content-Type"]:
603 error_text = "Invalid yaml format "
garciadeblas4568a372021-03-24 09:19:48 +0100604 indata = yaml.load(
605 cherrypy.request.body, Loader=yaml.SafeLoader
606 )
gcalvinode4adfe2018-10-30 11:46:09 +0100607 cherrypy.request.headers.pop("Content-File-MD5", None)
garciadeblas4568a372021-03-24 09:19:48 +0100608 elif (
609 "application/binary" in cherrypy.request.headers["Content-Type"]
610 or "application/gzip"
611 in cherrypy.request.headers["Content-Type"]
612 or "application/zip" in cherrypy.request.headers["Content-Type"]
613 or "text/plain" in cherrypy.request.headers["Content-Type"]
614 ):
tiernof27c79b2018-03-12 17:08:42 +0100615 indata = cherrypy.request.body # .read()
garciadeblas4568a372021-03-24 09:19:48 +0100616 elif (
617 "multipart/form-data"
618 in cherrypy.request.headers["Content-Type"]
619 ):
tiernoc94c3df2018-02-09 15:38:54 +0100620 if "descriptor_file" in kwargs:
621 filecontent = kwargs.pop("descriptor_file")
622 if not filecontent.file:
garciadeblas4568a372021-03-24 09:19:48 +0100623 raise NbiException(
624 "empty file or content", HTTPStatus.BAD_REQUEST
625 )
tiernof27c79b2018-03-12 17:08:42 +0100626 indata = filecontent.file # .read()
tiernoc94c3df2018-02-09 15:38:54 +0100627 if filecontent.content_type.value:
garciadeblas4568a372021-03-24 09:19:48 +0100628 cherrypy.request.headers[
629 "Content-Type"
630 ] = filecontent.content_type.value
tiernoc94c3df2018-02-09 15:38:54 +0100631 else:
632 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable,
633 # "Only 'Content-Type' of type 'application/json' or
634 # 'application/yaml' for input format are available")
635 error_text = "Invalid yaml format "
garciadeblas4568a372021-03-24 09:19:48 +0100636 indata = yaml.load(
637 cherrypy.request.body, Loader=yaml.SafeLoader
638 )
gcalvinode4adfe2018-10-30 11:46:09 +0100639 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100640 else:
641 error_text = "Invalid yaml format "
delacruzramob19cadc2019-10-08 10:18:02 +0200642 indata = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
gcalvinode4adfe2018-10-30 11:46:09 +0100643 cherrypy.request.headers.pop("Content-File-MD5", None)
tiernoc94c3df2018-02-09 15:38:54 +0100644 if not indata:
645 indata = {}
646
tiernoc94c3df2018-02-09 15:38:54 +0100647 format_yaml = False
648 if cherrypy.request.headers.get("Query-String-Format") == "yaml":
649 format_yaml = True
650
651 for k, v in kwargs.items():
652 if isinstance(v, str):
653 if v == "":
654 kwargs[k] = None
655 elif format_yaml:
656 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200657 kwargs[k] = yaml.load(v, Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200658 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100659 pass
garciadeblas4568a372021-03-24 09:19:48 +0100660 elif (
661 k.endswith(".gt")
662 or k.endswith(".lt")
663 or k.endswith(".gte")
664 or k.endswith(".lte")
665 ):
tiernoc94c3df2018-02-09 15:38:54 +0100666 try:
667 kwargs[k] = int(v)
tiernoe1281182018-05-22 12:24:36 +0200668 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100669 try:
670 kwargs[k] = float(v)
tiernoe1281182018-05-22 12:24:36 +0200671 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100672 pass
673 elif v.find(",") > 0:
674 kwargs[k] = v.split(",")
675 elif isinstance(v, (list, tuple)):
676 for index in range(0, len(v)):
677 if v[index] == "":
678 v[index] = None
679 elif format_yaml:
680 try:
delacruzramob19cadc2019-10-08 10:18:02 +0200681 v[index] = yaml.load(v[index], Loader=yaml.SafeLoader)
tiernoe1281182018-05-22 12:24:36 +0200682 except Exception:
tiernoc94c3df2018-02-09 15:38:54 +0100683 pass
684
tiernof27c79b2018-03-12 17:08:42 +0100685 return indata
tiernoc94c3df2018-02-09 15:38:54 +0100686 except (ValueError, yaml.YAMLError) as exc:
687 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
688 except KeyError as exc:
garciadeblas4568a372021-03-24 09:19:48 +0100689 raise NbiException(
690 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST
691 )
tiernob92094f2018-05-11 13:44:22 +0200692 except Exception as exc:
693 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST)
tiernoc94c3df2018-02-09 15:38:54 +0100694
695 @staticmethod
tierno701018c2019-06-25 11:13:14 +0000696 def _format_out(data, token_info=None, _format=None):
tiernoc94c3df2018-02-09 15:38:54 +0100697 """
698 return string of dictionary data according to requested json, yaml, xml. By default json
tiernof27c79b2018-03-12 17:08:42 +0100699 :param data: response to be sent. Can be a dict, text or file
tierno701018c2019-06-25 11:13:14 +0000700 :param token_info: Contains among other username and project
tierno5792d7d2019-08-30 15:37:12 +0000701 :param _format: The format to be set as Content-Type if data is a file
tiernoc94c3df2018-02-09 15:38:54 +0100702 :return: None
703 """
tierno0f98af52018-03-19 10:28:22 +0100704 accept = cherrypy.request.headers.get("Accept")
tiernof27c79b2018-03-12 17:08:42 +0100705 if data is None:
tierno0f98af52018-03-19 10:28:22 +0100706 if accept and "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100707 return html.format(
708 data, cherrypy.request, cherrypy.response, token_info
709 )
tierno09c073e2018-04-26 13:36:48 +0200710 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value
tiernof27c79b2018-03-12 17:08:42 +0100711 return
712 elif hasattr(data, "read"): # file object
713 if _format:
714 cherrypy.response.headers["Content-Type"] = _format
715 elif "b" in data.mode: # binariy asssumig zip
garciadeblas4568a372021-03-24 09:19:48 +0100716 cherrypy.response.headers["Content-Type"] = "application/zip"
tiernof27c79b2018-03-12 17:08:42 +0100717 else:
garciadeblas4568a372021-03-24 09:19:48 +0100718 cherrypy.response.headers["Content-Type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100719 # TODO check that cherrypy close file. If not implement pending things to close per thread next
720 return data
tierno0f98af52018-03-19 10:28:22 +0100721 if accept:
Frank Bryden02e700c2020-06-03 13:34:16 +0000722 if "text/html" in accept:
garciadeblas4568a372021-03-24 09:19:48 +0100723 return html.format(
724 data, cherrypy.request, cherrypy.response, token_info
725 )
Frank Brydenb5422da2020-08-10 11:44:11 +0000726 elif "application/yaml" in accept or "*/*" in accept:
tiernoc94c3df2018-02-09 15:38:54 +0100727 pass
garciadeblas4568a372021-03-24 09:19:48 +0100728 elif "application/json" in accept or (
729 cherrypy.response.status and cherrypy.response.status >= 300
730 ):
731 cherrypy.response.headers[
732 "Content-Type"
733 ] = "application/json; charset=utf-8"
Frank Bryden02e700c2020-06-03 13:34:16 +0000734 a = json.dumps(data, indent=4) + "\n"
garciadeblas4568a372021-03-24 09:19:48 +0100735 return a.encode("utf8")
736 cherrypy.response.headers["Content-Type"] = "application/yaml"
737 return yaml.safe_dump(
738 data,
739 explicit_start=True,
740 indent=4,
741 default_flow_style=False,
742 tags=False,
743 encoding="utf-8",
744 allow_unicode=True,
745 ) # , canonical=True, default_style='"'
tiernoc94c3df2018-02-09 15:38:54 +0100746
747 @cherrypy.expose
748 def index(self, *args, **kwargs):
tierno701018c2019-06-25 11:13:14 +0000749 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100750 try:
751 if cherrypy.request.method == "GET":
tierno701018c2019-06-25 11:13:14 +0000752 token_info = self.authenticator.authorize()
garciadeblas4568a372021-03-24 09:19:48 +0100753 outdata = token_info # Home page
tiernoc94c3df2018-02-09 15:38:54 +0100754 else:
garciadeblas4568a372021-03-24 09:19:48 +0100755 raise cherrypy.HTTPError(
756 HTTPStatus.METHOD_NOT_ALLOWED.value,
757 "Method {} not allowed for tokens".format(cherrypy.request.method),
758 )
tiernoc94c3df2018-02-09 15:38:54 +0100759
tierno701018c2019-06-25 11:13:14 +0000760 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100761
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100762 except (EngineException, AuthException) as e:
tierno701018c2019-06-25 11:13:14 +0000763 # cherrypy.log("index Exception {}".format(e))
tiernoc94c3df2018-02-09 15:38:54 +0100764 cherrypy.response.status = e.http_code.value
tierno701018c2019-06-25 11:13:14 +0000765 return self._format_out("Welcome to OSM!", token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100766
767 @cherrypy.expose
tierno55945e72018-04-06 16:40:27 +0200768 def version(self, *args, **kwargs):
tiernodfe09572018-04-24 10:41:10 +0200769 # TODO consider to remove and provide version using the static version file
tierno55945e72018-04-06 16:40:27 +0200770 try:
771 if cherrypy.request.method != "GET":
garciadeblas4568a372021-03-24 09:19:48 +0100772 raise NbiException(
773 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED
774 )
tierno55945e72018-04-06 16:40:27 +0200775 elif args or kwargs:
garciadeblas4568a372021-03-24 09:19:48 +0100776 raise NbiException(
777 "Invalid URL or query string for version",
778 HTTPStatus.METHOD_NOT_ALLOWED,
779 )
tierno9c630112019-08-29 14:21:41 +0000780 # TODO include version of other modules, pick up from some kafka admin message
tierno5792d7d2019-08-30 15:37:12 +0000781 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date}
782 return self._format_out(osm_nbi_version)
tierno55945e72018-04-06 16:40:27 +0200783 except NbiException as e:
784 cherrypy.response.status = e.http_code.value
785 problem_details = {
786 "code": e.http_code.name,
787 "status": e.http_code.value,
788 "detail": str(e),
789 }
790 return self._format_out(problem_details, None)
791
tierno12eac3c2020-03-19 23:22:08 +0000792 def domain(self):
793 try:
794 domains = {
garciadeblas4568a372021-03-24 09:19:48 +0100795 "user_domain_name": cherrypy.tree.apps["/osm"]
796 .config["authentication"]
797 .get("user_domain_name"),
798 "project_domain_name": cherrypy.tree.apps["/osm"]
799 .config["authentication"]
800 .get("project_domain_name"),
801 }
tierno12eac3c2020-03-19 23:22:08 +0000802 return self._format_out(domains)
803 except NbiException as e:
804 cherrypy.response.status = e.http_code.value
805 problem_details = {
806 "code": e.http_code.name,
807 "status": e.http_code.value,
808 "detail": str(e),
809 }
810 return self._format_out(problem_details, None)
811
tiernoa5035702019-07-29 08:54:42 +0000812 @staticmethod
813 def _format_login(token_info):
814 """
815 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will
816 log this information
817 :param token_info: Dictionary with token content
818 :return: None
819 """
820 cherrypy.request.login = token_info.get("username", "-")
821 if token_info.get("project_name"):
822 cherrypy.request.login += "/" + token_info["project_name"]
823 if token_info.get("id"):
824 cherrypy.request.login += ";session=" + token_info["id"][0:12]
825
tierno55945e72018-04-06 16:40:27 +0200826 @cherrypy.expose
tiernof27c79b2018-03-12 17:08:42 +0100827 def token(self, method, token_id=None, kwargs=None):
tierno701018c2019-06-25 11:13:14 +0000828 token_info = None
tiernoc94c3df2018-02-09 15:38:54 +0100829 # self.engine.load_dbase(cherrypy.request.app.config)
tiernof27c79b2018-03-12 17:08:42 +0100830 indata = self._format_in(kwargs)
831 if not isinstance(indata, dict):
garciadeblas4568a372021-03-24 09:19:48 +0100832 raise NbiException(
833 "Expected application/yaml or application/json Content-Type",
834 HTTPStatus.BAD_REQUEST,
835 )
tiernoa5035702019-07-29 08:54:42 +0000836
837 if method == "GET":
838 token_info = self.authenticator.authorize()
839 # for logging
840 self._format_login(token_info)
841 if token_id:
842 outdata = self.authenticator.get_token(token_info, token_id)
tiernoc94c3df2018-02-09 15:38:54 +0100843 else:
tiernoa5035702019-07-29 08:54:42 +0000844 outdata = self.authenticator.get_token_list(token_info)
845 elif method == "POST":
846 try:
847 token_info = self.authenticator.authorize()
848 except Exception:
849 token_info = None
850 if kwargs:
851 indata.update(kwargs)
852 # This is needed to log the user when authentication fails
853 cherrypy.request.login = "{}".format(indata.get("username", "-"))
garciadeblas4568a372021-03-24 09:19:48 +0100854 outdata = token_info = self.authenticator.new_token(
855 token_info, indata, cherrypy.request.remote
856 )
857 cherrypy.session["Authorization"] = outdata["_id"]
tiernoa5035702019-07-29 08:54:42 +0000858 self._set_location_header("admin", "v1", "tokens", outdata["_id"])
859 # for logging
860 self._format_login(token_info)
861
862 # cherrypy.response.cookie["Authorization"] = outdata["id"]
863 # cherrypy.response.cookie["Authorization"]['expires'] = 3600
864 elif method == "DELETE":
865 if not token_id and "id" in kwargs:
866 token_id = kwargs["id"]
867 elif not token_id:
868 token_info = self.authenticator.authorize()
869 # for logging
870 self._format_login(token_info)
871 token_id = token_info["_id"]
872 outdata = self.authenticator.del_token(token_id)
873 token_info = None
garciadeblas4568a372021-03-24 09:19:48 +0100874 cherrypy.session["Authorization"] = "logout"
tiernoa5035702019-07-29 08:54:42 +0000875 # cherrypy.response.cookie["Authorization"] = token_id
876 # cherrypy.response.cookie["Authorization"]['expires'] = 0
877 else:
garciadeblas4568a372021-03-24 09:19:48 +0100878 raise NbiException(
879 "Method {} not allowed for token".format(method),
880 HTTPStatus.METHOD_NOT_ALLOWED,
881 )
tiernoa5035702019-07-29 08:54:42 +0000882 return self._format_out(outdata, token_info)
tiernoc94c3df2018-02-09 15:38:54 +0100883
884 @cherrypy.expose
885 def test(self, *args, **kwargs):
garciadeblas4568a372021-03-24 09:19:48 +0100886 if not cherrypy.config.get("server.enable_test") or (
887 isinstance(cherrypy.config["server.enable_test"], str)
888 and cherrypy.config["server.enable_test"].lower() == "false"
889 ):
tierno4836bac2020-01-15 14:41:48 +0000890 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value
891 return "test URL is disabled"
tiernoc94c3df2018-02-09 15:38:54 +0100892 thread_info = None
tiernof27c79b2018-03-12 17:08:42 +0100893 if args and args[0] == "help":
garciadeblas4568a372021-03-24 09:19:48 +0100894 return (
895 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n"
896 "sleep/<time>\nmessage/topic\n</pre></html>"
897 )
tiernof27c79b2018-03-12 17:08:42 +0100898
899 elif args and args[0] == "init":
tiernoc94c3df2018-02-09 15:38:54 +0100900 try:
901 # self.engine.load_dbase(cherrypy.request.app.config)
902 self.engine.create_admin()
903 return "Done. User 'admin', password 'admin' created"
904 except Exception:
905 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
906 return self._format_out("Database already initialized")
tiernof27c79b2018-03-12 17:08:42 +0100907 elif args and args[0] == "file":
garciadeblas4568a372021-03-24 09:19:48 +0100908 return cherrypy.lib.static.serve_file(
909 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1],
910 "text/plain",
911 "attachment",
912 )
tiernof27c79b2018-03-12 17:08:42 +0100913 elif args and args[0] == "file2":
garciadeblas4568a372021-03-24 09:19:48 +0100914 f_path = (
915 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1]
916 )
tiernof27c79b2018-03-12 17:08:42 +0100917 f = open(f_path, "r")
918 cherrypy.response.headers["Content-type"] = "text/plain"
tiernof27c79b2018-03-12 17:08:42 +0100919 return f
tierno55945e72018-04-06 16:40:27 +0200920
tiernof27c79b2018-03-12 17:08:42 +0100921 elif len(args) == 2 and args[0] == "db-clear":
tiernof717cbe2018-12-03 16:35:42 +0000922 deleted_info = self.engine.db.del_list(args[1], kwargs)
923 return "{} {} deleted\n".format(deleted_info["deleted"], args[1])
924 elif len(args) and args[0] == "fs-clear":
925 if len(args) >= 2:
926 folders = (args[1],)
927 else:
928 folders = self.engine.fs.dir_ls(".")
929 for folder in folders:
930 self.engine.fs.file_delete(folder)
931 return ",".join(folders) + " folders deleted\n"
tiernoc94c3df2018-02-09 15:38:54 +0100932 elif args and args[0] == "login":
933 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +0100934 cherrypy.response.headers[
935 "WWW-Authenticate"
936 ] = 'Basic realm="Access to OSM site", charset="UTF-8"'
tiernoc94c3df2018-02-09 15:38:54 +0100937 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
938 elif args and args[0] == "login2":
939 if not cherrypy.request.headers.get("Authorization"):
garciadeblas4568a372021-03-24 09:19:48 +0100940 cherrypy.response.headers[
941 "WWW-Authenticate"
942 ] = 'Bearer realm="Access to OSM site"'
tiernoc94c3df2018-02-09 15:38:54 +0100943 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value
944 elif args and args[0] == "sleep":
945 sleep_time = 5
946 try:
947 sleep_time = int(args[1])
948 except Exception:
949 cherrypy.response.status = HTTPStatus.FORBIDDEN.value
950 return self._format_out("Database already initialized")
951 thread_info = cherrypy.thread_data
952 print(thread_info)
953 time.sleep(sleep_time)
954 # thread_info
955 elif len(args) >= 2 and args[0] == "message":
tiernob24258a2018-10-04 18:39:49 +0200956 main_topic = args[1]
957 return_text = "<html><pre>{} ->\n".format(main_topic)
tiernoc94c3df2018-02-09 15:38:54 +0100958 try:
garciadeblas4568a372021-03-24 09:19:48 +0100959 if cherrypy.request.method == "POST":
delacruzramob19cadc2019-10-08 10:18:02 +0200960 to_send = yaml.load(cherrypy.request.body, Loader=yaml.SafeLoader)
tierno55945e72018-04-06 16:40:27 +0200961 for k, v in to_send.items():
tiernob24258a2018-10-04 18:39:49 +0200962 self.engine.msg.write(main_topic, k, v)
tierno55945e72018-04-06 16:40:27 +0200963 return_text += " {}: {}\n".format(k, v)
garciadeblas4568a372021-03-24 09:19:48 +0100964 elif cherrypy.request.method == "GET":
tierno55945e72018-04-06 16:40:27 +0200965 for k, v in kwargs.items():
tiernof1509b22020-05-12 14:32:37 +0000966 v_dict = yaml.load(v, Loader=yaml.SafeLoader)
967 self.engine.msg.write(main_topic, k, v_dict)
968 return_text += " {}: {}\n".format(k, v_dict)
tiernoc94c3df2018-02-09 15:38:54 +0100969 except Exception as e:
tierno55945e72018-04-06 16:40:27 +0200970 return_text += "Error: " + str(e)
971 return_text += "</pre></html>\n"
972 return return_text
tiernoc94c3df2018-02-09 15:38:54 +0100973
974 return_text = (
garciadeblas4568a372021-03-24 09:19:48 +0100975 "<html><pre>\nheaders:\n args: {}\n".format(args)
976 + " kwargs: {}\n".format(kwargs)
977 + " headers: {}\n".format(cherrypy.request.headers)
978 + " path_info: {}\n".format(cherrypy.request.path_info)
979 + " query_string: {}\n".format(cherrypy.request.query_string)
980 + " session: {}\n".format(cherrypy.session)
981 + " cookie: {}\n".format(cherrypy.request.cookie)
982 + " method: {}\n".format(cherrypy.request.method)
983 + " session: {}\n".format(cherrypy.session.get("fieldname"))
984 + " body:\n"
985 )
tiernoc94c3df2018-02-09 15:38:54 +0100986 return_text += " length: {}\n".format(cherrypy.request.body.length)
987 if cherrypy.request.body.length:
988 return_text += " content: {}\n".format(
garciadeblas4568a372021-03-24 09:19:48 +0100989 str(
990 cherrypy.request.body.read(
991 int(cherrypy.request.headers.get("Content-Length", 0))
992 )
993 )
994 )
tiernoc94c3df2018-02-09 15:38:54 +0100995 if thread_info:
996 return_text += "thread: {}\n".format(thread_info)
997 return_text += "</pre></html>"
998 return return_text
999
tierno701018c2019-06-25 11:13:14 +00001000 @staticmethod
1001 def _check_valid_url_method(method, *args):
tiernof27c79b2018-03-12 17:08:42 +01001002 if len(args) < 3:
garciadeblas4568a372021-03-24 09:19:48 +01001003 raise NbiException(
1004 "URL must contain at least 'main_topic/version/topic'",
1005 HTTPStatus.METHOD_NOT_ALLOWED,
1006 )
tiernof27c79b2018-03-12 17:08:42 +01001007
tierno701018c2019-06-25 11:13:14 +00001008 reference = valid_url_methods
tiernof27c79b2018-03-12 17:08:42 +01001009 for arg in args:
1010 if arg is None:
1011 break
1012 if not isinstance(reference, dict):
garciadeblas4568a372021-03-24 09:19:48 +01001013 raise NbiException(
1014 "URL contains unexpected extra items '{}'".format(arg),
1015 HTTPStatus.METHOD_NOT_ALLOWED,
1016 )
tiernof27c79b2018-03-12 17:08:42 +01001017
1018 if arg in reference:
1019 reference = reference[arg]
1020 elif "<ID>" in reference:
1021 reference = reference["<ID>"]
1022 elif "*" in reference:
tierno74b53582020-06-18 10:52:37 +00001023 # if there is content
1024 if reference["*"]:
1025 reference = reference["*"]
tiernof27c79b2018-03-12 17:08:42 +01001026 break
1027 else:
garciadeblas4568a372021-03-24 09:19:48 +01001028 raise NbiException(
1029 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED
1030 )
tiernof27c79b2018-03-12 17:08:42 +01001031 if "TODO" in reference and method in reference["TODO"]:
garciadeblas4568a372021-03-24 09:19:48 +01001032 raise NbiException(
1033 "Method {} not supported yet for this URL".format(method),
1034 HTTPStatus.NOT_IMPLEMENTED,
1035 )
tierno2236d202018-05-16 19:05:16 +02001036 elif "METHODS" in reference and method not in reference["METHODS"]:
garciadeblas4568a372021-03-24 09:19:48 +01001037 raise NbiException(
1038 "Method {} not supported for this URL".format(method),
1039 HTTPStatus.METHOD_NOT_ALLOWED,
1040 )
tierno701018c2019-06-25 11:13:14 +00001041 return reference["ROLE_PERMISSION"] + method.lower()
tiernof27c79b2018-03-12 17:08:42 +01001042
1043 @staticmethod
tiernob24258a2018-10-04 18:39:49 +02001044 def _set_location_header(main_topic, version, topic, id):
tiernof27c79b2018-03-12 17:08:42 +01001045 """
1046 Insert response header Location with the URL of created item base on URL params
tiernob24258a2018-10-04 18:39:49 +02001047 :param main_topic:
tiernof27c79b2018-03-12 17:08:42 +01001048 :param version:
tiernob24258a2018-10-04 18:39:49 +02001049 :param topic:
tiernof27c79b2018-03-12 17:08:42 +01001050 :param id:
1051 :return: None
1052 """
1053 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT
garciadeblas4568a372021-03-24 09:19:48 +01001054 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format(
1055 main_topic, version, topic, id
1056 )
tiernof27c79b2018-03-12 17:08:42 +01001057 return
1058
tierno65ca36d2019-02-12 19:27:52 +01001059 @staticmethod
tierno701018c2019-06-25 11:13:14 +00001060 def _extract_query_string_operations(kwargs, method):
1061 """
1062
1063 :param kwargs:
1064 :return:
1065 """
1066 query_string_operations = []
1067 if kwargs:
1068 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"):
1069 if qs in kwargs and kwargs[qs].lower() != "false":
1070 query_string_operations.append(qs.lower() + ":" + method.lower())
1071 return query_string_operations
1072
1073 @staticmethod
1074 def _manage_admin_query(token_info, kwargs, method, _id):
tierno65ca36d2019-02-12 19:27:52 +01001075 """
1076 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT
1077 Check that users has rights to use them and returs the admin_query
tierno701018c2019-06-25 11:13:14 +00001078 :param token_info: token_info rights obtained by token
tierno65ca36d2019-02-12 19:27:52 +01001079 :param kwargs: query string input.
1080 :param method: http method: GET, POSST, PUT, ...
1081 :param _id:
1082 :return: admin_query dictionary with keys:
1083 public: True, False or None
1084 force: True or False
1085 project_id: tuple with projects used for accessing an element
1086 set_project: tuple with projects that a created element will belong to
1087 method: show, list, delete, write
1088 """
garciadeblas4568a372021-03-24 09:19:48 +01001089 admin_query = {
1090 "force": False,
1091 "project_id": (token_info["project_id"],),
1092 "username": token_info["username"],
1093 "admin": token_info["admin"],
1094 "public": None,
1095 "allow_show_user_project_role": token_info["allow_show_user_project_role"],
1096 }
tierno65ca36d2019-02-12 19:27:52 +01001097 if kwargs:
1098 # FORCE
1099 if "FORCE" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001100 if (
1101 kwargs["FORCE"].lower() != "false"
1102 ): # if None or True set force to True
tierno65ca36d2019-02-12 19:27:52 +01001103 admin_query["force"] = True
1104 del kwargs["FORCE"]
1105 # PUBLIC
1106 if "PUBLIC" in kwargs:
garciadeblas4568a372021-03-24 09:19:48 +01001107 if (
1108 kwargs["PUBLIC"].lower() != "false"
1109 ): # if None or True set public to True
tierno65ca36d2019-02-12 19:27:52 +01001110 admin_query["public"] = True
1111 else:
1112 admin_query["public"] = False
1113 del kwargs["PUBLIC"]
1114 # ADMIN
1115 if "ADMIN" in kwargs:
1116 behave_as = kwargs.pop("ADMIN")
1117 if behave_as.lower() != "false":
tierno701018c2019-06-25 11:13:14 +00001118 if not token_info["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001119 raise NbiException(
1120 "Only admin projects can use 'ADMIN' query string",
1121 HTTPStatus.UNAUTHORIZED,
1122 )
1123 if (
1124 not behave_as or behave_as.lower() == "true"
1125 ): # convert True, None to empty list
tierno65ca36d2019-02-12 19:27:52 +01001126 admin_query["project_id"] = ()
1127 elif isinstance(behave_as, (list, tuple)):
1128 admin_query["project_id"] = behave_as
garciadeblas4568a372021-03-24 09:19:48 +01001129 else: # isinstance(behave_as, str)
1130 admin_query["project_id"] = (behave_as,)
tierno65ca36d2019-02-12 19:27:52 +01001131 if "SET_PROJECT" in kwargs:
1132 set_project = kwargs.pop("SET_PROJECT")
1133 if not set_project:
1134 admin_query["set_project"] = list(admin_query["project_id"])
1135 else:
1136 if isinstance(set_project, str):
garciadeblas4568a372021-03-24 09:19:48 +01001137 set_project = (set_project,)
tierno65ca36d2019-02-12 19:27:52 +01001138 if admin_query["project_id"]:
1139 for p in set_project:
1140 if p not in admin_query["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001141 raise NbiException(
1142 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or "
1143 "'ADMIN='{p}'".format(p=p),
1144 HTTPStatus.UNAUTHORIZED,
1145 )
tierno65ca36d2019-02-12 19:27:52 +01001146 admin_query["set_project"] = set_project
1147
1148 # PROJECT_READ
1149 # if "PROJECT_READ" in kwargs:
1150 # admin_query["project"] = kwargs.pop("project")
tierno701018c2019-06-25 11:13:14 +00001151 # if admin_query["project"] == token_info["project_id"]:
tierno65ca36d2019-02-12 19:27:52 +01001152 if method == "GET":
1153 if _id:
1154 admin_query["method"] = "show"
1155 else:
1156 admin_query["method"] = "list"
1157 elif method == "DELETE":
1158 admin_query["method"] = "delete"
1159 else:
1160 admin_query["method"] = "write"
1161 return admin_query
1162
tiernoc94c3df2018-02-09 15:38:54 +01001163 @cherrypy.expose
garciadeblas4568a372021-03-24 09:19:48 +01001164 def default(
1165 self,
1166 main_topic=None,
1167 version=None,
1168 topic=None,
1169 _id=None,
1170 item=None,
1171 *args,
1172 **kwargs
1173 ):
tierno701018c2019-06-25 11:13:14 +00001174 token_info = None
tiernof27c79b2018-03-12 17:08:42 +01001175 outdata = None
1176 _format = None
tierno0f98af52018-03-19 10:28:22 +01001177 method = "DONE"
tiernob24258a2018-10-04 18:39:49 +02001178 engine_topic = None
tiernodb9dc582018-06-20 17:27:29 +02001179 rollback = []
tierno701018c2019-06-25 11:13:14 +00001180 engine_session = None
tiernoc94c3df2018-02-09 15:38:54 +01001181 try:
tiernob24258a2018-10-04 18:39:49 +02001182 if not main_topic or not version or not topic:
garciadeblas4568a372021-03-24 09:19:48 +01001183 raise NbiException(
1184 "URL must contain at least 'main_topic/version/topic'",
1185 HTTPStatus.METHOD_NOT_ALLOWED,
1186 )
1187 if main_topic not in (
1188 "admin",
1189 "vnfpkgm",
1190 "nsd",
1191 "nslcm",
1192 "pdu",
1193 "nst",
1194 "nsilcm",
1195 "nspm",
1196 ):
1197 raise NbiException(
1198 "URL main_topic '{}' not supported".format(main_topic),
1199 HTTPStatus.METHOD_NOT_ALLOWED,
1200 )
1201 if version != "v1":
1202 raise NbiException(
1203 "URL version '{}' not supported".format(version),
1204 HTTPStatus.METHOD_NOT_ALLOWED,
1205 )
tiernoc94c3df2018-02-09 15:38:54 +01001206
garciadeblas4568a372021-03-24 09:19:48 +01001207 if (
1208 kwargs
1209 and "METHOD" in kwargs
1210 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH")
1211 ):
tiernof27c79b2018-03-12 17:08:42 +01001212 method = kwargs.pop("METHOD")
1213 else:
1214 method = cherrypy.request.method
tierno65ca36d2019-02-12 19:27:52 +01001215
garciadeblas4568a372021-03-24 09:19:48 +01001216 role_permission = self._check_valid_url_method(
1217 method, main_topic, version, topic, _id, item, *args
1218 )
1219 query_string_operations = self._extract_query_string_operations(
1220 kwargs, method
1221 )
tiernob24258a2018-10-04 18:39:49 +02001222 if main_topic == "admin" and topic == "tokens":
tiernof27c79b2018-03-12 17:08:42 +01001223 return self.token(method, _id, kwargs)
garciadeblas4568a372021-03-24 09:19:48 +01001224 token_info = self.authenticator.authorize(
1225 role_permission, query_string_operations, _id
1226 )
tierno12eac3c2020-03-19 23:22:08 +00001227 if main_topic == "admin" and topic == "domains":
1228 return self.domain()
tierno701018c2019-06-25 11:13:14 +00001229 engine_session = self._manage_admin_query(token_info, kwargs, method, _id)
tiernof27c79b2018-03-12 17:08:42 +01001230 indata = self._format_in(kwargs)
tiernob24258a2018-10-04 18:39:49 +02001231 engine_topic = topic
preethika.p329b8182020-04-22 12:25:39 +05301232
vijay.r35ef2f72019-04-30 17:55:49 +05301233 if item and topic != "pm_jobs":
tiernob24258a2018-10-04 18:39:49 +02001234 engine_topic = item
tiernoc94c3df2018-02-09 15:38:54 +01001235
tiernob24258a2018-10-04 18:39:49 +02001236 if main_topic == "nsd":
1237 engine_topic = "nsds"
1238 elif main_topic == "vnfpkgm":
1239 engine_topic = "vnfds"
delacruzramo271d2002019-12-02 21:00:37 +01001240 if topic == "vnfpkg_op_occs":
1241 engine_topic = "vnfpkgops"
1242 if topic == "vnf_packages" and item == "action":
1243 engine_topic = "vnfpkgops"
tiernob24258a2018-10-04 18:39:49 +02001244 elif main_topic == "nslcm":
1245 engine_topic = "nsrs"
1246 if topic == "ns_lcm_op_occs":
1247 engine_topic = "nslcmops"
1248 if topic == "vnfrs" or topic == "vnf_instances":
1249 engine_topic = "vnfrs"
garciadeblas9750c5a2018-10-15 16:20:35 +02001250 elif main_topic == "nst":
1251 engine_topic = "nsts"
1252 elif main_topic == "nsilcm":
1253 engine_topic = "nsis"
1254 if topic == "nsi_lcm_op_occs":
delacruzramoc061f562019-04-05 11:00:02 +02001255 engine_topic = "nsilcmops"
tiernob24258a2018-10-04 18:39:49 +02001256 elif main_topic == "pdu":
1257 engine_topic = "pdus"
garciadeblas4568a372021-03-24 09:19:48 +01001258 if (
1259 engine_topic == "vims"
1260 ): # TODO this is for backward compatibility, it will be removed in the future
tiernob24258a2018-10-04 18:39:49 +02001261 engine_topic = "vim_accounts"
tiernoc94c3df2018-02-09 15:38:54 +01001262
preethika.p329b8182020-04-22 12:25:39 +05301263 if topic == "subscriptions":
1264 engine_topic = main_topic + "_" + topic
1265
tiernoc94c3df2018-02-09 15:38:54 +01001266 if method == "GET":
garciadeblas4568a372021-03-24 09:19:48 +01001267 if item in (
1268 "nsd_content",
1269 "package_content",
1270 "artifacts",
1271 "vnfd",
1272 "nsd",
1273 "nst",
1274 "nst_content",
1275 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001276 if item in ("vnfd", "nsd", "nst"):
tiernof27c79b2018-03-12 17:08:42 +01001277 path = "$DESCRIPTOR"
1278 elif args:
1279 path = args
tiernob24258a2018-10-04 18:39:49 +02001280 elif item == "artifacts":
tiernof27c79b2018-03-12 17:08:42 +01001281 path = ()
1282 else:
1283 path = None
garciadeblas4568a372021-03-24 09:19:48 +01001284 file, _format = self.engine.get_file(
1285 engine_session,
1286 engine_topic,
1287 _id,
1288 path,
1289 cherrypy.request.headers.get("Accept"),
1290 )
tiernof27c79b2018-03-12 17:08:42 +01001291 outdata = file
1292 elif not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001293 outdata = self.engine.get_item_list(
1294 engine_session, engine_topic, kwargs, api_req=True
1295 )
tiernoc94c3df2018-02-09 15:38:54 +01001296 else:
vijay.r35ef2f72019-04-30 17:55:49 +05301297 if item == "reports":
1298 # TODO check that project_id (_id in this context) has permissions
1299 _id = args[0]
K Sai Kiran57589552021-01-27 21:38:34 +05301300 filter_q = None
1301 if "vcaStatusRefresh" in kwargs:
1302 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]}
1303 outdata = self.engine.get_item(engine_session, engine_topic, _id, filter_q, True)
delacruzramo271d2002019-12-02 21:00:37 +01001304
tiernof27c79b2018-03-12 17:08:42 +01001305 elif method == "POST":
tiernobdebce92019-07-01 15:36:49 +00001306 cherrypy.response.status = HTTPStatus.CREATED.value
garciadeblas4568a372021-03-24 09:19:48 +01001307 if topic in (
1308 "ns_descriptors_content",
1309 "vnf_packages_content",
1310 "netslice_templates_content",
1311 ):
tiernof27c79b2018-03-12 17:08:42 +01001312 _id = cherrypy.request.headers.get("Transaction-Id")
1313 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001314 _id, _ = self.engine.new_item(
1315 rollback,
1316 engine_session,
1317 engine_topic,
1318 {},
1319 None,
1320 cherrypy.request.headers,
1321 )
1322 completed = self.engine.upload_content(
1323 engine_session,
1324 engine_topic,
1325 _id,
1326 indata,
1327 kwargs,
1328 cherrypy.request.headers,
1329 )
tiernof27c79b2018-03-12 17:08:42 +01001330 if completed:
tiernob24258a2018-10-04 18:39:49 +02001331 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001332 else:
1333 cherrypy.response.headers["Transaction-Id"] = _id
1334 outdata = {"id": _id}
tiernob24258a2018-10-04 18:39:49 +02001335 elif topic == "ns_instances_content":
1336 # creates NSR
garciadeblas4568a372021-03-24 09:19:48 +01001337 _id, _ = self.engine.new_item(
1338 rollback, engine_session, engine_topic, indata, kwargs
1339 )
tiernob24258a2018-10-04 18:39:49 +02001340 # creates nslcmop
1341 indata["lcmOperationType"] = "instantiate"
1342 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001343 nslcmop_id, _ = self.engine.new_item(
1344 rollback, engine_session, "nslcmops", indata, None
1345 )
tiernob24258a2018-10-04 18:39:49 +02001346 self._set_location_header(main_topic, version, topic, _id)
kuuse078f55e2019-05-16 19:24:21 +02001347 outdata = {"id": _id, "nslcmop_id": nslcmop_id}
tiernob24258a2018-10-04 18:39:49 +02001348 elif topic == "ns_instances" and item:
1349 indata["lcmOperationType"] = item
1350 indata["nsInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001351 _id, _ = self.engine.new_item(
1352 rollback, engine_session, "nslcmops", indata, kwargs
1353 )
1354 self._set_location_header(
1355 main_topic, version, "ns_lcm_op_occs", _id
1356 )
tierno65acb4d2018-04-06 16:42:40 +02001357 outdata = {"id": _id}
1358 cherrypy.response.status = HTTPStatus.ACCEPTED.value
garciadeblas9750c5a2018-10-15 16:20:35 +02001359 elif topic == "netslice_instances_content":
Felipe Vicens07f31722018-10-29 15:16:44 +01001360 # creates NetSlice_Instance_record (NSIR)
garciadeblas4568a372021-03-24 09:19:48 +01001361 _id, _ = self.engine.new_item(
1362 rollback, engine_session, engine_topic, indata, kwargs
1363 )
Felipe Vicens07f31722018-10-29 15:16:44 +01001364 self._set_location_header(main_topic, version, topic, _id)
garciadeblas9750c5a2018-10-15 16:20:35 +02001365 indata["lcmOperationType"] = "instantiate"
Felipe Vicens126af572019-06-05 19:13:04 +02001366 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001367 nsilcmop_id, _ = self.engine.new_item(
1368 rollback, engine_session, "nsilcmops", indata, kwargs
1369 )
kuuse078f55e2019-05-16 19:24:21 +02001370 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id}
garciadeblas9750c5a2018-10-15 16:20:35 +02001371 elif topic == "netslice_instances" and item:
1372 indata["lcmOperationType"] = item
Felipe Vicens126af572019-06-05 19:13:04 +02001373 indata["netsliceInstanceId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001374 _id, _ = self.engine.new_item(
1375 rollback, engine_session, "nsilcmops", indata, kwargs
1376 )
1377 self._set_location_header(
1378 main_topic, version, "nsi_lcm_op_occs", _id
1379 )
garciadeblas9750c5a2018-10-15 16:20:35 +02001380 outdata = {"id": _id}
1381 cherrypy.response.status = HTTPStatus.ACCEPTED.value
delacruzramo271d2002019-12-02 21:00:37 +01001382 elif topic == "vnf_packages" and item == "action":
1383 indata["lcmOperationType"] = item
1384 indata["vnfPkgId"] = _id
garciadeblas4568a372021-03-24 09:19:48 +01001385 _id, _ = self.engine.new_item(
1386 rollback, engine_session, "vnfpkgops", indata, kwargs
1387 )
1388 self._set_location_header(
1389 main_topic, version, "vnfpkg_op_occs", _id
1390 )
delacruzramo271d2002019-12-02 21:00:37 +01001391 outdata = {"id": _id}
1392 cherrypy.response.status = HTTPStatus.ACCEPTED.value
preethika.p329b8182020-04-22 12:25:39 +05301393 elif topic == "subscriptions":
garciadeblas4568a372021-03-24 09:19:48 +01001394 _id, _ = self.engine.new_item(
1395 rollback, engine_session, engine_topic, indata, kwargs
1396 )
preethika.p329b8182020-04-22 12:25:39 +05301397 self._set_location_header(main_topic, version, topic, _id)
1398 link = {}
1399 link["self"] = cherrypy.response.headers["Location"]
garciadeblas4568a372021-03-24 09:19:48 +01001400 outdata = {
1401 "id": _id,
1402 "filter": indata["filter"],
1403 "callbackUri": indata["CallbackUri"],
1404 "_links": link,
1405 }
preethika.p329b8182020-04-22 12:25:39 +05301406 cherrypy.response.status = HTTPStatus.CREATED.value
tiernof27c79b2018-03-12 17:08:42 +01001407 else:
garciadeblas4568a372021-03-24 09:19:48 +01001408 _id, op_id = self.engine.new_item(
1409 rollback,
1410 engine_session,
1411 engine_topic,
1412 indata,
1413 kwargs,
1414 cherrypy.request.headers,
1415 )
tiernob24258a2018-10-04 18:39:49 +02001416 self._set_location_header(main_topic, version, topic, _id)
tiernof27c79b2018-03-12 17:08:42 +01001417 outdata = {"id": _id}
tiernobdebce92019-07-01 15:36:49 +00001418 if op_id:
1419 outdata["op_id"] = op_id
1420 cherrypy.response.status = HTTPStatus.ACCEPTED.value
tiernob24258a2018-10-04 18:39:49 +02001421 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages")
tierno09c073e2018-04-26 13:36:48 +02001422
tiernoc94c3df2018-02-09 15:38:54 +01001423 elif method == "DELETE":
1424 if not _id:
garciadeblas4568a372021-03-24 09:19:48 +01001425 outdata = self.engine.del_item_list(
1426 engine_session, engine_topic, kwargs
1427 )
tierno09c073e2018-04-26 13:36:48 +02001428 cherrypy.response.status = HTTPStatus.OK.value
tiernoc94c3df2018-02-09 15:38:54 +01001429 else: # len(args) > 1
tierno22577432020-04-08 15:16:57 +00001430 # for NS NSI generate an operation
1431 op_id = None
tierno701018c2019-06-25 11:13:14 +00001432 if topic == "ns_instances_content" and not engine_session["force"]:
tiernob24258a2018-10-04 18:39:49 +02001433 nslcmop_desc = {
1434 "lcmOperationType": "terminate",
1435 "nsInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001436 "autoremove": True,
tiernob24258a2018-10-04 18:39:49 +02001437 }
garciadeblas4568a372021-03-24 09:19:48 +01001438 op_id, _ = self.engine.new_item(
1439 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs
1440 )
tierno22577432020-04-08 15:16:57 +00001441 if op_id:
1442 outdata = {"_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001443 elif (
1444 topic == "netslice_instances_content"
1445 and not engine_session["force"]
1446 ):
garciadeblas9750c5a2018-10-15 16:20:35 +02001447 nsilcmop_desc = {
1448 "lcmOperationType": "terminate",
Felipe Vicens126af572019-06-05 19:13:04 +02001449 "netsliceInstanceId": _id,
garciadeblas4568a372021-03-24 09:19:48 +01001450 "autoremove": True,
garciadeblas9750c5a2018-10-15 16:20:35 +02001451 }
garciadeblas4568a372021-03-24 09:19:48 +01001452 op_id, _ = self.engine.new_item(
1453 rollback, engine_session, "nsilcmops", nsilcmop_desc, None
1454 )
tierno22577432020-04-08 15:16:57 +00001455 if op_id:
1456 outdata = {"_id": op_id}
1457 # if there is not any deletion in process, delete
1458 if not op_id:
1459 op_id = self.engine.del_item(engine_session, engine_topic, _id)
1460 if op_id:
1461 outdata = {"op_id": op_id}
garciadeblas4568a372021-03-24 09:19:48 +01001462 cherrypy.response.status = (
1463 HTTPStatus.ACCEPTED.value
1464 if op_id
1465 else HTTPStatus.NO_CONTENT.value
1466 )
tierno09c073e2018-04-26 13:36:48 +02001467
tierno7ae10112018-05-18 14:36:02 +02001468 elif method in ("PUT", "PATCH"):
tiernobdebce92019-07-01 15:36:49 +00001469 op_id = None
tierno701018c2019-06-25 11:13:14 +00001470 if not indata and not kwargs and not engine_session.get("set_project"):
garciadeblas4568a372021-03-24 09:19:48 +01001471 raise NbiException(
1472 "Nothing to update. Provide payload and/or query string",
1473 HTTPStatus.BAD_REQUEST,
1474 )
1475 if (
1476 item in ("nsd_content", "package_content", "nst_content")
1477 and method == "PUT"
1478 ):
1479 completed = self.engine.upload_content(
1480 engine_session,
1481 engine_topic,
1482 _id,
1483 indata,
1484 kwargs,
1485 cherrypy.request.headers,
1486 )
tiernof27c79b2018-03-12 17:08:42 +01001487 if not completed:
1488 cherrypy.response.headers["Transaction-Id"] = id
tiernof27c79b2018-03-12 17:08:42 +01001489 else:
garciadeblas4568a372021-03-24 09:19:48 +01001490 op_id = self.engine.edit_item(
1491 engine_session, engine_topic, _id, indata, kwargs
1492 )
tiernobdebce92019-07-01 15:36:49 +00001493
1494 if op_id:
1495 cherrypy.response.status = HTTPStatus.ACCEPTED.value
1496 outdata = {"op_id": op_id}
1497 else:
1498 cherrypy.response.status = HTTPStatus.NO_CONTENT.value
1499 outdata = None
tiernoc94c3df2018-02-09 15:38:54 +01001500 else:
garciadeblas4568a372021-03-24 09:19:48 +01001501 raise NbiException(
1502 "Method {} not allowed".format(method),
1503 HTTPStatus.METHOD_NOT_ALLOWED,
1504 )
tiernoa6bb45d2019-06-14 09:45:39 +00001505
1506 # if Role information changes, it is needed to reload the information of roles
1507 if topic == "roles" and method != "GET":
1508 self.authenticator.load_operation_to_allowed_roles()
delacruzramoad682a52019-12-10 16:26:34 +01001509
garciadeblas4568a372021-03-24 09:19:48 +01001510 if (
1511 topic == "projects"
1512 and method == "DELETE"
1513 or topic in ["users", "roles"]
1514 and method in ["PUT", "PATCH", "DELETE"]
1515 ):
delacruzramoad682a52019-12-10 16:26:34 +01001516 self.authenticator.remove_token_from_cache()
1517
tierno701018c2019-06-25 11:13:14 +00001518 return self._format_out(outdata, token_info, _format)
tiernob24258a2018-10-04 18:39:49 +02001519 except Exception as e:
garciadeblas4568a372021-03-24 09:19:48 +01001520 if isinstance(
1521 e,
1522 (
1523 NbiException,
1524 EngineException,
1525 DbException,
1526 FsException,
1527 MsgException,
1528 AuthException,
1529 ValidationError,
1530 AuthconnException,
1531 ),
1532 ):
tiernob24258a2018-10-04 18:39:49 +02001533 http_code_value = cherrypy.response.status = e.http_code.value
1534 http_code_name = e.http_code.name
1535 cherrypy.log("Exception {}".format(e))
1536 else:
garciadeblas4568a372021-03-24 09:19:48 +01001537 http_code_value = (
1538 cherrypy.response.status
1539 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR
Felipe Vicense36ab852018-11-23 14:12:09 +01001540 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True)
tiernob24258a2018-10-04 18:39:49 +02001541 http_code_name = HTTPStatus.BAD_REQUEST.name
tierno3ace63c2018-05-03 17:51:43 +02001542 if hasattr(outdata, "close"): # is an open file
1543 outdata.close()
tiernob24258a2018-10-04 18:39:49 +02001544 error_text = str(e)
1545 rollback.reverse()
tiernodb9dc582018-06-20 17:27:29 +02001546 for rollback_item in rollback:
tierno3ace63c2018-05-03 17:51:43 +02001547 try:
tiernocc103432018-10-19 14:10:35 +02001548 if rollback_item.get("operation") == "set":
garciadeblas4568a372021-03-24 09:19:48 +01001549 self.engine.db.set_one(
1550 rollback_item["topic"],
1551 {"_id": rollback_item["_id"]},
1552 rollback_item["content"],
1553 fail_on_empty=False,
1554 )
preethika.p329b8182020-04-22 12:25:39 +05301555 elif rollback_item.get("operation") == "del_list":
garciadeblas4568a372021-03-24 09:19:48 +01001556 self.engine.db.del_list(
1557 rollback_item["topic"],
1558 rollback_item["filter"],
1559 fail_on_empty=False,
1560 )
tiernocc103432018-10-19 14:10:35 +02001561 else:
garciadeblas4568a372021-03-24 09:19:48 +01001562 self.engine.db.del_one(
1563 rollback_item["topic"],
1564 {"_id": rollback_item["_id"]},
1565 fail_on_empty=False,
1566 )
tierno3ace63c2018-05-03 17:51:43 +02001567 except Exception as e2:
garciadeblas4568a372021-03-24 09:19:48 +01001568 rollback_error_text = "Rollback Exception {}: {}".format(
1569 rollback_item, e2
1570 )
tiernob24258a2018-10-04 18:39:49 +02001571 cherrypy.log(rollback_error_text)
1572 error_text += ". " + rollback_error_text
1573 # if isinstance(e, MsgException):
1574 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format(
1575 # engine_topic[:-1], method, error_text)
tiernoc94c3df2018-02-09 15:38:54 +01001576 problem_details = {
tiernob24258a2018-10-04 18:39:49 +02001577 "code": http_code_name,
1578 "status": http_code_value,
1579 "detail": error_text,
tiernoc94c3df2018-02-09 15:38:54 +01001580 }
tierno701018c2019-06-25 11:13:14 +00001581 return self._format_out(problem_details, token_info)
tiernoc94c3df2018-02-09 15:38:54 +01001582 # raise cherrypy.HTTPError(e.http_code.value, str(e))
tiernoa5035702019-07-29 08:54:42 +00001583 finally:
1584 if token_info:
1585 self._format_login(token_info)
1586 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict):
1587 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"):
1588 if outdata.get(logging_id):
garciadeblas4568a372021-03-24 09:19:48 +01001589 cherrypy.request.login += ";{}={}".format(
1590 logging_id, outdata[logging_id][:36]
1591 )
tiernoc94c3df2018-02-09 15:38:54 +01001592
1593
tiernoc94c3df2018-02-09 15:38:54 +01001594def _start_service():
1595 """
1596 Callback function called when cherrypy.engine starts
1597 Override configuration with env variables
1598 Set database, storage, message configuration
1599 Init database with admin/admin user password
1600 """
tierno932499c2019-01-28 17:28:10 +00001601 global nbi_server
1602 global subscription_thread
tiernoc94c3df2018-02-09 15:38:54 +01001603 cherrypy.log.error("Starting osm_nbi")
1604 # update general cherrypy configuration
1605 update_dict = {}
1606
garciadeblas4568a372021-03-24 09:19:48 +01001607 engine_config = cherrypy.tree.apps["/osm"].config
tiernoc94c3df2018-02-09 15:38:54 +01001608 for k, v in environ.items():
1609 if not k.startswith("OSMNBI_"):
1610 continue
tiernoe1281182018-05-22 12:24:36 +02001611 k1, _, k2 = k[7:].lower().partition("_")
tiernoc94c3df2018-02-09 15:38:54 +01001612 if not k2:
1613 continue
1614 try:
1615 # update static configuration
garciadeblas4568a372021-03-24 09:19:48 +01001616 if k == "OSMNBI_STATIC_DIR":
1617 engine_config["/static"]["tools.staticdir.dir"] = v
1618 engine_config["/static"]["tools.staticdir.on"] = True
1619 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT":
1620 update_dict["server.socket_port"] = int(v)
1621 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST":
1622 update_dict["server.socket_host"] = v
tiernof5298be2018-05-16 14:43:57 +02001623 elif k1 in ("server", "test", "auth", "log"):
garciadeblas4568a372021-03-24 09:19:48 +01001624 update_dict[k1 + "." + k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001625 elif k1 in ("message", "database", "storage", "authentication"):
tiernof5298be2018-05-16 14:43:57 +02001626 # k2 = k2.replace('_', '.')
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001627 if k2 in ("port", "db_port"):
tiernoc94c3df2018-02-09 15:38:54 +01001628 engine_config[k1][k2] = int(v)
1629 else:
1630 engine_config[k1][k2] = v
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001631
tiernoc94c3df2018-02-09 15:38:54 +01001632 except ValueError as e:
1633 cherrypy.log.error("Ignoring environ '{}': " + str(e))
1634 except Exception as e:
1635 cherrypy.log.warn("skipping environ '{}' on exception '{}'".format(k, e))
1636
1637 if update_dict:
1638 cherrypy.config.update(update_dict)
tiernof5298be2018-05-16 14:43:57 +02001639 engine_config["global"].update(update_dict)
tiernoc94c3df2018-02-09 15:38:54 +01001640
1641 # logging cherrypy
garciadeblas4568a372021-03-24 09:19:48 +01001642 log_format_simple = (
1643 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s"
1644 )
1645 log_formatter_simple = logging.Formatter(
1646 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S"
1647 )
tiernoc94c3df2018-02-09 15:38:54 +01001648 logger_server = logging.getLogger("cherrypy.error")
1649 logger_access = logging.getLogger("cherrypy.access")
1650 logger_cherry = logging.getLogger("cherrypy")
1651 logger_nbi = logging.getLogger("nbi")
1652
tiernof5298be2018-05-16 14:43:57 +02001653 if "log.file" in engine_config["global"]:
garciadeblas4568a372021-03-24 09:19:48 +01001654 file_handler = logging.handlers.RotatingFileHandler(
1655 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0
1656 )
tiernoc94c3df2018-02-09 15:38:54 +01001657 file_handler.setFormatter(log_formatter_simple)
1658 logger_cherry.addHandler(file_handler)
1659 logger_nbi.addHandler(file_handler)
tiernob24258a2018-10-04 18:39:49 +02001660 # log always to standard output
garciadeblas4568a372021-03-24 09:19:48 +01001661 for format_, logger in {
1662 "nbi.server %(filename)s:%(lineno)s": logger_server,
1663 "nbi.access %(filename)s:%(lineno)s": logger_access,
1664 "%(name)s %(filename)s:%(lineno)s": logger_nbi,
1665 }.items():
tiernob24258a2018-10-04 18:39:49 +02001666 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_)
garciadeblas4568a372021-03-24 09:19:48 +01001667 log_formatter_cherry = logging.Formatter(
1668 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S"
1669 )
tiernob24258a2018-10-04 18:39:49 +02001670 str_handler = logging.StreamHandler()
1671 str_handler.setFormatter(log_formatter_cherry)
1672 logger.addHandler(str_handler)
tiernoc94c3df2018-02-09 15:38:54 +01001673
tiernof5298be2018-05-16 14:43:57 +02001674 if engine_config["global"].get("log.level"):
1675 logger_cherry.setLevel(engine_config["global"]["log.level"])
1676 logger_nbi.setLevel(engine_config["global"]["log.level"])
tiernoc94c3df2018-02-09 15:38:54 +01001677
1678 # logging other modules
garciadeblas4568a372021-03-24 09:19:48 +01001679 for k1, logname in {
1680 "message": "nbi.msg",
1681 "database": "nbi.db",
1682 "storage": "nbi.fs",
1683 }.items():
tiernoc94c3df2018-02-09 15:38:54 +01001684 engine_config[k1]["logger_name"] = logname
1685 logger_module = logging.getLogger(logname)
1686 if "logfile" in engine_config[k1]:
garciadeblas4568a372021-03-24 09:19:48 +01001687 file_handler = logging.handlers.RotatingFileHandler(
1688 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0
1689 )
tiernoc94c3df2018-02-09 15:38:54 +01001690 file_handler.setFormatter(log_formatter_simple)
1691 logger_module.addHandler(file_handler)
1692 if "loglevel" in engine_config[k1]:
1693 logger_module.setLevel(engine_config[k1]["loglevel"])
1694 # TODO add more entries, e.g.: storage
garciadeblas4568a372021-03-24 09:19:48 +01001695 cherrypy.tree.apps["/osm"].root.engine.start(engine_config)
1696 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config)
1697 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version)
1698 cherrypy.tree.apps["/osm"].root.authenticator.init_db(
1699 target_version=auth_database_version
1700 )
tiernobee508e2019-01-21 11:21:49 +00001701
tierno932499c2019-01-28 17:28:10 +00001702 # start subscriptions thread:
garciadeblas4568a372021-03-24 09:19:48 +01001703 subscription_thread = SubscriptionThread(
1704 config=engine_config, engine=nbi_server.engine
1705 )
tierno932499c2019-01-28 17:28:10 +00001706 subscription_thread.start()
1707 # Do not capture except SubscriptionException
1708
tiernob2e48bd2020-02-04 15:47:18 +00001709 backend = engine_config["authentication"]["backend"]
garciadeblas4568a372021-03-24 09:19:48 +01001710 cherrypy.log.error(
1711 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format(
1712 nbi_version, nbi_version_date, backend
1713 )
1714 )
tiernoc94c3df2018-02-09 15:38:54 +01001715
1716
1717def _stop_service():
1718 """
1719 Callback function called when cherrypy.engine stops
1720 TODO: Ending database connections.
1721 """
tierno932499c2019-01-28 17:28:10 +00001722 global subscription_thread
tierno65ca36d2019-02-12 19:27:52 +01001723 if subscription_thread:
1724 subscription_thread.terminate()
tierno932499c2019-01-28 17:28:10 +00001725 subscription_thread = None
garciadeblas4568a372021-03-24 09:19:48 +01001726 cherrypy.tree.apps["/osm"].root.engine.stop()
tiernoc94c3df2018-02-09 15:38:54 +01001727 cherrypy.log.error("Stopping osm_nbi")
1728
tierno2236d202018-05-16 19:05:16 +02001729
tiernof5298be2018-05-16 14:43:57 +02001730def nbi(config_file):
tierno932499c2019-01-28 17:28:10 +00001731 global nbi_server
tiernoc94c3df2018-02-09 15:38:54 +01001732 # conf = {
1733 # '/': {
1734 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
1735 # 'tools.sessions.on': True,
1736 # 'tools.response_headers.on': True,
1737 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')],
1738 # }
1739 # }
1740 # cherrypy.Server.ssl_module = 'builtin'
1741 # cherrypy.Server.ssl_certificate = "http/cert.pem"
1742 # cherrypy.Server.ssl_private_key = "http/privkey.pem"
1743 # cherrypy.Server.thread_pool = 10
1744 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]})
1745
1746 # cherrypy.config.update({'tools.auth_basic.on': True,
1747 # 'tools.auth_basic.realm': 'localhost',
1748 # 'tools.auth_basic.checkpassword': validate_password})
tierno932499c2019-01-28 17:28:10 +00001749 nbi_server = Server()
garciadeblas4568a372021-03-24 09:19:48 +01001750 cherrypy.engine.subscribe("start", _start_service)
1751 cherrypy.engine.subscribe("stop", _stop_service)
1752 cherrypy.quickstart(nbi_server, "/osm", config_file)
tiernof5298be2018-05-16 14:43:57 +02001753
1754
1755def usage():
garciadeblas4568a372021-03-24 09:19:48 +01001756 print(
1757 """Usage: {} [options]
tiernof5298be2018-05-16 14:43:57 +02001758 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg)
1759 -h|--help: shows this help
garciadeblas4568a372021-03-24 09:19:48 +01001760 """.format(
1761 sys.argv[0]
1762 )
1763 )
tierno2236d202018-05-16 19:05:16 +02001764 # --log-socket-host HOST: send logs to this host")
1765 # --log-socket-port PORT: send logs using this port (default: 9022)")
tiernoc94c3df2018-02-09 15:38:54 +01001766
1767
garciadeblas4568a372021-03-24 09:19:48 +01001768if __name__ == "__main__":
tiernof5298be2018-05-16 14:43:57 +02001769 try:
1770 # load parameters and configuration
1771 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"])
1772 # TODO add "log-socket-host=", "log-socket-port=", "log-file="
1773 config_file = None
1774 for o, a in opts:
1775 if o in ("-h", "--help"):
1776 usage()
1777 sys.exit()
1778 elif o in ("-c", "--config"):
1779 config_file = a
1780 # elif o == "--log-socket-port":
1781 # log_socket_port = a
1782 # elif o == "--log-socket-host":
1783 # log_socket_host = a
1784 # elif o == "--log-file":
1785 # log_file = a
1786 else:
1787 assert False, "Unhandled option"
1788 if config_file:
1789 if not path.isfile(config_file):
garciadeblas4568a372021-03-24 09:19:48 +01001790 print(
1791 "configuration file '{}' that not exist".format(config_file),
1792 file=sys.stderr,
1793 )
tiernof5298be2018-05-16 14:43:57 +02001794 exit(1)
1795 else:
garciadeblas4568a372021-03-24 09:19:48 +01001796 for config_file in (
1797 __file__[: __file__.rfind(".")] + ".cfg",
1798 "./nbi.cfg",
1799 "/etc/osm/nbi.cfg",
1800 ):
tiernof5298be2018-05-16 14:43:57 +02001801 if path.isfile(config_file):
1802 break
1803 else:
garciadeblas4568a372021-03-24 09:19:48 +01001804 print(
1805 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/",
1806 file=sys.stderr,
1807 )
tiernof5298be2018-05-16 14:43:57 +02001808 exit(1)
1809 nbi(config_file)
1810 except getopt.GetoptError as e:
1811 print(str(e), file=sys.stderr)
1812 # usage()
1813 exit(1)