Coverage for osm_nbi/nbi.py: 0%

803 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2024-06-27 02:46 +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 /ns_config_template O O 

64 /<nsConfigTemplateId> O O 

65 /template_content O O 

66 /pnf_descriptors 5 5 

67 /<pnfdInfoId> 5 5 5 

68 /pnfd_content 5 5 

69 /subscriptions 5 5 

70 /<subscriptionId> 5 X 

71 

72 /vnfpkgm/v1 

73 /vnf_packages_content O O 

74 /<vnfPkgId> O O 

75 /vnf_packages O5 O5 

76 /<vnfPkgId> O5 O5 5 

77 /package_content O5 O5 

78 /upload_from_uri X 

79 /vnfd O5 

80 /artifacts[/<artifactPath>] O5 

81 /subscriptions X X 

82 /<subscriptionId> X X 

83 

84 /nslcm/v1 

85 /ns_instances_content O O 

86 /<nsInstanceId> O O 

87 /ns_instances 5 5 

88 /<nsInstanceId> O5 O5 

89 instantiate O5 

90 terminate O5 

91 action O 

92 scale O5 

93 migrate O 

94 update 05 

95 heal O5 

96 /ns_lcm_op_occs 5 5 

97 /<nsLcmOpOccId> 5 5 5 

98 cancel 05 

99 /vnf_instances (also vnfrs for compatibility) O 

100 /<vnfInstanceId> O 

101 /subscriptions 5 5 

102 /<subscriptionId> 5 X 

103 

104 /pdu/v1 

105 /pdu_descriptors O O 

106 /<id> O O O O 

107 

108 /admin/v1 

109 /tokens O O 

110 /<id> O O 

111 /users O O 

112 /<id> O O O O 

113 /projects O O 

114 /<id> O O 

115 /vim_accounts (also vims for compatibility) O O 

116 /<id> O O O 

117 /wim_accounts O O 

118 /<id> O O O 

119 /sdns O O 

120 /<id> O O O 

121 /k8sclusters O O 

122 /<id> O O O 

123 /k8srepos O O 

124 /<id> O O 

125 /osmrepos O O 

126 /<id> O O 

127 

128 /nst/v1 O O 

129 /netslice_templates_content O O 

130 /<nstInfoId> O O O O 

131 /netslice_templates O O 

132 /<nstInfoId> O O O 

133 /nst_content O O 

134 /nst O 

135 /artifacts[/<artifactPath>] O 

136 /subscriptions X X 

137 /<subscriptionId> X X 

138 

139 /nsilcm/v1 

140 /netslice_instances_content O O 

141 /<SliceInstanceId> O O 

142 /netslice_instances O O 

143 /<SliceInstanceId> O O 

144 instantiate O 

145 terminate O 

146 action O 

147 /nsi_lcm_op_occs O O 

148 /<nsiLcmOpOccId> O O O 

149 /subscriptions X X 

150 /<subscriptionId> X X 

151 

152query string: 

153 Follows SOL005 section 4.3.2 It contains extra METHOD to override http method, FORCE to force. 

154 simpleFilterExpr := <attrName>["."<attrName>]*["."<op>]"="<value>[","<value>]* 

155 filterExpr := <simpleFilterExpr>["&"<simpleFilterExpr>]* 

156 op := "eq" | "neq" (or "ne") | "gt" | "lt" | "gte" | "lte" | "cont" | "ncont" 

157 attrName := string 

158 For filtering inside array, it must select the element of the array, or add ANYINDEX to apply the filtering over any 

159 item of the array, that is, pass if any item of the array pass the filter. 

160 It allows both ne and neq for not equal 

161 TODO: 4.3.3 Attribute selectors 

162 all_fields, fields=x,y,.., exclude_default, exclude_fields=x,y,... 

163 (none) … same as “exclude_default” 

164 all_fields … all attributes. 

165 fields=<list> … all attributes except all complex attributes with minimum cardinality of zero that are not 

166 conditionally mandatory, and that are not provided in <list>. 

167 exclude_fields=<list> … all attributes except those complex attributes with a minimum cardinality of zero that 

168 are not conditionally mandatory, and that are provided in <list>. 

169 exclude_default … all attributes except those complex attributes with a minimum cardinality of zero that are not 

170 conditionally mandatory, and that are part of the "default exclude set" defined in the present specification for 

171 the particular resource 

172 exclude_default and include=<list> … all attributes except those complex attributes with a minimum cardinality 

173 of zero that are not conditionally mandatory and that are part of the "default exclude set" defined in the 

174 present specification for the particular resource, but that are not part of <list> 

175 Additionally it admits some administrator values: 

176 FORCE: To force operations skipping dependency checkings 

177 ADMIN: To act as an administrator or a different project 

178 PUBLIC: To get public descriptors or set a descriptor as public 

179 SET_PROJECT: To make a descriptor available for other project 

180 

181Header field name Reference Example Descriptions 

182 Accept IETF RFC 7231 [19] application/json Content-Types that are acceptable for the response. 

183 This header field shall be present if the response is expected to have a non-empty message body. 

184 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the request. 

185 This header field shall be present if the request has a non-empty message body. 

186 Authorization IETF RFC 7235 [22] Bearer mF_9.B5f-4.1JqM The authorization token for the request. 

187 Details are specified in clause 4.5.3. 

188 Range IETF RFC 7233 [21] 1000-2000 Requested range of bytes from a file 

189Header field name Reference Example Descriptions 

190 Content-Type IETF RFC 7231 [19] application/json The MIME type of the body of the response. 

191 This header field shall be present if the response has a non-empty message body. 

192 Location IETF RFC 7231 [19] http://www.example.com/vnflcm/v1/vnf_instances/123 Used in redirection, or when a 

193 new resource has been created. 

194 This header field shall be present if the response status code is 201 or 3xx. 

195 In the present document this header field is also used if the response status code is 202 and a new resource was 

196 created. 

197 WWW-Authenticate IETF RFC 7235 [22] Bearer realm="example" Challenge if the corresponding HTTP request has not 

198 provided authorization, or error details if the corresponding HTTP request has provided an invalid authorization 

199 token. 

200 Accept-Ranges IETF RFC 7233 [21] bytes Used by the Server to signal whether or not it supports ranges for 

201 certain resources. 

202 Content-Range IETF RFC 7233 [21] bytes 21010-47021/ 47022 Signals the byte range that is contained in the 

203 response, and the total length of the file. 

204 Retry-After IETF RFC 7231 [19] Fri, 31 Dec 1999 23:59:59 GMT 

