Coverage for osm_nbi/nbi.py: 0%

792 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2024-06-30 10:14 +0000

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 

17import cherrypy 

18import time 

19import json 

20import yaml 

21import osm_nbi.html_out as html 

22import logging 

23import logging.handlers 

24import getopt 

25import sys 

26 

27from 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.utils import cef_event, cef_event_builder 

32from osm_nbi.validation import ValidationError 

33from osm_common.dbbase import DbException 

34from osm_common.fsbase import FsException 

35from osm_common.msgbase import MsgException 

36from http import HTTPStatus 

37from codecs import getreader 

38from os import environ, path 

39from osm_nbi import version as nbi_version, version_date as nbi_version_date 

40 

41__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>" 

42 

43__version__ = "0.1.3" # file version, not NBI version 

44version_date = "Aug 2019" 

45 

46database_version = "1.2" 

47auth_database_version = "1.0" 

48nbi_server = None # instance of Server class 

49subscription_thread = None # instance of SubscriptionThread class 

50cef_logger = None 

51 

52""" 

53North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented) 

54URL: /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 

149query 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 

178Header 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 

186Header 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 

204valid_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 

212valid_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 

651class NbiException(Exception): 

652 def __init__(self, message, http_code=HTTPStatus.METHOD_NOT_ALLOWED): 

653 Exception.__init__(self, message) 

654 self.http_code = http_code 

655 

656 

657class Server(object): 

658 instance = 0 

659 # to decode bytes to str 

660 reader = getreader("utf-8") 

661 

662 def __init__(self): 

663 self.instance += 1 

664 self.authenticator = Authenticator(valid_url_methods, valid_query_string) 

665 self.engine = Engine(self.authenticator) 

666 

667 def _format_in(self, kwargs): 

668 error_text = "" # error_text must be initialized outside try 

669 try: 

670 indata = None 

671 if cherrypy.request.body.length: 

672 error_text = "Invalid input format " 

673 

674 if "Content-Type" in cherrypy.request.headers: 

675 if "application/json" in cherrypy.request.headers["Content-Type"]: 

676 error_text = "Invalid json format " 

677 indata = json.load(self.reader(cherrypy.request.body)) 

678 cherrypy.request.headers.pop("Content-File-MD5", None) 

679 elif "application/yaml" in cherrypy.request.headers["Content-Type"]: 

680 error_text = "Invalid yaml format " 

681 indata = yaml.safe_load(cherrypy.request.body) 

682 cherrypy.request.headers.pop("Content-File-MD5", None) 

683 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 indata = cherrypy.request.body # .read() 

691 elif ( 

692 "multipart/form-data" 

693 in cherrypy.request.headers["Content-Type"] 

694 ): 

695 if "descriptor_file" in kwargs: 

696 filecontent = kwargs.pop("descriptor_file") 

697 if not filecontent.file: 

698 raise NbiException( 

699 "empty file or content", HTTPStatus.BAD_REQUEST 

700 ) 

701 indata = filecontent.file # .read() 

702 if filecontent.content_type.value: 

703 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 error_text = "Invalid yaml format " 

711 indata = yaml.safe_load(cherrypy.request.body) 

712 cherrypy.request.headers.pop("Content-File-MD5", None) 

713 else: 

714 error_text = "Invalid yaml format " 

715 indata = yaml.safe_load(cherrypy.request.body) 

716 cherrypy.request.headers.pop("Content-File-MD5", None) 

717 if not indata: 

718 indata = {} 

719 

720 format_yaml = False 

721 if cherrypy.request.headers.get("Query-String-Format") == "yaml": 

722 format_yaml = True 

723 

724 for k, v in kwargs.items(): 

725 if isinstance(v, str): 

726 if v == "": 

727 kwargs[k] = None 

728 elif format_yaml: 

729 try: 

730 kwargs[k] = yaml.safe_load(v) 

731 except Exception: 

732 pass 

733 elif ( 

734 k.endswith(".gt") 

735 or k.endswith(".lt") 

736 or k.endswith(".gte") 

737 or k.endswith(".lte") 

738 ): 

739 try: 

740 kwargs[k] = int(v) 

741 except Exception: 

742 try: 

743 kwargs[k] = float(v) 

744 except Exception: 

745 pass 

746 elif v.find(",") > 0: 

747 kwargs[k] = v.split(",") 

748 elif isinstance(v, (list, tuple)): 

749 for index in range(0, len(v)): 

750 if v[index] == "": 

751 v[index] = None 

752 elif format_yaml: 

753 try: 

754 v[index] = yaml.safe_load(v[index]) 

755 except Exception: 

756 pass 

757 

758 return indata 

759 except (ValueError, yaml.YAMLError) as exc: 

760 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST) 

761 except KeyError as exc: 

762 raise NbiException( 

763 "Query string error: " + str(exc), HTTPStatus.BAD_REQUEST 

764 ) 

765 except Exception as exc: 

766 raise NbiException(error_text + str(exc), HTTPStatus.BAD_REQUEST) 

767 

768 @staticmethod 

769 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 accept = cherrypy.request.headers.get("Accept") 

778 if data is None: 

779 if accept and "text/html" in accept: 

780 return html.format( 

781 data, cherrypy.request, cherrypy.response, token_info 

782 ) 

783 # cherrypy.response.status = HTTPStatus.NO_CONTENT.value 

784 return 

785 elif hasattr(data, "read"): # file object 

786 if _format: 

787 cherrypy.response.headers["Content-Type"] = _format 

788 elif "b" in data.mode: # binariy asssumig zip 

789 cherrypy.response.headers["Content-Type"] = "application/zip" 

790 else: 

791 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 return data 

794 if accept: 

795 if "text/html" in accept: 

796 return html.format( 

797 data, cherrypy.request, cherrypy.response, token_info 

798 ) 

799 elif "application/yaml" in accept or "*/*" in accept: 

800 pass 

801 elif "application/json" in accept or ( 

802 cherrypy.response.status and cherrypy.response.status >= 300 

803 ): 

804 cherrypy.response.headers[ 

805 "Content-Type" 

806 ] = "application/json; charset=utf-8" 

807 a = json.dumps(data, indent=4) + "\n" 

808 return a.encode("utf8") 

809 cherrypy.response.headers["Content-Type"] = "application/yaml" 

810 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 @cherrypy.expose 

821 def index(self, *args, **kwargs): 

822 token_info = None 

823 try: 

824 if cherrypy.request.method == "GET": 

825 token_info = self.authenticator.authorize() 

826 outdata = token_info # Home page 

827 else: 

828 raise cherrypy.HTTPError( 

829 HTTPStatus.METHOD_NOT_ALLOWED.value, 

830 "Method {} not allowed for tokens".format(cherrypy.request.method), 

831 ) 

832 

833 return self._format_out(outdata, token_info) 

834 

835 except (EngineException, AuthException) as e: 

836 # cherrypy.log("index Exception {}".format(e)) 

837 cherrypy.response.status = e.http_code.value 

838 return self._format_out("Welcome to OSM!", token_info) 

839 

840 @cherrypy.expose 

841 def version(self, *args, **kwargs): 

842 # TODO consider to remove and provide version using the static version file 

843 try: 

844 if cherrypy.request.method != "GET": 

845 raise NbiException( 

846 "Only method GET is allowed", HTTPStatus.METHOD_NOT_ALLOWED 

847 ) 

848 elif args or kwargs: 

849 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 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date} 

855 return self._format_out(osm_nbi_version) 

856 except NbiException as e: 

857 cherrypy.response.status = e.http_code.value 

858 problem_details = { 

859 "code": e.http_code.name, 

860 "status": e.http_code.value, 

861 "detail": str(e), 

862 } 

863 return self._format_out(problem_details, None) 

864 

865 def domain(self): 

866 try: 

867 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 return self._format_out(domains) 

876 except NbiException as e: 

877 cherrypy.response.status = e.http_code.value 

878 problem_details = { 

879 "code": e.http_code.name, 

880 "status": e.http_code.value, 

881 "detail": str(e), 

882 } 

883 return self._format_out(problem_details, None) 

884 

885 @staticmethod 

886 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 cherrypy.request.login = token_info.get("username", "-") 

894 if token_info.get("project_name"): 

895 cherrypy.request.login += "/" + token_info["project_name"] 

896 if token_info.get("id"): 

897 cherrypy.request.login += ";session=" + token_info["id"][0:12] 

898 

899 # NS Fault Management 

900 @cherrypy.expose 

901 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 if topic == "alarms": 

912 try: 

913 method = cherrypy.request.method 

914 role_permission = self._check_valid_url_method( 

915 method, "nsfm", version, topic, None, None, *args 

916 ) 

917 query_string_operations = self._extract_query_string_operations( 

918 kwargs, method 

919 ) 

920 

921 self.authenticator.authorize( 

922 role_permission, query_string_operations, None 

923 ) 

924 

925 # to handle get request 

926 if cherrypy.request.method == "GET": 

927 # if request is on basis of uuid 

928 if uuid and uuid != "None": 

929 try: 

930 alarm = self.engine.db.get_one("alarms", {"uuid": uuid}) 

931 alarm_action = self.engine.db.get_one( 

932 "alarms_action", {"uuid": uuid} 

933 ) 

934 alarm.update(alarm_action) 

935 vnf = self.engine.db.get_one( 

936 "vnfrs", {"nsr-id-ref": alarm["tags"]["ns_id"]} 

937 ) 

938 alarm["vnf-id"] = vnf["_id"] 

939 return self._format_out(str(alarm)) 

940 except Exception: 

941 return self._format_out("Please provide valid alarm uuid") 

942 elif ns_id and ns_id != "None": 

943 # if request is on basis of ns_id 

944 try: 

945 alarms = self.engine.db.get_list( 

946 "alarms", {"tags.ns_id": ns_id} 

947 ) 

948 for alarm in alarms: 

949 alarm_action = self.engine.db.get_one( 

950 "alarms_action", {"uuid": alarm["uuid"]} 

951 ) 

952 alarm.update(alarm_action) 

953 return self._format_out(str(alarms)) 

954 except Exception: 

955 return self._format_out("Please provide valid ns id") 

956 else: 

957 # to return only alarm which are related to given project 

958 project = self.engine.db.get_one( 

959 "projects", {"name": project_name} 

960 ) 

961 project_id = project.get("_id") 

962 ns_list = self.engine.db.get_list( 

963 "nsrs", {"_admin.projects_read": project_id} 

964 ) 

965 ns_ids = [] 

966 for ns in ns_list: 

967 ns_ids.append(ns.get("_id")) 

968 alarms = self.engine.db.get_list("alarms") 

969 alarm_list = [ 

970 alarm 

971 for alarm in alarms 

972 if alarm["tags"]["ns_id"] in ns_ids 

973 ] 

974 for alrm in alarm_list: 

975 action = self.engine.db.get_one( 

976 "alarms_action", {"uuid": alrm.get("uuid")} 

977 ) 

978 alrm.update(action) 

979 return self._format_out(str(alarm_list)) 

980 # to handle patch request for alarm update 

981 elif cherrypy.request.method == "PATCH": 

982 data = yaml.safe_load(cherrypy.request.body) 

983 try: 

984 # check if uuid is valid 

985 self.engine.db.get_one("alarms", {"uuid": data.get("uuid")}) 

986 except Exception: 

987 return self._format_out("Please provide valid alarm uuid.") 

988 if data.get("is_enable") is not None: 

989 if data.get("is_enable"): 

990 alarm_status = "ok" 

991 else: 

992 alarm_status = "disabled" 

993 self.engine.db.set_one( 

994 "alarms", 

995 {"uuid": data.get("uuid")}, 

996 {"alarm_status": alarm_status}, 

997 ) 

998 else: 

999 self.engine.db.set_one( 

1000 "alarms", 

1001 {"uuid": data.get("uuid")}, 

1002 {"threshold": data.get("threshold")}, 

1003 ) 

1004 return self._format_out("Alarm updated") 

1005 except Exception as e: 

1006 if isinstance( 

1007 e, 

1008 ( 

1009 NbiException, 

1010 EngineException, 

1011 DbException, 

1012 FsException, 

1013 MsgException, 

1014 AuthException, 

1015 ValidationError, 

1016 AuthconnException, 

1017 ), 

1018 ): 

1019 http_code_value = cherrypy.response.status = e.http_code.value 

1020 http_code_name = e.http_code.name 

1021 cherrypy.log("Exception {}".format(e)) 

1022 else: 

1023 http_code_value = ( 

1024 cherrypy.response.status 

1025 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR 

1026 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True) 

1027 http_code_name = HTTPStatus.BAD_REQUEST.name 

1028 problem_details = { 

1029 "code": http_code_name, 

1030 "status": http_code_value, 

1031 "detail": str(e), 

1032 } 

1033 return self._format_out(problem_details) 

1034 

1035 @cherrypy.expose 

1036 def token(self, method, token_id=None, kwargs=None): 

1037 token_info = None 

1038 # self.engine.load_dbase(cherrypy.request.app.config) 

1039 indata = self._format_in(kwargs) 

1040 if not isinstance(indata, dict): 

1041 raise NbiException( 

1042 "Expected application/yaml or application/json Content-Type", 

1043 HTTPStatus.BAD_REQUEST, 

1044 ) 

1045 

1046 if method == "GET": 

1047 token_info = self.authenticator.authorize() 

1048 # for logging 

1049 self._format_login(token_info) 

1050 if token_id: 

1051 outdata = self.authenticator.get_token(token_info, token_id) 

1052 else: 

1053 outdata = self.authenticator.get_token_list(token_info) 

1054 elif method == "POST": 

1055 try: 

1056 token_info = self.authenticator.authorize() 

1057 except Exception: 

1058 token_info = None 

1059 if kwargs: 

1060 indata.update(kwargs) 

1061 # This is needed to log the user when authentication fails 

1062 cherrypy.request.login = "{}".format(indata.get("username", "-")) 

1063 outdata = token_info = self.authenticator.new_token( 

1064 token_info, indata, cherrypy.request.remote 

1065 ) 

1066 cherrypy.session["Authorization"] = outdata["_id"] # pylint: disable=E1101 

1067 self._set_location_header("admin", "v1", "tokens", outdata["_id"]) 

1068 # for logging 

1069 self._format_login(token_info) 

1070 # password expiry check 

1071 if self.authenticator.check_password_expiry(outdata): 

1072 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 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 cherrypy.log("{}".format(cef_logger)) 

1090 elif method == "DELETE": 

1091 if not token_id and "id" in kwargs: 

1092 token_id = kwargs["id"] 

1093 elif not token_id: 

1094 token_info = self.authenticator.authorize() 

1095 # for logging 

1096 self._format_login(token_info) 

1097 token_id = token_info["_id"] 

1098 if current_backend != "keystone": 

1099 token_details = self.engine.db.get_one("tokens", {"_id": token_id}) 

1100 current_user = token_details.get("username") 

1101 current_project = token_details.get("project_name") 

1102 else: 

1103 current_user = "keystone backend" 

1104 current_project = "keystone backend" 

1105 outdata = self.authenticator.del_token(token_id) 

1106 token_info = None 

1107 cherrypy.session["Authorization"] = "logout" # pylint: disable=E1101 

1108 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 cherrypy.log("{}".format(cef_logger)) 

1119 # cherrypy.response.cookie["Authorization"] = token_id 

1120 # cherrypy.response.cookie["Authorization"]['expires'] = 0 

1121 else: 

1122 raise NbiException( 

1123 "Method {} not allowed for token".format(method), 

1124 HTTPStatus.METHOD_NOT_ALLOWED, 

1125 ) 

1126 return self._format_out(outdata, token_info) 

1127 

1128 @cherrypy.expose 

1129 def test(self, *args, **kwargs): 

1130 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 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value 

1135 return "test URL is disabled" 

1136 thread_info = None 

1137 if args and args[0] == "help": 

1138 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 elif args and args[0] == "init": 

1144 try: 

1145 # self.engine.load_dbase(cherrypy.request.app.config) 

1146 pid = self.authenticator.create_admin_project() 

1147 self.authenticator.create_admin_user(pid) 

1148 return "Done. User 'admin', password 'admin' created" 

1149 except Exception: 

1150 cherrypy.response.status = HTTPStatus.FORBIDDEN.value 

1151 return self._format_out("Database already initialized") 

1152 elif args and args[0] == "file": 

1153 return cherrypy.lib.static.serve_file( 

1154 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1], 

1155 "text/plain", 

1156 "attachment", 

1157 ) 

1158 elif args and args[0] == "file2": 

1159 f_path = ( 

1160 cherrypy.tree.apps["/osm"].config["storage"]["path"] + "/" + args[1] 

1161 ) 

1162 f = open(f_path, "r") 

1163 cherrypy.response.headers["Content-type"] = "text/plain" 

1164 return f 

1165 

1166 elif len(args) == 2 and args[0] == "db-clear": 

1167 deleted_info = self.engine.db.del_list(args[1], kwargs) 

1168 return "{} {} deleted\n".format(deleted_info["deleted"], args[1]) 

1169 elif len(args) and args[0] == "fs-clear": 

1170 if len(args) >= 2: 

1171 folders = (args[1],) 

1172 else: 

1173 folders = self.engine.fs.dir_ls(".") 

1174 for folder in folders: 

1175 self.engine.fs.file_delete(folder) 

1176 return ",".join(folders) + " folders deleted\n" 

1177 elif args and args[0] == "login": 

1178 if not cherrypy.request.headers.get("Authorization"): 

1179 cherrypy.response.headers[ 

1180 "WWW-Authenticate" 

1181 ] = 'Basic realm="Access to OSM site", charset="UTF-8"' 

1182 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value 

1183 elif args and args[0] == "login2": 

1184 if not cherrypy.request.headers.get("Authorization"): 

1185 cherrypy.response.headers[ 

1186 "WWW-Authenticate" 

1187 ] = 'Bearer realm="Access to OSM site"' 

1188 cherrypy.response.status = HTTPStatus.UNAUTHORIZED.value 

1189 elif args and args[0] == "sleep": 

1190 sleep_time = 5 

1191 try: 

1192 sleep_time = int(args[1]) 

1193 except Exception: 

1194 cherrypy.response.status = HTTPStatus.FORBIDDEN.value 

1195 return self._format_out("Database already initialized") 

1196 thread_info = cherrypy.thread_data 

1197 print(thread_info) 

1198 time.sleep(sleep_time) 

1199 # thread_info 

1200 elif len(args) >= 2 and args[0] == "message": 

1201 main_topic = args[1] 

1202 return_text = "<html><pre>{} ->\n".format(main_topic) 

1203 try: 

1204 if cherrypy.request.method == "POST": 

1205 to_send = yaml.safe_load(cherrypy.request.body) 

1206 for k, v in to_send.items(): 

1207 self.engine.msg.write(main_topic, k, v) 

1208 return_text += " {}: {}\n".format(k, v) 

1209 elif cherrypy.request.method == "GET": 

1210 for k, v in kwargs.items(): 

1211 v_dict = yaml.safe_load(v) 

1212 self.engine.msg.write(main_topic, k, v_dict) 

1213 return_text += " {}: {}\n".format(k, v_dict) 

1214 except Exception as e: 

1215 return_text += "Error: " + str(e) 

1216 return_text += "</pre></html>\n" 

1217 return return_text 

1218 

1219 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 return_text += " length: {}\n".format(cherrypy.request.body.length) 

1234 if cherrypy.request.body.length: 

1235 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 if thread_info: 

1243 return_text += "thread: {}\n".format(thread_info) 

1244 return_text += "</pre></html>" 

1245 return return_text 

1246 

1247 @staticmethod 

1248 def _check_valid_url_method(method, *args): 

1249 if len(args) < 3: 

1250 raise NbiException( 

1251 "URL must contain at least 'main_topic/version/topic'", 

1252 HTTPStatus.METHOD_NOT_ALLOWED, 

1253 ) 

1254 

1255 reference = valid_url_methods 

1256 for arg in args: 

1257 if arg is None: 

1258 break 

1259 if not isinstance(reference, dict): 

1260 raise NbiException( 

1261 "URL contains unexpected extra items '{}'".format(arg), 

1262 HTTPStatus.METHOD_NOT_ALLOWED, 

1263 ) 

1264 

1265 if arg in reference: 

1266 reference = reference[arg] 

1267 elif "<ID>" in reference: 

1268 reference = reference["<ID>"] 

1269 elif "*" in reference: 

1270 # if there is content 

1271 if reference["*"]: 

1272 reference = reference["*"] 

1273 break 

1274 else: 

1275 raise NbiException( 

1276 "Unexpected URL item {}".format(arg), HTTPStatus.METHOD_NOT_ALLOWED 

1277 ) 

1278 if "TODO" in reference and method in reference["TODO"]: 

1279 raise NbiException( 

1280 "Method {} not supported yet for this URL".format(method), 

1281 HTTPStatus.NOT_IMPLEMENTED, 

1282 ) 

1283 elif "METHODS" in reference and method not in reference["METHODS"]: 

1284 raise NbiException( 

1285 "Method {} not supported for this URL".format(method), 

1286 HTTPStatus.METHOD_NOT_ALLOWED, 

1287 ) 

1288 return reference["ROLE_PERMISSION"] + method.lower() 

1289 

1290 @staticmethod 

1291 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 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format( 

1302 main_topic, version, topic, id 

1303 ) 

1304 return 

1305 

1306 @staticmethod 

1307 def _extract_query_string_operations(kwargs, method): 

1308 """ 