205""" 

206 

207valid_query_string = ("ADMIN", "SET_PROJECT", "FORCE", "PUBLIC") 

208# ^ Contains possible administrative query string words: 

209# ADMIN=True(by default)|Project|Project-list: See all elements, or elements of a project 

210# (not owned by my session project). 

211# PUBLIC=True(by default)|False: See/hide public elements. Set/Unset a topic to be public 

212# FORCE=True(by default)|False: Force edition/deletion operations 

213# SET_PROJECT=Project|Project-list: Add/Delete the topic to the projects portfolio 

214 

215valid_url_methods = { 

216 # contains allowed URL and methods, and the role_permission name 

217 "admin": { 

218 "v1": { 

219 "tokens": { 

220 "METHODS": ("GET", "POST", "DELETE"), 

221 "ROLE_PERMISSION": "tokens:", 

222 "<ID>": {"METHODS": ("GET", "DELETE"), "ROLE_PERMISSION": "tokens:id:"}, 

223 }, 

224 "users": { 

225 "METHODS": ("GET", "POST"), 

226 "ROLE_PERMISSION": "users:", 

227 "<ID>": { 

228 "METHODS": ("GET", "DELETE", "PATCH"), 

229 "ROLE_PERMISSION": "users:id:", 

230 }, 

231 }, 

232 "projects": { 

233 "METHODS": ("GET", "POST"), 

234 "ROLE_PERMISSION": "projects:", 

235 "<ID>": { 

236 "METHODS": ("GET", "DELETE", "PATCH"), 

237 "ROLE_PERMISSION": "projects:id:", 

238 }, 

239 }, 

240 "roles": { 

241 "METHODS": ("GET", "POST"), 

242 "ROLE_PERMISSION": "roles:", 

243 "<ID>": { 

244 "METHODS": ("GET", "DELETE", "PATCH"), 

245 "ROLE_PERMISSION": "roles:id:", 

246 }, 

247 }, 

248 "vims": { 

249 "METHODS": ("GET", "POST"), 

250 "ROLE_PERMISSION": "vims:", 

251 "<ID>": { 

252 "METHODS": ("GET", "DELETE", "PATCH"), 

253 "ROLE_PERMISSION": "vims:id:", 

254 }, 

255 }, 

256 "vim_accounts": { 

257 "METHODS": ("GET", "POST"), 

258 "ROLE_PERMISSION": "vim_accounts:", 

259 "<ID>": { 

260 "METHODS": ("GET", "DELETE", "PATCH"), 

261 "ROLE_PERMISSION": "vim_accounts:id:", 

262 }, 

263 }, 

264 "wim_accounts": { 

265 "METHODS": ("GET", "POST"), 

266 "ROLE_PERMISSION": "wim_accounts:", 

267 "<ID>": { 

268 "METHODS": ("GET", "DELETE", "PATCH"), 

269 "ROLE_PERMISSION": "wim_accounts:id:", 

270 }, 

271 }, 

272 "sdns": { 

273 "METHODS": ("GET", "POST"), 

274 "ROLE_PERMISSION": "sdn_controllers:", 

275 "<ID>": { 

276 "METHODS": ("GET", "DELETE", "PATCH"), 

277 "ROLE_PERMISSION": "sdn_controllers:id:", 

278 }, 

279 }, 

280 "k8sclusters": { 

281 "METHODS": ("GET", "POST"), 

282 "ROLE_PERMISSION": "k8sclusters:", 

283 "<ID>": { 

284 "METHODS": ("GET", "DELETE", "PATCH"), 

285 "ROLE_PERMISSION": "k8sclusters:id:", 

286 }, 

287 }, 

288 "vca": { 

289 "METHODS": ("GET", "POST"), 

290 "ROLE_PERMISSION": "vca:", 

291 "<ID>": { 

292 "METHODS": ("GET", "DELETE", "PATCH"), 

293 "ROLE_PERMISSION": "vca:id:", 

294 }, 

295 }, 

296 "k8srepos": { 

297 "METHODS": ("GET", "POST"), 

298 "ROLE_PERMISSION": "k8srepos:", 

299 "<ID>": { 

300 "METHODS": ("GET", "DELETE"), 

301 "ROLE_PERMISSION": "k8srepos:id:", 

302 }, 

303 }, 

304 "osmrepos": { 

305 "METHODS": ("GET", "POST"), 

306 "ROLE_PERMISSION": "osmrepos:", 

307 "<ID>": { 

308 "METHODS": ("GET", "DELETE", "PATCH"), 

309 "ROLE_PERMISSION": "osmrepos:id:", 

310 }, 

311 }, 

312 "domains": { 

313 "METHODS": ("GET",), 

314 "ROLE_PERMISSION": "domains:", 

315 }, 

316 } 

317 }, 

318 "pdu": { 

319 "v1": { 

320 "pdu_descriptors": { 

321 "METHODS": ("GET", "POST"), 

322 "ROLE_PERMISSION": "pduds:", 

323 "<ID>": { 

324 "METHODS": ("GET", "POST", "DELETE", "PATCH", "PUT"), 

325 "ROLE_PERMISSION": "pduds:id:", 

326 }, 

327 }, 

328 } 

329 }, 

330 "nsd": { 

331 "v1": { 

332 "ns_descriptors_content": { 

333 "METHODS": ("GET", "POST"), 

334 "ROLE_PERMISSION": "nsds:", 

335 "<ID>": { 

336 "METHODS": ("GET", "PUT", "DELETE"), 

337 "ROLE_PERMISSION": "nsds:id:", 

338 }, 

339 }, 

340 "ns_descriptors": { 

341 "METHODS": ("GET", "POST"), 

342 "ROLE_PERMISSION": "nsds:", 

343 "<ID>": { 

344 "METHODS": ("GET", "DELETE", "PATCH"), 

345 "ROLE_PERMISSION": "nsds:id:", 

346 "nsd_content": { 

347 "METHODS": ("GET", "PUT"), 

348 "ROLE_PERMISSION": "nsds:id:content:", 

349 }, 

350 "nsd": { 

351 "METHODS": ("GET",), # descriptor inside package 

352 "ROLE_PERMISSION": "nsds:id:content:", 

353 }, 

354 "artifacts": { 

355 "METHODS": ("GET",), 

356 "ROLE_PERMISSION": "nsds:id:nsd_artifact:", 

357 "*": None, 

358 }, 

359 }, 

360 }, 

361 "ns_config_template": { 

362 "METHODS": ("GET", "POST"), 

363 "ROLE_PERMISSION": "ns_config_template:content:", 

364 "<ID>": { 

365 "METHODS": ("GET", "DELETE"), 

366 "ROLE_PERMISSION": "ns_config_template:id:", 

367 "template_content": { 

368 "METHODS": ("GET", "PUT"), 

369 "ROLE_PERMISSION": "ns_config_template:id:content:", 

370 }, 

371 }, 

372 }, 

373 "pnf_descriptors": { 

374 "TODO": ("GET", "POST"), 

375 "<ID>": { 

376 "TODO": ("GET", "DELETE", "PATCH"), 

377 "pnfd_content": {"TODO": ("GET", "PUT")}, 

378 }, 

379 }, 

380 "subscriptions": { 

381 "TODO": ("GET", "POST"), 

382 "<ID>": {"TODO": ("GET", "DELETE")}, 

383 }, 

384 } 

385 }, 

386 "vnfpkgm": { 

387 "v1": { 

388 "vnf_packages_content": { 

389 "METHODS": ("GET", "POST"), 

390 "ROLE_PERMISSION": "vnfds:", 

391 "<ID>": { 

392 "METHODS": ("GET", "PUT", "DELETE"), 

393 "ROLE_PERMISSION": "vnfds:id:", 

394 }, 

395 }, 

396 "vnf_packages": { 

397 "METHODS": ("GET", "POST"), 

398 "ROLE_PERMISSION": "vnfds:", 

399 "<ID>": { 

400 "METHODS": ("GET", "DELETE", "PATCH"), # GET: vnfPkgInfo 

401 "ROLE_PERMISSION": "vnfds:id:", 

402 "package_content": { 

403 "METHODS": ("GET", "PUT"), # package 

404 "ROLE_PERMISSION": "vnfds:id:", 

405 "upload_from_uri": { 

406 "METHODS": (), 

407 "TODO": ("POST",), 

408 "ROLE_PERMISSION": "vnfds:id:upload:", 

409 }, 

410 }, 

411 "vnfd": { 

412 "METHODS": ("GET",), # descriptor inside package 

413 "ROLE_PERMISSION": "vnfds:id:content:", 

414 }, 

415 "artifacts": { 

416 "METHODS": ("GET",), 

417 "ROLE_PERMISSION": "vnfds:id:vnfd_artifact:", 

418 "*": None, 

419 }, 

420 "action": { 

421 "METHODS": ("POST",), 

422 "ROLE_PERMISSION": "vnfds:id:action:", 

423 }, 

424 }, 

425 }, 

426 "subscriptions": { 

427 "TODO": ("GET", "POST"), 

428 "<ID>": {"TODO": ("GET", "DELETE")}, 

429 }, 

430 "vnfpkg_op_occs": { 

431 "METHODS": ("GET",), 

432 "ROLE_PERMISSION": "vnfds:vnfpkgops:", 

433 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnfds:vnfpkgops:id:"}, 

434 }, 

435 } 

436 }, 

437 "nslcm": { 

438 "v1": { 

439 "ns_instances_content": { 

440 "METHODS": ("GET", "POST"), 

441 "ROLE_PERMISSION": "ns_instances:", 

442 "<ID>": { 

443 "METHODS": ("GET", "DELETE"), 

444 "ROLE_PERMISSION": "ns_instances:id:", 

445 }, 

446 }, 

447 "ns_instances": { 

448 "METHODS": ("GET", "POST"), 

449 "ROLE_PERMISSION": "ns_instances:", 

450 "<ID>": { 

451 "METHODS": ("GET", "DELETE"), 

452 "ROLE_PERMISSION": "ns_instances:id:", 

453 "heal": { 

454 "METHODS": ("POST",), 

455 "ROLE_PERMISSION": "ns_instances:id:heal:", 

456 }, 

457 "scale": { 

458 "METHODS": ("POST",), 

459 "ROLE_PERMISSION": "ns_instances:id:scale:", 

460 }, 

461 "terminate": { 

462 "METHODS": ("POST",), 

463 "ROLE_PERMISSION": "ns_instances:id:terminate:", 

464 }, 

465 "instantiate": { 

466 "METHODS": ("POST",), 

467 "ROLE_PERMISSION": "ns_instances:id:instantiate:", 

468 }, 

469 "migrate": { 

470 "METHODS": ("POST",), 

471 "ROLE_PERMISSION": "ns_instances:id:migrate:", 

472 }, 

473 "action": { 

474 "METHODS": ("POST",), 

475 "ROLE_PERMISSION": "ns_instances:id:action:", 

476 }, 

477 "update": { 

478 "METHODS": ("POST",), 

479 "ROLE_PERMISSION": "ns_instances:id:update:", 

480 }, 

481 "verticalscale": { 

482 "METHODS": ("POST",), 

483 "ROLE_PERMISSION": "ns_instances:id:verticalscale:", 

484 }, 

485 }, 

486 }, 

487 "ns_lcm_op_occs": { 

488 "METHODS": ("GET",), 

489 "ROLE_PERMISSION": "ns_instances:opps:", 

490 "<ID>": { 

491 "METHODS": ("GET",), 

492 "ROLE_PERMISSION": "ns_instances:opps:id:", 

493 "cancel": { 

494 "METHODS": ("POST",), 

495 "ROLE_PERMISSION": "ns_instances:opps:cancel:", 

496 }, 

497 }, 

498 }, 

499 "vnfrs": { 

500 "METHODS": ("GET",), 

501 "ROLE_PERMISSION": "vnf_instances:", 

502 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"}, 

503 }, 

504 "vnf_instances": { 

505 "METHODS": ("GET",), 

506 "ROLE_PERMISSION": "vnf_instances:", 

507 "<ID>": {"METHODS": ("GET",), "ROLE_PERMISSION": "vnf_instances:id:"}, 

508 }, 

509 "subscriptions": { 

510 "METHODS": ("GET", "POST"), 

511 "ROLE_PERMISSION": "ns_subscriptions:", 

512 "<ID>": { 

513 "METHODS": ("GET", "DELETE"), 

514 "ROLE_PERMISSION": "ns_subscriptions:id:", 

515 }, 

516 }, 

517 } 

518 }, 

519 "vnflcm": { 

520 "v1": { 

521 "vnf_instances": { 

522 "METHODS": ("GET", "POST"), 

523 "ROLE_PERMISSION": "vnflcm_instances:", 

524 "<ID>": { 

525 "METHODS": ("GET", "DELETE"), 

526 "ROLE_PERMISSION": "vnflcm_instances:id:", 

527 "scale": { 

528 "METHODS": ("POST",), 

529 "ROLE_PERMISSION": "vnflcm_instances:id:scale:", 

530 }, 

531 "terminate": { 

532 "METHODS": ("POST",), 

533 "ROLE_PERMISSION": "vnflcm_instances:id:terminate:", 

534 }, 

535 "instantiate": { 

536 "METHODS": ("POST",), 

537 "ROLE_PERMISSION": "vnflcm_instances:id:instantiate:", 

538 }, 

539 }, 

540 }, 

541 "vnf_lcm_op_occs": { 

542 "METHODS": ("GET",), 

543 "ROLE_PERMISSION": "vnf_instances:opps:", 

544 "<ID>": { 

545 "METHODS": ("GET",), 

546 "ROLE_PERMISSION": "vnf_instances:opps:id:", 

547 }, 

548 }, 

549 "subscriptions": { 

550 "METHODS": ("GET", "POST"), 

551 "ROLE_PERMISSION": "vnflcm_subscriptions:", 

552 "<ID>": { 

553 "METHODS": ("GET", "DELETE"), 

554 "ROLE_PERMISSION": "vnflcm_subscriptions:id:", 

555 }, 

556 }, 

557 } 

558 }, 

559 "nst": { 

560 "v1": { 

561 "netslice_templates_content": { 

562 "METHODS": ("GET", "POST"), 

563 "ROLE_PERMISSION": "slice_templates:", 

564 "<ID>": { 

565 "METHODS": ("GET", "PUT", "DELETE"), 

566 "ROLE_PERMISSION": "slice_templates:id:", 

567 }, 

568 }, 

569 "netslice_templates": { 

570 "METHODS": ("GET", "POST"), 

571 "ROLE_PERMISSION": "slice_templates:", 

572 "<ID>": { 

573 "METHODS": ("GET", "DELETE"), 

574 "TODO": ("PATCH",), 

575 "ROLE_PERMISSION": "slice_templates:id:", 

576 "nst_content": { 

577 "METHODS": ("GET", "PUT"), 

578 "ROLE_PERMISSION": "slice_templates:id:content:", 

579 }, 

580 "nst": { 

581 "METHODS": ("GET",), # descriptor inside package 

582 "ROLE_PERMISSION": "slice_templates:id:content:", 

583 }, 

584 "artifacts": { 

585 "METHODS": ("GET",), 

586 "ROLE_PERMISSION": "slice_templates:id:content:", 

587 "*": None, 

588 }, 

589 }, 

590 }, 

591 "subscriptions": { 

592 "TODO": ("GET", "POST"), 

593 "<ID>": {"TODO": ("GET", "DELETE")}, 

594 }, 

595 } 

596 }, 

597 "nsilcm": { 

598 "v1": { 

599 "netslice_instances_content": { 

600 "METHODS": ("GET", "POST"), 

601 "ROLE_PERMISSION": "slice_instances:", 

602 "<ID>": { 

603 "METHODS": ("GET", "DELETE"), 

604 "ROLE_PERMISSION": "slice_instances:id:", 

605 }, 

606 }, 

607 "netslice_instances": { 

608 "METHODS": ("GET", "POST"), 

609 "ROLE_PERMISSION": "slice_instances:", 

610 "<ID>": { 

611 "METHODS": ("GET", "DELETE"), 

612 "ROLE_PERMISSION": "slice_instances:id:", 

613 "terminate": { 

614 "METHODS": ("POST",), 

615 "ROLE_PERMISSION": "slice_instances:id:terminate:", 

616 }, 

617 "instantiate": { 

618 "METHODS": ("POST",), 

619 "ROLE_PERMISSION": "slice_instances:id:instantiate:", 

620 }, 

621 "action": { 

622 "METHODS": ("POST",), 

623 "ROLE_PERMISSION": "slice_instances:id:action:", 

624 }, 

625 }, 

626 }, 

627 "nsi_lcm_op_occs": { 

628 "METHODS": ("GET",), 

629 "ROLE_PERMISSION": "slice_instances:opps:", 

630 "<ID>": { 

631 "METHODS": ("GET",), 

632 "ROLE_PERMISSION": "slice_instances:opps:id:", 

633 }, 

634 }, 

635 } 

636 }, 

637 "nspm": { 

638 "v1": { 

639 "pm_jobs": { 

640 "<ID>": { 

641 "reports": { 

642 "<ID>": { 

643 "METHODS": ("GET",), 

644 "ROLE_PERMISSION": "reports:id:", 

645 } 

646 } 

647 }, 

648 }, 

649 }, 

650 }, 

651 "nsfm": { 

652 "v1": { 

653 "alarms": { 

654 "METHODS": ("GET", "PATCH"), 

655 "ROLE_PERMISSION": "alarms:", 

656 "<ID>": { 

657 "METHODS": ("GET", "PATCH"), 

658 "ROLE_PERMISSION": "alarms:id:", 

659 }, 

660 } 

661 }, 

662 }, 

663} 

664 

665 

666class NbiException(Exception): 

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

668 Exception.__init__(self, message) 

669 self.http_code = http_code 

670 

671 

672class Server(object): 

673 instance = 0 

674 # to decode bytes to str 

675 reader = getreader("utf-8") 

676 

677 def __init__(self): 

678 self.instance += 1 

679 self.authenticator = Authenticator(valid_url_methods, valid_query_string) 

680 self.engine = Engine(self.authenticator) 

681 

682 def _format_in(self, kwargs): 

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

684 try: 

685 indata = None 

686 if cherrypy.request.body.length: 

687 error_text = "Invalid input format " 

688 

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

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

691 error_text = "Invalid json format " 

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

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

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

695 error_text = "Invalid yaml format " 

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

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

698 elif ( 

699 "application/binary" in cherrypy.request.headers["Content-Type"] 

700 or "application/gzip" 

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

702 or "application/zip" in cherrypy.request.headers["Content-Type"] 

703 or "text/plain" in cherrypy.request.headers["Content-Type"] 

704 ): 

705 indata = cherrypy.request.body # .read() 

706 elif ( 

707 "multipart/form-data" 

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

709 ): 

710 if "descriptor_file" in kwargs: 

711 filecontent = kwargs.pop("descriptor_file") 

712 if not filecontent.file: 

713 raise NbiException( 

714 "empty file or content", HTTPStatus.BAD_REQUEST 

715 ) 

716 indata = filecontent.file # .read() 

717 if filecontent.content_type.value: 

718 cherrypy.request.headers[ 

719 "Content-Type" 

720 ] = filecontent.content_type.value 

721 else: 

722 # raise cherrypy.HTTPError(HTTPStatus.Not_Acceptable, 

723 # "Only 'Content-Type' of type 'application/json' or 

724 # 'application/yaml' for input format are available") 

725 error_text = "Invalid yaml format " 

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

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

728 else: 

729 error_text = "Invalid yaml format " 

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

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

732 if not indata: 

733 indata = {} 

734 

735 format_yaml = False 

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

737 format_yaml = True 

738 

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

740 if isinstance(v, str): 

741 if v == "": 

742 kwargs[k] = None 

743 elif format_yaml: 

744 try: 

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

746 except Exception: 

747 pass 

748 elif ( 

749 k.endswith(".gt") 

750 or k.endswith(".lt") 

751 or k.endswith(".gte") 

752 or k.endswith(".lte") 

753 ): 

754 try: 

755 kwargs[k] = int(v) 

756 except Exception: 

757 try: 

758 kwargs[k] = float(v) 

759 except Exception: 

760 pass 

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

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

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

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

765 if v[index] == "": 

766 v[index] = None 

767 elif format_yaml: 

768 try: 

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

770 except Exception: 

771 pass 

772 

773 return indata 

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

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

776 except KeyError as exc: 

777 raise NbiException( 

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

779 ) 

780 except Exception as exc: 

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

782 

783 @staticmethod 

784 def _format_out(data, token_info=None, _format=None): 

785 """ 