1309 

1310 :param kwargs: 

1311 :return: 

1312 """ 

1313 query_string_operations = [] 

1314 if kwargs: 

1315 for qs in ("FORCE", "PUBLIC", "ADMIN", "SET_PROJECT"): 

1316 if qs in kwargs and kwargs[qs].lower() != "false": 

1317 query_string_operations.append(qs.lower() + ":" + method.lower()) 

1318 return query_string_operations 

1319 

1320 @staticmethod 

1321 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 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 if kwargs: 

1345 # FORCE 

1346 if "FORCE" in kwargs: 

1347 if ( 

1348 kwargs["FORCE"].lower() != "false" 

1349 ): # if None or True set force to True 

1350 admin_query["force"] = True 

1351 del kwargs["FORCE"] 

1352 # PUBLIC 

1353 if "PUBLIC" in kwargs: 

1354 if ( 

1355 kwargs["PUBLIC"].lower() != "false" 

1356 ): # if None or True set public to True 

1357 admin_query["public"] = True 

1358 else: 

1359 admin_query["public"] = False 

1360 del kwargs["PUBLIC"] 

1361 # ADMIN 

1362 if "ADMIN" in kwargs: 

1363 behave_as = kwargs.pop("ADMIN") 

1364 if behave_as.lower() != "false": 

1365 if not token_info["admin"]: 

1366 raise NbiException( 

1367 "Only admin projects can use 'ADMIN' query string", 

1368 HTTPStatus.UNAUTHORIZED, 

1369 ) 

1370 if ( 

1371 not behave_as or behave_as.lower() == "true" 

1372 ): # convert True, None to empty list 

1373 admin_query["project_id"] = () 

1374 elif isinstance(behave_as, (list, tuple)): 

1375 admin_query["project_id"] = behave_as 

1376 else: # isinstance(behave_as, str) 

1377 admin_query["project_id"] = (behave_as,) 

1378 if "SET_PROJECT" in kwargs: 

1379 set_project = kwargs.pop("SET_PROJECT") 

1380 if not set_project: 

1381 admin_query["set_project"] = list(admin_query["project_id"]) 

1382 else: 

1383 if isinstance(set_project, str): 

1384 set_project = (set_project,) 

1385 if admin_query["project_id"]: 

1386 for p in set_project: 

1387 if p not in admin_query["project_id"]: 

1388 raise NbiException( 

1389 "Unauthorized for 'SET_PROJECT={p}'. Try with 'ADMIN=True' or " 

1390 "'ADMIN='{p}'".format(p=p), 

1391 HTTPStatus.UNAUTHORIZED, 

1392 ) 

1393 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 if method == "GET": 

1400 if _id: 

1401 admin_query["method"] = "show" 

1402 else: 

1403 admin_query["method"] = "list" 

1404 elif method == "DELETE": 

1405 admin_query["method"] = "delete" 

1406 else: 

1407 admin_query["method"] = "write" 

1408 return admin_query 

1409 

1410 @cherrypy.expose 

1411 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 token_info = None 

1422 outdata = {} 

1423 _format = None 

1424 method = "DONE" 

1425 engine_topic = None 

1426 rollback = [] 

1427 engine_session = None 

1428 url_id = "" 

1429 log_mapping = { 

1430 "POST": "Creating", 

1431 "GET": "Fetching", 

1432 "DELETE": "Deleting", 

1433 "PUT": "Updating", 

1434 "PATCH": "Updating", 

1435 } 

1436 try: 

1437 if not main_topic or not version or not topic: 

1438 raise NbiException( 

1439 "URL must contain at least 'main_topic/version/topic'", 

1440 HTTPStatus.METHOD_NOT_ALLOWED, 

1441 ) 

1442 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 raise NbiException( 

1454 "URL main_topic '{}' not supported".format(main_topic), 

1455 HTTPStatus.METHOD_NOT_ALLOWED, 

1456 ) 

1457 if version != "v1": 

1458 raise NbiException( 

1459 "URL version '{}' not supported".format(version), 

1460 HTTPStatus.METHOD_NOT_ALLOWED, 

1461 ) 

1462 if _id is not None: 

1463 url_id = _id 

1464 

1465 if ( 

1466 kwargs 

1467 and "METHOD" in kwargs 

1468 and kwargs["METHOD"] in ("PUT", "POST", "DELETE", "GET", "PATCH") 

1469 ): 

1470 method = kwargs.pop("METHOD") 

1471 else: 

1472 method = cherrypy.request.method 

1473 

1474 role_permission = self._check_valid_url_method( 

1475 method, main_topic, version, topic, _id, item, *args 

1476 ) 

1477 query_string_operations = self._extract_query_string_operations( 

1478 kwargs, method 

1479 ) 

1480 if main_topic == "admin" and topic == "tokens": 

1481 return self.token(method, _id, kwargs) 

1482 token_info = self.authenticator.authorize( 

1483 role_permission, query_string_operations, _id 

1484 ) 

1485 if main_topic == "admin" and topic == "domains": 

1486 return self.domain() 

1487 engine_session = self._manage_admin_query(token_info, kwargs, method, _id) 

1488 indata = self._format_in(kwargs) 

1489 engine_topic = topic 

1490 

1491 if item and topic != "pm_jobs": 

1492 engine_topic = item 

1493 

1494 if main_topic == "nsd": 

1495 engine_topic = "nsds" 

1496 elif main_topic == "vnfpkgm": 

1497 engine_topic = "vnfds" 

1498 if topic == "vnfpkg_op_occs": 

1499 engine_topic = "vnfpkgops" 

1500 if topic == "vnf_packages" and item == "action": 

1501 engine_topic = "vnfpkgops" 

1502 elif main_topic == "nslcm": 

1503 engine_topic = "nsrs" 

1504 if topic == "ns_lcm_op_occs": 

1505 engine_topic = "nslcmops" 

1506 if topic == "vnfrs" or topic == "vnf_instances": 

1507 engine_topic = "vnfrs" 

1508 elif main_topic == "vnflcm": 

1509 if topic == "vnf_lcm_op_occs": 

1510 engine_topic = "vnflcmops" 

1511 elif main_topic == "nst": 

1512 engine_topic = "nsts" 

1513 elif main_topic == "nsilcm": 

1514 engine_topic = "nsis" 

1515 if topic == "nsi_lcm_op_occs": 

1516 engine_topic = "nsilcmops" 

1517 elif main_topic == "pdu": 

1518 engine_topic = "pdus" 

1519 if ( 

1520 engine_topic == "vims" 

1521 ): # TODO this is for backward compatibility, it will be removed in the future 

1522 engine_topic = "vim_accounts" 

1523 

1524 if topic == "subscriptions": 

1525 engine_topic = main_topic + "_" + topic 

1526 

1527 if method == "GET": 

1528 if item in ( 

1529 "nsd_content", 

1530 "package_content", 

1531 "artifacts", 

1532 "vnfd", 

1533 "nsd", 

1534 "nst", 

1535 "nst_content", 

1536 ): 

1537 if item in ("vnfd", "nsd", "nst"): 

1538 path = "$DESCRIPTOR" 

1539 elif args: 

1540 path = args 

1541 elif item == "artifacts": 

1542 path = () 

1543 else: 

1544 path = None 

1545 file, _format = self.engine.get_file( 

1546 engine_session, 

1547 engine_topic, 

1548 _id, 

1549 path, 

1550 cherrypy.request.headers.get("Accept"), 

1551 ) 

1552 outdata = file 

1553 elif not _id: 

1554 outdata = self.engine.get_item_list( 

1555 engine_session, engine_topic, kwargs, api_req=True 

1556 ) 

1557 else: 

1558 if item == "reports": 

1559 # TODO check that project_id (_id in this context) has permissions 

1560 _id = args[0] 

1561 filter_q = None 

1562 if "vcaStatusRefresh" in kwargs: 

1563 filter_q = {"vcaStatusRefresh": kwargs["vcaStatusRefresh"]} 

1564 outdata = self.engine.get_item( 

1565 engine_session, engine_topic, _id, filter_q, True 

1566 ) 

1567 

1568 elif method == "POST": 

1569 cherrypy.response.status = HTTPStatus.CREATED.value 

1570 if topic in ( 

1571 "ns_descriptors_content", 

1572 "vnf_packages_content", 

1573 "netslice_templates_content", 

1574 ): 

1575 _id = cherrypy.request.headers.get("Transaction-Id") 

1576 if not _id: 

1577 _id, _ = self.engine.new_item( 

1578 rollback, 

1579 engine_session, 

1580 engine_topic, 

1581 {}, 

1582 None, 

1583 cherrypy.request.headers, 

1584 ) 

1585 completed = self.engine.upload_content( 

1586 engine_session, 

1587 engine_topic, 

1588 _id, 

1589 indata, 

1590 kwargs, 

1591 cherrypy.request.headers, 

1592 ) 

1593 if completed: 

1594 self._set_location_header(main_topic, version, topic, _id) 

1595 else: 

1596 cherrypy.response.headers["Transaction-Id"] = _id 

1597 outdata = {"id": _id} 

1598 elif topic == "ns_instances_content": 

1599 # creates NSR 

1600 _id, _ = self.engine.new_item( 

1601 rollback, engine_session, engine_topic, indata, kwargs 

1602 ) 

1603 # creates nslcmop 

1604 indata["lcmOperationType"] = "instantiate" 

1605 indata["nsInstanceId"] = _id 

1606 nslcmop_id, _ = self.engine.new_item( 

1607 rollback, engine_session, "nslcmops", indata, None 

1608 ) 

1609 self._set_location_header(main_topic, version, topic, _id) 

1610 outdata = {"id": _id, "nslcmop_id": nslcmop_id} 

1611 elif topic == "ns_instances" and item: 

1612 indata["lcmOperationType"] = item 

1613 indata["nsInstanceId"] = _id 

1614 _id, _ = self.engine.new_item( 

1615 rollback, engine_session, "nslcmops", indata, kwargs 

1616 ) 

1617 self._set_location_header( 

1618 main_topic, version, "ns_lcm_op_occs", _id 

1619 ) 

1620 outdata = {"id": _id} 

1621 cherrypy.response.status = HTTPStatus.ACCEPTED.value 

1622 elif topic == "netslice_instances_content": 

1623 # creates NetSlice_Instance_record (NSIR) 

1624 _id, _ = self.engine.new_item( 

1625 rollback, engine_session, engine_topic, indata, kwargs 

1626 ) 

1627 self._set_location_header(main_topic, version, topic, _id) 

1628 indata["lcmOperationType"] = "instantiate" 

1629 indata["netsliceInstanceId"] = _id 

1630 nsilcmop_id, _ = self.engine.new_item( 

1631 rollback, engine_session, "nsilcmops", indata, kwargs 

1632 ) 

1633 outdata = {"id": _id, "nsilcmop_id": nsilcmop_id} 

1634 elif topic == "netslice_instances" and item: 

1635 indata["lcmOperationType"] = item 

1636 indata["netsliceInstanceId"] = _id 

1637 _id, _ = self.engine.new_item( 

1638 rollback, engine_session, "nsilcmops", indata, kwargs 

1639 ) 

1640 self._set_location_header( 

1641 main_topic, version, "nsi_lcm_op_occs", _id 

1642 ) 

1643 outdata = {"id": _id} 

1644 cherrypy.response.status = HTTPStatus.ACCEPTED.value 

1645 elif topic == "vnf_packages" and item == "action": 

1646 indata["lcmOperationType"] = item 

1647 indata["vnfPkgId"] = _id 

1648 _id, _ = self.engine.new_item( 

1649 rollback, engine_session, "vnfpkgops", indata, kwargs 

1650 ) 

1651 self._set_location_header( 

1652 main_topic, version, "vnfpkg_op_occs", _id 

1653 ) 

1654 outdata = {"id": _id} 

1655 cherrypy.response.status = HTTPStatus.ACCEPTED.value 

1656 elif topic == "subscriptions": 

1657 _id, _ = self.engine.new_item( 

1658 rollback, engine_session, engine_topic, indata, kwargs 

1659 ) 

1660 self._set_location_header(main_topic, version, topic, _id) 

1661 link = {} 

1662 link["self"] = cherrypy.response.headers["Location"] 

1663 outdata = { 

1664 "id": _id, 

1665 "filter": indata["filter"], 

1666 "callbackUri": indata["CallbackUri"], 

1667 "_links": link, 

1668 } 

1669 cherrypy.response.status = HTTPStatus.CREATED.value 

1670 elif topic == "vnf_instances" and item: 

1671 indata["lcmOperationType"] = item 

1672 indata["vnfInstanceId"] = _id 

1673 _id, _ = self.engine.new_item( 

1674 rollback, engine_session, "vnflcmops", indata, kwargs 

1675 ) 

1676 self._set_location_header( 

1677 main_topic, version, "vnf_lcm_op_occs", _id 

1678 ) 

1679 outdata = {"id": _id} 

1680 cherrypy.response.status = HTTPStatus.ACCEPTED.value 

1681 elif topic == "ns_lcm_op_occs" and item == "cancel": 

1682 indata["nsLcmOpOccId"] = _id 

1683 self.engine.cancel_item( 

1684 rollback, engine_session, "nslcmops", indata, None 

1685 ) 

1686 self._set_location_header(main_topic, version, topic, _id) 

1687 cherrypy.response.status = HTTPStatus.ACCEPTED.value 

1688 else: 

1689 _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 self._set_location_header(main_topic, version, topic, _id) 

1698 outdata = {"id": _id} 

1699 if op_id: 

1700 outdata["op_id"] = op_id 

1701 cherrypy.response.status = HTTPStatus.ACCEPTED.value 

1702 # TODO form NsdInfo when topic in ("ns_descriptors", "vnf_packages") 

1703 

1704 elif method == "DELETE": 

1705 if not _id: 

1706 outdata = self.engine.del_item_list( 

1707 engine_session, engine_topic, kwargs 

1708 ) 

1709 cherrypy.response.status = HTTPStatus.OK.value 

1710 else: # len(args) > 1 

1711 # for NS NSI generate an operation 

1712 op_id = None 

1713 if topic == "ns_instances_content" and not engine_session["force"]: 

1714 nslcmop_desc = { 

1715 "lcmOperationType": "terminate", 

1716 "nsInstanceId": _id, 

1717 "autoremove": True, 

1718 } 

1719 op_id, _ = self.engine.new_item( 

1720 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs 

1721 ) 

1722 if op_id: 

1723 outdata = {"_id": op_id} 

1724 elif ( 

1725 topic == "netslice_instances_content" 

1726 and not engine_session["force"] 

1727 ): 

1728 nsilcmop_desc = { 

1729 "lcmOperationType": "terminate", 

1730 "netsliceInstanceId": _id, 

1731 "autoremove": True, 

1732 } 

1733 op_id, _ = self.engine.new_item( 

1734 rollback, engine_session, "nsilcmops", nsilcmop_desc, None 

1735 ) 

1736 if op_id: 

1737 outdata = {"_id": op_id} 

1738 # if there is not any deletion in process, delete 

1739 if not op_id: 

1740 op_id = self.engine.del_item(engine_session, engine_topic, _id) 

1741 if op_id: 

1742 outdata = {"op_id": op_id} 

1743 cherrypy.response.status = ( 

1744 HTTPStatus.ACCEPTED.value 

1745 if op_id 

1746 else HTTPStatus.NO_CONTENT.value 

1747 ) 

1748 

1749 elif method in ("PUT", "PATCH"): 

1750 op_id = None 

1751 if not indata and not kwargs and not engine_session.get("set_project"): 

1752 raise NbiException( 

1753 "Nothing to update. Provide payload and/or query string", 

1754 HTTPStatus.BAD_REQUEST, 

1755 ) 

1756 if ( 

1757 item in ("nsd_content", "package_content", "nst_content") 

1758 and method == "PUT" 

1759 ): 

1760 completed = self.engine.upload_content( 

1761 engine_session, 

1762 engine_topic, 

1763 _id, 

1764 indata, 

1765 kwargs, 

1766 cherrypy.request.headers, 

1767 ) 

1768 if not completed: 

1769 cherrypy.response.headers["Transaction-Id"] = id 

1770 else: 

1771 op_id = self.engine.edit_item( 

1772 engine_session, engine_topic, _id, indata, kwargs 

1773 ) 

1774 

1775 if op_id: 

1776 cherrypy.response.status = HTTPStatus.ACCEPTED.value 

1777 outdata = {"op_id": op_id} 

1778 else: 

1779 cherrypy.response.status = HTTPStatus.NO_CONTENT.value 

1780 outdata = None 

1781 else: 

1782 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 if topic == "roles" and method != "GET": 

1789 self.authenticator.load_operation_to_allowed_roles() 

1790 

1791 if ( 

1792 topic == "projects" 

1793 and method == "DELETE" 

1794 or topic in ["users", "roles"] 

1795 and method in ["PUT", "PATCH", "DELETE"] 

1796 ): 

1797 self.authenticator.remove_token_from_cache() 

1798 

1799 if item is not None: 

1800 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 cherrypy.log("{}".format(cef_logger)) 

1814 else: 

1815 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 cherrypy.log("{}".format(cef_logger)) 

1829 return self._format_out(outdata, token_info, _format) 

1830 except Exception as e: 

1831 if isinstance( 

1832 e, 

1833 ( 

1834 NbiException, 

1835 EngineException, 

1836 DbException, 

1837 FsException, 

1838 MsgException, 

1839 AuthException, 

1840 ValidationError, 

1841 AuthconnException, 

1842 ), 

1843 ): 

1844 http_code_value = cherrypy.response.status = e.http_code.value 

1845 http_code_name = e.http_code.name 

1846 cherrypy.log("Exception {}".format(e)) 

1847 else: 

1848 http_code_value = ( 

1849 cherrypy.response.status 

1850 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR 

1851 cherrypy.log("CRITICAL: Exception {}".format(e), traceback=True) 

1852 http_code_name = HTTPStatus.BAD_REQUEST.name 

1853 if hasattr(outdata, "close"): # is an open file 

1854 outdata.close() 

1855 error_text = str(e) 

1856 rollback.reverse() 

1857 for rollback_item in rollback: 

1858 try: 

1859 if rollback_item.get("operation") == "set": 

1860 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 elif rollback_item.get("operation") == "del_list": 

1867 self.engine.db.del_list( 

1868 rollback_item["topic"], 

1869 rollback_item["filter"], 

1870 ) 

1871 else: 

1872 self.engine.db.del_one( 

1873 rollback_item["topic"], 

1874 {"_id": rollback_item["_id"]}, 

1875 fail_on_empty=False, 

1876 ) 

1877 except Exception as e2: 

1878 rollback_error_text = "Rollback Exception {}: {}".format( 

1879 rollback_item, e2 

1880 ) 

1881 cherrypy.log(rollback_error_text) 

1882 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 problem_details = { 

1887 "code": http_code_name, 

1888 "status": http_code_value, 

1889 "detail": error_text, 

1890 } 

1891 if item is not None and token_info is not None: 

1892 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 cherrypy.log("{}".format(cef_logger)) 

1907 elif token_info is not None: 

1908 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 cherrypy.log("{}".format(cef_logger)) 

1923 return self._format_out(problem_details, token_info) 

1924 # raise cherrypy.HTTPError(e.http_code.value, str(e)) 

1925 finally: 

1926 if token_info: 

1927 self._format_login(token_info) 

1928 if method in ("PUT", "PATCH", "POST") and isinstance(outdata, dict): 

1929 for logging_id in ("id", "op_id", "nsilcmop_id", "nslcmop_id"): 

1930 if outdata.get(logging_id): 

1931 cherrypy.request.login += ";{}={}".format( 

1932 logging_id, outdata[logging_id][:36] 

1933 ) 

1934 

1935 

1936def _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 cherrypy.log.error("Starting osm_nbi") 

1948 # update general cherrypy configuration 

1949 update_dict = {} 

1950 

1951 engine_config = cherrypy.tree.apps["/osm"].config 

1952 for k, v in environ.items(): 

1953 if k == "OSMNBI_USER_MANAGEMENT": 

1954 feature_state = eval(v.title()) 

1955 engine_config["authentication"]["user_management"] = feature_state 

1956 if not k.startswith("OSMNBI_"): 

1957 continue 

1958 k1, _, k2 = k[7:].lower().partition("_") 

1959 if not k2: 

1960 continue 

1961 try: 

1962 # update static configuration 

1963 if k == "OSMNBI_STATIC_DIR": 

1964 engine_config["/static"]["tools.staticdir.dir"] = v 

1965 engine_config["/static"]["tools.staticdir.on"] = True 

1966 elif k == "OSMNBI_SOCKET_PORT" or k == "OSMNBI_SERVER_PORT": 

1967 update_dict["server.socket_port"] = int(v) 

1968 elif k == "OSMNBI_SOCKET_HOST" or k == "OSMNBI_SERVER_HOST": 

1969 update_dict["server.socket_host"] = v 

1970 elif k1 in ("server", "test", "auth", "log"): 

1971 update_dict[k1 + "." + k2] = v 

1972 elif k1 in ("message", "database", "storage", "authentication"): 

1973 # k2 = k2.replace('_', '.') 

1974 if k2 in ("port", "db_port"): 

1975 engine_config[k1][k2] = int(v) 

1976 else: 

1977 engine_config[k1][k2] = v 

1978 

1979 except ValueError as e: 

1980 cherrypy.log.error("Ignoring environ '{}': " + str(e)) 

1981 except Exception as e: 

1982 cherrypy.log( 

1983 "WARNING: skipping environ '{}' on exception '{}'".format(k, e) 

1984 ) 

1985 

1986 if update_dict: 

1987 cherrypy.config.update(update_dict) 

1988 engine_config["global"].update(update_dict) 

1989 

1990 # logging cherrypy 

1991 log_format_simple = ( 

1992 "%(asctime)s %(levelname)s %(name)s %(filename)s:%(lineno)s %(message)s" 

1993 ) 

1994 log_formatter_simple = logging.Formatter( 

1995 log_format_simple, datefmt="%Y-%m-%dT%H:%M:%S" 

1996 ) 

1997 logger_server = logging.getLogger("cherrypy.error") 

1998 logger_access = logging.getLogger("cherrypy.access") 

1999 logger_cherry = logging.getLogger("cherrypy") 

2000 logger_nbi = logging.getLogger("nbi") 

2001 

2002 if "log.file" in engine_config["global"]: 

2003 file_handler = logging.handlers.RotatingFileHandler( 

2004 engine_config["global"]["log.file"], maxBytes=100e6, backupCount=9, delay=0 

2005 ) 

2006 file_handler.setFormatter(log_formatter_simple) 

2007 logger_cherry.addHandler(file_handler) 

2008 logger_nbi.addHandler(file_handler) 

2009 # log always to standard output 

2010 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 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_) 

2016 log_formatter_cherry = logging.Formatter( 

2017 log_format_cherry, datefmt="%Y-%m-%dT%H:%M:%S" 

2018 ) 

2019 str_handler = logging.StreamHandler() 

2020 str_handler.setFormatter(log_formatter_cherry) 

2021 logger.addHandler(str_handler) 

2022 

2023 if engine_config["global"].get("log.level"): 

2024 logger_cherry.setLevel(engine_config["global"]["log.level"]) 

2025 logger_nbi.setLevel(engine_config["global"]["log.level"]) 

2026 

2027 # logging other modules 

2028 for k1, logname in { 

2029 "message": "nbi.msg", 

2030 "database": "nbi.db", 

2031 "storage": "nbi.fs", 

2032 }.items(): 

2033 engine_config[k1]["logger_name"] = logname 

2034 logger_module = logging.getLogger(logname) 

2035 if "logfile" in engine_config[k1]: 

2036 file_handler = logging.handlers.RotatingFileHandler( 

2037 engine_config[k1]["logfile"], maxBytes=100e6, backupCount=9, delay=0 

2038 ) 

2039 file_handler.setFormatter(log_formatter_simple) 

2040 logger_module.addHandler(file_handler) 

2041 if "loglevel" in engine_config[k1]: 

2042 logger_module.setLevel(engine_config[k1]["loglevel"]) 

2043 # TODO add more entries, e.g.: storage 

2044 cherrypy.tree.apps["/osm"].root.engine.start(engine_config) 

2045 cherrypy.tree.apps["/osm"].root.authenticator.start(engine_config) 

2046 cherrypy.tree.apps["/osm"].root.engine.init_db(target_version=database_version) 

2047 cherrypy.tree.apps["/osm"].root.authenticator.init_db( 

2048 target_version=auth_database_version 

2049 ) 

2050 

2051 cef_logger = cef_event_builder(engine_config["authentication"]) 

2052 

2053 # start subscriptions thread: 

2054 subscription_thread = SubscriptionThread( 

2055 config=engine_config, engine=nbi_server.engine 

2056 ) 

2057 subscription_thread.start() 

2058 # Do not capture except SubscriptionException 

2059 

2060 backend = engine_config["authentication"]["backend"] 

2061 current_backend = backend 

2062 cherrypy.log.error( 

2063 "Starting OSM NBI Version '{} {}' with '{}' authentication backend".format( 

2064 nbi_version, nbi_version_date, backend 

2065 ) 

2066 ) 

2067 

2068 

2069def _stop_service(): 

2070 """ 