786 return string of dictionary data according to requested json, yaml, xml. By default json 

787 :param data: response to be sent. Can be a dict, text or file 

788 :param token_info: Contains among other username and project 

789 :param _format: The format to be set as Content-Type if data is a file 

790 :return: None 

791 """ 

792 accept = cherrypy.request.headers.get("Accept") 

793 if data is None: 

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

795 return html.format( 

796 data, cherrypy.request, cherrypy.response, token_info 

797 ) 

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

799 return 

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

801 if _format: 

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

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

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

805 else: 

806 cherrypy.response.headers["Content-Type"] = "text/plain" 

807 # TODO check that cherrypy close file. If not implement pending things to close per thread next 

808 return data 

809 if accept: 

810 if "text/html" in accept: 

811 return html.format( 

812 data, cherrypy.request, cherrypy.response, token_info 

813 ) 

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

815 pass 

816 elif "application/json" in accept or ( 

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

818 ): 

819 cherrypy.response.headers[ 

820 "Content-Type" 

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

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

823 return a.encode("utf8") 

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

825 return yaml.safe_dump( 

826 data, 

827 explicit_start=True, 

828 indent=4, 

829 default_flow_style=False, 

830 tags=False, 

831 encoding="utf-8", 

832 allow_unicode=True, 

833 ) # , canonical=True, default_style='"' 

834 

835 @cherrypy.expose 

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

837 token_info = None 

838 try: 

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

840 token_info = self.authenticator.authorize() 

841 outdata = token_info # Home page 

842 else: 

843 raise cherrypy.HTTPError( 

844 HTTPStatus.METHOD_NOT_ALLOWED.value, 

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

846 ) 

847 

848 return self._format_out(outdata, token_info) 

849 

850 except (EngineException, AuthException) as e: 

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

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

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

854 

855 @cherrypy.expose 

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

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

858 try: 

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

860 raise NbiException( 

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

862 ) 

863 elif args or kwargs: 

864 raise NbiException( 

865 "Invalid URL or query string for version", 

866 HTTPStatus.METHOD_NOT_ALLOWED, 

867 ) 

868 # TODO include version of other modules, pick up from some kafka admin message 

869 osm_nbi_version = {"version": nbi_version, "date": nbi_version_date} 

870 return self._format_out(osm_nbi_version) 

871 except NbiException as e: 

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

873 problem_details = { 

874 "code": e.http_code.name, 

875 "status": e.http_code.value, 

876 "detail": str(e), 

877 } 

878 return self._format_out(problem_details, None) 

879 

880 def domain(self): 

881 try: 

882 domains = { 

883 "user_domain_name": cherrypy.tree.apps["/osm"] 

884 .config["authentication"] 

885 .get("user_domain_name"), 

886 "project_domain_name": cherrypy.tree.apps["/osm"] 

887 .config["authentication"] 

888 .get("project_domain_name"), 

889 } 

890 return self._format_out(domains) 

891 except NbiException as e: 

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

893 problem_details = { 

894 "code": e.http_code.name, 

895 "status": e.http_code.value, 

896 "detail": str(e), 

897 } 

898 return self._format_out(problem_details, None) 

899 

900 @staticmethod 

901 def _format_login(token_info): 

902 """ 

903 Changes cherrypy.request.login to include username/project_name;session so that cherrypy access log will 

904 log this information 

905 :param token_info: Dictionary with token content 

906 :return: None 

907 """ 

908 cherrypy.request.login = token_info.get("username", "-") 

909 if token_info.get("project_name"): 

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

911 if token_info.get("id"): 

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

913 

914 # NS Fault Management 

915 @cherrypy.expose 

916 def nsfm( 

917 self, 

918 version=None, 

919 topic=None, 

920 uuid=None, 

921 project_name=None, 

922 ns_id=None, 

923 *args, 

924 **kwargs 

925 ): 

926 if topic == "alarms": 

927 try: 

928 method = cherrypy.request.method 

929 role_permission = self._check_valid_url_method( 

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

931 ) 

932 query_string_operations = self._extract_query_string_operations( 

933 kwargs, method 

934 ) 

935 

936 self.authenticator.authorize( 

937 role_permission, query_string_operations, None 

938 ) 

939 

940 # to handle get request 

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

942 # if request is on basis of uuid 

943 if uuid and uuid != "None": 

944 try: 

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

946 alarm_action = self.engine.db.get_one( 

947 "alarms_action", {"uuid": uuid} 

948 ) 

949 alarm.update(alarm_action) 

950 vnf = self.engine.db.get_one( 

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

952 ) 

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

954 return self._format_out(str(alarm)) 

955 except Exception: 

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

957 elif ns_id and ns_id != "None": 

958 # if request is on basis of ns_id 

959 try: 

960 alarms = self.engine.db.get_list( 

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

962 ) 

963 for alarm in alarms: 

964 alarm_action = self.engine.db.get_one( 

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

966 ) 

967 alarm.update(alarm_action) 

968 return self._format_out(str(alarms)) 

969 except Exception: 

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

971 else: 

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

973 project = self.engine.db.get_one( 

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

975 ) 

976 project_id = project.get("_id") 

977 ns_list = self.engine.db.get_list( 

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

979 ) 

980 ns_ids = [] 

981 for ns in ns_list: 

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

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

984 alarm_list = [ 

985 alarm 

986 for alarm in alarms 

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

988 ] 

989 for alrm in alarm_list: 

990 action = self.engine.db.get_one( 

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

992 ) 

993 alrm.update(action) 

994 return self._format_out(str(alarm_list)) 

995 # to handle patch request for alarm update 

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

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

998 try: 

999 # check if uuid is valid 

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

1001 except Exception: 

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

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

1004 if data.get("is_enable"): 

1005 alarm_status = "ok" 

1006 else: 

1007 alarm_status = "disabled" 

1008 self.engine.db.set_one( 

1009 "alarms", 

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

1011 {"alarm_status": alarm_status}, 

1012 ) 

1013 else: 

1014 self.engine.db.set_one( 

1015 "alarms", 

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

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

1018 ) 

1019 return self._format_out("Alarm updated") 

1020 except Exception as e: 

1021 if isinstance( 

1022 e, 

1023 ( 

1024 NbiException, 

1025 EngineException, 

1026 DbException, 

1027 FsException, 

1028 MsgException, 

1029 AuthException, 

1030 ValidationError, 

1031 AuthconnException, 

1032 ), 

1033 ): 

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

1035 http_code_name = e.http_code.name 

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

1037 else: 

1038 http_code_value = ( 

1039 cherrypy.response.status 

1040 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR 

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

1042 http_code_name = HTTPStatus.BAD_REQUEST.name 

1043 problem_details = { 

1044 "code": http_code_name, 

1045 "status": http_code_value, 

1046 "detail": str(e), 

1047 } 

1048 return self._format_out(problem_details) 

1049 

1050 @cherrypy.expose 

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

1052 token_info = None 

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

1054 indata = self._format_in(kwargs) 

1055 if not isinstance(indata, dict): 

1056 raise NbiException( 

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

1058 HTTPStatus.BAD_REQUEST, 

1059 ) 

1060 

1061 if method == "GET": 

1062 token_info = self.authenticator.authorize() 

1063 # for logging 

1064 self._format_login(token_info) 

1065 if token_id: 

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

1067 else: 

1068 outdata = self.authenticator.get_token_list(token_info) 

1069 elif method == "POST": 

1070 try: 

1071 token_info = self.authenticator.authorize() 

1072 except Exception: 

1073 token_info = None 

1074 if kwargs: 

1075 indata.update(kwargs) 

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

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

1078 outdata = token_info = self.authenticator.new_token( 

1079 token_info, indata, cherrypy.request.remote 

1080 ) 

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

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

1083 # for logging 

1084 self._format_login(token_info) 

1085 # password expiry check 

1086 if self.authenticator.check_password_expiry(outdata): 

1087 outdata = { 

1088 "id": outdata["id"], 

1089 "message": "change_password", 

1090 "user_id": outdata["user_id"], 

1091 } 

1092 # cherrypy.response.cookie["Authorization"] = outdata["id"] 

1093 # cherrypy.response.cookie["Authorization"]['expires'] = 3600 

1094 cef_event( 

1095 cef_logger, 

1096 { 

1097 "name": "User Login", 

1098 "sourceUserName": token_info.get("username"), 

1099 "message": "User Logged In, Project={} Outcome=Success".format( 

1100 token_info.get("project_name") 

1101 ), 

1102 }, 

1103 ) 

1104 cherrypy.log("{}".format(cef_logger)) 

1105 elif method == "DELETE": 

1106 if not token_id and "id" in kwargs: 

1107 token_id = kwargs["id"] 

1108 elif not token_id: 

1109 token_info = self.authenticator.authorize() 

1110 # for logging 

1111 self._format_login(token_info) 

1112 token_id = token_info["_id"] 

1113 if current_backend != "keystone": 

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

1115 current_user = token_details.get("username") 

1116 current_project = token_details.get("project_name") 

1117 else: 

1118 current_user = "keystone backend" 

1119 current_project = "keystone backend" 

1120 outdata = self.authenticator.del_token(token_id) 

1121 token_info = None 

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

1123 cef_event( 

1124 cef_logger, 

1125 { 

1126 "name": "User Logout", 

1127 "sourceUserName": current_user, 

1128 "message": "User Logged Out, Project={} Outcome=Success".format( 

1129 current_project 

1130 ), 

1131 }, 

1132 ) 

1133 cherrypy.log("{}".format(cef_logger)) 

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

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

1136 else: 

1137 raise NbiException( 

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

1139 HTTPStatus.METHOD_NOT_ALLOWED, 

1140 ) 

1141 return self._format_out(outdata, token_info) 

1142 

1143 @cherrypy.expose 

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

1145 if not cherrypy.config.get("server.enable_test") or ( 

1146 isinstance(cherrypy.config["server.enable_test"], str) 

1147 and cherrypy.config["server.enable_test"].lower() == "false" 

1148 ): 

1149 cherrypy.response.status = HTTPStatus.METHOD_NOT_ALLOWED.value 

1150 return "test URL is disabled" 

1151 thread_info = None 

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

1153 return ( 

1154 "<html><pre>\ninit\nfile/<name> download file\ndb-clear/table\nfs-clear[/folder]\nlogin\nlogin2\n" 

1155 "sleep/<time>\nmessage/topic\n</pre></html>" 

1156 ) 

1157 

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

1159 try: 

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

1161 pid = self.authenticator.create_admin_project() 

1162 self.authenticator.create_admin_user(pid) 

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

1164 except Exception: 

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

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

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

1168 return cherrypy.lib.static.serve_file( 

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

1170 "text/plain", 

1171 "attachment", 

1172 ) 

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

1174 f_path = ( 

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

1176 ) 

1177 f = open(f_path, "r") 

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

1179 return f 

1180 

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

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

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

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

1185 if len(args) >= 2: 

1186 folders = (args[1],) 

1187 else: 

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

1189 for folder in folders: 

1190 self.engine.fs.file_delete(folder) 

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

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

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

1194 cherrypy.response.headers[ 

1195 "WWW-Authenticate" 

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

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

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

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

1200 cherrypy.response.headers[ 

1201 "WWW-Authenticate" 

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

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

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

1205 sleep_time = 5 

1206 try: 

1207 sleep_time = int(args[1]) 

1208 except Exception: 

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

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

1211 thread_info = cherrypy.thread_data 

1212 print(thread_info) 

1213 time.sleep(sleep_time) 

1214 # thread_info 

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

1216 main_topic = args[1] 

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

1218 try: 

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

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

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

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

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

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

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

1226 v_dict = yaml.safe_load(v) 

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

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

1229 except Exception as e: 

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

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

1232 return return_text 

1233 

1234 return_text = ( 

1235 "<html><pre>\nheaders:\n args: {}\n".format(args) 

1236 + " kwargs: {}\n".format(kwargs) 

1237 + " headers: {}\n".format(cherrypy.request.headers) 

1238 + " path_info: {}\n".format(cherrypy.request.path_info) 

1239 + " query_string: {}\n".format(cherrypy.request.query_string) 

1240 + " session: {}\n".format(cherrypy.session) # pylint: disable=E1101 

1241 + " cookie: {}\n".format(cherrypy.request.cookie) 

1242 + " method: {}\n".format(cherrypy.request.method) 

1243 + " session: {}\n".format( 

1244 cherrypy.session.get("fieldname") # pylint: disable=E1101 

1245 ) 

1246 + " body:\n" 

1247 ) 

1248 return_text += " length: {}\n".format(cherrypy.request.body.length) 

1249 if cherrypy.request.body.length: 

1250 return_text += " content: {}\n".format( 

1251 str( 

1252 cherrypy.request.body.read( 

1253 int(cherrypy.request.headers.get("Content-Length", 0)) 

1254 ) 

1255 ) 

1256 ) 

1257 if thread_info: 

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

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

1260 return return_text 

1261 

1262 @staticmethod 

1263 def _check_valid_url_method(method, *args): 

1264 if len(args) < 3: 

1265 raise NbiException( 

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

1267 HTTPStatus.METHOD_NOT_ALLOWED, 

1268 ) 

1269 

1270 reference = valid_url_methods 

1271 for arg in args: 

1272 if arg is None: 

1273 break 

1274 if not isinstance(reference, dict): 

1275 raise NbiException( 

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

1277 HTTPStatus.METHOD_NOT_ALLOWED, 

1278 ) 

1279 

1280 if arg in reference: 

1281 reference = reference[arg] 

1282 elif "<ID>" in reference: 

1283 reference = reference["<ID>"] 

1284 elif "*" in reference: 

1285 # if there is content 

1286 if reference["*"]: 

1287 reference = reference["*"] 

1288 break 

1289 else: 

1290 raise NbiException( 

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

1292 ) 

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

1294 raise NbiException( 

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

1296 HTTPStatus.NOT_IMPLEMENTED, 

1297 ) 

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

1299 raise NbiException( 

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

1301 HTTPStatus.METHOD_NOT_ALLOWED, 

1302 ) 

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

1304 

1305 @staticmethod 

1306 def _set_location_header(main_topic, version, topic, id): 

1307 """ 