2071 Callback function called when cherrypy.engine stops 

2072 TODO: Ending database connections. 

2073 """ 

2074 global subscription_thread 

2075 if subscription_thread: 

2076 subscription_thread.terminate() 

2077 subscription_thread = None 

2078 cherrypy.tree.apps["/osm"].root.engine.stop() 

2079 cherrypy.log.error("Stopping osm_nbi") 

2080 

2081 

2082def 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 nbi_server = Server() 

2102 cherrypy.engine.subscribe("start", _start_service) 

2103 cherrypy.engine.subscribe("stop", _stop_service) 

2104 cherrypy.quickstart(nbi_server, "/osm", config_file) 

2105 

2106 

2107def usage(): 

2108 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 

2120if __name__ == "__main__": 

2121 try: 

2122 # load parameters and configuration 

2123 opts, args = getopt.getopt(sys.argv[1:], "hvc:", ["config=", "help"]) 

2124 # TODO add "log-socket-host=", "log-socket-port=", "log-file=" 

2125 config_file = None 

2126 for o, a in opts: 

2127 if o in ("-h", "--help"): 

2128 usage() 

2129 sys.exit() 

2130 elif o in ("-c", "--config"): 

2131 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 assert False, "Unhandled option" 

2140 if config_file: 

2141 if not path.isfile(config_file): 

2142 print( 

2143 "configuration file '{}' that not exist".format(config_file), 

2144 file=sys.stderr, 

2145 ) 

2146 exit(1) 

2147 else: 

2148 for config_file in ( 

2149 __file__[: __file__.rfind(".")] + ".cfg", 

2150 "./nbi.cfg", 

2151 "/etc/osm/nbi.cfg", 

2152 ): 

2153 if path.isfile(config_file): 

2154 break 

2155 else: 

2156 print( 

2157 "No configuration file 'nbi.cfg' found neither at local folder nor at /etc/osm/", 

2158 file=sys.stderr, 

2159 ) 

2160 exit(1) 

2161 nbi(config_file) 

2162 except getopt.GetoptError as e: 

2163 print(str(e), file=sys.stderr) 

2164 # usage() 

2165 exit(1)