1308 Insert response header Location with the URL of created item base on URL params 

1309 :param main_topic: 

1310 :param version: 

1311 :param topic: 

1312 :param id: 

1313 :return: None 

1314 """ 

1315 # Use cherrypy.request.base for absoluted path and make use of request.header HOST just in case behind aNAT 

1316 cherrypy.response.headers["Location"] = "/osm/{}/{}/{}/{}".format( 

1317 main_topic, version, topic, id 

1318 ) 

1319 return 

1320 

1321 @staticmethod 

1322 def _extract_query_string_operations(kwargs, method): 

1323 """ 

1324 

1325 :param kwargs: 

1326 :return: 

1327 """ 

1328 query_string_operations = [] 

1329 if kwargs: 

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

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

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

1333 return query_string_operations 

1334 

1335 @staticmethod 

1336 def _manage_admin_query(token_info, kwargs, method, _id): 

1337 """ 

1338 Processes the administrator query inputs (if any) of FORCE, ADMIN, PUBLIC, SET_PROJECT 

1339 Check that users has rights to use them and returs the admin_query 

1340 :param token_info: token_info rights obtained by token 

1341 :param kwargs: query string input. 

1342 :param method: http method: GET, POSST, PUT, ... 

1343 :param _id: 

1344 :return: admin_query dictionary with keys: 

1345 public: True, False or None 

1346 force: True or False 

1347 project_id: tuple with projects used for accessing an element 

1348 set_project: tuple with projects that a created element will belong to 

1349 method: show, list, delete, write 

1350 """ 

1351 admin_query = { 

1352 "force": False, 

1353 "project_id": (token_info["project_id"],), 

1354 "username": token_info["username"], 

1355 "admin": token_info["admin"], 

1356 "public": None, 

1357 "allow_show_user_project_role": token_info["allow_show_user_project_role"], 

1358 } 

1359 if kwargs: 

1360 # FORCE 

1361 if "FORCE" in kwargs: 

1362 if ( 

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

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

1365 admin_query["force"] = True 

1366 del kwargs["FORCE"] 

1367 # PUBLIC 

1368 if "PUBLIC" in kwargs: 

1369 if ( 

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

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

1372 admin_query["public"] = True 

1373 else: 

1374 admin_query["public"] = False 

1375 del kwargs["PUBLIC"] 

1376 # ADMIN 

1377 if "ADMIN" in kwargs: 

1378 behave_as = kwargs.pop("ADMIN") 

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

1380 if not token_info["admin"]: 

1381 raise NbiException( 

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

1383 HTTPStatus.UNAUTHORIZED, 

1384 ) 

1385 if ( 

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

1387 ): # convert True, None to empty list 

1388 admin_query["project_id"] = () 

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

1390 admin_query["project_id"] = behave_as 

1391 else: # isinstance(behave_as, str) 

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

1393 if "SET_PROJECT" in kwargs: 

1394 set_project = kwargs.pop("SET_PROJECT") 

1395 if not set_project: 

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

1397 else: 

1398 if isinstance(set_project, str): 

1399 set_project = (set_project,) 

1400 if admin_query["project_id"]: 

1401 for p in set_project: 

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

1403 raise NbiException( 

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

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

1406 HTTPStatus.UNAUTHORIZED, 

1407 ) 

1408 admin_query["set_project"] = set_project 

1409 

1410 # PROJECT_READ 

1411 # if "PROJECT_READ" in kwargs: 

1412 # admin_query["project"] = kwargs.pop("project") 

1413 # if admin_query["project"] == token_info["project_id"]: 

1414 if method == "GET": 

1415 if _id: 

1416 admin_query["method"] = "show" 

1417 else: 

1418 admin_query["method"] = "list" 

1419 elif method == "DELETE": 

1420 admin_query["method"] = "delete" 

1421 else: 

1422 admin_query["method"] = "write" 

1423 return admin_query 

1424 

1425 @cherrypy.expose 

1426 def default( 

1427 self, 

1428 main_topic=None, 

1429 version=None, 

1430 topic=None, 

1431 _id=None, 

1432 item=None, 

1433 *args, 

1434 **kwargs 

1435 ): 

1436 token_info = None 

1437 outdata = {} 

1438 _format = None 

1439 method = "DONE" 

1440 engine_topic = None 

1441 rollback = [] 

1442 engine_session = None 

1443 url_id = "" 

1444 log_mapping = { 

1445 "POST": "Creating", 

1446 "GET": "Fetching", 

1447 "DELETE": "Deleting", 

1448 "PUT": "Updating", 

1449 "PATCH": "Updating", 

1450 } 

1451 try: 

1452 if not main_topic or not version or not topic: 

1453 raise NbiException( 

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

1455 HTTPStatus.METHOD_NOT_ALLOWED, 

1456 ) 

1457 if main_topic not in ( 

1458 "admin", 

1459 "vnfpkgm", 

1460 "nsd", 

1461 "nslcm", 

1462 "pdu", 

1463 "nst", 

1464 "nsilcm", 

1465 "nspm", 

1466 "vnflcm", 

1467 ): 

1468 raise NbiException( 

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

1470 HTTPStatus.METHOD_NOT_ALLOWED, 

1471 ) 

1472 if version != "v1": 

1473 raise NbiException( 

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

1475 HTTPStatus.METHOD_NOT_ALLOWED, 

1476 ) 

1477 if _id is not None: 

1478 url_id = _id 

1479 

1480 if ( 

1481 kwargs 

1482 and "METHOD" in kwargs 

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

1484 ): 

1485 method = kwargs.pop("METHOD") 

1486 else: 

1487 method = cherrypy.request.method 

1488 

1489 role_permission = self._check_valid_url_method( 

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

1491 ) 

1492 query_string_operations = self._extract_query_string_operations( 

1493 kwargs, method 

1494 ) 

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

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

1497 token_info = self.authenticator.authorize( 

1498 role_permission, query_string_operations, _id 

1499 ) 

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

1501 return self.domain() 

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

1503 indata = self._format_in(kwargs) 

1504 engine_topic = topic 

1505 

1506 if item and topic != "pm_jobs": 

1507 engine_topic = item 

1508 

1509 if main_topic == "nsd": 

1510 engine_topic = "nsds" 

1511 if topic == "ns_config_template": 

1512 engine_topic = "nsconfigtemps" 

1513 elif main_topic == "vnfpkgm": 

1514 engine_topic = "vnfds" 

1515 if topic == "vnfpkg_op_occs": 

1516 engine_topic = "vnfpkgops" 

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

1518 engine_topic = "vnfpkgops" 

1519 elif main_topic == "nslcm": 

1520 engine_topic = "nsrs" 

1521 if topic == "ns_lcm_op_occs": 

1522 engine_topic = "nslcmops" 

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

1524 engine_topic = "vnfrs" 

1525 elif main_topic == "vnflcm": 

1526 if topic == "vnf_lcm_op_occs": 

1527 engine_topic = "vnflcmops" 

1528 elif main_topic == "nst": 

1529 engine_topic = "nsts" 

1530 elif main_topic == "nsilcm": 

1531 engine_topic = "nsis" 

1532 if topic == "nsi_lcm_op_occs": 

1533 engine_topic = "nsilcmops" 

1534 elif main_topic == "pdu": 

1535 engine_topic = "pdus" 

1536 if ( 

1537 engine_topic == "vims" 

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

1539 engine_topic = "vim_accounts" 

1540 

1541 if topic == "subscriptions": 

1542 engine_topic = main_topic + "_" + topic 

1543 

1544 if method == "GET": 

1545 if item in ( 

1546 "nsd_content", 

1547 "package_content", 

1548 "artifacts", 

1549 "vnfd", 

1550 "nsd", 

1551 "nst", 

1552 "nst_content", 

1553 "ns_config_template", 

1554 ): 

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

1556 path = "$DESCRIPTOR" 

1557 elif args: 

1558 path = args 

1559 elif item == "artifacts": 

1560 path = () 

1561 else: 

1562 path = None 

1563 file, _format = self.engine.get_file( 

1564 engine_session, 

1565 engine_topic, 

1566 _id, 

1567 path, 

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

1569 ) 

1570 outdata = file 

1571 elif not _id: 

1572 outdata = self.engine.get_item_list( 

1573 engine_session, engine_topic, kwargs, api_req=True 

1574 ) 

1575 else: 

1576 if item == "reports": 

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

1578 _id = args[0] 

1579 filter_q = None 

1580 if "vcaStatusRefresh" in kwargs: 

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

1582 outdata = self.engine.get_item( 

1583 engine_session, engine_topic, _id, filter_q, True 

1584 ) 

1585 

1586 elif method == "POST": 

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

1588 if topic in ( 

1589 "ns_descriptors_content", 

1590 "vnf_packages_content", 

1591 "netslice_templates_content", 

1592 "ns_config_template", 

1593 ): 

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

1595 if not _id: 

1596 _id, _ = self.engine.new_item( 

1597 rollback, 

1598 engine_session, 

1599 engine_topic, 

1600 {}, 

1601 None, 

1602 cherrypy.request.headers, 

1603 ) 

1604 completed = self.engine.upload_content( 

1605 engine_session, 

1606 engine_topic, 

1607 _id, 

1608 indata, 

1609 kwargs, 

1610 cherrypy.request.headers, 

1611 ) 

1612 if completed: 

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

1614 else: 

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

1616 outdata = {"id": _id} 

1617 elif topic == "ns_instances_content": 

1618 # creates NSR 

1619 _id, _ = self.engine.new_item( 

1620 rollback, engine_session, engine_topic, indata, kwargs 

1621 ) 

1622 # creates nslcmop 

1623 indata["lcmOperationType"] = "instantiate" 

1624 indata["nsInstanceId"] = _id 

1625 nslcmop_id, _ = self.engine.new_item( 

1626 rollback, engine_session, "nslcmops", indata, None 

1627 ) 

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

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

1630 elif topic == "ns_instances" and item: 

1631 indata["lcmOperationType"] = item 

1632 indata["nsInstanceId"] = _id 

1633 _id, _ = self.engine.new_item( 

1634 rollback, engine_session, "nslcmops", indata, kwargs 

1635 ) 

1636 self._set_location_header( 

1637 main_topic, version, "ns_lcm_op_occs", _id 

1638 ) 

1639 outdata = {"id": _id} 

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

1641 elif topic == "netslice_instances_content": 

1642 # creates NetSlice_Instance_record (NSIR) 

1643 _id, _ = self.engine.new_item( 

1644 rollback, engine_session, engine_topic, indata, kwargs 

1645 ) 

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

1647 indata["lcmOperationType"] = "instantiate" 

1648 indata["netsliceInstanceId"] = _id 

1649 nsilcmop_id, _ = self.engine.new_item( 

1650 rollback, engine_session, "nsilcmops", indata, kwargs 

1651 ) 

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

1653 elif topic == "netslice_instances" and item: 

1654 indata["lcmOperationType"] = item 

1655 indata["netsliceInstanceId"] = _id 

1656 _id, _ = self.engine.new_item( 

1657 rollback, engine_session, "nsilcmops", indata, kwargs 

1658 ) 

1659 self._set_location_header( 

1660 main_topic, version, "nsi_lcm_op_occs", _id 

1661 ) 

1662 outdata = {"id": _id} 

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

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

1665 indata["lcmOperationType"] = item 

1666 indata["vnfPkgId"] = _id 

1667 _id, _ = self.engine.new_item( 

1668 rollback, engine_session, "vnfpkgops", indata, kwargs 

1669 ) 

1670 self._set_location_header( 

1671 main_topic, version, "vnfpkg_op_occs", _id 

1672 ) 

1673 outdata = {"id": _id} 

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

1675 elif topic == "subscriptions": 

1676 _id, _ = self.engine.new_item( 

1677 rollback, engine_session, engine_topic, indata, kwargs 

1678 ) 

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

1680 link = {} 

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

1682 outdata = { 

1683 "id": _id, 

1684 "filter": indata["filter"], 

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

1686 "_links": link, 

1687 } 

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

1689 elif topic == "vnf_instances" and item: 

1690 indata["lcmOperationType"] = item 

1691 indata["vnfInstanceId"] = _id 

1692 _id, _ = self.engine.new_item( 

1693 rollback, engine_session, "vnflcmops", indata, kwargs 

1694 ) 

1695 self._set_location_header( 

1696 main_topic, version, "vnf_lcm_op_occs", _id 

1697 ) 

1698 outdata = {"id": _id} 

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

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

1701 indata["nsLcmOpOccId"] = _id 

1702 self.engine.cancel_item( 

1703 rollback, engine_session, "nslcmops", indata, None 

1704 ) 

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

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

1707 else: 

1708 _id, op_id = self.engine.new_item( 

1709 rollback, 

1710 engine_session, 

1711 engine_topic, 

1712 indata, 

1713 kwargs, 

1714 cherrypy.request.headers, 

1715 ) 

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

1717 outdata = {"id": _id} 

1718 if op_id: 

1719 outdata["op_id"] = op_id 

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

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

1722 

1723 elif method == "DELETE": 

1724 if not _id: 

1725 outdata = self.engine.del_item_list( 

1726 engine_session, engine_topic, kwargs 

1727 ) 

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

1729 else: # len(args) > 1 

1730 # for NS NSI generate an operation 

1731 op_id = None 

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

1733 nslcmop_desc = { 

1734 "lcmOperationType": "terminate", 

1735 "nsInstanceId": _id, 

1736 "autoremove": True, 

1737 } 

1738 op_id, _ = self.engine.new_item( 

1739 rollback, engine_session, "nslcmops", nslcmop_desc, kwargs 

1740 ) 

1741 if op_id: 

1742 outdata = {"_id": op_id} 

1743 elif ( 

1744 topic == "netslice_instances_content" 

1745 and not engine_session["force"] 

1746 ): 

1747 nsilcmop_desc = { 

1748 "lcmOperationType": "terminate", 

1749 "netsliceInstanceId": _id, 

1750 "autoremove": True, 

1751 } 

1752 op_id, _ = self.engine.new_item( 

1753 rollback, engine_session, "nsilcmops", nsilcmop_desc, None 

1754 ) 

1755 if op_id: 

1756 outdata = {"_id": op_id} 

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

1758 if not op_id: 

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

1760 if op_id: 

1761 outdata = {"op_id": op_id} 

1762 cherrypy.response.status = ( 

1763 HTTPStatus.ACCEPTED.value 

1764 if op_id 

1765 else HTTPStatus.NO_CONTENT.value 

1766 ) 

1767 

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

1769 op_id = None 

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

1771 raise NbiException( 

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

1773 HTTPStatus.BAD_REQUEST, 

1774 ) 

1775 if ( 

1776 item 

1777 in ( 

1778 "nsd_content", 

1779 "package_content", 

1780 "nst_content", 

1781 "template_content", 

1782 ) 

1783 and method == "PUT" 

1784 ): 

1785 completed = self.engine.upload_content( 

1786 engine_session, 

1787 engine_topic, 

1788 _id, 

1789 indata, 

1790 kwargs, 

1791 cherrypy.request.headers, 

1792 ) 

1793 if not completed: 

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

1795 else: 

1796 op_id = self.engine.edit_item( 

1797 engine_session, engine_topic, _id, indata, kwargs 

1798 ) 

1799 

1800 if op_id: 

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

1802 outdata = {"op_id": op_id} 

1803 else: 

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

1805 outdata = None 

1806 else: 

1807 raise NbiException( 

1808 "Method {} not allowed".format(method), 

1809 HTTPStatus.METHOD_NOT_ALLOWED, 

1810 ) 

1811 

1812 # if Role information changes, it is needed to reload the information of roles 

1813 if topic == "roles" and method != "GET": 

1814 self.authenticator.load_operation_to_allowed_roles() 

1815 

1816 if ( 

1817 topic == "projects" 

1818 and method == "DELETE" 

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

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

1821 ): 

1822 self.authenticator.remove_token_from_cache() 

1823 

1824 if item is not None: 

1825 cef_event( 

1826 cef_logger, 

1827 { 

1828 "name": "User Operation", 

1829 "sourceUserName": token_info.get("username"), 

1830 "message": "Performing {} operation on {} {}, Project={} Outcome=Success".format( 

1831 item, 

1832 topic, 

1833 url_id, 

1834 token_info.get("project_name"), 

1835 ), 

1836 }, 

1837 ) 

1838 cherrypy.log("{}".format(cef_logger)) 

1839 else: 

1840 cef_event( 

1841 cef_logger, 

1842 { 

1843 "name": "User Operation", 

1844 "sourceUserName": token_info.get("username"), 

1845 "message": "{} {} {}, Project={} Outcome=Success".format( 

1846 log_mapping[method], 

1847 topic, 

1848 url_id, 

1849 token_info.get("project_name"), 

1850 ), 

1851 }, 

1852 ) 

1853 cherrypy.log("{}".format(cef_logger)) 

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

1855 except Exception as e: 

1856 if isinstance( 

1857 e, 

1858 ( 

1859 NbiException, 

1860 EngineException, 

1861 DbException, 

1862 FsException, 

1863 MsgException, 

1864 AuthException, 

1865 ValidationError, 

1866 AuthconnException, 

1867 ), 

1868 ): 

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

1870 http_code_name = e.http_code.name 

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

1872 else: 

1873 http_code_value = ( 

1874 cherrypy.response.status 

1875 ) = HTTPStatus.BAD_REQUEST.value # INTERNAL_SERVER_ERROR 

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

1877 http_code_name = HTTPStatus.BAD_REQUEST.name 

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

1879 outdata.close() 

1880 error_text = str(e) 

1881 rollback.reverse() 

1882 for rollback_item in rollback: 

1883 try: 

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

1885 self.engine.db.set_one( 

1886 rollback_item["topic"], 

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

1888 rollback_item["content"], 

1889 fail_on_empty=False, 

1890 ) 

1891 elif rollback_item.get("operation") == "del_list": 

1892 self.engine.db.del_list( 

1893 rollback_item["topic"], 

1894 rollback_item["filter"], 

1895 ) 

1896 else: 

1897 self.engine.db.del_one( 

1898 rollback_item["topic"], 

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

1900 fail_on_empty=False, 

1901 ) 

1902 except Exception as e2: 

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

1904 rollback_item, e2 

1905 ) 

1906 cherrypy.log(rollback_error_text) 

1907 error_text += ". " + rollback_error_text 

1908 # if isinstance(e, MsgException): 

1909 # error_text = "{} has been '{}' but other modules cannot be informed because an error on bus".format( 

1910 # engine_topic[:-1], method, error_text) 

1911 problem_details = { 

1912 "code": http_code_name, 

1913 "status": http_code_value, 

1914 "detail": error_text, 

1915 } 

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

1917 cef_event( 

1918 cef_logger, 

1919 { 

1920 "name": "User Operation", 

1921 "sourceUserName": token_info.get("username", None), 

1922 "message": "Performing {} operation on {} {}, Project={} Outcome=Failure".format( 

1923 item, 

1924 topic, 

1925 url_id, 

1926 token_info.get("project_name", None), 

1927 ), 

1928 "severity": "2", 

1929 }, 

1930 ) 

1931 cherrypy.log("{}".format(cef_logger)) 

1932 elif token_info is not None: 

1933 cef_event( 

1934 cef_logger, 

1935 { 

1936 "name": "User Operation", 

1937 "sourceUserName": token_info.get("username", None), 

1938 "message": "{} {} {}, Project={} Outcome=Failure".format( 

1939 item, 

1940 topic, 

1941 url_id, 

1942 token_info.get("project_name", None), 

1943 ), 

1944 "severity": "2", 

1945 }, 

1946 ) 

1947 cherrypy.log("{}".format(cef_logger)) 

1948 return self._format_out(problem_details, token_info) 

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

1950 finally: 

1951 if token_info: 

1952 self._format_login(token_info) 

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

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

1955 if outdata.get(logging_id): 

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

1957 logging_id, outdata[logging_id][:36] 

1958 ) 

1959 

1960 

1961def _start_service(): 

1962 """ 

1963 Callback function called when cherrypy.engine starts 

1964 Override configuration with env variables 

1965 Set database, storage, message configuration 

1966 Init database with admin/admin user password 

1967 """ 

1968 global nbi_server 

1969 global subscription_thread 

1970 global cef_logger 

1971 global current_backend 

1972 cherrypy.log.error("Starting osm_nbi") 

1973 # update general cherrypy configuration 

1974 update_dict = {} 

1975 

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

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

1978 if k == "OSMNBI_USER_MANAGEMENT": 

1979 feature_state = eval(v.title()) 

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

1981 elif k == "OSMNBI_PWD_EXPIRE_DAYS": 

1982 pwd_expire_days = int(v) 

1983 engine_config["authentication"]["pwd_expire_days"] = pwd_expire_days 

1984 elif k == "OSMNBI_MAX_PWD_ATTEMPT": 

1985 max_pwd_attempt = int(v) 

1986 engine_config["authentication"]["max_pwd_attempt"] = max_pwd_attempt 

1987 elif k == "OSMNBI_ACCOUNT_EXPIRE_DAYS": 

1988 account_expire_days = int(v) 

1989 engine_config["authentication"]["account_expire_days"] = account_expire_days 

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

1991 continue 

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

1993 if not k2: 

1994 continue 

1995 try: 

1996 # update static configuration 

1997 if k == "OSMNBI_STATIC_DIR": 

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

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

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

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

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

2003 update_dict["server.socket_host"] = v 

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

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

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

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

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

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

2010 else: 

2011 engine_config[k1][k2] = v 

2012 

2013 except ValueError as e: 

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

2015 except Exception as e: 

2016 cherrypy.log( 

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

2018 ) 

2019 

2020 if update_dict: 

2021 cherrypy.config.update(update_dict) 

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

2023 

2024 # logging cherrypy 

2025 log_format_simple = ( 

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

2027 ) 

2028 log_formatter_simple = logging.Formatter( 

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

2030 ) 

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

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

2033 logger_cherry = logging.getLogger("cherrypy") 

2034 logger_nbi = logging.getLogger("nbi") 

2035 

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

2037 file_handler = logging.handlers.RotatingFileHandler( 

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

2039 ) 

2040 file_handler.setFormatter(log_formatter_simple) 

2041 logger_cherry.addHandler(file_handler) 

2042 logger_nbi.addHandler(file_handler) 

2043 # log always to standard output 

2044 for format_, logger in { 

2045 "nbi.server %(filename)s:%(lineno)s": logger_server, 

2046 "nbi.access %(filename)s:%(lineno)s": logger_access, 

2047 "%(name)s %(filename)s:%(lineno)s": logger_nbi, 

2048 }.items(): 

2049 log_format_cherry = "%(asctime)s %(levelname)s {} %(message)s".format(format_) 

2050 log_formatter_cherry = logging.Formatter( 

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

2052 ) 

2053 str_handler = logging.StreamHandler() 

2054 str_handler.setFormatter(log_formatter_cherry) 

2055 logger.addHandler(str_handler) 

2056 

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

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

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

2060 

2061 # logging other modules 

2062 for k1, logname in { 

2063 "message": "nbi.msg", 

2064 "database": "nbi.db", 

2065 "storage": "nbi.fs", 

2066 }.items(): 

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

2068 logger_module = logging.getLogger(logname) 

2069 if "logfile" in engine_config[k1]: 

2070 file_handler = logging.handlers.RotatingFileHandler( 

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

2072 ) 

2073 file_handler.setFormatter(log_formatter_simple) 

2074 logger_module.addHandler(file_handler) 

2075 if "loglevel" in engine_config[k1]: 

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

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

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

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

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

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

2082 target_version=auth_database_version 

2083 ) 

2084 

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

2086 

2087 # start subscriptions thread: 

2088 subscription_thread = SubscriptionThread( 

2089 config=engine_config, engine=nbi_server.engine 

2090 ) 

2091 subscription_thread.start() 

2092 # Do not capture except SubscriptionException 

2093 

2094 backend = engine_config["authentication"]["backend"] 

2095 current_backend = backend 

2096 cherrypy.log.error( 

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

2098 nbi_version, nbi_version_date, backend 

2099 ) 

2100 ) 

2101 

2102 

2103def _stop_service(): 

2104 """ 

2105 Callback function called when cherrypy.engine stops 

2106 TODO: Ending database connections. 

2107 """ 

2108 global subscription_thread 

2109 if subscription_thread: 

2110 subscription_thread.terminate() 

2111 subscription_thread = None 

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

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

2114 

2115 

2116def nbi(config_file): 

2117 global nbi_server 

2118 # conf = { 

2119 # '/': { 

2120 # #'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 

2121 # 'tools.sessions.on': True, 

2122 # 'tools.response_headers.on': True, 

2123 # # 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 

2124 # } 

2125 # } 

2126 # cherrypy.Server.ssl_module = 'builtin' 

2127 # cherrypy.Server.ssl_certificate = "http/cert.pem" 

2128 # cherrypy.Server.ssl_private_key = "http/privkey.pem" 

2129 # cherrypy.Server.thread_pool = 10 

2130 # cherrypy.config.update({'Server.socket_port': config["port"], 'Server.socket_host': config["host"]}) 

2131 

2132 # cherrypy.config.update({'tools.auth_basic.on': True, 

2133 # 'tools.auth_basic.realm': 'localhost', 

2134 # 'tools.auth_basic.checkpassword': validate_password}) 

2135 nbi_server = Server() 

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

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

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

2139 

2140 

2141def usage(): 

2142 print( 

2143 """Usage: {} [options] 

2144 -c|--config [configuration_file]: loads the configuration file (default: ./nbi.cfg) 

2145 -h|--help: shows this help 

2146 """.format( 

2147 sys.argv[0] 

2148 ) 

2149 ) 

2150 # --log-socket-host HOST: send logs to this host") 

2151 # --log-socket-port PORT: send logs using this port (default: 9022)") 

2152 

2153 

2154if __name__ == "__main__": 

2155 try: 

2156 # load parameters and configuration 

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

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

2159 config_file = None 

2160 for o, a in opts: 

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

2162 usage() 

2163 sys.exit() 

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

2165 config_file = a 

2166 # elif o == "--log-socket-port": 

2167 # log_socket_port = a 

2168 # elif o == "--log-socket-host": 

2169 # log_socket_host = a 

2170 # elif o == "--log-file": 

2171 # log_file = a 

2172 else: 

2173 assert False, "Unhandled option" 

2174 if config_file: 

2175 if not path.isfile(config_file): 

2176 print( 

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

2178 file=sys.stderr, 

2179 ) 

2180 exit(1) 

2181 else: 

2182 for config_file in ( 

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

2184 "./nbi.cfg", 

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

2186 ): 

2187 if path.isfile(config_file): 

2188 break 

2189 else: 

2190 print( 

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

2192 file=sys.stderr, 

2193 ) 

2194 exit(1) 

2195 nbi(config_file) 

2196 except getopt.GetoptError as e: 

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

2198 # usage() 

2199 exit(1)