diff options
219 files changed, 8082 insertions, 1584 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..2421e386c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.exclude": { + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true + } +}
\ No newline at end of file diff --git a/cds-ui/designer-client/angular.json b/cds-ui/designer-client/angular.json index 6219bf68a..a84b95e39 100644 --- a/cds-ui/designer-client/angular.json +++ b/cds-ui/designer-client/angular.json @@ -25,21 +25,23 @@ ], "styles": [ "src/styles.css", + "./node_modules/datatables.net-dt/css/jquery.dataTables.css", "./node_modules/bootstrap/dist/css/bootstrap.min.css", "./node_modules/@angular/material/prebuilt-themes/purple-green.css", "./node_modules/font-awesome/css/font-awesome.css", "./node_modules/jointjs/css/layout.css", "./node_modules/jointjs/css/themes/material.css", "./node_modules/jointjs/css/themes/default.css" - ], "scripts": [ + "./node_modules/jquery/dist/jquery.js", + "./node_modules/datatables.net/js/jquery.dataTables.js", + "./node_modules/bootstrap/dist/js/bootstrap.js", "./node_modules/ace-builds/src-min/ace.js", "./node_modules/ace-builds/src-min/theme-eclipse.js", "./node_modules/ace-builds/src-min/theme-tomorrow_night_bright.js", "./node_modules/ace-builds/src-min/mode-json.js", "./node_modules/ace-builds/src-min/mode-javascript.js", - "./node_modules/ace-builds/src-min/mode-python.js", "./node_modules/ace-builds/src-min/mode-xml.js", "./node_modules/ace-builds/src-min/mode-kotlin.js", "./node_modules/ace-builds/src-min/mode-text.js", @@ -52,7 +54,6 @@ "./node_modules/ace-builds/src-min/ext-language_tools.js", "./node_modules/ace-builds/src-min/worker-json.js", "./node_modules/ace-builds/src-min/worker-javascript.js", - "./node_modules/jquery/dist/jquery.js", "./node_modules/lodash/index.js", "./node_modules/backbone/backbone.js", "./node_modules/jointjs/dist/joint.js" @@ -60,12 +61,10 @@ }, "configurations": { "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], "optimization": true, "outputHashing": "all", "sourceMap": false, @@ -75,8 +74,7 @@ "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, - "budgets": [ - { + "budgets": [{ "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" @@ -126,7 +124,7 @@ ], "scripts": [ - + ] } }, @@ -159,4 +157,4 @@ } }, "defaultProject": "designer-client" -} +}
\ No newline at end of file diff --git a/cds-ui/designer-client/package.json b/cds-ui/designer-client/package.json index 22defe502..89e107fec 100644 --- a/cds-ui/designer-client/package.json +++ b/cds-ui/designer-client/package.json @@ -24,14 +24,19 @@ "@angular/router": "~8.2.9", "@ng-bootstrap/ng-bootstrap": "^5.1.1", "angular-animations": "0.0.10", + "angular-datatables": "^8.0.0", "angular-font-awesome": "^3.1.2", "angular-material-expansion-panel": "^0.7.2", "backbone": "^1.4.0", "bootstrap": "^4.3.1", + "dagre": "^0.8.5", + "datatables.net": "^1.10.20", + "datatables.net-dt": "^1.10.20", "file-saver": "^2.0.2", "font-awesome": "^4.7.0", + "graphlib": "^2.1.8", "jointjs": "^3.0.4", - "jquery": "^3.1.1", + "jquery": "^3.4.1", "json2typescript": "^1.2.3", "lodash": "^4.17.15", "ng-sidebar": "^9.1.1", @@ -48,10 +53,11 @@ "@angular/compiler-cli": "~8.2.9", "@angular/language-service": "~8.2.9", "@types/backbone": "^1.4.1", + "@types/datatables.net": "^1.10.18", "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "@types/jointjs": "^2.0.0", - "@types/jquery": "^3.3.31", + "@types/jquery": "^3.3.33", "@types/lodash": "^3.10.1", "@types/node": "~8.9.4", "codelyzer": "^5.0.0", diff --git a/cds-ui/designer-client/proxy.conf.json b/cds-ui/designer-client/proxy.conf.json index 11ed26767..6b81a884a 100644 --- a/cds-ui/designer-client/proxy.conf.json +++ b/cds-ui/designer-client/proxy.conf.json @@ -4,5 +4,11 @@ "secure": false, "logLevel": "debug", "changeOrigin": true + }, + "/resourcedictionary/*": { + "target": "https://localhost:3000", + "secure": false, + "logLevel": "debug", + "changeOrigin": true } } diff --git a/cds-ui/designer-client/src/app/common/constants/app-constants.ts b/cds-ui/designer-client/src/app/common/constants/app-constants.ts index 387c35342..14cb001c8 100644 --- a/cds-ui/designer-client/src/app/common/constants/app-constants.ts +++ b/cds-ui/designer-client/src/app/common/constants/app-constants.ts @@ -106,6 +106,7 @@ export const ResourceDictionaryURLs = { saveResourceDictionary: '/resourcedictionary/save', searchResourceDictionaryByTags: '/resourcedictionary/search', searchResourceDictionaryByName: '', + searchResourceDictionaryByNames: '/resourcedictionary/search/by-names', getSources: '/resourcedictionary/source-mapping', getModelType: '/resourcedictionary/model-type', getResourceDictionary: '/resourcedictionary/model-type/by-definition' @@ -117,3 +118,6 @@ export const ControllerCatalogURLs = { getDefinition: '/controllercatalog/model-type/by-definition', getDerivedFrom: '/controllercatalog/model-type/by-derivedfrom' }; + + +export const ActionElementTypeName = 'app.ActionElement'; diff --git a/cds-ui/designer-client/src/app/common/core/services/api.typed.service.ts b/cds-ui/designer-client/src/app/common/core/services/api.typed.service.ts index 9c9477f33..d4851ded5 100644 --- a/cds-ui/designer-client/src/app/common/core/services/api.typed.service.ts +++ b/cds-ui/designer-client/src/app/common/core/services/api.typed.service.ts @@ -22,9 +22,9 @@ limitations under the License. ============LICENSE_END============================================ */ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders, HttpResponse, HttpHeaderResponse, HttpParams } from '@angular/common/http'; -import { Observable, of } from 'rxjs'; +import {Injectable} from '@angular/core'; +import {HttpClient, HttpParams} from '@angular/common/http'; +import {Observable} from 'rxjs'; @Injectable() export class ApiService<T> { @@ -60,4 +60,8 @@ export class ApiService<T> { const options = {params: httpParams}; return this.httpClient.get<T>(url, options); } + + getCustomized(url: string, params?: any): Observable<any> { + return this.httpClient.get(url, params); + } } diff --git a/cds-ui/designer-client/src/app/common/core/stores/Store.ts b/cds-ui/designer-client/src/app/common/core/stores/Store.ts index 1d5b0afc1..0be804270 100644 --- a/cds-ui/designer-client/src/app/common/core/stores/Store.ts +++ b/cds-ui/designer-client/src/app/common/core/stores/Store.ts @@ -1,4 +1,4 @@ -import {Observable, BehaviorSubject} from 'rxjs'; +import { Observable, BehaviorSubject } from 'rxjs'; import { Injectable } from '@angular/core'; export class Store<T> { @@ -19,4 +19,8 @@ export class Store<T> { this.subject.next(nextState); } + public unsubscribe() { + this.subject.unsubscribe(); + } + } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.html index 804aad057..df1911a7d 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.html @@ -1,7 +1,133 @@ -<app-header> -</app-header> -<p>package-viewing works! +<app-header></app-header> +<div class="new-wrapper"> + <div class="container-fluid main-container"> + <header class="page-title"> + <div class="row"> + <h2 class="col m-0"> + <ul class="breadcrumb-header"> + <li><a routerLink="/packages">CBA Packages</a></li> + <li>Package Name</li> + </ul> + </h2> + <div class="col d-flex justify-content-end header-button-save"> + <button class="float btn btn-sm btn-outline-secondary" (click)="goBacktoDashboard()">Discard + Changes</button> + <button class="float btn btn-sm btn-primary" (click)="editBluePrint()">Apply Changes</button> + </div> + </div> + </header> - {{viewedPackage!.artifactName}}} -</p> + + + <div class="container-fluid body-container"> + + <div class="container"> + <!-- <div class="creat-action-container"> + <a href="#" class="action-button"> + <i class="icon-clone" aria-hidden="true"></i> + <span>Clone</span> + </a> + + <a href="#" class="action-button"> + <i class="icon-archive" aria-hidden="true"></i> + <span>Archive</span> + </a> + + <a href="#" class="action-button delete"> + <i class="icon-delete" aria-hidden="true"></i> + <span>Delete</span> + </a> + </div>--> + + <div class="card creat-card view-package-container"> + <div class="row"> + <div class="col-8"> + <div class="row"> + <div class="col d-flex"> + <i class="package-type-icon icon-designer-mode"></i> + <div class="package-name-container"> + <div class="row"> + <div class="col-12 package-name deployed"> + {{viewedPackage.artifactName}} + <span>.vLB.CDS</span> + <i class="icon-deploy"></i> + </div> + <div class="col-12 package-description"> + Last modified {{ viewedPackage.createdDate | date:'short' }} By + {{viewedPackage.updatedBy}} + </div> + </div> + + </div> + </div> + </div> + </div> + <div class="col-4 package-view-button"> + <button class="btn btn-sm btn-outline-secondary" (click)="deployCurrentPackage()"><i + class="fa fa-play-circle"></i> Deploy</button> + <button class="btn btn-sm btn-outline-secondary" + (click)="downloadPackage(viewedPackage.artifactName,viewedPackage.artifactVersion)"><i + class="fa"></i> Download</button> + + <button class="btn btn-sm btn-primary" (click)="goToDesignerMode()">Designer Mode</button> + </div> + </div> + + </div> + + <nav class="row"> + <!--Nav Tabs--> + <div class="col"> + <div class="nav nav-tabs " id="nav-tab" role="tablist"> + <a class="nav-item nav-link active" id="nav-metadata-tab" data-toggle="tab" + href="#nav-metadata" role="tab" aria-controls="nav-metadata" aria-selected="true" + autofocus #nameit (focusout)="saveMetaData()">METADATA</a> + <a class="nav-item nav-link" id="nav-template-tab" data-toggle="tab" href="#nav-template" + role="tab" aria-controls="nav-template" aria-selected="false">TEMPLATE & MAPPING</a> + <a class="nav-item nav-link" id="nav-scripts-tab" data-toggle="tab" href="#nav-scripts" + role="tab" aria-controls="nav-scripts" aria-selected="false">SCRIPTS</a> + <a class="nav-item nav-link" id="nav-imports-tab" data-toggle="tab" href="#nav-imports" + role="tab" aria-controls="nav-imports" aria-selected="false">IMPORTS</a> + <a class="nav-item nav-link" id="nav-authentication-tab" data-toggle="tab" + href="#nav-authentication" role="tab" aria-controls="nav-authentication" + aria-selected="false">EXTERNAL SYSTEM AUTHENTICATION PROPERTIES</a> + </div> + </div> + + </nav> + <div class="row mt-4"> + <div class="col"> + <div class="tab-content" id="nav-tabContent"> + <div class="tab-pane fade show active" id="nav-metadata" role="tabpanel" + aria-labelledby="nav-metadata-tab"> + <app-metadata-tab></app-metadata-tab> + </div> + <div class="tab-pane fade" id="nav-template" role="tabpanel" + aria-labelledby="nav-template-tab"> + <app-template-mapping></app-template-mapping> + </div> + <div class="tab-pane fade" id="nav-scripts" role="tabpanel" + aria-labelledby="nav-scripts-tab"> + <app-scripts-tab></app-scripts-tab> + </div> + <div class="tab-pane fade" id="nav-imports" role="tabpanel" + aria-labelledby="nav-imports-tab"> + <app-imports-tab></app-imports-tab> + + </div> + <div class="tab-pane fade" id="nav-authentication" role="tabpanel" + aria-labelledby="nav-authentication-tab"> + <div class="card creat-card"> + <div class="editor-container"> + <app-dsl-definitions-tab></app-dsl-definitions-tab> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +</div> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.ts index 84fdafb36..6de76f949 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.component.ts @@ -1,8 +1,18 @@ -import {Component, OnInit} from '@angular/core'; -import {ActivatedRoute} from '@angular/router'; -import {PackageStore} from './package.store'; +import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; import {BluePrintDetailModel} from '../model/BluePrint.detail.model'; - +import {PackageCreationStore} from '../package-creation/package-creation.store'; +import {FilesContent, FolderNodeElement, MetaDataTabModel} from '../package-creation/mapping-models/metadata/MetaDataTab.model'; +import {MetadataTabComponent} from '../package-creation/metadata-tab/metadata-tab.component'; +import * as JSZip from 'jszip'; +import {ConfigurationDashboardService} from './configuration-dashboard.service'; +import {VlbDefinition} from '../package-creation/mapping-models/definitions/VlbDefinition'; +import {DslDefinition} from '../package-creation/mapping-models/CBAPacakge.model'; +import {PackageCreationUtils} from '../package-creation/package-creation.utils'; +import {PackageCreationModes} from '../package-creation/creationModes/PackageCreationModes'; +import {PackageCreationBuilder} from '../package-creation/creationModes/PackageCreationBuilder'; +import {saveAs} from 'file-saver'; +import {DesignerStore} from '../designer/designer.store'; @Component({ selector: 'app-configuration-dashboard', @@ -11,24 +21,192 @@ import {BluePrintDetailModel} from '../model/BluePrint.detail.model'; }) export class ConfigurationDashboardComponent implements OnInit { viewedPackage: BluePrintDetailModel = new BluePrintDetailModel(); + @ViewChild(MetadataTabComponent, {static: false}) + private metadataTabComponent: MetadataTabComponent; + + entryDefinitionKeys: string[] = ['template_tags', 'user-groups', + 'author-email', 'template_version', 'template_name', 'template_author']; + @ViewChild('nameit', {static: true}) + private elementRef: ElementRef; + + private zipFile: JSZip = new JSZip(); + private filesData: any = []; + private folder: FolderNodeElement = new FolderNodeElement(); - constructor(private route: ActivatedRoute, private configurationStore: PackageStore) { + private currentBlob = new Blob(); + + constructor(private route: ActivatedRoute, private configurationDashboardService: ConfigurationDashboardService, + private packageCreationStore: PackageCreationStore, + private packageCreationUtils: PackageCreationUtils, + private router: Router, + private designerStore: DesignerStore) { + } + ngOnInit() { + this.elementRef.nativeElement.focus(); const id = this.route.snapshot.paramMap.get('id'); - this.configurationStore.getPagedPackages(id); - this.configurationStore.state$.subscribe( - el => { - if (el && el.configuration) { - this.viewedPackage = el.configuration; + this.configurationDashboardService.getPagedPackages(id).subscribe( + (bluePrintDetailModels) => { + if (bluePrintDetailModels) { + this.viewedPackage = bluePrintDetailModels[0]; + this.downloadCBAPackage(bluePrintDetailModels); } + }); + } + + + private downloadCBAPackage(bluePrintDetailModels: BluePrintDetailModel) { + this.configurationDashboardService.downloadResource( + bluePrintDetailModels[0].artifactName + '/' + bluePrintDetailModels[0].artifactVersion).subscribe(response => { + const blob = new Blob([response], {type: 'application/octet-stream'}); + this.currentBlob = blob; + this.zipFile.loadAsync(blob).then((zip) => { + Object.keys(zip.files).forEach((filename) => { + console.log(filename); + zip.files[filename].async('string').then((fileData) => { + if (fileData) { + if (filename.includes('Scripts/')) { + this.setScripts(filename, fileData); + } else if (filename.includes('Templates/')) { + if (filename.includes('-mapping.')) { + this.setMapping(filename, fileData); + } else if (filename.includes('-template.')) { + this.setTemplates(filename, fileData); + } + } else if (filename.includes('Definitions/')) { + this.setImports(filename, fileData); + } else if (filename.includes('TOSCA-Metadata/')) { + const metaDataTabInfo: MetaDataTabModel = this.getMetaDataTabInfo(fileData); + // console.log(metaDataTabInfo); + this.setMetaData(metaDataTabInfo, bluePrintDetailModels[0]); + } + } + }); + }); + }); + }); + } + + private setScripts(filename: string, fileData: any) { + this.packageCreationStore.addScripts(filename, fileData); + } + + private setImports(filename: string, fileData: any) { + if (filename.includes('blueprint.json') || filename.includes('vLB_CDS.json')) { + let definition = new VlbDefinition(); + definition = fileData as VlbDefinition; + definition = JSON.parse(fileData); + const dslDefinition = new DslDefinition(); + dslDefinition.content = this.packageCreationUtils.transformToJson(definition.dsl_definitions); + const mapOfCustomKeys = new Map<string, string>(); + for (const metadataKey in definition.metadata) { + if (!this.entryDefinitionKeys.includes(metadataKey + '')) { + mapOfCustomKeys.set(metadataKey + '', definition.metadata[metadataKey + '']); + } + } + this.packageCreationStore.changeDslDefinition(dslDefinition); + this.packageCreationStore.setCustomKeys(mapOfCustomKeys); + // console.log(definition.topology_template.content); + if (definition.topology_template.content) { + this.designerStore.saveSourceContent(definition.topology_template.content); } - ); + } else { + this.packageCreationStore.addDefinition(filename, fileData); + } + } + private setTemplates(filename: string, fileData: any) { + this.packageCreationStore.addTemplate(filename, fileData); } - ngOnInit() { + private setMapping(fileName: string, fileData: string) { + this.packageCreationStore.addMapping(fileName, fileData); + } + + editBluePrint() { + this.packageCreationStore.state$.subscribe( + cbaPackage => { + console.log(cbaPackage); + FilesContent.clear(); + let packageCreationModes: PackageCreationModes; + cbaPackage = PackageCreationModes.mapModeType(cbaPackage); + cbaPackage.metaData = PackageCreationModes.setEntryPoint(cbaPackage.metaData); + packageCreationModes = PackageCreationBuilder.getCreationMode(cbaPackage); + packageCreationModes.execute(cbaPackage, this.packageCreationUtils); + this.filesData.push(this.folder.TREE_DATA); + this.saveBluePrintToDataBase(); + }); + } + + private setMetaData(metaDataObject: MetaDataTabModel, bluePrintDetailModel: BluePrintDetailModel) { + metaDataObject.description = bluePrintDetailModel.artifactDescription; + this.packageCreationStore.changeMetaData(metaDataObject); + + } + + saveMetaData() { + this.metadataTabComponent.saveMetaDataToStore(); + } + + getMetaDataTabInfo(fileData: string) { + const metaDataTabModel = new MetaDataTabModel(); + const arrayOfLines = fileData.split('\n'); + metaDataTabModel.entryFileName = arrayOfLines[3].split(':')[1]; + metaDataTabModel.name = arrayOfLines[4].split(':')[1]; + metaDataTabModel.version = arrayOfLines[5].split(':')[1]; + metaDataTabModel.mode = arrayOfLines[6].split(':')[1]; + metaDataTabModel.templateTags = new Set<string>(arrayOfLines[7].split(':')[1].split(',')); + console.log(metaDataTabModel.mode); + return metaDataTabModel; + } + saveBluePrintToDataBase() { + this.create(); + this.zipFile.generateAsync({type: 'blob'}) + .then(blob => { + this.packageCreationStore.saveBluePrint(blob); + this.router.navigate(['/packages']); + }); } + + create() { + FilesContent.getMapOfFilesNamesAndContent().forEach((value, key) => { + this.zipFile.folder(key.split('/')[0]); + this.zipFile.file(key, value); + }); + + } + + goBacktoDashboard() { + this.router.navigate(['/packages']); + } + + downloadPackage(artifactName: string, artifactVersion: string) { + this.configurationDashboardService.downloadResource(artifactName + '/' + artifactVersion).subscribe(response => { + const blob = new Blob([response], {type: 'application/octet-stream'}); + saveAs(blob, artifactName + '-' + artifactVersion + '-CBA.zip'); + }); + } + + deployCurrentPackage() { + console.log('happened'); + /* this.zipFile.generateAsync({type: 'blob'}) + .then(blob => { + const formData = new FormData(); + formData.append('file', this.currentBlob); + this.configurationDashboardService.deployPost(formData) + .subscribe(data => { + }, error => { + }); + this.router.navigate(['/packages']); + }); + */ + this.router.navigate(['/packages']); + } + + goToDesignerMode() { + this.router.navigate(['/packages/designer']); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.service.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.service.ts index 51d0e9db1..164d76601 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.service.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/configuration-dashboard.service.ts @@ -9,14 +9,23 @@ import {BluePrintDetailModel} from '../model/BluePrint.detail.model'; providedIn: 'root' }) export class ConfigurationDashboardService { - - constructor(private api: ApiService<BluePrintDetailModel>) { } - getBluePrintModel(id: string): Observable<BluePrintDetailModel> { + private getBluePrintModel(id: string): Observable<BluePrintDetailModel> { return this.api.getOne(BlueprintURLs.getOneBlueprint + '/' + id); + } + + getPagedPackages(id: string) { + return this.getBluePrintModel(id); + } + + public downloadResource(path: string) { + return this.api.getCustomized(BlueprintURLs.download + path, {responseType: 'blob'}); + } + deployPost(body: any | null): Observable<any> { + return this.api.post(BlueprintURLs.deploy, body, {responseType: 'text'}); } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.css index 799407093..37a6f9235 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.css +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.css @@ -268,7 +268,7 @@ p.compType-4{ color: #fff; } .actionBtns .btn:last-child{ - padding-left: 34px; + padding-left: 34px !important; background: url(src/assets/img/icon-import-blue.svg) 12px center #fff no-repeat; border: solid 1px #D0DFF1; color: #1B3E6F; @@ -279,6 +279,8 @@ p.compType-4{ } .componentsList{ padding-bottom: 0; + height: calc( 100vh - 218px)!important; + overflow: scroll; } .custom-control.custom-checkbox:hover, .custom-control-label:hover{ @@ -342,11 +344,11 @@ p.compType-4{ /*CANVAS*/ .editBar{ - width: 350px; + width: 200px; margin: 0 auto 0; padding: 6px 10px; background:#F4F9FE; - border: solid 1px #E8EFF8; + /* border: solid 1px #E8EFF8; */ box-shadow: 0 2px 6px rgba(47, 83, 151, .1); } .editBar .btn-group{ @@ -366,7 +368,7 @@ p.compType-4{ } .viewBtns .btn{ background-position: 10px center; - padding-left: 30px; + padding-left: 30px!important; } .viewBtns .topologySource{ background-image: url(src/assets/img/icon-topologyView-active.svg); @@ -548,3 +550,30 @@ p.compType-4{ font-size: 10px; } +.source-button{ + position: absolute; + z-index: 9999999; + top: 69px; + left: 50%; +} +/*jointjs paper*/ +/* #board-paper { + position: relative; + border: 1px solid gray; + display: inline-block; + background: transparent; + overflow: hidden; +} +#board-paper svg { + background: transparent; +} +#board-paper svg .link { + z-index: 2; +} +.html-element { + position: absolute; + background: #F4F9FE; + pointer-events: none; + -webkit-user-select: none; + z-index: 2; +} */ diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.html index 8ec735aec..1a2219bab 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.html @@ -55,26 +55,27 @@ </div> </div> </header> +<div class="source-button editBar"> + <div class="btn-group viewBtns" role="group"> + <button type="button" class="btn btn-secondary topologySource active">Designer</button> + <button [routerLink]="['/designer/source']" type="button" class="btn btn-secondary topologyView">Scripting</button> + </div> +</div> <ng-sidebar-container class="sidebar-container"> <!-- Controller SideBar --> <ng-sidebar [(opened)]="controllerSideBar" [sidebarClass]="'demo-sidebar controllerSidebar container-fluid'" [mode]="'push'" #sidebarLeft> <div class="row"> - <div class="col-12 p-0"> - <form> - <input type="text" class="form-control input-search-controller" - placeholder="Search actions and functions"> - </form> - </div> + <h1 class="col-12">Actions</h1> <div class="col-12 text-center p-0"> <div class="btn-group actionBtns" role="group"> <button (click)="insertCustomActionIntoBoard()" type="button" class="btn">Insert Custom</button> - <button type="button" class="btn">Import Action</button> + <!-- <button type="button" class="btn">Import Action</button> --> </div> </div> - <div class="col-12 actionsList"> + <!-- <div class="col-12 actionsList"> <b>Select from other packages:</b> <div class="actions-scroll"> <div class="custom-control custom-checkbox"> @@ -106,32 +107,16 @@ <button type="button" class="btn btn-secondary mr-3">Insert</button> <button type="button" class="btn btn-secondary">Cancel</button> </div> - </div> + </div> --> <h1 class="col-12">Functions</h1> + <b>Drag and drop function to Action’s box</b> <div id="palette-paper" class="col-12 componentsList"> - <b>Drag and drop function to Action’s box</b> - <ul class="list-group actions-scroll"> - <!-- <li class="list-group-item" *ngFor="let function of viewedFunctions"> - <p class="compType-1">{{function.modelName}}</p> - </li> --> - <li class="list-group-item"> - <p class="compType-2">component-netconf-executor</p> - </li> - <li class="list-group-item"> - <p class="compType-3">component-remote-ansible-executor</p> - </li> - <li class="list-group-item"> - <p class="compType-4">dg-generic</p> - </li> - <li class="list-group-item"> - <p class="compType-1">component-resource-resolution</p> - </li> - </ul> </div> </div> </ng-sidebar> <!-- Page content --> + <div ng-sidebar-content id="board-paper"> <button class="rotate" (click)="_toggleSidebar1()"> <span> @@ -139,6 +124,7 @@ <i class="fa fa-angle-double-left"></i> </span> </button> + <!-- Canvas --> <div class="editBar text-center"> <div class="btn-group mr-2" role="group" aria-label="First group"> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts index b19f5699b..5adce7ea0 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.component.ts @@ -1,9 +1,39 @@ -import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +/* +============LICENSE_START========================================== +=================================================================== +Copyright (C) 2019 Orange. All rights reserved. +=================================================================== + +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the License); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +============LICENSE_END============================================ +*/ + +import dagre from 'dagre'; +import graphlib from 'graphlib'; +import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; import * as joint from 'jointjs'; import './jointjs/elements/palette.function.element'; import './jointjs/elements/action.element'; import './jointjs/elements/board.function.element'; - +import { DesignerStore } from './designer.store'; +import { ActionElementTypeName } from 'src/app/common/constants/app-constants'; +import { GraphUtil } from './graph.util'; +import { GraphGenerator } from './graph.generator.util'; +import { FunctionsStore } from './functions.store'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged } from 'rxjs/operators'; @Component({ @@ -12,23 +42,26 @@ import './jointjs/elements/board.function.element'; styleUrls: ['./designer.component.css'], encapsulation: ViewEncapsulation.None }) -export class DesignerComponent implements OnInit { +export class DesignerComponent implements OnInit, OnDestroy { private controllerSideBar: boolean; private attributesSideBar: boolean; - //to generate Ids for dragged function elements - private fuctionIdCounter=0; - private actionIdCounter=0; boardGraph: joint.dia.Graph; boardPaper: joint.dia.Paper; paletteGraph: joint.dia.Graph; palettePaper: joint.dia.Paper; + private ngUnsubscribe = new Subject(); + private opt = { tx: 100, ty: 100 }; - constructor() { + constructor(private designerStore: DesignerStore, + private functionStore: FunctionsStore, + private graphUtil: GraphUtil, + private graphGenerator: GraphGenerator) { this.controllerSideBar = true; this.attributesSideBar = false; + } private _toggleSidebar1() { this.controllerSideBar = !this.controllerSideBar; @@ -53,23 +86,66 @@ export class DesignerComponent implements OnInit { ngOnInit() { this.initializeBoard(); this.initializePalette(); - // this.createEditBarOverThePaper(); - const list = [ - { modelName: 'component-netconf-executor'}, - { modelName: 'component-remote-ansible-executor' }, - { modelName: 'dg-generic' }, - { modelName: 'component-resource-resolution' }]; + this.stencilPaperEventListeners(); - const cells = this.buildPaletteGraphFromList(list); - this.paletteGraph.resetCells(cells); + /** + * the code to retrieve from server is commented + */ + this.functionStore.state$ + .pipe( + distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)), + takeUntil(this.ngUnsubscribe)) + .subscribe(state => { + + if (state.serverFunctions) { + console.log('inside subscriotn on functions store -->', state.serverFunctions); + console.log(state); + // this.viewedFunctions = state.functions; + const list = state.serverFunctions; + + const cells = this.graphUtil.buildPaletteGraphFromList(list); + this.paletteGraph.resetCells(cells); + + let idx = 0; + cells.forEach(cell => { + cell.translate(5, (cell.attributes.size.height + 5) * idx++); + }); + } + }); - let idx = 0; - cells.forEach(cell => { - console.log(cell); - cell.translate(5, (cell.attributes.size.height + 5) * idx++); + this.designerStore.state$ + .pipe( + distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)), + takeUntil(this.ngUnsubscribe)) + .subscribe(state => { + if (state.sourceContent) { + console.log('inside desinger.component---> ', state); + // generate graph from store objects if exist + const topologtTemplate = JSON.parse(state.sourceContent); + console.log(topologtTemplate); + delete state.sourceContent; + this.graphGenerator.populate(topologtTemplate, this.boardGraph); + + console.log('all cells', this.boardGraph.getCells()); + /** + * auto arrange elements in graph + * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph + */ + joint.layout.DirectedGraph.layout( this.boardGraph.getCells(), { + dagre, + graphlib, + setLinkVertices: false, + marginX: 10, + marginY: 10, + clusterPadding: { top: 100, left: 30, right: 10, bottom: 100 }, + rankDir: 'TB' + }); + } + }); + + // action triggering + this.functionStore.retrieveFuntions(); - }); - this.stencilPaperEventListeners(); } initializePalette() { @@ -78,22 +154,26 @@ export class DesignerComponent implements OnInit { this.palettePaper = new joint.dia.Paper({ el: $('#palette-paper'), model: this.paletteGraph, - height: 300, width: 300, - gridSize: 1, + height: $('#palette-paper').height(), + // background: { + // color: 'rgba(0, 255, 0, 0.3)' + // }, interactive: false + // elements in paletter need to be fixed, please refer to flying paper concept }); } } initializeBoard() { if (!this.boardGraph) { + console.log('initializeBoard...'); this.boardGraph = new joint.dia.Graph(); this.boardPaper = new joint.dia.Paper({ el: $('#board-paper'), model: this.boardGraph, height: 720, - width: 1200, + width: 1100, gridSize: 10, drawGrid: true, // background: { @@ -120,22 +200,21 @@ export class DesignerComponent implements OnInit { this.boardGraph.on('change:position', (cell) => { - var parentId = cell.get('parent'); - if (!parentId) return; + const parentId = cell.get('parent'); + if (!parentId) { + // this is action + return; + } - var parent = this.boardGraph.getCell(parentId); - - var parentBbox = parent.getBBox(); - var cellBbox = cell.getBBox(); + const parent = this.boardGraph.getCell(parentId); - console.log("parent ", parentBbox); - console.log("cell ", cellBbox); + const parentBbox = parent.getBBox(); + const cellBbox = cell.getBBox(); if (parentBbox.containsPoint(cellBbox.origin()) && parentBbox.containsPoint(cellBbox.topRight()) && parentBbox.containsPoint(cellBbox.corner()) && parentBbox.containsPoint(cellBbox.bottomLeft())) { - // All the four corners of the child are inside // the parent area. return; @@ -145,54 +224,18 @@ export class DesignerComponent implements OnInit { cell.set('position', cell.previous('position')); }); } + console.log('done initializing Board...'); } insertCustomActionIntoBoard() { - this.actionIdCounter++; - const element = this.createCustomAction("action_"+ this.actionIdCounter, 'Action' + this.actionIdCounter); - this.boardGraph.addCell(element); - } - - createCustomAction(id: string, label: string) { - const element = new joint.shapes.app.ActionElement({ - id: id - }); - element.attr('#label/text', label); - return element; - } - - buildPaletteGraphFromList(list: any) { - const elements = []; - - console.log(list); - list.forEach(element => { - elements.push(this.createFuctionElementForPalette(element.modelName)); - }); - - return elements; - } - - createFuctionElementForPalette(label: string) { - const element = new joint.shapes.palette.FunctionElement({ - id: label - }); - element.attr('#label/text', label); - element.attr('type', label); - return element; - } - - createFuctionElementForBoard(id :String, label :string, type :string) { - const boardElement = new joint.shapes.board.FunctionElement({ - id: id - }); - boardElement.attr('#label/text', label); - boardElement.attr('#type/text', type); - return boardElement; + console.log('saving action to store action workflow....'); + const actionName = this.graphUtil.generateNewActionName(); + this.graphUtil.createCustomActionWithName(actionName, this.boardGraph); + this.designerStore.addDeclarativeWorkFlow(actionName); } stencilPaperEventListeners() { this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => { - console.log('pointerdown 2'); $('body').append(` <div id="flyPaper" @@ -232,116 +275,62 @@ export class DesignerComponent implements OnInit { if (mouseupX > target.left && mouseupX < target.left + this.boardPaper.$el.width() && mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) { - // const clonedShape = flyShape.clone(); - const type = flyShape.attributes.attrs.type; - console.log(type); - - //create board function element of the same type of palette function - //board function element is different in design from the palette function element - this.fuctionIdCounter++; - console.log(this.fuctionIdCounter); - const functionElementForBoard = - this.createFuctionElementForBoard("fucntion_" + this.fuctionIdCounter, 'execute', type); - - functionElementForBoard.position(mouseupX - target.left - offset.x, mouseupY - target.top - offset.y); - this.boardGraph.addCell(functionElementForBoard); - const cellViewsBelow = - this.boardPaper.findViewsFromPoint(functionElementForBoard.getBBox().center()); - console.log(cellViewsBelow); - if (cellViewsBelow.length) { - let cellViewBelow; - cellViewsBelow.forEach( cellItem => { - if (cellItem.model.id !== functionElementForBoard.id) { - cellViewBelow = cellItem; + const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape); + // step name is CDS realted terminology, please refer to tosca types + const stepName = functionType; + const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition( + stepName, functionType, + mouseupX, mouseupY, + target, offset, + this.boardGraph); + + const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper); + + if (parentCell && + parentCell.model.attributes.type === ActionElementTypeName && + this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) { + + if (this.graphUtil.isEmptyParent(parentCell.model)) { + // first function in action + const actionName = parentCell.model.attributes.attrs['#label'].text; + this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType); + if (functionType === 'dg-generic') { + this.designerStore.addDgGenericNodeTemplate(stepName); + } else { + this.designerStore.addNodeTemplate(stepName, functionType); } - }); + } else { + // second action means there was a dg-generic node before + this.designerStore.addNodeTemplate(stepName, functionType); + // this will fail if multiple dg-generic were added + // TODO prevent multi functions of the same type inside the same action + const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0]; + const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode); + this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName); + } + // Prevent recursive embedding. - if (cellViewBelow && cellViewBelow.model.get('parent') !== functionElementForBoard.id) { - console.log(cellViewBelow); - cellViewBelow.model.embed(functionElementForBoard); + if (parentCell && + parentCell.model.get('parent') !== functionElementForBoard.id) { + parentCell.model.embed(functionElementForBoard); } - console.log(this.boardGraph); + } else { + console.log('function dropped outside action or not allowed, rolling back...'); + alert('function dropped outside action or not allowed, rolling back...'); + functionElementForBoard.remove(); } - } $('body').off('mousemove.fly').off('mouseup.fly'); // flyShape.remove(); $('#flyPaper').remove(); }); }); + console.log('done stencilPaperEventListeners()...'); } - /** - * this is a way to add the button like zoom in , zoom out , and source over jointjs paper - * may be used if no other way is found - */ - // createEditBarOverThePaper() { - // joint.shapes["html"] = {}; - // joint.shapes["html"].Element = joint.shapes.basic.Rect.extend({ - // defaults: joint.util.deepSupplement({ - // type: 'html.Element' - // }, joint.shapes.basic.Rect.prototype.defaults) - // }); - // joint.shapes["html"].ElementView = joint.dia.ElementView.extend({ - - // template: [ - // '<div>', - // '<div id="editbar" class="editBar text-center">', - // '<div class="btn-group mr-2" role="group" aria-label="First group">', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Undo">', - // '<img src="/assets/img/icon-undoActive.svg">', - // '</button>', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Redo">', - // '<img src="/assets/img/icon-redo.svg">', - // '</button>', - // '</div>', - // '<div class="btn-group mr-2" role="group" aria-label="Second group">', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Zoom Out">', - // '<img src="/assets/img/icon-zoomOut.svg">', - // '</button>', - // '<button type="button" class="btn btn-secondary pl-0 pr-0">100%</button>', - // '<button type="button" class="btn btn-secondary tooltip-bottom" data-tooltip="Zoom In">', - // '<img src="/assets/img/icon-zoomIn.svg">', - // '</button>', - // '</div>', - // '<div class="btn-group viewBtns" role="group" aria-label="Third group">', - // '<button type="button" class="btn btn-secondary topologySource active">View</button>', - // '<button type="button" class="btn btn-secondary topologyView">Source</button>', - // '</div>', - // '</div>', - // '</div>' - // ].join(''), - // initialize: function () { - // _.bindAll(this, 'updateBox'); - // joint.dia.ElementView.prototype.initialize.apply(this, arguments); - - // this.$box = $(_.template(this.template)()); - // // Prevent paper from handling pointerdown. - // this.$box.find('input,select').on('mousedown click', function (evt) { - // evt.stopPropagation(); - // }); - // this.model.on('change', this.updateBox, this); - // this.updateBox(); - // }, - // render: function () { - // joint.dia.ElementView.prototype.render.apply(this, arguments); - // this.paper.$el.prepend(this.$box); - // this.updateBox(); - // return this; - // }, - // updateBox: function () { - // // Set the position and dimension of the box so that it covers the JointJS element. - // var bbox = this.model.getBBox(); - // this.$box.css({ - // width: bbox.width, - // height: bbox.height, - // left: bbox.x, - // top: bbox.y, - // transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' - // }); - // } - // }); - - // } + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.service.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.service.ts index c4564254f..aa3a6a668 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.service.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.service.ts @@ -23,7 +23,7 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs'; import {ApiService} from '../../../../common/core/services/api.typed.service'; import {ResourceDictionaryURLs} from '../../../../common/constants/app-constants'; -import {ModelType} from '../model/ModelType.model'; +import {ModelType} from './model/ModelType.model'; @Injectable({ diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.store.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.store.ts index be98eec88..ba8b2f0f1 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.store.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/designer.store.ts @@ -22,8 +22,9 @@ limitations under the License. import {Injectable} from '@angular/core'; import {Store} from '../../../../common/core/stores/Store'; import {DesignerService} from './designer.service'; -import {ModelType} from '../model/ModelType.model'; -import {DesignerDashboardState} from '../model/designer-dashboard.state'; +import {DesignerDashboardState} from './model/designer.dashboard.state'; +import { DeclarativeWorkflow } from './model/designer.workflow'; +import { NodeTemplate } from './model/desinger.nodeTemplate.model'; @Injectable({ @@ -35,15 +36,109 @@ export class DesignerStore extends Store<DesignerDashboardState> { super(new DesignerDashboardState()); } - public getFuntions() { - const modelDefinitionType = 'node_type'; - this.designerService.getFunctions(modelDefinitionType).subscribe( - (modelType: ModelType[]) => { - console.log(modelType); - this.setState({ - ...this.state, - functions: modelType, - }); - }); + /** + * adds empty workflow with name only. + * called when blank action is added to the board + * declarative workflow just contain the steps but its order is determind by dg-graph + */ + addDeclarativeWorkFlow(workflowName: string) { + this.setState({ + ...this.state, + template: { + ...this.state.template, + workflows: { + ...this.state.template.workflows, + [workflowName]: new DeclarativeWorkflow() + } + } + }); + } + + addStepToDeclarativeWorkFlow(workflowName: string, stepName: string, stepType: string) { + this.setState({ + ...this.state, + template: { + ...this.state.template, + workflows: { + ...this.state.template.workflows, + [workflowName]: { + ...this.state.template.workflows[workflowName], + steps: { + [stepName]: { + target: stepType, + description: '' + } + } + } + } + } + }); + } + + saveSourceContent(code: string) { + const topologyTemplate = JSON.parse(code); + this.setState({ + ...this.state, + sourceContent: code, + template: topologyTemplate + }); + } + + + /** + * adding node tempates is a separate action of adding the steps to the workflow + * you can add node template and don't add workflow step when you add dependencies for the + * dg-generic function for example + */ + addNodeTemplate(nodeTemplateName: string, type: string) { + this.setState({ + ...this.state, + template: { + ...this.state.template, + node_templates: { + ...this.state.template.node_templates, + [nodeTemplateName]: new NodeTemplate(type) + } + } + }); + } + + addDgGenericNodeTemplate(nodeTemplateName: string) { + const node = new NodeTemplate('dg-generic'); + node.properties = { + 'dependency-node-templates': [] + }; + this.setState({ + ...this.state, + template: { + ...this.state.template, + node_templates: { + ...this.state.template.node_templates, + [nodeTemplateName]: node + } + } + }); + } + + addDgGenericDependency(dgGenericNodeName: string, dependency: string) { + const props = this.state.template.node_templates[dgGenericNodeName].properties; + this.setState({ + ...this.state, + template: { + ...this.state.template, + node_templates: { + ...this.state.template.node_templates, + [dgGenericNodeName]: { + ...this.state.template.node_templates[dgGenericNodeName], + properties: { + 'dependency-node-templates': [ + ...props['dependency-node-templates'], + dependency + ] + } + } + } + } + }); } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/package.store.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions.store.ts index efbaef8bd..86814179d 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/configuration-dashboard/package.store.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions.store.ts @@ -21,38 +21,28 @@ limitations under the License. import {Injectable} from '@angular/core'; import {Store} from '../../../../common/core/stores/Store'; -import {BluePrintDetailModel} from '../model/BluePrint.detail.model'; -import {ConfigurationDashboardService} from './configuration-dashboard.service'; -import {PackageDashboardState} from '../model/package-dashboard.state'; +import {DesignerService} from './designer.service'; +import {ModelType} from './model/ModelType.model'; +import { FunctionsState } from './model/functions.state'; @Injectable({ providedIn: 'root' }) -export class PackageStore extends Store<PackageDashboardState> { +export class FunctionsStore extends Store<FunctionsState> { - - constructor(private configurationDashboardService: ConfigurationDashboardService) { - super(new PackageDashboardState()); + constructor(private designerService: DesignerService) { + super(new FunctionsState()); } - getPagedPackages(id: string) { - this.configurationDashboardService.getBluePrintModel(id).subscribe( - (bluePrintDetailModels) => { + public retrieveFuntions() { + const modelDefinitionType = 'node_type'; + this.designerService.getFunctions(modelDefinitionType).subscribe( + (modelTypeList: ModelType[]) => { this.setState({ ...this.state, - configuration: bluePrintDetailModels[0] + serverFunctions: modelTypeList, }); }); - /* bluePrintDetailModels.forEach( - bluePrintDetailModel => { - this.setState({ - ...this.state, - configuration: bluePrintDetailModel - }); - });*/ - - } - } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.css deleted file mode 100644 index e69de29bb..000000000 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.css +++ /dev/null diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.html deleted file mode 100644 index b27f91f49..000000000 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.html +++ /dev/null @@ -1,21 +0,0 @@ -<h1 class="col-12">Functions</h1> -<div class="col-12 componentsList"> - <b>Drag and drop function to Action’s box</b> - <ul class="list-group actions-scroll" > - <li class="list-group-item" *ngFor="let function of viewedFunctions"> - <p class="compType-1">{{function.modelName}}</p> - </li> - <!--<li class="list-group-item"> - <p class="compType-2">component-netconf-executor</p> - </li> - <li class="list-group-item"> - <p class="compType-3">component-remote-ansible-executor</p> - </li> - <li class="list-group-item"> - <p class="compType-4">dg-generic</p> - </li> - <li class="list-group-item"> - <p class="compType-1">component-resource-resolution</p> - </li>--> - </ul> -</div> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.spec.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.spec.ts deleted file mode 100644 index eec909b01..000000000 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FunctionsComponent } from './functions.component'; - -describe('FunctionsComponent', () => { - let component: FunctionsComponent; - let fixture: ComponentFixture<FunctionsComponent>; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ FunctionsComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(FunctionsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.ts deleted file mode 100644 index 5a86150e8..000000000 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/functions/functions.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import {DesignerStore} from '../designer.store'; -import {ModelType} from '../../model/ModelType.model'; - - -@Component({ - selector: 'app-functions', - templateUrl: './functions.component.html', - styleUrls: ['./functions.component.css'] -}) -export class FunctionsComponent implements OnInit { - viewedFunctions: ModelType[] = []; - - constructor(private designerStore: DesignerStore) { - - this.designerStore.state$.subscribe(state => { - console.log(state); - if (state.functions) { - this.viewedFunctions = state.functions; - } - }); - } - - ngOnInit() { - this.designerStore.getFuntions(); - } - -} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.generator.util.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.generator.util.ts new file mode 100644 index 000000000..8e1d88907 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.generator.util.ts @@ -0,0 +1,113 @@ +/* +============LICENSE_START========================================== +=================================================================== +Copyright (C) 2019 Orange. All rights reserved. +=================================================================== + +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the License); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +============LICENSE_END============================================ +*/ +import { TopologyTemplate } from './model/designer.topologyTemplate.model'; +import { Injectable } from '@angular/core'; +import { GraphUtil } from './graph.util'; +import { NodeTemplate } from './model/desinger.nodeTemplate.model'; + +@Injectable({ + providedIn: 'root' +}) +export class GraphGenerator { + + constructor(private graphUtil: GraphUtil) { + } + + /** + * loops over workflows + * create action element + * from steps --> create function element + * add function element to action element + * example toplogyTemplate + * + * { + * "workflows": { + * "Action1": { + * "steps": { + * "STEP_NAME": { + * "target": "NODE_TEMPLATE_NAME", + * "description": "" + * } + * } + * } + * }, + * "node_templates": { + * "NODE_TEMPLATE_NAME": { + * "type": "dg-generic", + * "properties": { + * "dependency-node-templates": [ + * "component-config-snapshots-executor", + * "component-jython-executor" + * ] + * } + * }, + * "component-config-snapshots-executor": { + * "type": "component-config-snapshots-executor", + * "properties": { } + * }, + * "component-jython-executor": { + * "type": "component-jython-executor", + * "properties": { } + * } + * } + * } + */ + + public populate(topologyTempalte: TopologyTemplate, + boardGraph: joint.dia.Graph) { + + Object.keys(topologyTempalte.workflows).forEach(workFlowName => { + console.log('drawing workflow item --> ', workFlowName); + + // create action element + const actionElement = + this.graphUtil.createCustomActionWithName(workFlowName, boardGraph); + + // create board function elements + const workflow = topologyTempalte.workflows[workFlowName].steps; + const stepName = Object.keys(workflow)[0]; + if (stepName) { + const nodeTemplateName = workflow[stepName].target; + const functionType = topologyTempalte.node_templates[nodeTemplateName].type; + console.log('draw function with ', stepName, functionType); + + this.graphUtil.dropFunctionOverActionRelativeToParent( + actionElement, + stepName , functionType, boardGraph); + + // TODO handle dg-generic case (multi-step in the same action) + if (functionType === 'dg-generic') { + const props = topologyTempalte.node_templates[nodeTemplateName].properties; + console.log('dg props', props); + props['dependency-node-templates'].forEach(dependencyStepName => { + const dependencyType = topologyTempalte.node_templates[dependencyStepName].type; + console.log('dependencyType', dependencyType); + this.graphUtil.dropFunctionOverActionRelativeToParent( + actionElement, + dependencyStepName, dependencyType, boardGraph); + + }); + } + } + }); + + } +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.util.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.util.ts new file mode 100644 index 000000000..fd4dd35e4 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/graph.util.ts @@ -0,0 +1,228 @@ +/* +============LICENSE_START========================================== +=================================================================== +Copyright (C) 2019 Orange. All rights reserved. +=================================================================== + +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the License); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +============LICENSE_END============================================ +*/ + +import * as joint from 'jointjs'; +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class GraphUtil { + + actionIdCounter = 0; + // to generate Ids for dragged function elements + private fuctionIdCounter = 0; + + createCustomAction(boardGraph: joint.dia.Graph) { + const actionName = this.generateNewActionName(); + const actionId = this.generateNewActionId(); + const element = new joint.shapes.app.ActionElement({ + id: actionId + }); + element.attr('#label/text', actionName); + boardGraph.addCell(element); + return element; + } + + generateNewActionName() { + this.actionIdCounter++; + const actionName = 'Action' + this.actionIdCounter; + return actionName; + } + + private generateNewActionId() { + const actionName = + (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)) + .toUpperCase(); + return actionName; + } + + createCustomActionWithName(actionName: string, boardGraph: joint.dia.Graph) { + const actionId = this.generateNewActionId(); + const element = new joint.shapes.app.ActionElement({ + id: actionId + }); + element.attr('#label/text', actionName); + boardGraph.addCell(element); + return element; + } + + buildPaletteGraphFromList(list: any) { + const elements = []; + list.forEach(element => { + elements.push(this.createFuctionElementForPalette(element.modelName)); + }); + + return elements; + } + + createFuctionElementForPalette(label: string) { + const element = new joint.shapes.palette.FunctionElement({ + id: label + }); + element.attr('#label/text', label); + element.attr('type', label); + return element; + } + + createFuctionElementForBoard( label: string, type: string) { + this.fuctionIdCounter++; + const id = 'fucntion_' + this.fuctionIdCounter; + const boardElement = new joint.shapes.board.FunctionElement({ + id + }); + boardElement.attr('#label/text', label); + boardElement.attr('#type/text', type); + return boardElement; + } + + getParent(functionElementForBoard: joint.shapes.board.FunctionElement, boardPaper: joint.dia.Paper) { + const cellViewsBelow = boardPaper.findViewsFromPoint(functionElementForBoard.getBBox().center()); + let cellViewBelow; + if (cellViewsBelow.length) { + cellViewsBelow.forEach(cellItem => { + if (cellItem.model.id !== functionElementForBoard.id) { + cellViewBelow = cellItem; + } + }); + } + return cellViewBelow; + } + + /** + * trigger actions related to Function dropped over the board: + * - create board function element of the same type of palette function + * as board function element is different from the palette function element + * - save function to parent action in store + */ + dropFunctionOverActionWithPosition( + label: string, type: string, + mouseupX: number, mouseupY: number, + target: JQuery.Coordinates, offset: { x: number; y: number; }, + boardGraph: joint.dia.Graph) { + + const functionElementForBoard = this.dropFunctionOverAction(label, type, boardGraph); + functionElementForBoard.position(mouseupX - target.left - offset.x, mouseupY - target.top - offset.y); + + return functionElementForBoard; + } + + + dropFunctionOverActionRelativeToParent( + parent: joint.shapes.app.ActionElement, + label: string, type: string, + boardGraph: joint.dia.Graph) { + + const functionElementForBoard = this.dropFunctionOverAction(label, type, boardGraph); + parent.embed(functionElementForBoard); + functionElementForBoard.position({ parentRelative: true }); + + return functionElementForBoard; + } + + + dropFunctionOverAction( + label: string, type: string, + boardGraph: joint.dia.Graph) { + + // function name is the same as function type + // actually functionName here refers step name in CDS tosca model + // and function type is the nodeTempalteName + const functionElementForBoard = + this.createFuctionElementForBoard(label, type); + boardGraph.addCell(functionElementForBoard); + return functionElementForBoard; + } + + getFunctionTypeFromPaletteFunction(cell: joint.shapes.palette.FunctionElement) { + return cell.attributes.attrs.type; + } + + getFunctionTypeFromBoardFunction(cell: joint.shapes.board.FunctionElement) { + return cell.attributes.attrs['#type'].text; + } + + getFunctionNameFromBoardFunction(cell: joint.shapes.board.FunctionElement) { + return cell.attributes.attrs['#label'].text; + } + + canEmpedMoreChildern(parentCell: joint.shapes.app.ActionElement, boardGraph: joint.dia.Graph): boolean { + if (!parentCell.get('embeds')) { + return true; + } + const types = this.getChildernTypes(parentCell, boardGraph); + return parentCell.get('embeds').length < 1 || + types.includes('dg-generic'); + } + + + getChildernTypes(parentCell: joint.shapes.app.ActionElement, + boardGraph: joint.dia.Graph): string[] { + if (parentCell.get('embeds')) { + return parentCell.get('embeds').map((cellName) => { + const child = boardGraph.getCell(cellName) as joint.shapes.board.FunctionElement; + const functionType = this.getFunctionTypeFromBoardFunction(child); + console.log('functionType', functionType); + return functionType; + }); + } else { + return []; + } + } + + getDgGenericChild(parentCell: joint.shapes.app.ActionElement, + boardGraph: joint.dia.Graph): + joint.shapes.board.FunctionElement[] { + if (parentCell.get('embeds')) { + return parentCell.get('embeds') + .filter((cellName) => { + const child = boardGraph.getCell(cellName) as joint.shapes.board.FunctionElement; + const functionType = this.getFunctionTypeFromBoardFunction(child); + return functionType === 'dg-generic'; + }) + .map((cellName) => { + const child = boardGraph.getCell(cellName) as joint.shapes.board.FunctionElement; + return child; + }); + } else { + return []; + } + } + + isEmptyParent(parentCell: joint.shapes.app.ActionElement): boolean { + return !parentCell.get('embeds') || parentCell.get('embeds').length < 1; + } + + getActionSiblings(actionCell: joint.shapes.app.ActionElement, + boardGraph: joint.dia.Graph + ): joint.shapes.app.ActionElement[] { + const cellId = actionCell.id; + const siblings = boardGraph.getCells().filter(elem => { + const type = elem.attributes.type; + const elemId = elem.id; + return (type !== undefined && type === 'app.ActionElement' + && elemId !== cellId); + }) as joint.shapes.app.ActionElement[]; + console.log('siblings', siblings); + return siblings; + } + +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/jointjs/elements/action.element.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/jointjs/elements/action.element.ts index 212905814..7960e83d1 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/jointjs/elements/action.element.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/jointjs/elements/action.element.ts @@ -1,4 +1,5 @@ import * as joint from 'jointjs'; +import { ActionElementTypeName } from 'src/app/common/constants/app-constants'; /** * please refer to documentation in file palette.function.element.ts to get more details * about how to create new element type and define it in typescript @@ -18,7 +19,7 @@ const rectWidth = 616; const rectHeight = 381; // custom element implementation // https://resources.jointjs.com/tutorials/joint/tutorials/custom-elements.html#markup -const ActionElement = joint.shapes.standard.Rectangle.define('app.ActionElement', { +const ActionElement = joint.shapes.standard.Rectangle.define(ActionElementTypeName, { size: {width: rectWidth, height: rectHeight} }, { diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/ModelType.model.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/ModelType.model.ts index c8498fa36..c8498fa36 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/ModelType.model.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/ModelType.model.ts diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.dashboard.state.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.dashboard.state.ts new file mode 100644 index 000000000..1a14021f4 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.dashboard.state.ts @@ -0,0 +1,33 @@ +/* +============LICENSE_START========================================== +=================================================================== +Copyright (C) 2019 Orange. All rights reserved. +=================================================================== + +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the License); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +============LICENSE_END============================================ +*/ + +import {ModelType} from './ModelType.model'; +import { TopologyTemplate } from './designer.topologyTemplate.model'; + +export class DesignerDashboardState { + + template: TopologyTemplate; + sourceContent: string; + + constructor() { + this.template = new TopologyTemplate(); + } +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.topologyTemplate.model.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.topologyTemplate.model.ts new file mode 100644 index 000000000..b85a6139a --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.topologyTemplate.model.ts @@ -0,0 +1,13 @@ +import { DeclarativeWorkflow } from './designer.workflow'; +import { NodeTemplate } from './desinger.nodeTemplate.model'; + +export class TopologyTemplate { + + workflows: {}; + 'node_templates': {}; + + constructor() { + this.workflows = {}; + this.node_templates = {}; + } +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.workflow.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.workflow.ts new file mode 100644 index 000000000..0687c1f47 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/designer.workflow.ts @@ -0,0 +1,14 @@ +export class Workflow { + inputs: {}; + outputs?: {}; +} + +export class DeclarativeWorkflow implements Workflow { + steps: {}; + inputs: {}; + outputs?: {}; + + constructor() { + this.steps = {}; + } +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/desinger.nodeTemplate.model.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/desinger.nodeTemplate.model.ts new file mode 100644 index 000000000..8715e44c2 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/desinger.nodeTemplate.model.ts @@ -0,0 +1,15 @@ +export class NodeTemplate { + type: string; + properties?: { + 'dependency-node-templates'?: string[] + }; + interfaces?: {}; + artifacts?: {}; + cabapilities?: {}; + requirements?: {}; + + constructor(type) { + this.type = type; + this.properties = {}; + } +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/designer-dashboard.state.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/functions.state.ts index dc65c009f..329c38deb 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/designer-dashboard.state.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/model/functions.state.ts @@ -21,9 +21,11 @@ limitations under the License. import {ModelType} from './ModelType.model'; -export class DesignerDashboardState { +export class FunctionsState { - functions: ModelType[]; - actions: string[]; + serverFunctions: ModelType[]; + constructor() { + this.serverFunctions = []; + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.css new file mode 100644 index 000000000..01ae599a4 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.css @@ -0,0 +1,580 @@ +.dsl-editor { + height: 500px; +} + +body{ + background-image: linear-gradient(-45deg, #000 9%, #fff 0) !important; + background-size: 6px 6px !important; +} + + +/*Header*/ +header{ + height: 60px; + background-color: #1B3E6F; + box-shadow: 0 4px 10px rgba(238, 240, 245, 1.0); +} +.logo{ + float: left; + width: 50px; + height: 60px; + background: url(/assets/img/logo-icon.svg) center center #fff no-repeat; +} + +/**Bread Crumb**/ +.breadcrumb{ + padding: 9px 20px; + background: transparent; + line-height: 40px; +} +.breadcrumb a, +.breadcrumb a:hover{ + color: #fff; +} +.breadcrumb .breadcrumb-item{ + font-size: 12px; + font-weight: bold; +} +.breadcrumb .breadcrumb-item:first-child{ + font-size: 16px; +} +.breadcrumb-item + .breadcrumb-item::before{ + color: #fff; +} +.breadcrumb .breadcrumb-item.active p{ + display: inline; + padding: 4px 10px; + background: #F4F9FE; + border-radius: 10px; + color: #C3CDDB; + font-size: 10px; +} +.sidebar-container{ + height: calc(100vh - 60px) !important; +} +/**Topology Actions**/ +.topology-actions{ + margin: 0; + height: 60px; +} +.topology-actions > li{ + height: 59px; + display: inline-block; + padding: 0 20px; +} +.topology-actions > li:first-child{ + border-right: solid 1px #16396A; +} +.topology-actions .btn-group{ + margin-top: 11px; +} +.btn-topology-action, +.btn-topology-action:hover{ + margin: 0 6px; + padding: 6px 10px; + color: #fff; + border-radius: 50%; + border: solid .5px #fff; +} +.btn-topology-action:last-child{ + margin-right: 0; +} +.btn-topology-action .fa{ + width: 16px; + height: 16px; + text-align: center; +} +.topology-actions .dropdown-text, +.dropdown-toggle:hover ~ .dropdown-text, +.dropdown-toggle:focus ~ .dropdown-text{ + top: 7px; + text-indent: 15px; + background: #1273EB; + border-radius: 15px; + border: 0; + box-shadow: none; + color: #fff; + font-weight: bold; + font-size: 13px; +} +.topology-actions .dropdown-text::after{ + right: 15px; + top: 13px; + border-width: 6px 6px 0 6px; + border-color: #fff transparent transparent transparent; +} +.topology-actions .dropdown-toggle:focus ~ .dropdown-text::after{ + top: 13px; + border-width: 0 6px 6px 6px; + border-color: transparent transparent #fff transparent +} +.topology-actions .dropdown-content:hover, +.topology-actions .dropdown-toggle:focus ~ .dropdown-content{ + padding: 12px 0; + text-indent: 0; + background: #fff; + border: 0; + border-radius: 2px; + box-shadow: 0 2px 6px rgba(47, 83, 151, .15) +} +.topology-actions .dropdown-content a{ + padding: 0 20px; + color: #1B3E6F; + font-size: 13px; +} +.topology-actions .dropdown-content a:hover{ + background: #F4F9FE; + text-decoration: none; +} + + + + + + + + + + + + + + + + + + +/*Rotated Text*/ +button.rotate{ + position: absolute; + margin-top: 1px; + padding: 0; + background: transparent; + border: 0; +} +.rotate{ + vertical-align: bottom; + /* text-align: center; */ +} +.rotate span{ + display: inline-table !important; + -ms-writing-mode: tb-rl; + -webkit-writing-mode: vertical-rl; + writing-mode: vertical-rl !important; + transform: rotate(180deg); + white-space: nowrap; + background: #1B3E6F; + padding: 15px 12px; + font-weight: bold; + font-size: 12px; + color:#fff; + /* border-bottom-left-radius: 2px; */ + border-top-left-radius: 2px; +} +.rotate i{ + margin-right: 3px; + margin-top: 9px; + font-size: 15px; +} +.rotate span:first-child{ + margin-bottom: 0; +} +.rotate a:hover{ + text-decoration: none; +} + +/*ACTIONS & COMPONENTS MENU*/ +.input-search-controller{ + height: 50px; + padding-left: 30px; + background: url(src/assets/img/icon-search-light.svg) #fff 10px center no-repeat; + border-radius: 0; + border: 0; + border-bottom: solid 1px #D7E7F9; + color: #1B3E6F; + font-size: 13px; +} +.input-search-controller::placeholder{ + color: #D0D7E4; + font-size: 11px; +} +.input-search-controller:focus{ + + box-shadow: 0 2px 6px 0 rgba(47, 83, 151, .15); + border-color: #DEE8F3; +} +.actions-scroll{ + max-height: 29vh; + overflow-y: auto; + margin-top: 12px; + margin-bottom: 20px; +} +.componentsList p{ + margin-bottom: 0; + padding-left: 30px; + background-position: left center; + background-repeat: no-repeat; +} +p.compType-1{ + background-image: url(/assets/img/icon-comType1-sm.svg); +} +p.compType-2{ + background-image: url(/assets/img/icon-comType2-sm.svg); +} +p.compType-3{ + background-image: url(/assets/img/icon-comType3-sm.svg); +} +p.compType-4{ + background-image: url(/assets/img/icon-comType4-sm.svg); +} +/*Actions Wrapper*/ +.actions-wrapper{ + position: absolute; + width: 100%; + top: 0; +} +.actions-container{ + width: 92%; + margin: 0 auto; + background: red; +} + +.controllerSidebar{ + width: 320px; + background: #F4F9FE; + border: solid 1px #C1CDDD; + box-shadow: 0 2px 6px rgba(47, 83, 151, .10); +} +.controllerSidebar h1{ + margin-bottom: 15px; + padding: 12px 0 12px 12px; + background: #fff; + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + color: #C3CDDB; +} +.controllerSidebar b{ + font-size: 12px; + color: #C3CDDB; +} +.actionBtns .btn{ + margin: 0 15px 12px; + padding: 9px 20px; + border-radius: 2px !important; + font-size: 12px; + font-weight: bold; +} +.actionBtns .btn:first-child{ + background: #1B3E6F; + border: solid 1px #1B3E6F; + color: #fff; +} +.actionBtns .btn:last-child{ + padding-left: 34px !important; + background: url(src/assets/img/icon-import-blue.svg) 12px center #fff no-repeat; + border: solid 1px #D0DFF1; + color: #1B3E6F; +} +.actionsList, +.componentsList{ + padding: 0 12px 20px; +} +.componentsList{ + padding-bottom: 0; +} +.custom-control.custom-checkbox:hover, +.custom-control-label:hover{ + cursor: pointer; +} +.actionsList .custom-checkbox, +.componentsList .list-group-item{ + margin-bottom: 10px; + padding-left: 40px; + background: #fff; + box-shadow: 0 2px 6px rgba(47, 83, 151, .15); + border-radius: 2px; +} +.actionsList .custom-control-label{ + width: 100%; + padding: 6px; + vertical-align: unset; + color: #1B3E6F; + font-size: 14px; + line-height: 20px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} +.actionsList .custom-control-label::before, +.actionsList .custom-control-label::after{ + top: 1.25rem; +} +.actionsList .custom-control-label p{ + color: #C7D0DD; + font-size: 12px; +} +.custom-control-input:checked ~ .custom-control-label{ + background-color: #1B3E6F !important; + color: #fff; +} +.inserActionBtns .btn{ + border-radius: 15px !important; + padding: 6px 20px; + font-size: 12px; + font-weight: bold; + border: 0; + +} +.inserActionBtns .btn:first-child{ + background: #1273EB; + border: solid 1px #1273EB; + color: #fff; +} +.inserActionBtns .btn:last-child{ + background: #fff; + border: solid 1px #D9E6F2; + color: #C3CDDB; +} +/*Components List*/ +.componentsList .list-group-item{ + padding-left: 36px; + border: 0; + font-size: 14px; + background: url(src/assets/img/icon-drag.svg) #fff 20px center no-repeat; +} + +/*CANVAS*/ +.editBar{ + width: 200px; + margin: 0 auto 0; + padding: 6px 10px; + background:#F4F9FE; + /* border: solid 1px #E8EFF8; */ + box-shadow: 0 2px 6px rgba(47, 83, 151, .1); +} +.editBar .btn-group{ + box-shadow: 0 2px 6px rgba(47, 83, 151, .15); +} +.editBar .btn{ + background-color: #fff; + background-repeat: no-repeat; + background-position: left center; + border: 0; + color: #1B3E6F; + font-size: 10px; +} +.editBar .btn.active{ + background-color: #1B3E6F !important; + color: #fff; +} +.viewBtns .btn{ + background-position: 10px center; + padding-left: 30px!important; +} +.viewBtns .topologySource{ + background-image: url(src/assets/img/icon-topologyView-active.svg); +} +.viewBtns .topologyView{ + background-image: url(src/assets/img/icon-topologySource.svg); +} +.card.actionContainer{ + margin: 20px 20px 40px 60px; + background: transparent; + border: 0; +} +.actionContainer .card-header{ + padding: 0; + background: transparent; + border: 0; +} +.actionContainer .card-header span{ + padding: 12px 20px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + font-size: 12px; + line-height: 38px; + font-weight: bold; + color: #1B3E6F; + background: #C3CDDB; +} +.actionContainer .card-body{ + min-height: 170px; + padding: 15px 20px !important; + border: solid 1px #C3CDDB; + background: #fff; + box-shadow: 0 2px 6px rgba(18, 115, 235, .1); +} +.actionContainer a{ + display: inline-block; + width: 230px; + height: 130px; + margin: 20px; + padding: 24px; + background: #1B3E6F; + color: #fff !important; + text-align: center; + border-radius: 2px; + border: solid 1px #1B3E6F; +} +.actionContainer a:hover{ + cursor: pointer; + border: dashed 1px #E9FCC6; +}.componentContainer img{ + height: 38px; +} +.componentContainer h2{ + margin-top: 9px; + font-size: 14px; + font-weight: bold; +} +.componentContainer p{ + font-size: 12px; +} + +/*ATTRIBUTES SIDE BAR*/ +.attributesSideBar{ + width: 396px; + padding: 0; +} +.attributesSideBar .attributesContainer{ + background: #fff; + border: solid 1px #C1CDDD; + box-shadow: 0 2px 6px rgba(47, 83, 151, .1); +} +.closeBar{ + float: right; + width: 90%; + height: 40px; + background: url(/assets/img/icon-close.svg) center center #DCE8F4 no-repeat ; + border: 0; + outline: 0; +} +.closeBar:focus{ + outline: none; +} +.attributesContainer h1{ + margin-bottom: 10px; + padding: 12px 0 12px 15px; + background: #DEE8F3; + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + color: #1B3E6F; +} +.actionName{ + margin-bottom: 21px; +} +.attributesContainer label{ + color: #1B3E6F; + text-transform: uppercase; + font-size: 11px; + font-weight: bold; +} +.attributesContainer .form-group{ + margin-bottom: 9px; +} +.attributesContainer .form-control{ + border-color: #F0F5FC; + border-radius: 2px; + box-shadow: 0 2px 6px rgba(47, 83, 151, .1); + color: #103D73; + font-size: 13px; +} +.attributesContainer .form-control:focus{ + border-color: #66bfff; + box-shadow: 0 0 0 4px rgba(0,149,255,0.15); +} +.attributesContainer .form-control::placeholder{ + color: #CFD7E5; +} +.scrolll{ + max-height: 88.75vh; + overflow-y: auto; +} +.accordion > .card{ + margin-bottom: 0 !important; + border: 0; +} +.accordion > .card .card-header{ + margin: 0; + padding: 0; + background-color: #F4F9FE; + border: 0; + border-radius: 0; +} +.accordion > .card .card-body{ + padding-bottom: 10px !important; +} +.accordion .btn-link{ + padding: 0; + color: #C3CDDB; + font-weight: bold; + font-size: 13px; + text-transform: uppercase; + line-height: 38px; +} +.accordion .btn-link:hover{ + color: #103D73; + text-decoration: unset; +} +.accordion .card-header .btn-link[aria-expanded="true"]:after, +.accordion .card-header .btn-link[aria-expanded="false"]:after{ + margin-right: 9px; + font-family: 'FontAwesome'; + float: left; + font-weight: normal; + font-size: 12px; +} +.accordion .card-header .btn-link[aria-expanded="true"]:after{ + content: "\f078"; +} +.accordion .card-header .btn-link[aria-expanded="false"]:after{ + content: "\f054"; +} +.btn-addAttribute{ + width: 20px; + height: 20px; + background-image: url(/assets/img/icon-add.svg); + background-position: center center; + background-repeat: no-repeat; + vertical-align: sub; +} +.btn-addAttribute:hover{ + background-image: url(/assets/img/icon-add-hover.svg); +} +.btn-deleteAttribute{ + padding: 5px 10px; + background: #FFE6E7; + border: solid .5px #FFC9CB; + border-radius: 2px; + color: #FF6469; + font-size: 10px; + +} +.source-button{ + position: absolute; + z-index: 9999999; + top: 69px; + left: 50%; +} +/*jointjs paper*/ +/* #board-paper { + position: relative; + border: 1px solid gray; + display: inline-block; + background: transparent; + overflow: hidden; +} +#board-paper svg { + background: transparent; +} +#board-paper svg .link { + z-index: 2; +} +.html-element { + position: absolute; + background: #F4F9FE; + pointer-events: none; + -webkit-user-select: none; + z-index: 2; +} */ diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.html new file mode 100644 index 000000000..2a558517c --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.html @@ -0,0 +1,80 @@ +<header> + <div class="row m-0"> + <div class="col pl-0"> + <p class="logo mb-0"></p> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb mb-0"> + <li class="breadcrumb-item"> + <a href="#">CBA Packages</a> + </li> + <li class="breadcrumb-item"> + <a href="#">Package Name</a> + </li> + <li class="breadcrumb-item active" aria-current="page"> + <p class="mb-0">Topology View</p> + </li> + </ol> + </nav> + </div> + <div class="col pr-0 text-right"> + <ul class="topology-actions"> + <li> + <div class="btn-group" role="group" aria-label="Basic example"> + <a href="#" role="button" aria-pressed="true" class="btn-topology-action float tooltip-bottom" + data-tooltip="Preview"> + <i class="fa fa-eye"></i> + </a> + <a href="#" role="button" aria-pressed="true" class="btn-topology-action float tooltip-bottom" + data-tooltip="Download"> + <i class="fa fa-download"></i> + </a> + <a href="#" role="button" aria-pressed="true" class="btn-topology-action float tooltip-bottom" + data-tooltip="Share"> + <i class="fa fa-share-square"></i> + </a> + </div> + </li> + <li> + <div class="dropdown"> + <input class="dropdown-toggle" type="text"> + <div class="dropdown-text">Save</div> + <ul class="dropdown-content"> + <li> + <a href="">Save</a> + </li> + <li> + <a href="">Save & Deploy</a> + </li> + </ul> + </div> + </li> + </ul> + + + </div> + </div> +</header> +<div class="source-button editBar"> + <div class="btn-group viewBtns" role="group"> + <button (click)="convertAndOpenInDesingerView()" type="button" class="btn btn-secondary topologySource">Designer</button> + <button type="button" + class="btn btn-secondary topologyView active">Scripting</button> + </div> +</div> +<ng-sidebar-container class="sidebar-container"> + <!-- Controller SideBar --> + <ng-sidebar [(opened)]="controllerSideBar" [sidebarClass]="'demo-sidebar controllerSidebar container-fluid'" + [mode]="'push'" #sidebarLeft> + <div class="row"> + + <h1 class="col-12">Actions</h1> + + + </div> + </ng-sidebar> +<div ng-sidebar-content id="board-paper"> + <ace-editor [(text)]="content" [mode]="'json'" [autoUpdateContent]="true" [durationBeforeCallback]="1000" + [theme]="'tomorrow_night_bright'" #editor style="height:500px"> + </ace-editor> + + </div> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.ts new file mode 100644 index 000000000..34194e42f --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/designer/source-view/source-view.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { DesignerStore } from '../designer.store'; +import { PackageCreationUtils } from '../../package-creation/package-creation.utils'; +import { RouterLink, Router } from '@angular/router'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'app-designer-source-view', + templateUrl: './source-view.component.html', + styleUrls: ['./source-view.component.css'] +}) +export class DesignerSourceViewComponent implements OnInit, OnDestroy { + + content = ''; + lang = 'json'; + private controllerSideBar: boolean; + private ngUnsubscribe = new Subject(); + + constructor(private store: DesignerStore, + private packageCreationUtils: PackageCreationUtils, + private router: Router) { + this.controllerSideBar = true; + } + + ngOnInit() { + this.store.state$.subscribe( + state => { + console.log(state); + this.content = this.packageCreationUtils.transformToJson(state.template); + }); + + } + + convertAndOpenInDesingerView() { + // TODO validate json against scheme + console.log('convertAndOpenInDesingerView ...', this.content); + this.store.saveSourceContent(this.content); + this.router.navigateByUrl('/packages/designer'); + } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/package-dashboard.state.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/package-dashboard.state.ts index 638e68c06..b010f7a69 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/package-dashboard.state.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/model/package-dashboard.state.ts @@ -20,9 +20,21 @@ limitations under the License. */ -import {BluePrintDetailModel} from './BluePrint.detail.model'; +import { BluePrintDetailModel } from './BluePrint.detail.model'; +import { Mapping, Scripts, Template } from '../package-creation/mapping-models/CBAPacakge.model'; -export class PackageDashboardState { +export class PackageDashboardState { configuration: BluePrintDetailModel; + public scripts: Scripts; + public templates: Template; + public mapping: Mapping; + public imports: Map<string, string>; + + constructor() { + this.scripts = new Scripts(); + this.templates = new Template(); + this.mapping = new Mapping(); + this.imports = new Map<string, string>(); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/DesignerCreationMode.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/DesignerCreationMode.ts index b44e844be..e1efc3c22 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/DesignerCreationMode.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/DesignerCreationMode.ts @@ -7,6 +7,7 @@ import { PackageCreationUtils } from '../package-creation.utils'; export class DesignerCreationMode extends PackageCreationModes { + // Refactor methods params to be in constructor level constructor() { super(); } @@ -15,6 +16,7 @@ export class DesignerCreationMode extends PackageCreationModes { this.addToscaMetaDataFile(cbaPackage.metaData); this.createDefinitionsFolder(cbaPackage, packageCreationUtils); this.addScriptsFolder(cbaPackage.scripts); + this.addTemplateFolder(cbaPackage); } private addScriptsFolder(scripts: Scripts) { @@ -23,28 +25,50 @@ export class DesignerCreationMode extends PackageCreationModes { }); } + private addTemplateFolder(cbaPackage: CBAPackage) { + // Create Template Files Folder + cbaPackage.templates.files.forEach((value, key) => { + FilesContent.putData(key, value); + }); + // Create Mapping Files Folder + cbaPackage.mapping.files.forEach((value, key) => { + FilesContent.putData(key, value); + }); + } + private createDefinitionsFolder(cbaPackage: CBAPackage, packageCreationUtils: PackageCreationUtils) { cbaPackage.definitions.imports.forEach((valueOfFile, key) => { FilesContent.putData(key, valueOfFile); }); - const filenameEntry = 'Definitions/vLB_CDS.json'; + const filenameEntry = 'Definitions/blueprint.json'; const vlbDefinition: VlbDefinition = new VlbDefinition(); const metadata: Metadata = new Metadata(); metadata.template_author = 'Shaaban Ebrahim'; metadata.template_name = cbaPackage.metaData.name; - metadata.template_tags = cbaPackage.metaData.tags; metadata.template_version = cbaPackage.metaData.version; metadata['author-email'] = 'shaaban.eltanany.ext@orange.com'; metadata['user-groups'] = 'test'; - cbaPackage.definitions.metaDataTab.mapOfCustomKey.forEach((customKeyValue, key) => { + cbaPackage.metaData.mapOfCustomKey.forEach((customKeyValue, key) => { metadata[key] = customKeyValue; }); + // create Tags + let fullTags = ''; + let setCount = 0; + cbaPackage.metaData.templateTags.forEach(val => { + setCount++; + if (setCount === cbaPackage.metaData.templateTags.size) { + fullTags += val; + } else { + fullTags += val + ', '; + } + }); + metadata.template_tags = fullTags; vlbDefinition.metadata = metadata; const files: Import[] = []; cbaPackage.definitions.imports.forEach((valueOfFile, key) => { - files.push({ file: valueOfFile }); + files.push({ file: key }); }); console.log(vlbDefinition); vlbDefinition.imports = files; diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/PackageCreationModes.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/PackageCreationModes.ts index 2d234958c..8ccf0c39e 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/PackageCreationModes.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/creationModes/PackageCreationModes.ts @@ -11,7 +11,7 @@ export abstract class PackageCreationModes { public static setEntryPoint(metaDataTab: MetaDataTabModel) { if (metaDataTab.mode.startsWith(ModeType.Designer)) { - metaDataTab.entryFileName = 'Definitions/vLB_CDS.json'; + metaDataTab.entryFileName = 'Definitions/blueprint.json'; } else { // TODO Not implemented metaDataTab.entryFileName = ''; @@ -31,6 +31,16 @@ export abstract class PackageCreationModes { } getValueOfMetaData(metaDataTab: MetaDataTabModel): string { + let tags = ''; + let count = 0; + for (const tag of metaDataTab.templateTags) { + count++; + if (count === metaDataTab.templateTags.size) { + tags += tag; + } else { + tags += tag + ', '; + } + } return 'TOSCA-Meta-File-Version: 1.0.0\n' + 'CSAR-Version: 1.0\n' + 'Created-By: Shaaban Ebrahim <shaaban.eltanany.ext@orange.con>\n' + @@ -38,7 +48,7 @@ export abstract class PackageCreationModes { 'Template-Name:' + metaDataTab.name + '\n' + 'Template-Version:' + metaDataTab.version + '\n' + 'Template-Type: ' + metaDataTab.mode + '\n' + - 'Template-Tags:' + metaDataTab.tags; + 'Template-Tags:' + tags; } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.html index 6fb1dcfd8..a67d12bcf 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.html @@ -1,5 +1,5 @@ <ace-editor [(text)]="dslDefinition.content" [mode]="'javascript'" [autoUpdateContent]="true" - [durationBeforeCallback]="1000" [theme]="'tomorrow_night_bright'" #editor style="height:300px;"> + [durationBeforeCallback]="1000" (textChanged)="textChanged($event)" [theme]="'tomorrow_night_bright'" #editor style="height:300px;"> </ace-editor> -<!-- <app-source-editor [lang]="'javascript'" (textChanged)="textChanged($event)" [(text)]="dslDefinition.content"></app-source-editor> -->
\ No newline at end of file +<!-- <app-source-editor [lang]="'javascript'" (textChanged)="textChanged($event)" [(text)]="dslDefinition.content"></app-source-editor> --> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.ts index 7171e730e..1297bc14e 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/dsl-definitions-tab/dsl-definitions-tab.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit } from '@angular/core'; -import { DslDefinition } from '../mapping-models/CBAPacakge.model'; -import { PackageCreationStore } from '../package-creation.store'; +import {Component, OnInit} from '@angular/core'; +import {DslDefinition} from '../mapping-models/CBAPacakge.model'; +import {PackageCreationStore} from '../package-creation.store'; @Component({ selector: 'app-dsl-definitions-tab', @@ -16,11 +16,15 @@ export class DslDefinitionsTabComponent implements OnInit { } ngOnInit() { - this.packageCreationStore.changeDslDefinition(this.dslDefinition); + this.packageCreationStore.state$.subscribe(cbaPackage => { + if (cbaPackage && cbaPackage.definitions && cbaPackage.definitions.dslDefinition) { + this.dslDefinition.content = cbaPackage.definitions.dslDefinition.content; + } + }); } textChanged(event) { - console.log('event changed'); + this.packageCreationStore.changeDslDefinition(this.dslDefinition); } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.html index 344e7f599..d80ed16b2 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.html @@ -5,7 +5,7 @@ <a class="enter-link" href="#"><i class="icon-enter"></i></a> </div> <span class="import-container-span">Or you can also <a href="#" data-toggle="modal" - data-target="#exampleModal">Import File</a></span> + data-target="#importModal">Import File</a></span> </div> @@ -47,7 +47,7 @@ <div [id]="'id-'+mapIndex" class="collapse" [attr.aria-labelledby]="'head-'+mapIndex" data-parent="#accordion"> <div class="card-body"> - <ace-editor [(text)]="file.value" [mode]="'json'" [autoUpdateContent]="true" + <ace-editor [(text)]="file.value" (textChange)="textChanges($event,file.key)" [mode]="'json'" [autoUpdateContent]="true" [durationBeforeCallback]="1000" [theme]="'tomorrow_night_bright'" #editor style="height:300px;"> </ace-editor> @@ -82,12 +82,12 @@ </div> -<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" +<div class="modal fade" id="importModal" tabindex="-1" role="dialog" aria-labelledby="importModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> - <h5 class="modal-title" id="exampleModalLabel">Import File</h5> + <h5 class="modal-title" id="importModalLabel">Import File</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> @@ -130,4 +130,4 @@ </div> </div> </div> -</div>
\ No newline at end of file +</div> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.ts index 106fe3090..dc0cf7f3b 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/imports-tab/imports-tab.component.ts @@ -18,6 +18,8 @@ export class ImportsTabComponent implements OnInit { public files: NgxFileDropEntry[] = []; constructor(private packageCreationStore: PackageCreationStore, private packageCreationUtils: PackageCreationUtils) { + } + ngOnInit(): void { this.packageCreationStore.state$.subscribe(cbaPackage => { if (cbaPackage.definitions && cbaPackage.definitions.imports && cbaPackage.definitions.imports.size > 0) { this.definitionFiles = cbaPackage.definitions.imports; @@ -25,10 +27,6 @@ export class ImportsTabComponent implements OnInit { }); } - ngOnInit(): void { - // TODO - } - public dropped(files: NgxFileDropEntry[]) { this.files = files; for (const droppedFile of files) { @@ -74,4 +72,8 @@ export class ImportsTabComponent implements OnInit { resetTheUploadedFiles() { this.uploadedFiles = []; } + + textChanges(code: any, key: string) { + this.packageCreationStore.addDefinition(key, code); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/CBAPacakge.model.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/CBAPacakge.model.ts index d94a64ca3..a37339d90 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/CBAPacakge.model.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/CBAPacakge.model.ts @@ -1,8 +1,10 @@ import {MetaDataTabModel} from './metadata/MetaDataTab.model'; + + export class Definition { - public metaDataTab: MetaDataTabModel; + // public metaDataTab: MetaDataTabModel; public imports: Map<string, string>; public dslDefinition: DslDefinition; @@ -10,7 +12,7 @@ export class Definition { constructor() { this.imports = new Map<string, string>(); - this.metaDataTab = new MetaDataTabModel(); + // this.metaDataTab = new MetaDataTabModel(); this.dslDefinition = new DslDefinition(); } @@ -19,10 +21,10 @@ export class Definition { return this; } - public setMetaData(metaDataTab: MetaDataTabModel) { - this.metaDataTab = metaDataTab; - return this; - } + // public setMetaData(metaDataTab: MetaDataTabModel) { + // this.metaDataTab = metaDataTab; + // return this; + // } public setDslDefinition(dslDefinition: DslDefinition): Definition { this.dslDefinition = dslDefinition; @@ -34,6 +36,23 @@ export class DslDefinition { content: string; } +export class Base { + public files: Map<string, string>; + + constructor() { + this.files = new Map<string, string>(); + } + + public setContent(key: string, value: string) { + this.files.set(key, value); + return this; + } + + public getValue(key: string): string { + return this.files.get(key); + } +} + export class Scripts { public files: Map<string, string>; @@ -59,6 +78,13 @@ export class Template { this.files.set(key, value); return this; } + + public getValue(key: string): string { + return this.files.get(key); + } +} + +export class Mapping extends Base { } export class CBAPackage { @@ -67,6 +93,7 @@ export class CBAPackage { public definitions: Definition; public scripts: Scripts; public templates: Template; + public mapping: Mapping; constructor() { @@ -74,6 +101,7 @@ export class CBAPackage { this.scripts = new Scripts(); this.metaData = new MetaDataTabModel(); this.templates = new Template(); + this.mapping = new Mapping(); } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/ResourceDictionary.model.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/ResourceDictionary.model.ts new file mode 100644 index 000000000..558d1c7d0 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/ResourceDictionary.model.ts @@ -0,0 +1,23 @@ +import { JsonObject, JsonProperty } from 'json2typescript'; + +@JsonObject('ResourceDictionary') +export class ResourceDictionary { + @JsonProperty() + name: string; + @JsonProperty('creation_date') + creationDate: string; + @JsonProperty('data_type') + dataType: string; + @JsonObject('definition') + definition?: any | null; + @JsonProperty('description') + description: string; + @JsonProperty('entry_schema') + entrySchema: string; + @JsonProperty('esource_dictionary_group') + resourceDictionaryGroup: string; + @JsonProperty('tags') + tags: string; + @JsonProperty('upadted_by') + updatedBy: string; +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/definitions/VlbDefinition.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/definitions/VlbDefinition.ts index c4f3ee8bd..630baa1ac 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/definitions/VlbDefinition.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/definitions/VlbDefinition.ts @@ -1,5 +1,9 @@ import { Any, JsonObject, JsonProperty } from 'json2typescript'; +@JsonObject('topology_template') +export class TemplateTopology { + public content: string; +} @JsonObject export class VlbDefinition { @@ -10,14 +14,15 @@ export class VlbDefinition { imports: Import[]; // tslint:disable-next-line: variable-name dsl_definitions: DslContent; - // topology_template: TopologyTemplate; + // tslint:disable-next-line: variable-name + topology_template: TemplateTopology; } @JsonObject('dsl_definitions') export class DslContent { } - +// Refactor varaibles name and use JsonConverteri @JsonObject('metadata') export class Metadata { @JsonProperty('template_author') @@ -38,6 +43,8 @@ export class Metadata { @JsonProperty('dictionary_group') // tslint:disable-next-line:variable-name dictionary_group: string; + @JsonProperty('template_tags') + templateTags: string; /* @JsonProperty('custom_keys', {String}, false) diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/mappingAdapter.model.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/mappingAdapter.model.ts new file mode 100644 index 000000000..b4de578b9 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/mappingAdapter.model.ts @@ -0,0 +1,45 @@ +import { ResourceDictionary } from './ResourceDictionary.model'; +import { JsonObject, JsonProperty, JsonConvert } from 'json2typescript'; + +// Convert ResourceDictionary object to store Mapping. +export class MappingAdapter { + + constructor( + private resourceDictionary: ResourceDictionary, + private dependancies: Map<string, Array<string>>, + private dependanciesSource: Map<string, string>) { } + + ToMapping(): Mapping { + const mapping = new Mapping(); + mapping.name = this.resourceDictionary.name; + mapping.dictionaryName = this.resourceDictionary.name; + mapping.property = this.resourceDictionary.definition.property; + mapping.inputParam = false; + mapping.dictionarySource = this.dependanciesSource.get(mapping.name); + if (this.dependancies.get(mapping.name)) { + mapping.dependencies = this.dependancies.get(mapping.name); + } else { + mapping.dependencies = []; + } + mapping.version = 0; + return mapping; + } +} + +@JsonObject('Mapping') +export class Mapping { + @JsonProperty('name') + name: string; + @JsonProperty() + property: any; + @JsonProperty('input-param', Boolean) + inputParam: boolean; + @JsonProperty('dictionary-name') + dictionaryName: string; + @JsonProperty('dictionary-source') + dictionarySource: string; + @JsonProperty() + dependencies: string[]; + @JsonProperty() + version: number; +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/metadata/MetaDataTab.model.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/metadata/MetaDataTab.model.ts index df723d58f..7200e1210 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/metadata/MetaDataTab.model.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/mapping-models/metadata/MetaDataTab.model.ts @@ -26,10 +26,15 @@ export class MetaDataTabModel { name: string; description: string; version: string; - tags: string; mapOfCustomKey: Map<string, string> = new Map<string, string>(); entryFileName: string; templateName: string; + templateTags: Set<string> = new Set<string>(); + + setCustomKey(mapOfCustomKey: Map<string, string>) { + this.mapOfCustomKey = mapOfCustomKey; + return this; + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.css index e69de29bb..856f458f0 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.css +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.css @@ -0,0 +1,4 @@ +.fa-times-circle:hover { + cursor: pointer; + color: #1f64c3 +}
\ No newline at end of file diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.html index 09393c403..89724be79 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.html @@ -2,13 +2,13 @@ <div class="single-line"> <label class="label-name">Mode</label> <label name="trst" *ngFor="let mode of modes; let i = index"> - <input class="form-check-input" [(ngModel)]="metaDataTab.mode" type="radio" - name="exampleRadios" id="exampleRadios1" value={{mode.name}}> + <input class="form-check-input" [(ngModel)]="modeType" type="radio" name="radioMode" id="radioMode" + [value]="mode.name"> <span> - <i [className]="mode.style" aria-hidden="true" [id]="mode.name"></i> + <i [className]="mode.style" aria-hidden="true" [id]="mode.name"></i> {{mode.name}} - </span> + </span> </label> </div> <div class="single-line"> @@ -20,53 +20,50 @@ <option>Library Instance 3</option> <option>Library Instance 4</option> <option>Library Instance 5</option> - </select> + </select> </div> </div> </div> <div class="card creat-card"> - <div class="single-line-model error"> + <div class="single-line-model"> <label class="label-name">Name <span>*</span></label> <div class="label-input"> - <input type="input" - [(ngModel)]="metaDataTab.name" placeholder="Topology name.vLB.CDS"> + <input type="input" [readOnly]="!packageNameAndVersionEnables" [(ngModel)]="metaDataTab.name" + placeholder="Topology name.vLB.CDS"> </div> - <div class="model-note-container error-message"> - Package name already exists with this version. Please enter a different name or enter different version number. - </div> + <!--<div class="model-note-container error-message"> + Package name already exists with this version. Please enter a different name or enter different version + number. + </div>--> </div> - + <div class="single-line-model"> <label class="label-name">Version <span>*</span></label> <div class="label-input"> - <input type="input" [(ngModel)]="metaDataTab.version" - (input)="validatePackageNameAndVersion()" - placeholder="Example: 1.0.0"> + <input type="input" [readOnly]="!packageNameAndVersionEnables" [(ngModel)]="metaDataTab.version" + (input)="validatePackageNameAndVersion()" placeholder="Example: 1.0.0"> </div> <div class="model-note-container error-message">{{errorMessage}}</div> </div> <div class="single-line-model"> <label class="label-name">Description</label> <div class="label-input"> - <input type="input" [(ngModel)]="metaDataTab.description" - placeholder="Descripe the package"> + <input type="input" [(ngModel)]="metaDataTab.description" placeholder="Descripe the package"> </div> </div> - + <div class="single-line-model"> <label class="label-name">tags</label> <div class="label-input"> - <input type="input" [(ngModel)]="metaDataTab.tags" - placeholder="Ex., vDNS-CDS"> - + <input type="input" (keyup.enter)="addTag($event)" [(ngModel)]="metaDataTab.tags" + placeholder="Ex., vDNS-CDS"> + </div> <div class="model-note-container tag-notes">Seprate tags with comma or space</div> <div class="model-note-container tages-container"> - <span class="single-tage">vDNS-CDS <a href="#"> <i class="fa fa-times-circle"></i></a></span> - <span class="single-tage">vDNS-CDS <a href="#"> <i class="fa fa-times-circle"></i></a></span> - <span class="single-tage">vDNS-CDS <a href="#"> <i class="fa fa-times-circle"></i></a></span> - <span class="single-tage">vDNS-CDS <a href="#"> <i class="fa fa-times-circle"></i></a></span> + <span *ngFor="let tag of tags" class="single-tage">{{tag}} <i (click)="removeTag(tag)" + class="fa fa-times-circle"></i></span> </div> </div> </div> @@ -75,59 +72,48 @@ <div class="card creat-card"> <div class="single-line"> <h5 class="label-name"> - Custom key + Custom key </h5> </div> - <div class="single-custom-key"> - <div class="single-line-custom-key"> - <label class="label-name"><span>1-</span> Name</label> - <div class="label-input"> - <input name="key" type="input" placeholder="Enter name"> + <div *ngFor="let map of customKeysMap | keyvalue; let i=index" class="single-custom-key"> + <div class="single-line-custom-key"> + <label class="label-name"><span>{{i + 1}}-</span> Name</label> + <div class="label-input"> + <input value="{{map.key}}" name="key" type="input" placeholder="Enter name"> + </div> </div> - </div> - <div class="single-line-custom-key"> - <label class="label-name">Value</label> - <div class="label-input"> - <input name="value" type="input" - placeholder="Enter value"> + <div class="single-line-custom-key"> + <label class="label-name">Value</label> + <div class="label-input"> + <input value="{{map.value}}" name="value" type="input" placeholder="Enter value"> + </div> </div> - </div> - <div class="single-line-custom-key-delete"><button class="custom-key-delete"><i aria-hidden="true" class="icon-delete"></i></button></div> - </div> - <div class="single-custom-key"> - <div class="single-line-custom-key"> - <label class="label-name"><span>2-</span> Name</label> - <div class="label-input"> - <input name="key" type="input" placeholder="Enter name"> - </div> - </div> - <div class="single-line-custom-key"> - <label class="label-name">Value</label> - <div class="label-input"> - <input name="value" type="input" - placeholder="Enter value"> + <div class="single-line-custom-key-delete"> + <button (click)="removeKey($event,map.key)" class="custom-key-delete"><i aria-hidden="true" + class="icon-delete"></i></button> </div> </div> - <div class="single-line-custom-key-delete"><button class="custom-key-delete"><i aria-hidden="true" class="icon-delete"></i></button></div> - </div> - <div class="single-custom-key"> - <div class="single-line-custom-key"> - <label class="label-name"><span>3-</span> Name</label> - <div class="label-input"> - <input name="key" type="input" placeholder="Enter name"> + <div class="single-custom-key"> + <div class="single-line-custom-key"> + <label class="label-name"><span>{{customKeysMap.size + 1}}.</span> Name</label> + <div class="label-input"> + <input (keyup.enter)="addCustomKey()" name="key" type="input" class="mapKey" + placeholder="Enter name"> + </div> </div> - </div> - <div class="single-line-custom-key"> - <label class="label-name">Value</label> - <div class="label-input"> - <input name="value" type="input" - placeholder="Enter value"> + <div class="single-line-custom-key"> + <label class="label-name">Value</label> + <div class="label-input"> + <input (keyup.enter)="addCustomKey()" class="mapValue" name="value" type="input" + placeholder="Enter value"> + </div> </div> + <!-- <div class="single-line-custom-key-delete"><button (click)="removeKey($event)" + class="custom-key-delete"><i aria-hidden="true" class="icon-delete"></i></button></div> --> </div> - </div> </div> </div> -</div> +</div>
\ No newline at end of file diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.ts index 97040ee90..3a9b7b880 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/metadata-tab/metadata-tab.component.ts @@ -1,7 +1,8 @@ -import {Component, OnInit} from '@angular/core'; -import {PackageCreationService} from '../package-creation.service'; -import {MetaDataTabModel} from '../mapping-models/metadata/MetaDataTab.model'; -import {PackageCreationStore} from '../package-creation.store'; +import { Component, OnInit } from '@angular/core'; +import { PackageCreationService } from '../package-creation.service'; +import { MetaDataTabModel } from '../mapping-models/metadata/MetaDataTab.model'; +import { PackageCreationStore } from '../package-creation.store'; +import { ActivatedRoute } from '@angular/router'; @Component({ @@ -10,21 +11,90 @@ import {PackageCreationStore} from '../package-creation.store'; styleUrls: ['./metadata-tab.component.css'] }) export class MetadataTabComponent implements OnInit { - + packageNameAndVersionEnables = true; counter = 0; - modes: object[] = [ - {name: 'Designer Mode', style: 'mode-icon icon-designer-mode'}, - {name: 'Scripting Mode', style: 'mode-icon icon-scripting-mode'}, - {name: 'Generic Script Mode', style: 'mode-icon icon-generic-script-mode'}]; + tags = new Set<string>(); + customKeysMap = new Map(); + modes: any[] = [ + { name: 'Designer Mode', style: 'mode-icon icon-designer-mode' }]; + /* {name: 'Scripting Mode', style: 'mode-icon icon-scripting-mode'}, + {name: 'Generic Script Mode', style: 'mode-icon icon-generic-script-mode'}];*/ + modeType = this.modes[0].name; private metaDataTab: MetaDataTabModel = new MetaDataTabModel(); private errorMessage: string; - constructor(private packageCreationService: PackageCreationService, private packageCreationStore: PackageCreationStore) { + constructor( + private route: ActivatedRoute, + private packageCreationService: PackageCreationService, + private packageCreationStore: PackageCreationStore + ) { } ngOnInit() { - this.packageCreationStore.changeMetaData(this.metaDataTab); + this.metaDataTab.templateTags = this.tags; + this.metaDataTab.mapOfCustomKey = this.customKeysMap; + this.metaDataTab.mode = this.modeType; + + const id = this.route.snapshot.paramMap.get('id'); + id ? this.packageNameAndVersionEnables = false : + this.packageNameAndVersionEnables = true; + this.packageCreationStore.state$.subscribe(element => { + + if (element && element.metaData) { + + this.metaDataTab.name = element.metaData.name; + this.metaDataTab.version = element.metaData.version; + this.metaDataTab.description = element.metaData.description; + this.tags = element.metaData.templateTags; + this.metaDataTab.templateTags = this.tags; + console.log(element); + if (element.metaData.mode && element.metaData.mode.includes('DEFAULT')) { + this.metaDataTab.mode = 'Designer Mode'; + this.modeType = this.metaDataTab.mode; + } + + this.customKeysMap = element.metaData.mapOfCustomKey; + // this.tags = element.metaData.templateTags; + + } + }); + } + + removeTag(value) { + // console.log(event); + this.tags.delete(value); + } + + addTag(event) { + const value = event.target.value; + console.log(value); + if (value && value.trim().length > 0) { + event.target.value = ''; + this.tags.add(value); + } + } + + removeKey(event, key) { + console.log(event); + this.customKeysMap.delete(key); + } + + addCustomKey() { + // tslint:disable-next-line: no-string-literal + const key = document.getElementsByClassName('mapKey')[0]; + // tslint:disable-next-line: no-string-literal + const value = document.getElementsByClassName('mapValue')[0]; + + // tslint:disable-next-line: no-string-literal + if (key['value'] && value['value']) { + // tslint:disable-next-line: no-string-literal + this.customKeysMap.set(key['value'], value['value']); + // tslint:disable-next-line: no-string-literal + key['value'] = ''; + // tslint:disable-next-line: no-string-literal + value['value'] = ''; + } } validatePackageNameAndVersion() { @@ -39,4 +109,8 @@ export class MetadataTabComponent implements OnInit { } } + + saveMetaDataToStore() { + this.packageCreationStore.changeMetaData(this.metaDataTab); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.html index 906904855..67beca2a1 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.html @@ -2,21 +2,21 @@ <div class="new-wrapper"> <div class="container-fluid main-container"> - <header class="page-title"> - <div class="row"> - <h2 class="col m-0"> - <ul class="breadcrumb-header"> - <li>CBA Packages</li> - <li>Package Name</li> - </ul> - </h2> - <div class="col d-flex justify-content-end header-button-save"> - <button class="float btn btn-sm btn-outline-secondary" disabled>Discard Changes</button> - <button class="float btn btn-sm btn-primary" (click)="saveBluePrint()">Save</button> + <header class="page-title"> + <div class="row"> + <h2 class="col m-0"> + <ul class="breadcrumb-header"> + <li><a routerLink="/packages">CBA Packages</a></li> + <li>Package Name</li> + </ul> + </h2> + <div class="col d-flex justify-content-end header-button-save"> + <button class="float btn btn-sm btn-outline-secondary" (click)="goBackToDashBorad()">Discard + Changes</button> + <button class="float btn btn-sm btn-primary" (click)="saveBluePrint()">Save</button> + </div> </div> - </div> - </header> - + </header> <div class="container-fluid body-container"> @@ -39,93 +39,23 @@ </a> </div> - <div class="card creat-card view-package-container"> - <div class="row"> - <div class="col-8"> - <div class="row"> - <div class="col d-flex"> - <i class="package-type-icon icon-designer-mode"></i> - <div class="package-name-container"> - <div class="row"> - <div class="col-12 package-name deployed"> - Package Name - <span>.vLB.CDS</span> - <i class="icon-deploy"></i> - </div> - <div class="col-12 package-description"> - Last modified Oct 4, 2019 03:48 PM By Ahmed Abbas - </div> - </div> - <!-- <div class="row"> - <div class="col-4"> - <div class="package-view-title">Author Name</div> - <p>Abdelmuhaimen Seaudi</p> - </div> - <div class="col-4"> - <div class="package-view-title">Author Email</div> - <p>abdelmuhaimen.seaudi@orange.com</p> - </div> - <div class="col-4"> - <div class="package-view-title">Contributions</div> - <ul class="package-contributers"> - <li> - <button type="button" class="border-fade" data-toggle="tooltip" - data-placement="bottom" - title="User name"> - <img src="/assets/img/img-user1.jpeg"> - </button> - </li> - <li> - <button type="button" data-toggle="tooltip" data-placement="bottom" - title="User name"> - <img src="/assets/img/img-user2.jpg"> - </button> - </li> - <li> - <button type="button" data-toggle="tooltip" data-placement="bottom" - title="User name"> - <img src="/assets/img/img-user3.jpg"> - </button> - </li> - <li> - <a href="">5 contributors</a> - </li> - </ul> - </div> - </div> --> - </div> - </div> - </div> - </div> - <div class="col-4 package-view-button"> - <button class="btn btn-sm btn-outline-secondary"><i class="fa fa-play-circle"></i> Deploy</button> - <button class="btn btn-sm btn-primary">Designer Mode</button> - </div> - </div> - - </div> <nav class="row"> <!--Nav Tabs--> <div class="col"> <div class="nav nav-tabs " id="nav-tab" role="tablist"> <a class="nav-item nav-link active" id="nav-metadata-tab" data-toggle="tab" - href="#nav-metadata" - role="tab" aria-controls="nav-metadata" - aria-selected="true">METADATA</a> + href="#nav-metadata" role="tab" aria-controls="nav-metadata" aria-selected="false" + autofocus #nameit (focusout)="test()">METADATA</a> <a class="nav-item nav-link" id="nav-template-tab" data-toggle="tab" href="#nav-template" - role="tab" aria-controls="nav-template" - aria-selected="false">TEMPLATE & MAPPING</a> + role="tab" aria-controls="nav-template" aria-selected="false">TEMPLATE & MAPPING</a> <a class="nav-item nav-link" id="nav-scripts-tab" data-toggle="tab" href="#nav-scripts" - role="tab" aria-controls="nav-scripts" - aria-selected="false">SCRIPTS</a> + role="tab" aria-controls="nav-scripts" aria-selected="false">SCRIPTS</a> <a class="nav-item nav-link" id="nav-imports-tab" data-toggle="tab" href="#nav-imports" - role="tab" aria-controls="nav-imports" - aria-selected="false">IMPORTS</a> + role="tab" aria-controls="nav-imports" aria-selected="false">IMPORTS</a> <a class="nav-item nav-link" id="nav-authentication-tab" data-toggle="tab" - href="#nav-authentication" - role="tab" aria-controls="nav-authentication" - aria-selected="false">EXTERNAL SYSTEM AUTHENTICATION PROPERTIES</a> + href="#nav-authentication" role="tab" aria-controls="nav-authentication" + aria-selected="false">EXTERNAL SYSTEM AUTHENTICATION PROPERTIES</a> </div> </div> @@ -134,24 +64,24 @@ <div class="col"> <div class="tab-content" id="nav-tabContent"> <div class="tab-pane fade show active" id="nav-metadata" role="tabpanel" - aria-labelledby="nav-metadata-tab"> + aria-labelledby="nav-metadata-tab"> <app-metadata-tab></app-metadata-tab> </div> <div class="tab-pane fade" id="nav-template" role="tabpanel" - aria-labelledby="nav-template-tab"> + aria-labelledby="nav-template-tab"> <app-template-mapping></app-template-mapping> </div> <div class="tab-pane fade" id="nav-scripts" role="tabpanel" - aria-labelledby="nav-scripts-tab"> + aria-labelledby="nav-scripts-tab"> <app-scripts-tab></app-scripts-tab> </div> <div class="tab-pane fade" id="nav-imports" role="tabpanel" - aria-labelledby="nav-imports-tab"> + aria-labelledby="nav-imports-tab"> <app-imports-tab></app-imports-tab> </div> <div class="tab-pane fade" id="nav-authentication" role="tabpanel" - aria-labelledby="nav-authentication-tab"> + aria-labelledby="nav-authentication-tab"> <div class="card creat-card"> <div class="editor-container"> <app-dsl-definitions-tab></app-dsl-definitions-tab> @@ -163,5 +93,5 @@ </div> </div> </div> -</div> -</div> + </div> +</div>
\ No newline at end of file diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.ts index 58c04e83e..42db2688e 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.component.ts @@ -19,15 +19,17 @@ limitations under the License. ============LICENSE_END============================================ */ -import { Component, OnInit } from '@angular/core'; -import { FilesContent, FolderNodeElement, MetaDataTabModel } from './mapping-models/metadata/MetaDataTab.model'; +import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {FilesContent, FolderNodeElement, MetaDataTabModel} from './mapping-models/metadata/MetaDataTab.model'; import * as JSZip from 'jszip'; -import { PackageCreationStore } from './package-creation.store'; -import { Definition } from './mapping-models/CBAPacakge.model'; -import { PackageCreationModes } from './creationModes/PackageCreationModes'; -import { PackageCreationBuilder } from './creationModes/PackageCreationBuilder'; -import { PackageCreationUtils } from './package-creation.utils'; +import {PackageCreationStore} from './package-creation.store'; +import {Definition} from './mapping-models/CBAPacakge.model'; +import {PackageCreationModes} from './creationModes/PackageCreationModes'; +import {PackageCreationBuilder} from './creationModes/PackageCreationBuilder'; +import {PackageCreationUtils} from './package-creation.utils'; +import {MetadataTabComponent} from './metadata-tab/metadata-tab.component'; +import {Router} from '@angular/router'; @Component({ @@ -36,24 +38,33 @@ import { PackageCreationUtils } from './package-creation.utils'; styleUrls: ['./package-creation.component.css'] }) export class PackageCreationComponent implements OnInit { + + // adding initial referencing to designer mode + + + constructor(private packageCreationStore: PackageCreationStore, + private packageCreationUtils: PackageCreationUtils, + private router: Router) { + } + counter = 0; modes: object[] = [ - { name: 'Designer Mode', style: 'mode-icon icon-designer-mode' }, - { name: 'Scripting Mode', style: 'mode-icon icon-scripting-mode' }]; + {name: 'Designer Mode', style: 'mode-icon icon-designer-mode'}, + {name: 'Scripting Mode', style: 'mode-icon icon-scripting-mode'}]; private metaDataTab: MetaDataTabModel = new MetaDataTabModel(); private folder: FolderNodeElement = new FolderNodeElement(); private zipFile: JSZip = new JSZip(); private filesData: any = []; private definition: Definition = new Definition(); - // adding initial referencing to designer mode + @ViewChild(MetadataTabComponent, {static: false}) + private metadataTabComponent: MetadataTabComponent; - - constructor(private packageCreationStore: PackageCreationStore, private packageCreationUtils: PackageCreationUtils) { - } + @ViewChild('nameit', {static: true}) + private elementRef: ElementRef; ngOnInit() { - + this.elementRef.nativeElement.focus(); } saveBluePrint() { @@ -76,9 +87,10 @@ export class PackageCreationComponent implements OnInit { saveBluePrintToDataBase() { this.create(); - this.zipFile.generateAsync({ type: 'blob' }) + this.zipFile.generateAsync({type: 'blob'}) .then(blob => { this.packageCreationStore.saveBluePrint(blob); + this.router.navigate(['/packages']); }); } @@ -88,23 +100,14 @@ export class PackageCreationComponent implements OnInit { this.zipFile.folder(key.split('/')[0]); this.zipFile.file(key, value); }); - /*this.folder.TREE_DATA.forEach((path) => { - const name = path.name; - if (path.children) { - this.zipFile.folder(name); - path.children.forEach(children => { - const name2 = children.name; - if (FilesContent.getMapOfFilesNamesAndContent().has(name2)) { - this.zipFile.file(name + '/' + name2, FilesContent.getMapOfFilesNamesAndContent().get(name2)); - } else { - // this.zipFile.file(name2, FilesContent.getMapOfFilesNamesAndContent().get(name2)); - } - - }); - - } - });*/ + } + test() { + this.metadataTabComponent.saveMetaDataToStore(); + } + goBackToDashBorad() { + this.router.navigate(['/packages']); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.service.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.service.ts index 36da6a42f..494c9e555 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.service.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.service.ts @@ -23,9 +23,10 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs'; import {ApiService} from '../../../../common/core/services/api.service'; -import {BlueprintURLs} from '../../../../common/constants/app-constants'; +import {BlueprintURLs, ResourceDictionaryURLs} from '../../../../common/constants/app-constants'; import {PackagesApiService} from '../packages-api.service'; import {PackagesStore} from '../packages.store'; +import { ResourceDictionary } from './mapping-models/ResourceDictionary.model'; @Injectable({ providedIn: 'root' @@ -62,4 +63,7 @@ export class PackageCreationService { }); } + getTemplateAndMapping(variables: string[]): Observable<ResourceDictionary[]> { + return this.api.post(ResourceDictionaryURLs.searchResourceDictionaryByNames, variables); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.store.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.store.ts index 3dae2e570..0808223cd 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.store.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/package-creation.store.ts @@ -19,14 +19,15 @@ limitations under the License. ============LICENSE_END============================================ */ -import {Injectable} from '@angular/core'; +import { Injectable } from '@angular/core'; -import {Store} from '../../../../common/core/stores/Store'; +import { Store } from '../../../../common/core/stores/Store'; -import {CBAPackage, DslDefinition} from './mapping-models/CBAPacakge.model'; -import {PackageCreationService} from './package-creation.service'; -import {FolderNodeElement, MetaDataTabModel} from './mapping-models/metadata/MetaDataTab.model'; -import * as JSZip from 'jszip'; +import { CBAPackage, DslDefinition } from './mapping-models/CBAPacakge.model'; +import { PackageCreationService } from './package-creation.service'; +import { MetaDataTabModel } from './mapping-models/metadata/MetaDataTab.model'; +import { Observable } from 'rxjs'; +import { ResourceDictionary } from './mapping-models/ResourceDictionary.model'; @Injectable({ @@ -34,8 +35,6 @@ import * as JSZip from 'jszip'; }) export class PackageCreationStore extends Store<CBAPackage> { - private folder: FolderNodeElement = new FolderNodeElement(); - private zipFile: JSZip = new JSZip(); constructor(private packageCreationService: PackageCreationService) { super(new CBAPackage()); @@ -49,6 +48,13 @@ export class PackageCreationStore extends Store<CBAPackage> { }); } + setCustomKeys(mapOfCustomKey: Map<string, string>) { + this.setState({ + ...this.state, + metaData: this.state.metaData.setCustomKey(mapOfCustomKey) + }); + } + changeDslDefinition(dslDefinition: DslDefinition) { this.setState({ @@ -67,7 +73,6 @@ export class PackageCreationStore extends Store<CBAPackage> { } addScripts(name: string, content: string) { - this.setState({ ...this.state, scripts: this.state.scripts.setScripts(name, content) @@ -93,4 +98,19 @@ export class PackageCreationStore extends Store<CBAPackage> { templates: this.state.templates.setTemplates(filePath, fileContent) }); } + + addMapping(filePath: string, fileContent: string) { + this.setState({ + ...this.state, + mapping: this.state.mapping.setContent(filePath, fileContent) + }); + } + + getTemplateAndMapping(variables: string[]): Observable<ResourceDictionary[]> { + return this.packageCreationService.getTemplateAndMapping(variables); + } + + clear() { + this.setState(new CBAPackage()); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.html index 824152035..5dd68ed72 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.html @@ -24,15 +24,16 @@ aria-expanded="false" class="btn btn-link collapsed" data-toggle="collapse"> <i class="icon-file-code"></i> {{file.key}} </button> - <a (click)="removeFile(mapIndex)" class="accordion-delete"><i class="icon-delete"></i></a> + <a (click)="removeFile(file.key,mapIndex)" class="accordion-delete"><i + class="icon-delete"></i></a> </h5> </div> <div [attr.aria-labelledby]="'head-script-'+mapIndex" [id]="'id-script-'+mapIndex" class="collapse" data-parent="#accordion-script"> <div class="card-body"> - <ace-editor [(text)]="file.value" [mode]="'kotlin'" [autoUpdateContent]="true" - [durationBeforeCallback]="1000" [theme]="'tomorrow_night_bright'" #editor - style="height:300px;"> + <ace-editor [(text)]="file.value" (textChange)="textChanges($event,file.key)" [mode]="'kotlin'" + [autoUpdateContent]="true" [durationBeforeCallback]="1000" [theme]="'tomorrow_night_bright'" + #editor style="height:300px;"> </ace-editor> </div> </div> @@ -54,7 +55,7 @@ </button> </div> <div class="modal-body"> - <ngx-file-drop accept=".kt" (onFileDrop)="dropped($event)" (onFileLeave)="fileLeave($event)" + <ngx-file-drop accept=".kt,.py" (onFileDrop)="dropped($event)" (onFileLeave)="fileLeave($event)" (onFileOver)="fileOver($event)" dropZoneLabel="Drop files here"> <ng-template let-openFileSelector="openFileSelector" ngx-file-drop-content-tmp> <div class="folder-upload"> @@ -68,7 +69,7 @@ Files </button> </div> - <div class="folder-upload-type">Allowed file type: Kotlin(Kt)</div> + <div class="folder-upload-type">Allowed file type: Kotlin(kt), Python(py)</div> </ng-template> </ngx-file-drop> <div *ngFor="let item of uploadedFiles; let i=index" class="upload-table"> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.ts index eee291bba..5387489a2 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/scripts-tab/scripts-tab.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'; import { PackageCreationStore } from '../package-creation.store'; -import { PackageCreationUtils } from '../package-creation.utils'; import 'ace-builds/src-noconflict/ace'; import 'ace-builds/webpack-resolver'; @@ -17,7 +16,12 @@ export class ScriptsTabComponent implements OnInit { public files: NgxFileDropEntry[] = []; private fileNames: Set<string> = new Set(); - constructor(private packageCreationStore: PackageCreationStore, private packageCreationUtils: PackageCreationUtils) { + constructor( + private packageCreationStore: PackageCreationStore, + ) { } + + + ngOnInit() { this.packageCreationStore.state$.subscribe(cbaPackage => { if (cbaPackage.scripts && cbaPackage.scripts.files && cbaPackage.scripts.files.size > 0) { this.scriptsFiles = cbaPackage.scripts.files; @@ -25,10 +29,6 @@ export class ScriptsTabComponent implements OnInit { }); } - - ngOnInit() { - } - public dropped(files: NgxFileDropEntry[]) { this.files = files; for (const droppedFile of files) { @@ -43,11 +43,18 @@ export class ScriptsTabComponent implements OnInit { } } - removeFile(fileIndex: number) { - console.log(this.uploadedFiles[fileIndex]); - const filename = 'Scripts/' + this.uploadedFiles[fileIndex].name; - this.packageCreationStore.removeFileFromState(filename); - this.uploadedFiles.splice(fileIndex, 1); + removeFile(filePath: string, FileIndex: number) { + const filename = filePath.split('/')[2] || ''; + // const filename = 'Scripts/' + this.getFileType(this.uploadedFiles[fileIndex].name) + '/' + this.uploadedFiles[fileIndex].name; + this.packageCreationStore.removeFileFromState(filePath); + // remove from upload files array + // tslint:disable-next-line: prefer-for-of + for (let i = 0; i < this.uploadedFiles.length; i++) { + if (this.uploadedFiles[i].name === filename) { + this.uploadedFiles.splice(i, 1); + break; + } + } } public fileOver(event) { @@ -64,7 +71,7 @@ export class ScriptsTabComponent implements OnInit { droppedFile.file((file: File) => { const fileReader = new FileReader(); fileReader.onload = (e) => { - this.packageCreationStore.addScripts('Scripts/' + droppedFile.name, + this.packageCreationStore.addScripts('Scripts/' + this.getFileType(droppedFile.name) + '/' + droppedFile.name, fileReader.result.toString()); }; fileReader.readAsText(file); @@ -73,7 +80,22 @@ export class ScriptsTabComponent implements OnInit { } } + getFileType(filename: string): string { + let fileType = ''; + const fileExtension = filename.substring(filename.lastIndexOf('.') + 1); + if (fileExtension === 'py') { + fileType = 'python'; + } else if (fileExtension === 'kt') { + fileType = 'kotlin'; + } + return fileType; + } + resetTheUploadedFiles() { this.uploadedFiles = []; } + + textChanges(code: any, key: string) { + this.packageCreationStore.addScripts(key, code); + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/TemplateAndMapping.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/TemplateAndMapping.ts new file mode 100644 index 000000000..abfe4982b --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/TemplateAndMapping.ts @@ -0,0 +1,7 @@ +export class TemplateAndMapping { + isTemplate = false; + isMapping = false; + + constructor() { + } +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.html index 3c92bc7c7..8a43b010b 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.html @@ -17,7 +17,7 @@ <div class="card-header" id="headingOne"> <h5 class="mb-0 d-flex justify-content-between"> <button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" - aria-controls="collapseOne"> + aria-controls="collapseOne"> 1. Create Template </button> @@ -29,25 +29,24 @@ <div class="single-line"> <label class="label-name">Template Type</label> <div class="label-input"> - <label name="trst"> - <input class="form-check-input" type="radio" name="exampleRadios" id="exampleRadios1" - value=Velcoity> - + <label name="trst" (click)="allowedExt=['.vtl']"> + <input class="form-check-input" [(ngModel)]="templateExt" type="radio" + name="exampleRadios" id="exampleRadios1" value=Velcoity> <span> Velcoity </span> </label> - <label name="trst"> - <input class="form-check-input" type="radio" name="exampleRadios" id="exampleRadios1" - value=Velcoity> + <label name="trst" (click)="allowedExt=['.j2','.jinja2']"> + <input class="form-check-input" [(ngModel)]="templateExt" type="radio" + name="exampleRadios" id="exampleRadios1" value=Jinja> <span> Jinja </span> </label> - <label name="trst"> - <input class="form-check-input" type="radio" name="exampleRadios" id="exampleRadios1" - value=Velcoity> + <label name="trst" (click)="allowedExt=['.kt']"> + <input class="form-check-input" [(ngModel)]="templateExt" type="radio" + name="exampleRadios" id="exampleRadios1" value=Kotlin> <span> Kotlin @@ -55,12 +54,13 @@ </label> </div> </div> - <div class="create-template-import">Use the editor to add parameters or you can also <a href="#" - data-toggle="modal" - data-target="#exampleModal">Import - File</a></div> + <div class="create-template-import">Use the editor to add parameters or you can also + <a href="#" data-toggle="modal" (click)="allowedExt=[getFileExtension()]" + data-target="#templateModal">Import + File</a></div> <div class="editor-container"> - <app-dsl-definitions-tab></app-dsl-definitions-tab> + <app-source-editor (textChange)="textChanges($event,templateInfo.fileName)" + [(text)]="templateFileContent"></app-source-editor> </div> </div> </div> @@ -69,7 +69,7 @@ <div class="card-header" id="headingTwo"> <h5 class="mb-0"> <button class="btn btn-link collapsed" data-toggle="collapse" data-target="#collapseTwo" - aria-expanded="false" aria-controls="collapseTwo"> + aria-expanded="false" aria-controls="collapseTwo"> 2. Manage Mapping </button> </h5> @@ -78,14 +78,15 @@ <div class="card-body"> <h6 class="text-center">Select a source to load config parameters</h6> <div class="text-center"> - <a href="#" class="mapping-source-load"> + <a href="#" (click)="getMappingTableFromTemplate($event)" class="mapping-source-load"> <i class="icon-current-template"></i> - <br/> + <br /> <span>Use Current Template Instance</span> </a> - <a href="#" data-toggle="modal" data-target="#exampleModal" class="mapping-source-load"> + <a href="#" (click)="allowedExt=['.csv']" data-toggle="modal" data-target="#templateModal" + class="mapping-source-load"> <i class="icon-Upload-attribute"></i> - <br/> + <br /> <div>Upload attribute list</div> <div class="source-load-note">(Should be comma delimited file)</div> </a> @@ -101,37 +102,121 @@ </div> </div> + <div id="mapping-table" [hidden]="resourceDictionaryRes?.length == 0" class="mx-4 my-2"> + <table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" class="row-border hover"> + <thead> + <tr> + <th>Required</th> + <th>Parameter Name</th> + <th>Dictionary Name</th> + <th>Dictionary Source</th> + <th>Dependancies</th> + <th>Default</th> + <th>Data Type</th> + <th>Entry Schema</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let dict of resourceDictionaryRes"> + <td> + <i *ngIf="dict.definition?.property?.required" class="fa fa-check-square mx-2"></i> + <i *ngIf="!dict.definition?.property?.required" class="fa fa-square mx-2"></i> + </td> + <td>{{ dict.name }}</td> + <td>{{ dict.name }}</td> + <td> + <select class="custom-select" (click)="selectSource(dict,$event)"> + <option *ngFor="let val of dict.definition.sources | keyvalue"> + {{initMap(dict.name,val)}} + </option> + + </select> + </td> + <td> + <!-- <select class="custom-select"> + <option *ngFor="let val of getKeys(dependancies)"> + {{ getValue(dict.name)}}</option> + + </select> --> + <input type="text" class="form-control" [ngModel]="getValue(dict.name)"> + <!-- {{ dict.definition.sources }} --> + </td> + <td>{{ dict.definition?.property?.default }}</td> + <td>{{ dict.definition?.property?.type }}</td> + <td>{{ dict.definition?.property['entry_schema'] }}</td> + </tr> + </tbody> + </table> + </div> + + <div id="mapping-table" [hidden]="mappingRes?.length == 0" class="mx-4 my-2"> + <table datatable [dtOptions]="dtOptions" [dtTrigger]="resTableDtTrigger" class="row-border hover"> + <thead> + <tr> + <th>Required</th> + <th>Parameter Name</th> + <th>Dictionary Name</th> + <th>Dictionary Source</th> + <th>Dependancies</th> + <th>Default</th> + <th>Data Type</th> + <th>Entry Schema</th> + </tr> + </thead> + <tbody> + <tr *ngFor="let dict of mappingRes"> + <td> + <i *ngIf="dict.definition?.property?.required" class="fa fa-check-square mx-2"></i> + <i *ngIf="!dict.definition?.property?.required" class="fa fa-square mx-2"></i> + </td> + <td>{{ dict['name'] }}</td> + <td>{{ dict['name'] }}</td> + <td> + <input type="text" class="form-control" [value]="dict['dictionary-source']" + disabled> + + </td> + <td> + <input type="text" class="form-control" [value]="dict['dependencies']" disabled> + <!-- {{ dict.definition.sources }} --> + </td> + <td>{{ dict['property']['default'] }}</td> + <td>{{ dict['property']['type'] }}</td> + <td>{{ dict['property']['entry_schema'] }}</td> + </tr> + </tbody> + </table> + </div> </div> </div> - <div class="template-mapping-action"> <button class="btn btn-sm btn-outline-secondary">Cancel</button> - <button class="btn btn-sm btn-primary">Finish</button> + <button (click)="saveToStore()" class="btn btn-sm btn-primary">Finish</button> </div> </div> </div> -<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" - aria-hidden="true"> +<div class="modal fade" id="templateModal" tabindex="-1" role="dialog" aria-labelledby="templateModalLabel" + aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> - <h5 class="modal-title" id="exampleModalLabel">Import File</h5> + <h5 class="modal-title" id="templateModalLabel">Import File</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> - <ngx-file-drop dropZoneLabel="Drop files here" (onFileDrop)="dropped($event)" - (onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)"> + <ngx-file-drop [accept]="allowedExt" dropZoneLabel="Drop files here" (onFileDrop)="dropped($event)" + (onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)"> <ng-template ngx-file-drop-content-tmp let-openFileSelector="openFileSelector"> <div class="folder-upload"> - <img src="assets/img/folder-upload.svg"/> + <img src="assets/img/folder-upload.svg" /> </div> <div class="folder-upload-text"> Drag & Drop file @@ -141,15 +226,17 @@ Files </button> </div> - <div class="folder-upload-type">Allowed file type: json</div> + <div class="folder-upload-type">Allowed file type: + {{allowedExt}} + </div> </ng-template> </ngx-file-drop> <div class="upload-table" *ngFor="let item of uploadedFiles; let i=index"> <table class="table"> <thead> - <tr> - <th>Name : {{ item.name }}</th> - </tr> + <tr> + <th>Name : {{ item.name }}</th> + </tr> </thead> </table> </div> @@ -157,13 +244,13 @@ <div class="modal-footer"> <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal" - (click)="resetTheUploadedFiles()">Cancel + (click)="resetTheUploadedFiles()">Cancel </button> <button type="button" class="btn btn-sm btn-primary" data-dismiss="modal" - (click)="setFilesToStore()"> + (click)="uploadFile();openListView()"> Import </button> </div> </div> </div> -</div> +</div>
\ No newline at end of file diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.ts index 752bd510b..628d963ce 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component.ts @@ -1,24 +1,121 @@ -import {Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {FileSystemFileEntry, NgxFileDropEntry} from 'ngx-file-drop'; -import {PackageCreationStore} from '../../package-creation.store'; +import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; +import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'; +import { PackageCreationStore } from '../../package-creation.store'; +import { TemplateInfo, TemplateStore } from '../../template.store'; +import { Subject } from 'rxjs'; +import { ResourceDictionary } from '../../mapping-models/ResourceDictionary.model'; +import { DataTableDirective } from 'angular-datatables'; +import { Mapping, MappingAdapter } from '../../mapping-models/mappingAdapter.model'; +import { PackageCreationUtils } from '../../package-creation.utils'; +import { JsonConvert } from 'json2typescript'; @Component({ selector: 'app-templ-mapp-creation', templateUrl: './templ-mapp-creation.component.html', styleUrls: ['./templ-mapp-creation.component.css'] }) -export class TemplMappCreationComponent implements OnInit { +export class TemplMappCreationComponent implements OnInit, OnDestroy { + @Output() showListViewParent = new EventEmitter<any>(); public uploadedFiles: FileSystemFileEntry[] = []; private fileNames: Set<string> = new Set(); - + private jsonConvert = new JsonConvert(); public files: NgxFileDropEntry[] = []; fileName: any; + templateInfo = new TemplateInfo(); + private variables: string[] = []; + dtOptions: DataTables.Settings = {}; + // We use this trigger because fetching the list of persons can be quite long, + // thus we ensure the data is fetched before rendering + dtTrigger = new Subject(); + resTableDtTrigger = new Subject(); + resourceDictionaryRes: ResourceDictionary[] = []; + allowedExt = ['.vtl']; + @ViewChild(DataTableDirective, { static: false }) + dtElement: DataTableDirective; + MappingAdapter: MappingAdapter; + mapping = new Map(); + templateFileContent: string; + templateExt = 'Velcoity'; + dependancies = new Map<string, Array<string>>(); + dependanciesSource = new Map<string, string>(); + mappingRes = []; + + - constructor(private packageCreationStore: PackageCreationStore) { + constructor( + private packageCreationStore: PackageCreationStore, + private templateStore: TemplateStore, + private packageCreationUtils: PackageCreationUtils + ) { } ngOnInit() { + this.templateStore.state$.subscribe(templateInfo => { + console.log('----------'); + console.log(templateInfo); + this.templateInfo = templateInfo; + this.fileName = templateInfo.fileName.split('/')[1]; + if (templateInfo.type === 'mapping') { + this.mappingRes = templateInfo.mapping; + this.resourceDictionaryRes = []; + this.resTableDtTrigger.next(); + } else { + + this.templateFileContent = templateInfo.fileContent; + } + }); + + this.dtOptions = { + pagingType: 'full_numbers', + pageLength: 10, + destroy: true, + retrieve: true, + }; + } + + getFileExtension() { + switch (this.templateExt) { + case 'Velcoity': + return '.vtl'; + case 'Koltin': + return '.ktl'; + case 'Jinja': + return '.j2'; + default: + return '.vtl'; + } + } + + public getTemplateVariable(fileContent: string) { + const variables: string[] = []; + const stringsSlittedByBraces = fileContent.split('${'); + const stringsDefaultByDollarSignOnly = fileContent.split('"$'); + + for (let i = 1; i < stringsSlittedByBraces.length; i++) { + const element = stringsSlittedByBraces[i]; + if (element) { + const firstElement = element.split('}')[0]; + if (!variables.includes(firstElement)) { + variables.push(firstElement); + } else { + console.log(firstElement); + } + } + } + + for (let i = 1; i < stringsDefaultByDollarSignOnly.length; i++) { + const element = stringsDefaultByDollarSignOnly[i]; + if (element && !element.includes('$')) { + const firstElement = element.split('"')[0] + .replace('{', '') + .replace('}', '').trim(); + if (!variables.includes(firstElement)) { + variables.push(firstElement); + } + } + } + return variables; } public dropped(files: NgxFileDropEntry[]) { @@ -34,24 +131,59 @@ export class TemplMappCreationComponent implements OnInit { } } - removeFile(fileIndex: number) { - /*const filename = 'Definitions/' + this.uploadedFiles[fileIndex].name; - this.packageCreationStore.removeFileFromDefinition(filename); - this.uploadedFiles.splice(fileIndex, 1);*/ + uploadFile() { + this.dependancies.clear(); + this.dependanciesSource.clear(); + if (this.allowedExt.includes('.csv')) { + this.fetchCSVkeys(); + } else { + this.setTemplateFilesToStore(); + } } - setFilesToStore() { + fetchCSVkeys() { for (const droppedFile of this.uploadedFiles) { droppedFile.file((file: File) => { const fileReader = new FileReader(); fileReader.onload = (e) => { - this.packageCreationStore.addTemplate('Templates/' + this.fileName, - fileReader.result.toString()); + this.variables = fileReader.result.toString().split(','); + console.log(this.variables); + this.getMappingTableFromTemplate(null); }; fileReader.readAsText(file); }); + } + this.uploadedFiles = []; + } + private convertDictionaryToMap(resourceDictionaries: ResourceDictionary[]): Mapping[] { + const mapArray: Mapping[] = []; + for (const resourceDictionary of resourceDictionaries) { + this.MappingAdapter = new MappingAdapter(resourceDictionary, this.dependancies, this.dependanciesSource); + mapArray.push(this.MappingAdapter.ToMapping()); } + console.log(mapArray); + return mapArray; + } + + setTemplateFilesToStore() { + for (const droppedFile of this.uploadedFiles) { + droppedFile.file((file: File) => { + const fileReader = new FileReader(); + fileReader.onload = (e) => { + this.templateFileContent = fileReader.result.toString(); + this.variables = this.getTemplateVariable(this.templateFileContent); + + }; + fileReader.readAsText(file); + }); + } + this.uploadedFiles = []; + } + + textChanges(code: any, fileName: string) { + // this.packageCreationStore.addTemplate(fileName, code); + this.templateFileContent = code; } public fileOver(event) { @@ -61,8 +193,114 @@ export class TemplMappCreationComponent implements OnInit { public fileLeave(event) { console.log(event); } - + // resetTheUploadedFiles() { this.uploadedFiles = []; } + + openListView() { + this.showListViewParent.emit('tell parent to open create views'); + } + + getMappingTableFromTemplate(e) { + this.resourceDictionaryRes = []; + if (e) { + e.preventDefault(); + } + if (this.variables && this.variables.length > 0) { + console.log('base'); + this.packageCreationStore.getTemplateAndMapping(this.variables).subscribe(res => { + this.mappingRes = []; + this.resourceDictionaryRes = res; + console.log(this.resourceDictionaryRes); + this.rerender(); + }); + } + } + + initMap(key, map) { + if (!this.dependanciesSource.has(key)) { + this.dependanciesSource.set(key, map.key); + } + return map.key; + } + saveToStore() { + console.log(this.dependancies); + console.log(this.dependanciesSource); + if (this.fileName) { + // Save Mapping to Store + if (this.resourceDictionaryRes && this.resourceDictionaryRes.length > 0) { + const mapArray = this.convertDictionaryToMap(this.resourceDictionaryRes); + this.packageCreationStore.addMapping('Templates/' + this.fileName + '-mapping.json', + this.packageCreationUtils.transformToJson(this.jsonConvert.serialize(mapArray))); + this.resourceDictionaryRes = []; + } + // Save Template to store + if (this.templateFileContent) { + this.packageCreationStore.addTemplate('Templates/' + this.fileName + '-template' + this.getFileExtension(), + this.templateFileContent); + this.templateFileContent = ''; + } + } else { + + } + } + + selectSource(dict, e) { + const source = e.target.value; + let keyDepend = null; + try { + keyDepend = dict.definition.sources[source].properties['key-dependencies'] || null; + } catch (e) { } + console.log(dict); + console.log(source); + if (keyDepend) { + this.dependancies.set(dict.name, keyDepend); + } else { + // this.dependancies.delete(dict.name); + // this.dependanciesSource.delete(dict.name); + } + this.dependanciesSource.set(dict.name, source); + console.log(this.dependancies); + console.log(this.dependanciesSource); + } + + getKeys(map: Map<string, any>) { + return Array.from(map.keys()); + } + + getValue(key) { + return this.dependancies.get(key); + } + + rerender(): void { + if (this.dtElement.dtInstance) { + console.log('rerender'); + this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => { + dtInstance.destroy(); + this.dtElement.dtOptions = this.dtOptions; + this.dtElement.dtTrigger.next(); + dtInstance.draw(); + }); + } else { + this.dtTrigger.next(); + } + } + + ngOnDestroy(): void { + // Do not forget to unsubscribe the event + this.dtTrigger.unsubscribe(); + } +} + +class DependancyVal { + source: string; + keyDepend: any; + constructor( + source: string, + keyDepend: any + ) { + this.source = source; + this.keyDepend = keyDepend; + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.css b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.css index e69de29bb..054b5686e 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.css +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.css @@ -0,0 +1,3 @@ +.template-mapping-list { + cursor: pointer; +}
\ No newline at end of file diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.html index ddf06c824..ab97159b6 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.html @@ -1,8 +1,6 @@ - - - <a (click)="openCreationView()" class="create-template-mapping-button"> - <i class="fa fa-plus"></i> <span>Create</span> - </a> +<a *ngIf="isCreate" (click)="openCreationView()" class="create-template-mapping-button"> + <i class="fa fa-plus"></i> <span>Create</span> +</a> <div class="template-mapping-accordion"> @@ -10,8 +8,8 @@ <div class="card"> <div class="card-header" id="headingThree"> <h5 class="mb-0 d-flex justify-content-between"> - <button class="btn btn-link" data-toggle="collapse" data-target="#collapseThree" aria-expanded="true" - aria-controls="collapseThree"> + <button class="btn btn-link" data-toggle="collapse" data-target="#collapseThree" + aria-expanded="true" aria-controls="collapseThree"> Template and Mapping List </button> @@ -19,138 +17,19 @@ </div> <div id="collapseThree" class="collapse show" aria-labelledby="headingThree" data-parent="#accordion"> - <div class="card-body max-height-list" *ngFor="let file of templates.files | keyvalue; let mapIndex = index"> - <div class="row"> - <div class="col"> - <a href="#" class="template-mapping-list active">{{file.key}} - <span>Mapping</span> - <span>Template</span> - </a> - </div> - <!-- <div class="col"> - <a href="#" class="template-mapping-list">vf-module-1 - <span>Mapping</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-2 - <span>Mapping</span> - </a> - </div>--> - </div> - <!-- <div class="row"> - <div class="col"> - <a href="#" class="template-mapping-list">hostname - <span>Mapping</span> - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-1 - <span>Mapping</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-2 - <span>Mapping</span> - </a> - </div> - </div> - <div class="row"> - <div class="col"> - <a href="#" class="template-mapping-list">hostname - - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-1 - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-2 - <span>Mapping</span> - <span>Template</span> - </a> - </div> - </div> - <div class="row"> - <div class="col"> - <a href="#" class="template-mapping-list">hostname - - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-1 - <span>Mapping</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-2 - <span>Mapping</span> - </a> - </div> - </div> - <div class="row"> - <div class="col"> - <a href="#" class="template-mapping-list">hostname - <span>Mapping</span> - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-1 - <span>Mapping</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-2 - <span>Mapping</span> - </a> - </div> - </div> + <div class="card-body max-height-list"> <div class="row"> - <div class="col"> - <a href="#" class="template-mapping-list">hostname - <span>Mapping</span> - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-1 - <span>Mapping</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-2 - <span>Mapping</span> + <!-- <div class="col-4" style="color:white" *ngFor="let file of templates.files | keyvalue; let mapIndex = index">--> + <div class="col-4" style="color:white" *ngFor="let file of getKeys(templateAndMappingMap)"> + <a (click)="setSourceCodeEditor(file)" class="template-mapping-list active">{{file}} + <span *ngIf="getValue(file).isMapping">Mapping</span> + <span *ngIf="getValue(file).isTemplate">Template</span> </a> </div> </div> - <div class="row"> - <div class="col"> - <a href="#" class="template-mapping-list">hostname - - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-1 - <span>Template</span> - </a> - </div> - <div class="col"> - <a href="#" class="template-mapping-list">vf-module-2 - <span>Mapping</span> - <span>Template</span> - </a> - </div> - </div>--> </div> </div> </div> - + </div> -</div> +</div>
\ No newline at end of file diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.ts index 5cb41c35e..372fbca03 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component.ts @@ -1,6 +1,10 @@ -import {Component, EventEmitter, OnInit, Output} from '@angular/core'; -import {PackageCreationStore} from '../../package-creation.store'; -import {Template} from '../../mapping-models/CBAPacakge.model'; +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { PackageCreationStore } from '../../package-creation.store'; +import { Mapping, Template } from '../../mapping-models/CBAPacakge.model'; +import { TemplateInfo, TemplateStore } from '../../template.store'; +import { TemplateAndMapping } from '../TemplateAndMapping'; +import { ActivatedRoute } from '@angular/router'; + @Component({ selector: 'app-templ-mapp-listing', @@ -9,21 +13,97 @@ import {Template} from '../../mapping-models/CBAPacakge.model'; }) export class TemplMappListingComponent implements OnInit { @Output() showCreationViewParentNotification = new EventEmitter<any>(); + private templateAndMappingMap = new Map<string, TemplateAndMapping>(); private templates: Template; + private mapping: Mapping; + isCreate = true; + + constructor( + private packageCreationStore: PackageCreationStore, + private templateStore: TemplateStore, + private route: ActivatedRoute + ) { + } - constructor(private packageCreationStore: PackageCreationStore) { + ngOnInit() { + if (this.route.snapshot.paramMap.has('id')) { + this.isCreate = false; + } this.packageCreationStore.state$.subscribe(cba => { if (cba.templates) { this.templates = cba.templates; + this.mapping = cba.mapping; + console.log(this.mapping); + let templateAndMapping; + this.templateAndMappingMap.clear(); + this.templates.files.forEach((value, key) => { + templateAndMapping = new TemplateAndMapping(); + templateAndMapping.isTemplate = true; + const isFromTemplate = true; + this.setIsMappingOrTemplate(key, templateAndMapping, isFromTemplate); + }); + this.mapping.files.forEach((value, key) => { + templateAndMapping = new TemplateAndMapping(); + templateAndMapping.isMapping = true; + const isFromTemplate = false; + this.setIsMappingOrTemplate(key, templateAndMapping, isFromTemplate); + }); + console.log('hello there '); + console.log(this.templateAndMappingMap); } }); } - ngOnInit() { + private setIsMappingOrTemplate(key: string, templateAndMapping: TemplateAndMapping, isFromTemplate: boolean) { + const nameOfFile = key.split('/')[1].split('.')[0].split('-')[0]; + // const fullName = nameOfFile + ',' + key.split('.'); + if (this.templateAndMappingMap.has(nameOfFile)) { + const templateAndMappingExisted = this.templateAndMappingMap.get(nameOfFile); + !isFromTemplate ? templateAndMappingExisted.isMapping = true : templateAndMappingExisted.isTemplate = true; + this.templateAndMappingMap.set(nameOfFile, templateAndMappingExisted); + } else { + this.templateAndMappingMap.set(nameOfFile, templateAndMapping); + } + } openCreationView() { this.showCreationViewParentNotification.emit('tell parent to open create views'); } + setSourceCodeEditor(key: string) { + const templateKey = 'Templates/' + key + '-template.vtl'; + this.packageCreationStore.state$.subscribe(cba => { + console.log('cba ------'); + console.log(cba); + console.log(key); + console.log(this.templateAndMappingMap); + if (cba.templates && cba.templates.files.has(templateKey)) { + const fileContent = cba.templates.getValue(templateKey.trim()); + console.log(fileContent); + const templateInfo = new TemplateInfo(); + templateInfo.fileContent = fileContent; + templateInfo.fileName = templateKey; + this.templateStore.changeTemplateInfo(templateInfo); + } + const mappingKey = 'Templates/' + key + '-mapping.json'; + if (cba.mapping && cba.mapping.files.has(mappingKey)) { + const obj = JSON.parse(cba.mapping.getValue(mappingKey)); + const templateInfo = new TemplateInfo(); + templateInfo.mapping = obj; + templateInfo.fileName = mappingKey; + templateInfo.type = 'mapping'; + this.templateStore.changeTemplateInfo(templateInfo); + } + }); + } + + getKeys(templateAndMappingMap: Map<string, TemplateAndMapping>) { + return Array.from(this.templateAndMappingMap.keys()); + } + + getValue(file: string) { + return this.templateAndMappingMap.get(file); + } + } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.html index 83f3c84c8..f8cfe7a6a 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.html @@ -1,9 +1,9 @@ -<div *ngIf="!creationView"> - <app-templ-mapp-listing - (showCreationViewParentNotification)="openCreationView($event)"> - </app-templ-mapp-listing> +<div *ngIf="!listView"> + <app-templ-mapp-listing + (showCreationViewParentNotification)="openCreationView($event)"> + </app-templ-mapp-listing> </div> -<div *ngIf="creationView"> - <app-templ-mapp-creation></app-templ-mapp-creation> +<div *ngIf="!creationView"> + <app-templ-mapp-creation (showListViewParent)="openListView($event)"></app-templ-mapp-creation> </div> diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.ts index 7e9ae1639..106765834 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template-mapping/template-mapping.component.ts @@ -1,20 +1,37 @@ import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { PackageCreationStore } from '../package-creation.store'; @Component({ - selector: 'app-template-mapping', - templateUrl: './template-mapping.component.html', - styleUrls: ['./template-mapping.component.css'] + selector: 'app-template-mapping', + templateUrl: './template-mapping.component.html', + styleUrls: ['./template-mapping.component.css'] }) export class TemplateMappingComponent implements OnInit { - creationView = false; + creationView = true; + listView = false; - constructor() { } + constructor(private route: ActivatedRoute, private pakcageStore: PackageCreationStore) { + } - ngOnInit() { - } + ngOnInit() { + if (this.route.snapshot.paramMap.has('id')) { + console.log('Edit mode'); + this.creationView = false; + this.listView = false; + } else { + console.log('Create mode'); + this.pakcageStore.clear(); + } + } + openCreationView() { + this.creationView = false; + this.listView = true; + } - openCreationView() { - this.creationView = true; - } + openListView() { + this.listView = false; + this.creationView = false; + } } diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template.store.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template.store.ts new file mode 100644 index 000000000..9c8775514 --- /dev/null +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/package-creation/template.store.ts @@ -0,0 +1,58 @@ +/* +============LICENSE_START========================================== +=================================================================== +Copyright (C) 2019 Orange. All rights reserved. +=================================================================== + +Unless otherwise specified, all software contained herein is licensed +under the Apache License, Version 2.0 (the License); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +============LICENSE_END============================================ +*/ + +import { Injectable } from '@angular/core'; + +import { Store } from '../../../../common/core/stores/Store'; + + +export class TemplateInfo { + fileName: string; + fileContent: string; + type: string; + mapping = []; + + + constructor() { + this.fileName = ''; + this.fileContent = ''; + this.type = ''; + } + + +} + +@Injectable({ + providedIn: 'root' +}) +export class TemplateStore extends Store<TemplateInfo> { + + + constructor() { + super(new TemplateInfo()); + } + + changeTemplateInfo(templateInfo: TemplateInfo) { + this.setState(templateInfo); + } + + +} diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages-dashboard/package-list/package-list.component.html b/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages-dashboard/package-list/package-list.component.html index 7f6c0a6f4..9322ee783 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages-dashboard/package-list/package-list.component.html +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages-dashboard/package-list/package-list.component.html @@ -7,7 +7,8 @@ </div> <div class="card-footer row"> <div class="col"> - <a routerLink="/packages/createPackage" role="button" aria-pressed="true" class="btn-create-package float">Create + <a routerLink="/packages/createPackage" role="button" aria-pressed="true" + class="btn-create-package float">Create </a> </div> <div class="col"> @@ -25,10 +26,11 @@ <div class="card-body"> <div class="row"> <div class="col-9 pr-0"> - <h5 class="card-title" [routerLink]="['/packages/package', bluePrint.id]" (click)="testDispatch(bluePrint)"> + <a class="card-title" [routerLink]="['/packages/package', bluePrint.id]" + (click)="testDispatch(bluePrint)"> <img class="icon-deployed" src="/assets/img/icon-deploy.svg"> {{bluePrint.artifactName}} - </h5> + </a> </div> <div class="col-3"> @@ -56,25 +58,24 @@ <div class="row"> <div class="col"> <p class="mb-0">Last modified {{ bluePrint.createdDate | date:'short' }} - </p> - <p>By {{bluePrint.updatedBy}}</p> + </p> + <p>By {{bluePrint.updatedBy}}</p> <ul class="package-contributers"> <li> <button type="button" class="border-fade" data-toggle="tooltip" - data-placement="bottom" - title="User name"> + data-placement="bottom" title="User name"> <img src="/assets/img/img-user1.jpeg"> </button> </li> <li> <button type="button" data-toggle="tooltip" data-placement="bottom" - title="User name"> + title="User name"> <img src="/assets/img/img-user2.jpg"> </button> </li> <li> <button type="button" data-toggle="tooltip" data-placement="bottom" - title="User name"> + title="User name"> <img src="/assets/img/img-user3.jpg"> </button> </li> @@ -101,4 +102,4 @@ </div> </div> -</div> +</div>
\ No newline at end of file diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.module.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.module.ts index 7935e12a3..66c7b498a 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.module.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.module.ts @@ -1,33 +1,34 @@ -import {NgModule} from '@angular/core'; -import {CommonModule, JsonPipe} from '@angular/common'; -import {ApiService} from '../../../common/core/services/api.typed.service'; -import {PackagesRoutingModule} from './packages.routing.module'; -import {NgbPaginationModule} from '@ng-bootstrap/ng-bootstrap'; -import {SharedModulesModule} from '../../shared-modules/shared-modules.module'; -import {PackagesDashboardComponent} from './packages-dashboard/packages-dashboard.component'; -import {PackageListComponent} from './packages-dashboard/package-list/package-list.component'; -import {DesignerComponent} from './designer/designer.component'; -import {SidebarModule} from 'ng-sidebar'; -import {PackagePaginationComponent} from './packages-dashboard/package-pagination/package-pagination.component'; -import {SortPackagesComponent} from './packages-dashboard/sort-packages/sort-packages.component'; -import {PackagesHeaderComponent} from './packages-dashboard/packages-header/packages-header.component'; -import {PackagesSearchComponent} from './packages-dashboard/search-by-packages/search-by-packages.component'; -import {TagsFilteringComponent} from './packages-dashboard/filter-by-tags/filter-by-tags.component'; -import {ConfigurationDashboardComponent} from './configuration-dashboard/configuration-dashboard.component'; -import {FunctionsComponent} from './designer/functions/functions.component'; -import {ActionsComponent} from './designer/actions/actions.component'; -import {PackageCreationComponent} from './package-creation/package-creation.component'; -import {FormsModule} from '@angular/forms'; -import {ImportsTabComponent} from './package-creation/imports-tab/imports-tab.component'; -import {NgxFileDropModule} from 'ngx-file-drop'; -import {TemplateMappingComponent} from './package-creation/template-mapping/template-mapping.component'; -import {SourceEditorComponent} from './source-editor/source-editor.component'; -import {ScriptsTabComponent} from './package-creation/scripts-tab/scripts-tab.component'; -import {AceEditorModule} from 'ng2-ace-editor'; -import {MetadataTabComponent} from './package-creation/metadata-tab/metadata-tab.component'; -import {DslDefinitionsTabComponent} from './package-creation/dsl-definitions-tab/dsl-definitions-tab.component'; +import { NgModule } from '@angular/core'; +import { CommonModule, JsonPipe } from '@angular/common'; +import { ApiService } from '../../../common/core/services/api.typed.service'; +import { PackagesRoutingModule } from './packages.routing.module'; +import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap'; +import { SharedModulesModule } from '../../shared-modules/shared-modules.module'; +import { PackagesDashboardComponent } from './packages-dashboard/packages-dashboard.component'; +import { PackageListComponent } from './packages-dashboard/package-list/package-list.component'; +import { DesignerComponent } from './designer/designer.component'; +import { SidebarModule } from 'ng-sidebar'; +import { PackagePaginationComponent } from './packages-dashboard/package-pagination/package-pagination.component'; +import { SortPackagesComponent } from './packages-dashboard/sort-packages/sort-packages.component'; +import { PackagesHeaderComponent } from './packages-dashboard/packages-header/packages-header.component'; +import { PackagesSearchComponent } from './packages-dashboard/search-by-packages/search-by-packages.component'; +import { TagsFilteringComponent } from './packages-dashboard/filter-by-tags/filter-by-tags.component'; +import { ConfigurationDashboardComponent } from './configuration-dashboard/configuration-dashboard.component'; +import { ActionsComponent } from './designer/actions/actions.component'; +import { PackageCreationComponent } from './package-creation/package-creation.component'; +import { FormsModule } from '@angular/forms'; +import { ImportsTabComponent } from './package-creation/imports-tab/imports-tab.component'; +import { NgxFileDropModule } from 'ngx-file-drop'; +import { TemplateMappingComponent } from './package-creation/template-mapping/template-mapping.component'; +import { SourceEditorComponent } from './source-editor/source-editor.component'; +import { ScriptsTabComponent } from './package-creation/scripts-tab/scripts-tab.component'; +import { AceEditorModule } from 'ng2-ace-editor'; +import { MetadataTabComponent } from './package-creation/metadata-tab/metadata-tab.component'; +import { DslDefinitionsTabComponent } from './package-creation/dsl-definitions-tab/dsl-definitions-tab.component'; import { TemplMappCreationComponent } from './package-creation/template-mapping/templ-mapp-creation/templ-mapp-creation.component'; import { TemplMappListingComponent } from './package-creation/template-mapping/templ-mapp-listing/templ-mapp-listing.component'; +import { DataTablesModule } from 'angular-datatables'; +import { DesignerSourceViewComponent } from './designer/source-view/source-view.component'; @NgModule({ declarations: [PackagesDashboardComponent, @@ -39,7 +40,6 @@ import { TemplMappListingComponent } from './package-creation/template-mapping/t SortPackagesComponent, ConfigurationDashboardComponent, PackagesHeaderComponent, - FunctionsComponent, ActionsComponent, PackageCreationComponent, ImportsTabComponent, @@ -50,6 +50,7 @@ import { TemplMappListingComponent } from './package-creation/template-mapping/t ScriptsTabComponent, MetadataTabComponent, DslDefinitionsTabComponent, + DesignerSourceViewComponent, ], imports: [ CommonModule, @@ -59,7 +60,8 @@ import { TemplMappListingComponent } from './package-creation/template-mapping/t SidebarModule.forRoot(), FormsModule, NgxFileDropModule, - AceEditorModule + AceEditorModule, + DataTablesModule ], providers: [ApiService, JsonPipe], bootstrap: [] diff --git a/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.routing.module.ts b/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.routing.module.ts index 913bb1081..ad06cf15f 100644 --- a/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.routing.module.ts +++ b/cds-ui/designer-client/src/app/modules/feature-modules/packages/packages.routing.module.ts @@ -4,6 +4,7 @@ import {PackagesDashboardComponent} from './packages-dashboard/packages-dashboar import {DesignerComponent} from './designer/designer.component'; import {PackageCreationComponent} from './package-creation/package-creation.component'; import {ConfigurationDashboardComponent} from './configuration-dashboard/configuration-dashboard.component'; +import { DesignerSourceViewComponent } from './designer/source-view/source-view.component'; const routes: Routes = [ @@ -12,6 +13,7 @@ const routes: Routes = [ component: PackagesDashboardComponent }, {path: 'designer', component: DesignerComponent}, + { path: 'designer/source', component: DesignerSourceViewComponent }, {path: 'package/:id', component: ConfigurationDashboardComponent}, {path: 'createPackage', component: PackageCreationComponent}, ]; diff --git a/cds-ui/designer-client/src/app/modules/shared-modules/header/header.component.html b/cds-ui/designer-client/src/app/modules/shared-modules/header/header.component.html index f57ab100b..502e098d3 100644 --- a/cds-ui/designer-client/src/app/modules/shared-modules/header/header.component.html +++ b/cds-ui/designer-client/src/app/modules/shared-modules/header/header.component.html @@ -5,14 +5,14 @@ <div class="overflow-container"> <ul class="menu-dropdown"> <li class="active"> - <a routerLink="packages/list">Packages</a> + <a routerLink="/packages">Packages</a> <span class="icon"> <!-- <i class="fa fa-dashboard"></i> --> <i class="icon-package"></i> </span> </li> <li class="menu-hasdropdown"> - <a href="#">Data Dictionary</a> + <a >Data Dictionary</a> <span class="icon"> <i class="icon-dictionary"></i> </span> diff --git a/cds-ui/designer-client/tslint.json b/cds-ui/designer-client/tslint.json index ecbd7cf88..f85fc68d9 100644 --- a/cds-ui/designer-client/tslint.json +++ b/cds-ui/designer-client/tslint.json @@ -87,10 +87,5 @@ }, "rulesDirectory": [ "codelyzer" - ], - "linterOptions": { - "exclude": [ - "src/app/modules/feature-modules/packages/designer/designer.component.ts" - ] - } + ] } diff --git a/cds-ui/pom.xml b/cds-ui/pom.xml index 122823419..4a9f0fdad 100644 --- a/cds-ui/pom.xml +++ b/cds-ui/pom.xml @@ -24,7 +24,7 @@ limitations under the License. <parent> <groupId>org.onap.ccsdk.parent</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>1.5.1</version> + <version>1.5.2</version> <relativePath/> </parent> diff --git a/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/activation-blueprint.json b/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/activation-blueprint.json index 639c21490..85a056c5e 100644 --- a/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/activation-blueprint.json +++ b/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/activation-blueprint.json @@ -179,6 +179,10 @@ "another-mapping": { "type": "artifact-mapping-resource", "file": "Definitions/another-mapping.json" + }, + "notemplate-mapping": { + "type": "artifact-mapping-resource", + "file": "Definitions/notemplate-mapping.json" } } }, diff --git a/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/notemplate-mapping.json b/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/notemplate-mapping.json new file mode 100644 index 000000000..5aa3b3dcb --- /dev/null +++ b/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/notemplate-mapping.json @@ -0,0 +1,36 @@ +[ + { + "name": "service-instance-id", + "input-param": true, + "property": { + "type": "string" + }, + "dictionary-name": "service-instance-id", + "dictionary-source": "input", + "dependencies": [ + ] + }, + { + "name": "vnf-id", + "input-param": true, + "property": { + "type": "string" + }, + "dictionary-name": "vnf-id", + "dictionary-source": "input", + "dependencies": [] + }, + { + "name": "vnf_name", + "input-param": false, + "property": { + "type": "string" + }, + "dictionary-name": "vnf_name", + "dictionary-source": "sdnc", + "dependencies": [ + "service-instance-id", + "vnf-id" + ] + } +]
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/test-blueprint/pom.xml b/components/model-catalog/blueprint-model/test-blueprint/pom.xml index f9dab0227..29daa768e 100644 --- a/components/model-catalog/blueprint-model/test-blueprint/pom.xml +++ b/components/model-catalog/blueprint-model/test-blueprint/pom.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> @@ -35,4 +34,8 @@ <module>capability_cli</module> <module>resource-audit</module> </modules> + + <properties> + <sonar.skip>true</sonar.skip> + </properties> </project> diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/PNF_CDS_RESTCONF.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/PNF_CDS_RESTCONF.json new file mode 100644 index 000000000..d84fd8182 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/PNF_CDS_RESTCONF.json @@ -0,0 +1,424 @@ +{ + "tosca_definitions_version" : "controller_blueprint_1_0_0", + "metadata" : { + "template_author" : "Raj Gumma", + "author-email" : "raj.gumma@est.tech", + "user-groups" : "ADMIN, OPERATION", + "template_name" : "PNF_CDS_RESTCONF", + "template_version" : "1.0.0", + "template_tags" : "PNF, Restconf, config, configuration, software upgrade" + }, + "imports" : [ { + "file" : "Definitions/data_types.json" + }, { + "file" : "Definitions/relationship_types.json" + }, { + "file" : "Definitions/artifact_types.json" + }, { + "file" : "Definitions/node_types.json" + }, { + "file" : "Definitions/policy_types.json" + } ], + "dsl_definitions" : { + "config-deploy-properties" : { + "resolution-key" : { + "get_input" : "resolution-key" + } + }, + "preCheck-properties" : { + "resolution-key" : { + "get_input" : "resolution-key" + } + }, + "downloadNESw-properties" : { + "resolution-key" : { + "get_input" : "resolution-key" + } + }, + "activateNESw-properties" : { + "resolution-key" : { + "get_input" : "resolution-key" + } + }, + "postCheck-properties" : { + "resolution-key" : { + "get_input" : "resolution-key" + } + } + }, + "topology_template" : { + "workflows" : { + "config-assign" : { + "steps" : { + "activate-process" : { + "description" : "Create a configlet", + "target" : "config-assign", + "activities" : [ { + "call_operation" : "" + } ] + } + }, + "inputs" : { + "resolution-key" : { + "required" : true, + "type" : "string" + }, + "store-result" : { + "required" : true, + "type" : "boolean" + }, + "config-assign-properties" : { + "description" : "Dynamic PropertyDefinition for workflow(config-assign).", + "required" : true, + "type" : "dt-config-assign-properties" + } + } + }, + "config-deploy" : { + "steps" : { + "activate-process" : { + "description" : "Send a configlet to the pnf", + "target" : "config-deploy", + "activities" : [ { + "call_operation" : "" + } ] + } + }, + "inputs" : { + "resolution-key" : { + "required" : true, + "type" : "string" + }, + "config-deploy-properties" : { + "description" : "Dynamic PropertyDefinition for workflow(config-deploy).", + "required" : true, + "type" : "dt-config-deploy-properties" + } + } + }, + "preCheck" : { + "steps" : { + "activate-process" : { + "description" : "Check if pnf ready for sw upgrade", + "target" : "preCheck", + "activities" : [ { + "call_operation" : "" + } ] + } + }, + "inputs" : { + "resolution-key" : { + "required" : true, + "type" : "string" + }, + "preCheck-properties" : { + "description" : "Dynamic PropertyDefinition for preCheck workflow(software-upgrade).", + "required" : true, + "type" : "dt-preCheck-properties" + } + } + }, + "downloadNESw" : { + "steps" : { + "activate-process" : { + "description" : "Trigger download new software for sw upgrade", + "target" : "downloadNESw", + "activities" : [ { + "call_operation" : "" + } ] + } + }, + "inputs" : { + "resolution-key" : { + "required" : true, + "type" : "string" + }, + "downloadNESw-properties" : { + "description" : "Dynamic PropertyDefinition for downloadNESw workflow(software-upgrade).", + "required" : true, + "type" : "dt-downloadNESw-properties" + } + } + }, + "activateNESw" : { + "steps" : { + "activate-process" : { + "description" : "Trigger activation of target software version for pnf upgrade", + "target" : "activateNESw", + "activities" : [ { + "call_operation" : "" + } ] + } + }, + "inputs" : { + "resolution-key" : { + "required" : true, + "type" : "string" + }, + "activateNESw-properties" : { + "description" : "Dynamic PropertyDefinition for activateNESw workflow(software-upgrade).", + "required" : true, + "type" : "dt-activateNESw-properties" + } + } + }, + "postCheck" : { + "steps" : { + "activate-process" : { + "description" : "Check if pnf upgrade is completed", + "target" : "postCheck", + "activities" : [ { + "call_operation" : "" + } ] + } + }, + "inputs" : { + "resolution-key" : { + "required" : true, + "type" : "string" + }, + "postCheck-properties" : { + "description" : "Dynamic PropertyDefinition for postCheck workflow(software-upgrade).", + "required" : true, + "type" : "dt-postCheck-properties" + } + } + } + }, + "node_templates" : { + "config-assign" : { + "type" : "component-resource-resolution", + "interfaces" : { + "ResourceResolutionComponent" : { + "operations" : { + "process" : { + "inputs" : { + "resolution-key" : { + "get_input" : "resolution-key" + }, + "store-result" : true, + "artifact-prefix-names" : [ "config-assign" ] + }, + "outputs" : { + "resource-assignment-params" : { + "get_attribute" : [ "SELF", "assignment-params" ] + }, + "status" : "success" + } + } + } + } + }, + "artifacts" : { + "config-assign-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/config-assign-restconf-configlet-template.vtl" + }, + "config-assign-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/config-assign-pnf-mapping.json" + } + } + }, + "config-deploy" : { + "type" : "component-script-executor", + "interfaces" : { + "ComponentScriptExecutor" : { + "operations" : { + "process" : { + "implementation" : { + "primary" : "component-script", + "timeout" : 180, + "operation_host" : "SELF" + }, + "inputs" : { + "script-type" : "kotlin", + "script-class-reference" : "cba.pnf.config.aai.RestconfConfigDeploy", + "dynamic-properties" : "*config-deploy-properties" + }, + "outputs" : { + "response-data" : "", + "status" : "success" + } + } + } + } + }, + "artifacts" : { + "config-deploy-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/restconf-mount-template.vtl" + }, + "config-deploy-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/config-deploy-pnf-mapping.json" + } + } + }, + "preCheck" : { + "type" : "component-script-executor", + "interfaces" : { + "ComponentScriptExecutor" : { + "operations" : { + "process" : { + "implementation" : { + "primary" : "component-script", + "timeout" : 180, + "operation_host" : "SELF" + }, + "inputs" : { + "script-type" : "kotlin", + "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade", + "dynamic-properties" : "*preCheck-properties" + }, + "outputs" : { + "response-data" : "", + "status" : "success" + } + } + } + } + }, + "artifacts" : { + "mount-node-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/restconf-mount-template.vtl" + }, + "mount-node-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/pnf-software-upgrade-mapping.json" + } + } + }, + "downloadNESw" : { + "type" : "component-script-executor", + "interfaces" : { + "ComponentScriptExecutor" : { + "operations" : { + "process" : { + "implementation" : { + "primary" : "component-script", + "timeout" : 180, + "operation_host" : "SELF" + }, + "inputs" : { + "script-type" : "kotlin", + "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade", + "dynamic-properties" : "*downloadNESw-properties" + }, + "outputs" : { + "response-data" : "", + "status" : "success" + } + } + } + } + }, + "artifacts" : { + "mount-node-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/restconf-mount-template.vtl" + }, + "mount-node-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/pnf-software-upgrade-mapping.json" + }, + "configure-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/pnf-swug-config-template.vtl" + }, + "configure-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/pnf-software-upgrade-mapping.json" + }, + "download-ne-sw-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/pnf-swug-download-ne-sw-template.vtl" + }, + "download-ne-sw-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/pnf-software-upgrade-mapping.json" + } + } + }, + "activateNESw" : { + "type" : "component-script-executor", + "interfaces" : { + "ComponentScriptExecutor" : { + "operations" : { + "process" : { + "implementation" : { + "primary" : "component-script", + "timeout" : 180, + "operation_host" : "SELF" + }, + "inputs" : { + "script-type" : "kotlin", + "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade", + "dynamic-properties" : "*activateNESw-properties" + }, + "outputs" : { + "response-data" : "", + "status" : "success" + } + } + } + } + }, + "artifacts" : { + "mount-node-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/restconf-mount-template.vtl" + }, + "mount-node-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/pnf-software-upgrade-mapping.json" + }, + "configure-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/pnf-swug-config-template.vtl" + }, + "configure-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/pnf-software-upgrade-mapping.json" + } + } + }, + "postCheck" : { + "type" : "component-script-executor", + "interfaces" : { + "ComponentScriptExecutor" : { + "operations" : { + "process" : { + "implementation" : { + "primary" : "component-script", + "timeout" : 180, + "operation_host" : "SELF" + }, + "inputs" : { + "script-type" : "kotlin", + "script-class-reference" : "cba.pnf.swug.RestconfSoftwareUpgrade", + "dynamic-properties" : "*postCheck-properties" + }, + "outputs" : { + "response-data" : "", + "status" : "success" + } + } + } + } + }, + "artifacts" : { + "mount-node-template" : { + "type" : "artifact-template-velocity", + "file" : "Templates/restconf-mount-template.vtl" + }, + "mount-node-mapping" : { + "type" : "artifact-mapping-resource", + "file" : "Definitions/pnf-software-upgrade-mapping.json" + } + } + } + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/artifact_types.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/artifact_types.json index 6ec3b4105..6ec3b4105 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/artifact_types.json +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/artifact_types.json diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/config-assign-pnf-mapping.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/config-assign-pnf-mapping.json index fe51488c7..fe51488c7 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/config-assign-pnf-mapping.json +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/config-assign-pnf-mapping.json diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/config-deploy-pnf-mapping.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/config-deploy-pnf-mapping.json index dd8889c80..dd8889c80 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/config-deploy-pnf-mapping.json +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/config-deploy-pnf-mapping.json diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/data_types.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/data_types.json new file mode 100644 index 000000000..cd91f4b3a --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/data_types.json @@ -0,0 +1,75 @@ +{ + "data_types" : { + "dt-config-assign-properties" : { + "description" : "Dynamic DataType definition for workflow(config-assign).", + "version" : "1.0.0", + "properties" : { }, + "derived_from" : "tosca.datatypes.Dynamic" + }, + "dt-config-deploy-properties" : { + "description" : "Dynamic DataType definition for workflow(config-deploy).", + "version" : "1.0.0", + "properties" : { + "pnf-ipv4-address" : { + "type" : "string" + }, + "pnf-id" : { + "type" : "string" + } + }, + "derived_from" : "tosca.datatypes.Dynamic" + }, + "dt-preCheck-properties": { + "description": "Dynamic DataType definition for the preCheck workflow(upgrade-software).", + "version": "1.0.0", + "properties": { + "pnf-id": { + "type": "string" + }, + "target-software-version": { + "type": "string" + } + }, + "derived_from": "tosca.datatypes.Dynamic" + }, + "dt-downloadNESw-properties": { + "description": "Dynamic DataType definition for the downloadNESw workflow(upgrade-software).", + "version": "1.0.0", + "properties": { + "pnf-id": { + "type": "string" + }, + "target-software-version": { + "type": "string" + } + }, + "derived_from": "tosca.datatypes.Dynamic" + }, + "dt-activateNESw-properties": { + "description": "Dynamic DataType definition for the activateNESw workflow(upgrade-software).", + "version": "1.0.0", + "properties": { + "pnf-id": { + "type": "string" + }, + "target-software-version": { + "type": "string" + } + }, + "derived_from": "tosca.datatypes.Dynamic" + }, + "dt-postCheck-properties": { + "description": "Dynamic DataType definition for the postCheck workflow(upgrade-software).", + "version": "1.0.0", + "properties": { + "pnf-id": { + "type": "string" + }, + "target-software-version": { + "type": "string" + } + }, + "derived_from": "tosca.datatypes.Dynamic" + } + } +}
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/node_types.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/node_types.json index bfae6779e..bfae6779e 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/node_types.json +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/node_types.json diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/pnf-software-upgrade-mapping.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/pnf-software-upgrade-mapping.json new file mode 100644 index 000000000..2c3de2e49 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/pnf-software-upgrade-mapping.json @@ -0,0 +1,36 @@ +[ + { + "name": "pnf-id", + "input-param": true, + "property": { + "type": "string" + }, + "dictionary-name": "pnf-id", + "dictionary-source": "input", + "dependencies": [ + ] + }, + { + "name": "target-software-version", + "input-param": true, + "property": { + "type": "string" + }, + "dictionary-name": "target-software-version", + "dictionary-source": "input", + "dependencies": [ + ] + }, + { + "name": "pnf-ipv4-address", + "input-param": false, + "property": { + "type": "string" + }, + "dictionary-name": "pnf-ipaddress-aai", + "dictionary-source": "aai-data", + "dependencies": [ + "pnf-id" + ] + } +] diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/policy_types.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/policy_types.json index 1e44cc70a..1e44cc70a 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/policy_types.json +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/policy_types.json diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/relationship_types.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/relationship_types.json index 4ddd7a57c..4ddd7a57c 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/relationship_types.json +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/relationship_types.json diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/resources_definition_types.json b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/resources_definition_types.json index 235a05d27..235a05d27 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/resources_definition_types.json +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Definitions/resources_definition_types.json diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Environments/source-db.properties b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Environments/source-db.properties index 49a7eb47b..49a7eb47b 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Environments/source-db.properties +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Environments/source-db.properties diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Environments/source-rest.properties b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Environments/source-rest.properties index bc1eb7417..bc1eb7417 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Environments/source-rest.properties +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Environments/source-rest.properties diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Plans/CONFIG_configAssign.xml b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_configAssign.xml index a3eedf172..a3eedf172 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Plans/CONFIG_configAssign.xml +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_configAssign.xml diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Plans/CONFIG_configDeploy.xml b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_configDeploy.xml index f4e1b996f..f4e1b996f 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Plans/CONFIG_configDeploy.xml +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_configDeploy.xml diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_upgradeSoftware.xml b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_upgradeSoftware.xml new file mode 100644 index 000000000..52a9900b2 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Plans/CONFIG_upgradeSoftware.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<service-logic xmlns="http://www.onap.org/sdnc/svclogic" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.onap.org/sdnc/svclogic ./svclogic.xsd" module="CONFIG" version="1.0.0"> + <method rpc="ResourceAssignAndActivate" mode="sync"> + <block atomic="true"> + <execute plugin="upgrade-software" method="process"> + <outcome value="failure"> + <return status="failure"/> + </outcome> + <outcome value="success"> + <return status="success"/> + </outcome> + </execute> + </block> + </method> +</service-logic> diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Scripts/kotlin/RestconfConfigDeploy.kt b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfConfigDeploy.kt index 6a034ab94..730565959 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Scripts/kotlin/RestconfConfigDeploy.kt +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfConfigDeploy.kt @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= -* Copyright (C) 2019 Nordix Foundation. +* Copyright (C) 2020 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,11 +31,14 @@ import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restco import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException import org.onap.ccsdk.cds.controllerblueprints.core.logger +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse class RestconfConfigDeploy : AbstractScriptComponentFunction() { private val CONFIGLET_TEMPLATE_NAME = "config-assign" private val CONFIGLET_RESOURCE_PATH = "yang-ext:mount/mynetconf:netconflist" private val RESTCONF_SERVER_IDENTIFIER = "sdnc" + private val mapper = ObjectMapper() private val log = logger(AbstractScriptComponentFunction::class.java) override suspend fun processNB(executionRequest: ExecutionServiceInput) { @@ -58,9 +61,17 @@ class RestconfConfigDeploy : AbstractScriptComponentFunction() { val currentConfig: Any = restconfDeviceConfig(webclientService, deviceID, CONFIGLET_RESOURCE_PATH) log.info("Current configuration subtree : $currentConfig") //Apply configlet - restconfApplyDeviceConfig(webclientService, deviceID, CONFIGLET_RESOURCE_PATH, + val result = restconfApplyDeviceConfig(webclientService, deviceID, CONFIGLET_RESOURCE_PATH, storedContentFromResolvedArtifactNB(resolutionKey, CONFIGLET_TEMPLATE_NAME), - mutableMapOf("Content-Type" to "application/yang.patch+json")) + mutableMapOf("Content-Type" to "application/yang.patch+json")) as WebClientResponse<*> + + val jsonResult = mapper.readTree((result.body).toString()) + + if (jsonResult.get("ietf-yang-patch:yang-patch-status").get("errors") != null) { + log.error("There was an error configuring device") + } else { + log.info("Device has been configured succesfully") + } } catch (err: Exception) { log.error("an error occurred while configuring device {}", err) diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfSoftwareUpgrade.kt b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfSoftwareUpgrade.kt new file mode 100644 index 000000000..0540efe80 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Scripts/kotlin/RestconfSoftwareUpgrade.kt @@ -0,0 +1,205 @@ +/* +* ============LICENSE_START======================================================= +* Copyright (C) 2020 Nordix Foundation. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* ============LICENSE_END========================================================= + */ + + +package cba.pnf.swug + +import com.fasterxml.jackson.databind.node.ObjectNode +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.contentFromResolvedArtifactNB +import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfApplyDeviceConfig +import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfDeviceConfig +import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfMountDevice +import org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor.restconfUnMountDevice +import org.onap.ccsdk.cds.blueprintsprocessor.rest.restClientService +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintRetryException +import org.onap.ccsdk.cds.controllerblueprints.core.logger +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintDependencyService +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils + +class RestconfSoftwareUpgrade : AbstractScriptComponentFunction() { + + private val RESTCONF_SERVER_IDENTIFIER = "sdnc" + private val CONFIGLET_RESOURCE_PATH = "yang-ext:mount/pnf-sw-upgrade:software-upgrade" + private val log = logger(AbstractScriptComponentFunction::class.java) + private val TARGET_SOFTWARE_PATH = "$CONFIGLET_RESOURCE_PATH/upgrade-package/" + + override suspend fun processNB(executionRequest: ExecutionServiceInput) { + + // Extract request properties + val model= validatedPayload(executionRequest) + + log.info("Blueprint invoked for ${model.resolutionKey} for SW Upgrade : " + + "${model.action} for sw version ${model.targetSwVersion} on pnf: ${model.deviceId}") + + try { + val mountPayload = contentFromResolvedArtifactNB("mount-node") + log.debug("Mount Payload : $mountPayload") + restconfMountDevice(model.client, model.deviceId, mountPayload, mutableMapOf("Content-Type" to "application/json")) + + when (model.action) { + Action.PRE_CHECK -> processPreCheck(model) + Action.DOWNLOAD_NE_SW -> processDownloadNESw(model) + Action.ACTIVATE_NE_SW -> processActivateNESw(model) + Action.POST_CHECK -> processPostCheck(model) + Action.CANCEL -> processCancel(model) + } + + } catch (err: Exception) { + log.error("an error occurred while configuring device {}", err) + } finally { + restconfUnMountDevice(model.client, model.deviceId, "") + } + } + + private fun validatedPayload(executionRequest: ExecutionServiceInput): SoftwareUpgradeModel { + val properties = requestPayloadActionProperty(executionRequest.actionIdentifiers.actionName + "-properties")!!.get(0) + if(!properties?.get("pnf-id")?.textValue().isNullOrEmpty() && + !properties?.get("target-software-version")?.textValue().isNullOrEmpty()) { + return SoftwareUpgradeModel(getDynamicProperties("resolution-key").asText(), + BluePrintDependencyService.restClientService(RESTCONF_SERVER_IDENTIFIER), + properties.get("pnf-id").textValue(), properties.get("target-software-version").textValue(), + Action.getEnumFromActionName(executionRequest.actionIdentifiers.actionName)) + }else{ + throw BluePrintException("Invalid parameters sent to CDS. Request parameters pnf-id or target-software-version missing") + } + } + + private suspend fun processPreCheck(model: SoftwareUpgradeModel) { + log.debug("In PNF SW upgrade : processPreCheck") + //Log the current configuration for the subtree + val payloadObject = getCurrentConfig(model) + log.debug("Current sw version on pnf : ${payloadObject.get("software-upgrade")?.get("upgrade-package")?.get(0)?.get("software-version")?.asText()}") + log.info("PNF is Healthy!") + } + + private suspend fun processDownloadNESw(model: SoftwareUpgradeModel) { + log.debug("In PNF SW upgrade : processDownloadNESw") + //Check if there is existing config for the targeted software version + + var downloadConfigPayload: String + if (checkIfSwReadyToPerformAction(Action.PRE_CHECK, model)) { + downloadConfigPayload = contentFromResolvedArtifactNB("configure") + downloadConfigPayload =downloadConfigPayload.replace("%id%", model.yangId) + } + else { + downloadConfigPayload = contentFromResolvedArtifactNB("download-ne-sw") + model.yangId=model.targetSwVersion + } + downloadConfigPayload = downloadConfigPayload.replace("%actionName%", Action.DOWNLOAD_NE_SW.name) + log.info("Config Payload to start download : $downloadConfigPayload") + + //Apply configlet + restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, downloadConfigPayload, + mutableMapOf("Content-Type" to "application/yang.patch+json")) + + //Poll PNF for Download action's progress + checkExecution(model) + } + + private suspend fun processActivateNESw(model: SoftwareUpgradeModel) { + log.debug("In PNF SW upgrade : processActivateNESw") + //Check if the software is downloaded and ready to be activated + if (checkIfSwReadyToPerformAction(Action.DOWNLOAD_NE_SW, model)) { + var activateConfigPayload: String = contentFromResolvedArtifactNB("configure") + activateConfigPayload = activateConfigPayload.replace("%actionName%", Action.ACTIVATE_NE_SW.name) + activateConfigPayload = activateConfigPayload.replace("%id%", model.yangId) + log.info("Config Payload to start activate : $activateConfigPayload") + //Apply configlet + restconfApplyDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH, activateConfigPayload, + mutableMapOf("Content-Type" to "application/yang.patch+json")) + + //Poll PNF for Activate action's progress + checkExecution(model) + } else { + throw BluePrintRetryException("Software Download not completed for device(${model.deviceId}) to activate sw version: ${model.targetSwVersion}") + } + } + + private suspend fun processPostCheck(model: SoftwareUpgradeModel) { + log.info("In PNF SW upgrade : processPostCheck") + //Log the current configuration for the subtree + if (checkIfSwReadyToPerformAction(Action.POST_CHECK, model)) { + log.info("PNF is healthy post activation!") + } + } + + private fun processCancel(model :SoftwareUpgradeModel) { + //This is for future implementation of cancel step during software upgrade + log.info("In PNF SW upgrade : processCancel") + } + + private suspend fun getCurrentConfig(model: SoftwareUpgradeModel) : ObjectNode{ + val currentConfig: BlueprintWebClientService.WebClientResponse<String> = restconfDeviceConfig(model.client, model.deviceId, CONFIGLET_RESOURCE_PATH) + return JacksonUtils.jsonNode(currentConfig.body) as ObjectNode + } + private suspend fun checkExecution(model: SoftwareUpgradeModel) { + val checkExecutionBlock: suspend (Int) -> String = { + val result = restconfDeviceConfig(model.client, model.deviceId, TARGET_SOFTWARE_PATH.plus(model.yangId)) + if (result.body.contains(model.action.completionStatus)) { + log.info("${model.action.name} is complete") + result.body + } else { + throw BluePrintRetryException("Waiting for device(${model.deviceId}) to activate sw version ${model.targetSwVersion}") + } + } + model.client.retry<String>(10, 0, 1000, checkExecutionBlock) + + } + + private suspend fun checkIfSwReadyToPerformAction(action : Action, model: SoftwareUpgradeModel): Boolean { + val configBody = getCurrentConfig(model) + configBody.get("software-upgrade")?.get("upgrade-package")?.iterator()?.forEach { item -> + if (model.targetSwVersion == item.get("software-version")?.asText() && + action.completionStatus == item?.get("current-status")?.asText()) { + model.yangId= item.get("id").textValue() + return true + } + } + return false + } + + override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { + log.info("Recover function called!") + log.info("Execution request : $executionRequest") + log.error("Exception", runtimeException) + } +} + +enum class Action(val actionName: String, val completionStatus: String) { + PRE_CHECK("preCheck", "INITIALIZED"), + DOWNLOAD_NE_SW("downloadNESw", "DOWNLOAD_COMPLETED"), + ACTIVATE_NE_SW("activateNESw", "ACTIVATION_COMPLETED"), + POST_CHECK("postCheck", "ACTIVATION_COMPLETED"), + CANCEL("cancel", "CANCELLED") + ; + companion object{ + fun getEnumFromActionName(name: String): Action { + for(value in values()){ + if (value.actionName==name) return value + } + throw BluePrintException("Invalid Action sent to CDS") + } + } +} + +data class SoftwareUpgradeModel(val resolutionKey: String, val client: BlueprintWebClientService, val deviceId: String, + val targetSwVersion: String, val action: Action, var yangId: String = "")
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/TOSCA-Metadata/TOSCA.meta b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/TOSCA-Metadata/TOSCA.meta new file mode 100644 index 000000000..467964604 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/TOSCA-Metadata/TOSCA.meta @@ -0,0 +1,7 @@ +TOSCA-Meta-File-Version: 1.0.0 +CSAR-Version: 1.0 +Created-By: Raj Gumma <raj.gumma@est.tech> +Entry-Definitions: Definitions/PNF_CDS_RESTCONF.json +Template-Name: PNF_CDS_RESTCONF +Template-Version: 1.0.0 +Template-Tags: PNF_CDS_RESTCONF diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Templates/config-assign-restconf-configlet-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/config-assign-restconf-configlet-template.vtl index af91ba00d..af91ba00d 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Templates/config-assign-restconf-configlet-template.vtl +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/config-assign-restconf-configlet-template.vtl diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-config-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-config-template.vtl new file mode 100644 index 000000000..5e52f6779 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-config-template.vtl @@ -0,0 +1,22 @@ +{ + "ietf-restconf:yang-patch": { + "patch-id": "patch-1", + "edit": [ + { + "edit-id": "edit1", + "operation": "merge", + "target": "/", + "value": { + "software-upgrade": { + "upgrade-package": [ + { + "id": "%id%", + "action": "%actionName%" + } + ] + } + } + } + ] + } +}
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-download-ne-sw-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-download-ne-sw-template.vtl new file mode 100644 index 000000000..695b66866 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/pnf-swug-download-ne-sw-template.vtl @@ -0,0 +1,28 @@ +{ + "ietf-restconf:yang-patch": { + "patch-id": "patch-1", + "edit": [ + { + "edit-id": "edit1", + "operation": "merge", + "target": "/", + "value": { + "software-upgrade": { + "upgrade-package": [ + { + "id": "${target-software-version}", + "current-status": "INITIALIZED", + "action": "%actionName%", + "user-label": "trial software update", + "uri": "sftp://127.0.0.1/test_software_2.img", + "software-version": "${target-software-version}", + "user": "test_user", + "password": "test_password" + } + ] + } + } + } + ] + } +}
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Templates/config-deploy-restconf-mount-template.vtl b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/restconf-mount-template.vtl index 8098b05d8..8098b05d8 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Templates/config-deploy-restconf-mount-template.vtl +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Templates/restconf-mount-template.vtl diff --git a/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Tests/uat.yaml b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Tests/uat.yaml new file mode 100644 index 000000000..c2fd3f788 --- /dev/null +++ b/components/model-catalog/blueprint-model/uat-blueprints/PNF_CDS_RESTCONF/Tests/uat.yaml @@ -0,0 +1,374 @@ +%YAML 1.1 +--- +processes: + - name: config-assign + request: + commonHeader: &commonHeader + originatorId: sdnc + requestId: "123456-1000" + subRequestId: sub-123456-1000 + actionIdentifiers: &assign-ai + blueprintName: PNF_CDS_RESTCONF + blueprintVersion: "1.0.0" + actionName: config-assign + mode: sync + payload: + config-assign-request: + resolution-key: &resKey "RES-KEY 61" + config-assign-properties: + service-instance-id: siid_1234 + pnf-id: &pnfId pnf-id-2019-07-12 + service-model-uuid: service-model-uuid + pnf-customization-uuid: pnf-customization-uuid + expectedResponse: + commonHeader: *commonHeader + actionIdentifiers: *assign-ai + status: + code: 200 + eventType: EVENT_COMPONENT_EXECUTED + errorMessage: null + message: success + payload: + config-assign-response: {} + - name: config-deploy + request: + commonHeader: *commonHeader + actionIdentifiers: &deploy-ai + actionName: config-deploy + blueprintName: PNF_CDS_RESTCONF + blueprintVersion: "1.0.0" + mode: sync + payload: + config-deploy-request: + resolution-key: *resKey + config-deploy-properties: + service-instance-id: siid_1234 + pnf-id: *pnfId + service-model-uuid: service-model-uuid + pnf-customization-uuid: pnf-customization-uuid + expectedResponse: + commonHeader: *commonHeader + actionIdentifiers: *deploy-ai + payload: + config-deploy-response: {} + status: + code: 200 + errorMessage: null + eventType: EVENT_COMPONENT_EXECUTED + message: success + - name: preCheck + request: + commonHeader: &swugCommonHeader + originatorId: sdnc + requestId: "123456-1000" + subRequestId: sub-123456-1000 + actionIdentifiers: &preCheck-ai + blueprintName: PNF_CDS_RESTCONF + blueprintVersion: "1.0.0" + actionName: preCheck + mode: sync + payload: + preCheck-request: + resolution-key: &resKey "RES-KEY 61" + preCheck-properties: &actionProps + service-instance-id: siid_1234 + pnf-id: &pnfId pnf-id-2019-07-12 + target-software-version: "2.0.2" + service-model-uuid: service-model-uuid + pnf-customization-uuid: pnf-customization-uuid + expectedResponse: + commonHeader: *swugCommonHeader + actionIdentifiers: *preCheck-ai + status: + code: 200 + eventType: EVENT_COMPONENT_EXECUTED + errorMessage: null + message: success + payload: + preCheck-response: {} + - name: downloadNESw + request: + commonHeader: *swugCommonHeader + actionIdentifiers: &download-ai + blueprintName: PNF_CDS_RESTCONF + blueprintVersion: "1.0.0" + actionName: downloadNESw + mode: sync + payload: + downloadNESw-request: + resolution-key: *resKey + downloadNESw-properties: *actionProps + expectedResponse: + commonHeader: *swugCommonHeader + actionIdentifiers: *download-ai + status: + code: 200 + eventType: EVENT_COMPONENT_EXECUTED + errorMessage: null + message: success + payload: + downloadNESw-response: {} + - name: activateNESw + request: + commonHeader: *swugCommonHeader + actionIdentifiers: &activate-ai + blueprintName: PNF_CDS_RESTCONF + blueprintVersion: "1.0.0" + actionName: activateNESw + mode: sync + payload: + activateNESw-request: + resolution-key: *resKey + activateNESw-properties: *actionProps + expectedResponse: + commonHeader: *swugCommonHeader + actionIdentifiers: *activate-ai + status: + code: 200 + eventType: EVENT_COMPONENT_EXECUTED + errorMessage: null + message: success + payload: + activateNESw-response: {} + - name: postCheck + request: + commonHeader: *swugCommonHeader + actionIdentifiers: &postCheck-ai + blueprintName: PNF_CDS_RESTCONF + blueprintVersion: "1.0.0" + actionName: postCheck + mode: sync + payload: + postCheck-request: + resolution-key: *resKey + postCheck-properties: *actionProps + expectedResponse: + commonHeader: *swugCommonHeader + actionIdentifiers: *postCheck-ai + status: + code: 200 + eventType: EVENT_COMPONENT_EXECUTED + errorMessage: null + message: success + payload: + postCheck-response: {} +external-services: + - selector: aai-data + expectations: + - request: + method: GET + path: [ /aai/v14/network/pnfs/pnf, *pnfId] + headers: + Accept: application/json + response: + headers: + Content-Type: application/json + body: + ipaddress-v4-oam: &pnfAddress 13.13.13.13 + ipaddress-v6-oam: 1::13 + - selector: sdnc + expectations: + - request: + method: PUT + path: &configUri [ /restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]] + headers: + Content-Type: application/json + body: + node: + - node-id: *pnfId + netconf-node-topology:protocol: { name: TLS } + netconf-node-topology:host: *pnfAddress + netconf-node-topology:key-based: + username: netconf + key-id: ODL_private_key_0 + netconf-node-topology:port: 6513 + netconf-node-topology:tcp-only: false + netconf-node-topology:max-connection-attempts: 5 + response: + status: 201 + - request: + method: GET + path: [ /restconf/operational, *nodeIdentifier] + response: + body: + node: [ { netconf-node-topology:connection-status: connected }] + - request: + method: GET + path: [*configUri, &configletResourcePath yang-ext:mount/mynetconf:netconflist] + response: + body: {} + - request: + method: PATCH + path: [*configUri, *configletResourcePath] + headers: + Content-Type: application/yang.patch+json + body: + ietf-restconf:yang-patch: + patch-id: patch-1 + edit: + - edit-id: edit1 + operation: merge + target: / + value: { netconflist: { netconf: [ { netconf-id: "10", netconf-param: "1000" }]}} + - edit-id: edit2 + operation: merge + target: / + value: { netconflist: { netconf: [ { netconf-id: "20", netconf-param: "2000" }]}} + - edit-id: edit3 + operation: merge + target: / + value: { netconflist: { netconf: [ { netconf-id: "30", netconf-param: "3000" }]}} + response: + body: + ietf-yang-patch:yang-patch-status: + patch-id: patch-1 + ok: [ + null + ] + ### External expectations for Software Upgrade + - request: + method: GET + path: &ConfigSwUgUri [*configUri, &configletResourcePath yang-ext:mount/pnf-sw-upgrade:software-upgrade] + headers: + Accept: application/json + responses: + - headers: + Content-Type: application/json + body: + software-upgrade: + upgrade-package: + - id: 2.0.1 + current-status: INITIALIZED + user-label: trial software update + uri: sftp:127.0.0.1/test_software_1.img + software-version: 2.0.1 + user: test_user + password: test_password + - headers: + Content-Type: application/json + body: + software-upgrade: + upgrade-package: + - id: 2.0.1 + current-status: INITIALIZED + user-label: trial software update + uri: sftp:127.0.0.1/test_software_1.img + software-version: 2.0.1 + user: test_user + password: test_password + - headers: + Content-Type: application/json + body: + software-upgrade: + upgrade-package: + - id: 2.0.1 + current-status: INITIALIZED + user-label: trial software update + uri: sftp:127.0.0.1/test_software_1.img + software-version: 2.0.1 + user: test_user + password: test_password + - id: 2.0.2 + current-status: DOWNLOAD_COMPLETED + state-change-time: '2020-02-20T13:03:21Z' + software-version: 2.0.2 + user-label: trial software update + uri: sftp:127.0.0.1/test_software_1.img + user: test_user + password: test_password + - headers: + Content-Type: application/json + body: + software-upgrade: + upgrade-package: + - id: 2.0.1 + current-status: INITIALIZED + user-label: trial software update + uri: sftp:127.0.0.1/test_software_1.img + software-version: 2.0.1 + user: test_user + password: test_password + - id: 2.0.2 + current-status: ACTIVATION_COMPLETED + state-change-time: '2020-02-20T13:03:21Z' + software-version: 2.0.2 + user-label: trial software update + uri: sftp:127.0.0.1/test_software_1.img + user: test_user + password: test_password + - request: + method: PATCH + path: *ConfigSwUgUri + headers: + Content-Type: application/yang.patch+json + body: + ietf-restconf:yang-patch: + patch-id: patch-1 + edit: + - edit-id: edit1 + operation: merge + target: "/" + response: + headers: + Content-Type: application/yang.patch-status+json + body: + { ietf-yang-patch:yang-patch-status: {patch-id: patch-1, ok: [ ] } } + times: 2 + - request: + method: GET + path: [*ConfigSwUgUri, upgrade-package/2.0.2] + headers: + Accept: application/json + responses: + - headers: + Content-Type: application/json + body: + upgrade-package: + - id: 2.0.2 + current-status: DOWNLOAD_IN_PROGRESS + state-change-time: '2020-02-20T12:17:34.984Z' + software-version: 2.0.2 + - headers: + Content-Type: application/json + body: + upgrade-package: + - id: 2.0.2 + current-status: DOWNLOAD_IN_PROGRESS + state-change-time: '2020-02-20T12:52:30Z' + software-version: 2.0.2 + - headers: + Content-Type: application/json + body: + upgrade-package: + - id: 2.0.2 + current-status: DOWNLOAD_COMPLETED + state-change-time: '2020-02-20T13:03:21Z' + software-version: 2.0.2 + - headers: + Content-Type: application/json + body: + upgrade-package: + - id: 2.0.2 + current-status: ACTIVATION_IN_PROGRESS + state-change-time: '2020-02-20T13:05:08Z' + software-version: 2.0.2 + - headers: + Content-Type: application/json + body: + upgrade-package: + - id: 2.0.2 + current-status: ACTIVATION_IN_PROGRESS + state-change-time: '2020-02-20T12:52:30Z' + software-version: 2.0.2 + - headers: + Content-Type: application/json + body: + upgrade-package: + - id: 2.0.2 + current-status: ACTIVATION_COMPLETED + state-change-time: '2020-02-20T13:07:12Z' + software-version: 2.0.2 + - request: + method: DELETE + path: *configUri + times: 5
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/README.md b/components/model-catalog/blueprint-model/uat-blueprints/README.md index 56cb32989..ffbc15aec 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/README.md +++ b/components/model-catalog/blueprint-model/uat-blueprints/README.md @@ -61,7 +61,8 @@ message Uat { message Expectation { required Request request = 1; - required Response response = 2; + optional Response response = 2; + repeated Response responses = 3; } message ExternalService { diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml index 85b10c611..a58d089ad 100644 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml +++ b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config/Tests/uat.yaml @@ -64,7 +64,7 @@ external-services: expectations: - request: method: PUT - path: &configUri [ restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]] + path: &configUri [ /restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]] headers: Content-Type: application/json body: @@ -82,7 +82,7 @@ external-services: status: 201 - request: method: GET - path: [ restconf/operational, *nodeIdentifier] + path: [ /restconf/operational, *nodeIdentifier] response: body: node: [ { netconf-node-topology:connection-status: connected }] diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/data_types.json b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/data_types.json deleted file mode 100644 index a0804bb3f..000000000 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/data_types.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "data_types" : { - "dt-config-assign-properties" : { - "description" : "Dynamic DataType definition for workflow(config-assign).", - "version" : "1.0.0", - "properties" : { }, - "derived_from" : "tosca.datatypes.Dynamic" - }, - "dt-config-deploy-properties" : { - "description" : "Dynamic DataType definition for workflow(config-deploy).", - "version" : "1.0.0", - "properties" : { - "pnf-ipv4-address" : { - "type" : "string" - }, - "pnf-id" : { - "type" : "string" - } - }, - "derived_from" : "tosca.datatypes.Dynamic" - } - } -}
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/pnf_config_aai.json b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/pnf_config_aai.json deleted file mode 100644 index 3ef585cb4..000000000 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Definitions/pnf_config_aai.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "tosca_definitions_version" : "controller_blueprint_1_0_0", - "metadata" : { - "template_author" : "Rahul Tyagi", - "author-email" : "rahul.tyagi@est.tech", - "user-groups" : "ADMIN, OPERATION", - "template_name" : "pnf_config_aai", - "template_version" : "1.0.0", - "template_tags" : "pnf, restconf, config, configuration" - }, - "imports" : [ { - "file" : "Definitions/data_types.json" - }, { - "file" : "Definitions/relationship_types.json" - }, { - "file" : "Definitions/artifact_types.json" - }, { - "file" : "Definitions/node_types.json" - }, { - "file" : "Definitions/policy_types.json" - } ], - "dsl_definitions" : { - "config-deploy-properties" : { - "resolution-key" : { - "get_input" : "resolution-key" - } - } - }, - "topology_template" : { - "workflows" : { - "config-assign" : { - "steps" : { - "activate-process" : { - "description" : "Create a configlet", - "target" : "config-assign", - "activities" : [ { - "call_operation" : "" - } ] - } - }, - "inputs" : { - "resolution-key" : { - "required" : true, - "type" : "string" - }, - "store-result" : { - "required" : true, - "type" : "boolean" - }, - "config-assign-properties" : { - "description" : "Dynamic PropertyDefinition for workflow(config-assign).", - "required" : true, - "type" : "dt-config-assign-properties" - } - } - }, - "config-deploy" : { - "steps" : { - "activate-process" : { - "description" : "Send a configlet to the pnf", - "target" : "config-deploy", - "activities" : [ { - "call_operation" : "" - } ] - } - }, - "inputs" : { - "resolution-key" : { - "required" : true, - "type" : "string" - }, - "config-deploy-properties" : { - "description" : "Dynamic PropertyDefinition for workflow(config-deploy).", - "required" : true, - "type" : "dt-config-deploy-properties" - } - } - } - }, - "node_templates" : { - "config-assign" : { - "type" : "component-resource-resolution", - "interfaces" : { - "ResourceResolutionComponent" : { - "operations" : { - "process" : { - "inputs" : { - "resolution-key" : { - "get_input" : "resolution-key" - }, - "store-result" : true, - "artifact-prefix-names" : [ "config-assign" ] - }, - "outputs" : { - "resource-assignment-params" : { - "get_attribute" : [ "SELF", "assignment-params" ] - }, - "status" : "success" - } - } - } - } - }, - "artifacts" : { - "config-assign-template" : { - "type" : "artifact-template-velocity", - "file" : "Templates/config-assign-restconf-configlet-template.vtl" - }, - "config-assign-mapping" : { - "type" : "artifact-mapping-resource", - "file" : "Definitions/config-assign-pnf-mapping.json" - } - } - }, - "config-deploy" : { - "type" : "component-script-executor", - "interfaces" : { - "ComponentScriptExecutor" : { - "operations" : { - "process" : { - "implementation" : { - "primary" : "component-script", - "timeout" : 180, - "operation_host" : "SELF" - }, - "inputs" : { - "script-type" : "kotlin", - "script-class-reference" : "cba.pnf.config.aai.RestconfConfigDeploy", - "dynamic-properties" : "*config-deploy-properties" - }, - "outputs" : { - "response-data" : "", - "status" : "success" - } - } - } - } - }, - "artifacts" : { - "config-deploy-template" : { - "type" : "artifact-template-velocity", - "file" : "Templates/config-deploy-restconf-mount-template.vtl" - }, - "config-deploy-mapping" : { - "type" : "artifact-mapping-resource", - "file" : "Definitions/config-deploy-pnf-mapping.json" - } - } - } - } - } -}
\ No newline at end of file diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/TOSCA-Metadata/TOSCA.meta b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/TOSCA-Metadata/TOSCA.meta deleted file mode 100644 index 903600836..000000000 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/TOSCA-Metadata/TOSCA.meta +++ /dev/null @@ -1,7 +0,0 @@ -TOSCA-Meta-File-Version: 1.0.0 -CSAR-Version: 1.0 -Created-By: Rahul Tyagi -Entry-Definitions: Definitions/pnf_config_aai.json -Template-Name: pnf_config_aai -Template-Version: 1.0.0 -Template-Tags: pnf_config_aai diff --git a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Tests/uat.yaml b/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Tests/uat.yaml deleted file mode 100644 index 0692eea7f..000000000 --- a/components/model-catalog/blueprint-model/uat-blueprints/pnf_config_aai/Tests/uat.yaml +++ /dev/null @@ -1,130 +0,0 @@ -%YAML 1.1 ---- -processes: - - name: config-assign - request: - commonHeader: &commonHeader - originatorId: sdnc - requestId: "123456-1000" - subRequestId: sub-123456-1000 - actionIdentifiers: &assign-ai - blueprintName: pnf_config_aai - blueprintVersion: "1.0.0" - actionName: config-assign - mode: sync - payload: - config-assign-request: - resolution-key: &resKey "RES-KEY 61" - config-assign-properties: - service-instance-id: siid_1234 - pnf-id: &pnfId pnf-id-2019-07-12 - service-model-uuid: service-model-uuid - pnf-customization-uuid: pnf-customization-uuid - expectedResponse: - commonHeader: *commonHeader - actionIdentifiers: *assign-ai - status: - code: 200 - eventType: EVENT_COMPONENT_EXECUTED - errorMessage: null - message: success - payload: - config-assign-response: {} - - name: config-deploy - request: - commonHeader: *commonHeader - actionIdentifiers: &deploy-ai - actionName: config-deploy - blueprintName: pnf_config_aai - blueprintVersion: "1.0.0" - mode: sync - payload: - config-deploy-request: - resolution-key: *resKey - config-deploy-properties: - service-instance-id: siid_1234 - pnf-id: *pnfId - service-model-uuid: service-model-uuid - pnf-customization-uuid: pnf-customization-uuid - expectedResponse: - commonHeader: *commonHeader - actionIdentifiers: *deploy-ai - payload: - config-deploy-response: {} - status: - code: 200 - errorMessage: null - eventType: EVENT_COMPONENT_EXECUTED - message: success - -external-services: - - selector: aai-data - expectations: - - request: - method: GET - path: [ /aai/v14/network/pnfs/pnf, *pnfId] - headers: - Accept: application/json - response: - headers: - Content-Type: application/json - body: - ipaddress-v4-oam: &pnfAddress 13.13.13.13 - ipaddress-v6-oam: 1::13 - - selector: sdnc - expectations: - - request: - method: PUT - path: &configUri [ restconf/config, &nodeIdentifier [network-topology:network-topology/topology/topology-netconf/node, *pnfId]] - headers: - Content-Type: application/json - body: - node: - - node-id: *pnfId - netconf-node-topology:protocol: { name: TLS } - netconf-node-topology:host: *pnfAddress - netconf-node-topology:key-based: - username: netconf - key-id: ODL_private_key_0 - netconf-node-topology:port: 6513 - netconf-node-topology:tcp-only: false - netconf-node-topology:max-connection-attempts: 5 - response: - status: 201 - - request: - method: GET - path: [ restconf/operational, *nodeIdentifier] - response: - body: - node: [ { netconf-node-topology:connection-status: connected }] - - request: - method: GET - path: [*configUri, &configletResourcePath yang-ext:mount/mynetconf:netconflist] - response: - body: {} - - request: - method: PATCH - path: [*configUri, *configletResourcePath] - headers: - Content-Type: application/yang.patch+json - body: - ietf-restconf:yang-patch: - patch-id: patch-1 - edit: - - edit-id: edit1 - operation: merge - target: / - value: { netconflist: { netconf: [ { netconf-id: "10", netconf-param: "1000" }]}} - - edit-id: edit2 - operation: merge - target: / - value: { netconflist: { netconf: [ { netconf-id: "20", netconf-param: "2000" }]}} - - edit-id: edit3 - operation: merge - target: / - value: { netconflist: { netconf: [ { netconf-id: "30", netconf-param: "3000" }]}} - - request: - method: DELETE - path: *configUri - - diff --git a/components/model-catalog/definition-type/starter-type/node_type/component-resource-resolution.json b/components/model-catalog/definition-type/starter-type/node_type/component-resource-resolution.json index 1c81b7f6e..ff1b5260e 100644 --- a/components/model-catalog/definition-type/starter-type/node_type/component-resource-resolution.json +++ b/components/model-catalog/definition-type/starter-type/node_type/component-resource-resolution.json @@ -38,6 +38,11 @@ "required": false, "type": "string" }, + "resolution-summary": { + "description": "Enable resolution-summary output", + "required": false, + "type": "boolean" + }, "artifact-prefix-names": { "required": true, "description": "Template , Resource Assignment Artifact Prefix names", diff --git a/ms/blueprintsprocessor/application/pom.xml b/ms/blueprintsprocessor/application/pom.xml index b007ac7fb..18a61e0e6 100755 --- a/ms/blueprintsprocessor/application/pom.xml +++ b/ms/blueprintsprocessor/application/pom.xml @@ -46,9 +46,9 @@ <dependencies> <dependency> - <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId> - <artifactId>blueprint-core</artifactId> - <exclusions> + <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId> + <artifactId>blueprint-core</artifactId> + <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> @@ -66,6 +66,12 @@ </exclusions> </dependency> + <!-- Error Catalog Services --> + <dependency> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog-services</artifactId> + </dependency> + <!-- North Bound --> <dependency> <groupId>org.onap.ccsdk.cds.blueprintsprocessor</groupId> @@ -99,6 +105,10 @@ </dependency> <dependency> <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId> + <artifactId>restful-executor</artifactId> + </dependency> + <dependency> + <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId> <artifactId>ansible-awx-executor</artifactId> </dependency> <dependency> @@ -153,6 +163,9 @@ <dependency> <groupId>com.nhaarman.mockitokotlin2</groupId> <artifactId>mockito-kotlin</artifactId> + <!-- It's unusual but 'compile' here is the right scope since mockito is being used at runtime by + the UatServices (/api/v1/uat/spy and /api/v1/uat/verify endpoints) --> + <scope>compile</scope> </dependency> <dependency> <groupId>com.schibsted.spt.data</groupId> @@ -299,6 +312,10 @@ <target>1.8</target> </configuration> </plugin> + <plugin> + <groupId>pl.project13.maven</groupId> + <artifactId>git-commit-id-plugin</artifactId> + </plugin> </plugins> </build> diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintDatabaseConfiguration.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintDatabaseConfiguration.kt index 58464cb10..e9557aed9 100644 --- a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintDatabaseConfiguration.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintDatabaseConfiguration.kt @@ -31,7 +31,8 @@ import javax.sql.DataSource @Configuration @Import(BluePrintDBLibConfiguration::class) @EnableJpaRepositories( - basePackages = ["org.onap.ccsdk.cds.controllerblueprints", "org.onap.ccsdk.cds.blueprintsprocessor"], + basePackages = ["org.onap.ccsdk.cds.controllerblueprints", "org.onap.ccsdk.cds.blueprintsprocessor", + "org.onap.ccsdk.cds.error.catalog"], entityManagerFactoryRef = "primaryEntityManager", transactionManagerRef = "primaryTransactionManager" ) @@ -43,7 +44,8 @@ open class BlueprintDatabaseConfiguration(primaryDataSourceProperties: PrimaryDa open fun primaryEntityManager(): LocalContainerEntityManagerFactoryBean { return primaryEntityManager( "org.onap.ccsdk.cds.controllerblueprints", - "org.onap.ccsdk.cds.blueprintsprocessor" + "org.onap.ccsdk.cds.blueprintsprocessor", + "org.onap.ccsdk.cds.error.catalog" ) } diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.kt index 1d1baeeef..97b7d28b0 100644 --- a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.kt @@ -30,7 +30,10 @@ import org.springframework.context.annotation.ComponentScan */ @SpringBootApplication @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class, HazelcastAutoConfiguration::class]) -@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +@ComponentScan( + basePackages = ["org.onap.ccsdk.cds.error.catalog", + "org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"] +) open class BlueprintProcessorApplication fun main(args: Array<String>) { diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorExceptionHandler.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorExceptionHandler.kt new file mode 100644 index 000000000..f241e3f42 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorExceptionHandler.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2018-2019 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor + +import org.onap.ccsdk.cds.error.catalog.services.ErrorCatalogExceptionHandler +import org.onap.ccsdk.cds.error.catalog.services.ErrorCatalogService +import org.springframework.web.bind.annotation.RestControllerAdvice + +@RestControllerAdvice("org.onap.ccsdk.cds") +open class BlueprintProcessorExceptionHandler(private val errorCatalogService: ErrorCatalogService) : + ErrorCatalogExceptionHandler(errorCatalogService) diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/ColorMarker.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/ColorMarker.kt index 9ae3ff805..9ae3ff805 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/ColorMarker.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/ColorMarker.kt diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/LogColor.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/LogColor.kt index f0cba2670..f0cba2670 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/LogColor.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/LogColor.kt diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/MockInvocationLogger.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/MockInvocationLogger.kt index f8e6bd486..f8e6bd486 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/MockInvocationLogger.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/logging/MockInvocationLogger.kt diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/InvalidUatDefinition.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/InvalidUatDefinition.kt new file mode 100644 index 000000000..4a756411f --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/InvalidUatDefinition.kt @@ -0,0 +1,22 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.onap.ccsdk.cds.blueprintsprocessor.uat.utils + +class InvalidUatDefinition(message: String) : RuntimeException(message) diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/JsonNormalizer.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/JsonNormalizer.kt index 39caa0178..39caa0178 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/JsonNormalizer.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/JsonNormalizer.kt diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/PathDeserializer.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/PathDeserializer.kt index 6c5759155..6c5759155 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/PathDeserializer.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/PathDeserializer.kt diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/RequiredMapEntriesMatcher.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/RequiredMapEntriesMatcher.kt index 0f98d7213..0f98d7213 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/RequiredMapEntriesMatcher.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/RequiredMapEntriesMatcher.kt diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatDefinition.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatDefinition.kt index c45ac45c6..17b79f588 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatDefinition.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatDefinition.kt @@ -47,18 +47,30 @@ data class RequestDefinition( ) @JsonInclude(JsonInclude.Include.NON_EMPTY) -data class ResponseDefinition(val status: Int = 200, val body: JsonNode? = null) { +data class ResponseDefinition(val status: Int = 200, val body: JsonNode? = null, val headers: Map<String, String> = mapOf("Content-Type" to "application/json")) { companion object { - val DEFAULT_RESPONSE = ResponseDefinition() + val DEFAULT_RESPONSES = listOf(ResponseDefinition()) } } @JsonInclude(JsonInclude.Include.NON_EMPTY) -data class ExpectationDefinition( +class ExpectationDefinition( val request: RequestDefinition, - val response: ResponseDefinition = ResponseDefinition.DEFAULT_RESPONSE -) + response: ResponseDefinition?, + responses: List<ResponseDefinition>? = null, + val times: String = ">= 1" +) { + val responses: List<ResponseDefinition> = resolveOneOrMany(response, responses, ResponseDefinition.DEFAULT_RESPONSES) + + companion object { + fun <T> resolveOneOrMany(one: T?, many: List<T>?, defaultMany: List<T>): List<T> = when { + many != null -> many + one != null -> listOf(one) + else -> defaultMany + } + } +} @JsonInclude(JsonInclude.Include.NON_EMPTY) data class ServiceDefinition(val selector: String, val expectations: List<ExpectationDefinition>) @@ -97,6 +109,6 @@ data class UatDefinition( companion object { fun load(mapper: ObjectMapper, spec: String): UatDefinition = - mapper.convertValue(Yaml().load(spec), UatDefinition::class.java) + mapper.convertValue(Yaml().load(spec), UatDefinition::class.java) } } diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatExecutor.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatExecutor.kt index a904fa9b6..d120e71d6 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatExecutor.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatExecutor.kt @@ -24,9 +24,10 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.argThat import com.nhaarman.mockitokotlin2.atLeast -import com.nhaarman.mockitokotlin2.atLeastOnce +import com.nhaarman.mockitokotlin2.atMost import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.times import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions import com.nhaarman.mockitokotlin2.whenever @@ -44,6 +45,7 @@ import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.notNullValue import org.hamcrest.MatcherAssert.assertThat import org.mockito.Answers +import org.mockito.verification.VerificationMode import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse @@ -77,7 +79,8 @@ class UatExecutor( companion object { private const val NOOP_PASSWORD_PREFIX = "{noop}" - + private const val PROPERTY_IN_UAT = "IN_UAT" + private val TIMES_SPEC_REGEX = "([<>]=?)?\\s*(\\d+)".toRegex() private val log: Logger = LoggerFactory.getLogger(UatExecutor::class.java) private val mockLoggingListener = MockInvocationLogger(markerOf(COLOR_MOCKITO)) } @@ -103,23 +106,24 @@ class UatExecutor( fun execute(uat: UatDefinition, cbaBytes: ByteArray): UatDefinition { val defaultHeaders = listOf(BasicHeader(HttpHeaders.AUTHORIZATION, clientAuthToken())) val httpClient = HttpClientBuilder.create() - .setDefaultHeaders(defaultHeaders) - .build() + .setDefaultHeaders(defaultHeaders) + .build() // Only if externalServices are defined val mockInterceptor = MockPreInterceptor() // Always defined and used, whatever the case val spyInterceptor = SpyPostInterceptor(mapper) restClientFactory.setInterceptors(mockInterceptor, spyInterceptor) try { - // Configure mocked external services and save their expected requests for further validation - val requestsPerClient = uat.externalServices.associateBy( - { service -> - createRestClientMock(service.expectations).also { restClient -> - // side-effect: register restClient to override real instance - mockInterceptor.registerMock(service.selector, restClient) - } - }, - { service -> service.expectations.map { it.request } } + markUatBegin() + // Configure mocked external services and save their expectations for further validation + val expectationsPerClient = uat.externalServices.associateBy( + { service -> + createRestClientMock(service.expectations).also { restClient -> + // side-effect: register restClient to override real instance + mockInterceptor.registerMock(service.selector, restClient) + } + }, + { service -> service.expectations } ) val newProcesses = httpClient.use { client -> @@ -130,26 +134,27 @@ class UatExecutor( log.info("Executing process '${process.name}'") val responseNormalizer = JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec) val actualResponse = processBlueprint( - client, process.request, - process.expectedResponse, responseNormalizer + client, process.request, + process.expectedResponse, responseNormalizer ) ProcessDefinition( - process.name, - process.request, - actualResponse, - process.responseNormalizerSpec + process.name, + process.request, + actualResponse, + process.responseNormalizerSpec ) } } // Validate requests to external services - for ((mockClient, requests) in requestsPerClient) { - requests.forEach { request -> - verify(mockClient, atLeastOnce()).exchangeResource( - eq(request.method), - eq(request.path), - argThat { assertJsonEquals(request.body, this) }, - argThat(RequiredMapEntriesMatcher(request.headers)) + for ((mockClient, expectations) in expectationsPerClient) { + expectations.forEach { expectation -> + val request = expectation.request + verify(mockClient, evalVerificationMode(expectation.times)).exchangeResource( + eq(request.method), + eq(request.path), + argThat { assertJsonEquals(request.body, this) }, + argThat(RequiredMapEntriesMatcher(request.headers)) ) } // Don't mind the invocations to the overloaded exchangeResource(String, String, String) @@ -158,40 +163,51 @@ class UatExecutor( } val newExternalServices = spyInterceptor.getSpies() - .map(SpyService::asServiceDefinition) + .map(SpyService::asServiceDefinition) return UatDefinition(newProcesses, newExternalServices) } finally { restClientFactory.clearInterceptors() + markUatEnd() } } + private fun markUatBegin() { + System.setProperty(PROPERTY_IN_UAT, "1") + } + + private fun markUatEnd() { + System.clearProperty(PROPERTY_IN_UAT) + } + private fun createRestClientMock(restExpectations: List<ExpectationDefinition>): BlueprintWebClientService { val restClient = mock<BlueprintWebClientService>( - defaultAnswer = Answers.RETURNS_SMART_NULLS, - // our custom verboseLogging handler - invocationListeners = arrayOf(mockLoggingListener) + defaultAnswer = Answers.RETURNS_SMART_NULLS, + // our custom verboseLogging handler + invocationListeners = arrayOf(mockLoggingListener) ) // Delegates to overloaded exchangeResource(String, String, String, Map<String, String>) whenever(restClient.exchangeResource(any(), any(), any())) - .thenAnswer { invocation -> - val method = invocation.arguments[0] as String - val path = invocation.arguments[1] as String - val request = invocation.arguments[2] as String - restClient.exchangeResource(method, path, request, emptyMap()) - } + .thenAnswer { invocation -> + val method = invocation.arguments[0] as String + val path = invocation.arguments[1] as String + val request = invocation.arguments[2] as String + restClient.exchangeResource(method, path, request, emptyMap()) + } for (expectation in restExpectations) { - whenever( - restClient.exchangeResource( - eq(expectation.request.method), - eq(expectation.request.path), - any(), - any() - ) + var stubbing = whenever( + restClient.exchangeResource( + eq(expectation.request.method), + eq(expectation.request.path), + any(), + any() + ) ) - .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString())) + for (response in expectation.responses) { + stubbing = stubbing.thenReturn(WebClientResponse(response.status, response.body.toString())) + } } return restClient } @@ -199,9 +215,9 @@ class UatExecutor( @Throws(AssertionError::class) private fun uploadBlueprint(client: HttpClient, cbaBytes: ByteArray) { val multipartEntity = MultipartEntityBuilder.create() - .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) - .addBinaryBody("file", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip") - .build() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addBinaryBody("file", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip") + .build() val request = HttpPost("$baseUrl/api/v1/blueprint-model/publish").apply { entity = multipartEntity } @@ -236,6 +252,19 @@ class UatExecutor( return mapper.readTree(actualResponse)!! } + private fun evalVerificationMode(times: String): VerificationMode { + val matchResult = TIMES_SPEC_REGEX.matchEntire(times) ?: throw InvalidUatDefinition( + "Time specification '$times' does not follow expected format $TIMES_SPEC_REGEX") + val counter = matchResult.groups[2]!!.value.toInt() + return when (matchResult.groups[1]?.value) { + ">=" -> atLeast(counter) + ">" -> atLeast(counter + 1) + "<=" -> atMost(counter) + "<" -> atMost(counter - 1) + else -> times(counter) + } + } + @Throws(AssertionError::class) private fun assertJsonEquals(expected: JsonNode?, actual: String): Boolean { // special case @@ -249,15 +278,15 @@ class UatExecutor( } private fun localServerPort(): Int = - (environment.getProperty("local.server.port") - ?: environment.getRequiredProperty("blueprint.httpPort")).toInt() + (environment.getProperty("local.server.port") + ?: environment.getRequiredProperty("blueprint.httpPort")).toInt() private fun clientAuthToken(): String { val username = environment.getRequiredProperty("security.user.name") val password = environment.getRequiredProperty("security.user.password") val plainPassword = when { password.startsWith(NOOP_PASSWORD_PREFIX) -> password.substring( - NOOP_PASSWORD_PREFIX.length) + NOOP_PASSWORD_PREFIX.length) else -> username } return "Basic " + Base64Utils.encodeToString("$username:$plainPassword".toByteArray()) @@ -271,7 +300,7 @@ class UatExecutor( } override fun getInstance(selector: String): BlueprintWebClientService? = - mocks[selector] + mocks[selector] fun registerMock(selector: String, client: BlueprintWebClientService) { mocks[selector] = client @@ -293,7 +322,7 @@ class UatExecutor( } fun getSpies(): List<SpyService> = - spies.values.toList() + spies.values.toList() } private class SpyService( @@ -301,14 +330,14 @@ class UatExecutor( val selector: String, private val realService: BlueprintWebClientService ) : - BlueprintWebClientService by realService { + BlueprintWebClientService by realService { private val expectations: MutableList<ExpectationDefinition> = mutableListOf() override fun exchangeResource(methodType: String, path: String, request: String): WebClientResponse<String> = - exchangeResource(methodType, path, request, - DEFAULT_HEADERS - ) + exchangeResource(methodType, path, request, + DEFAULT_HEADERS + ) override fun exchangeResource( methodType: String, @@ -317,7 +346,7 @@ class UatExecutor( headers: Map<String, String> ): WebClientResponse<String> { val requestDefinition = - RequestDefinition(methodType, path, headers, toJson(request)) + RequestDefinition(methodType, path, headers, toJson(request)) val realAnswer = realService.exchangeResource(methodType, path, request, headers) val responseBody = when { // TODO: confirm if we need to normalize the response here @@ -325,12 +354,12 @@ class UatExecutor( else -> null } val responseDefinition = - ResponseDefinition(realAnswer.status, responseBody) + ResponseDefinition(realAnswer.status, responseBody) expectations.add( - ExpectationDefinition( - requestDefinition, - responseDefinition - ) + ExpectationDefinition( + requestDefinition, + responseDefinition + ) ) return realAnswer } @@ -340,7 +369,7 @@ class UatExecutor( } fun asServiceDefinition() = - ServiceDefinition(selector, expectations) + ServiceDefinition(selector, expectations) private fun toJson(str: String): JsonNode? { return when { @@ -351,8 +380,8 @@ class UatExecutor( companion object { private val DEFAULT_HEADERS = mapOf( - HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE, - HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE + HttpHeaders.CONTENT_TYPE to MediaType.APPLICATION_JSON_VALUE, + HttpHeaders.ACCEPT to MediaType.APPLICATION_JSON_VALUE ) } } diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatServices.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatServices.kt index f40b903de..f40b903de 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatServices.kt +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/utils/UatServices.kt diff --git a/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties b/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties index fffc2f4c6..5beebd8e8 100755 --- a/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties +++ b/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties @@ -27,6 +27,11 @@ server.port=8081 security.user.password: {bcrypt}$2a$10$duaUzVUVW0YPQCSIbGEkQOXwafZGwQ/b32/Ys4R1iwSSawFgz7QNu security.user.name: ccsdkapps +# Error Managements +error.catalog.applicationId=cds +error.catalog.type=properties +error.catalog.errorDefinitionDir=./src/main/resources/ + ### START -Controller Blueprints Properties # Load Resource Source Mappings resourceSourceMappings=processor-db=source-db,input=source-input,default=source-default,sdnc=source-rest,aai-data=source-rest,capability=source-capability,vault-data=source-rest,rest=source-rest,script=source-capability @@ -137,4 +142,4 @@ blueprintsprocessor.messageproducer.self-service-api.topic=producer.t #blueprintsprocessor.messageconsumer.prioritize-input.type=kafka-streams-basic-auth #blueprintsprocessor.messageconsumer.prioritize-input.bootstrapServers=127.0.0.1:9092 #blueprintsprocessor.messageconsumer.prioritize-input.applicationId=cds-controller -#blueprintsprocessor.messageconsumer.prioritize-input.topic=prioritize-input-topic
\ No newline at end of file +#blueprintsprocessor.messageconsumer.prioritize-input.topic=prioritize-input-topic diff --git a/ms/blueprintsprocessor/application/src/main/resources/application-local.yml b/ms/blueprintsprocessor/application/src/main/resources/application-local.yml index de2cf4e52..f2843322c 100644 --- a/ms/blueprintsprocessor/application/src/main/resources/application-local.yml +++ b/ms/blueprintsprocessor/application/src/main/resources/application-local.yml @@ -38,10 +38,10 @@ blueprintsprocessor: remoteScriptCommand: enabled: true restclient: - sdncodl: + sdnc: password: Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U type: basic-auth - url: http://localhost:8282/ + url: http://localhost:8282 username: admin restconfEnabled: true controllerblueprints: diff --git a/ms/blueprintsprocessor/application/src/main/resources/application.properties b/ms/blueprintsprocessor/application/src/main/resources/application.properties index d6082bfa9..74549b0ae 100755 --- a/ms/blueprintsprocessor/application/src/main/resources/application.properties +++ b/ms/blueprintsprocessor/application/src/main/resources/application.properties @@ -60,6 +60,11 @@ blueprints.processor.functions.python.executor.modulePaths=/opt/app/onap/scripts security.user.password: {bcrypt}$2a$10$duaUzVUVW0YPQCSIbGEkQOXwafZGwQ/b32/Ys4R1iwSSawFgz7QNu security.user.name: ccsdkapps +# Error Managements +error.catalog.applicationId=cds +error.catalog.type=properties +error.catalog.errorDefinitionDir=/opt/app/onap/config/ + # Used in Health Check #endpoints.user.name=ccsdkapps #endpoints.user.password=ccsdkapps @@ -130,5 +135,4 @@ cdslistener.healthcheck.mapping-service-name-with-service-link=[SDC Listener ser #Actuator properties management.endpoints.web.exposure.include=* management.endpoint.health.show-details=always - - +management.info.git.mode=full diff --git a/ms/blueprintsprocessor/application/src/main/resources/error-messages_en.properties b/ms/blueprintsprocessor/application/src/main/resources/error-messages_en.properties new file mode 100644 index 000000000..71196ce16 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/resources/error-messages_en.properties @@ -0,0 +1,91 @@ +# +# Copyright © 2020 IBM, Bell Canada +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.onap.ccsdk.cds.blueprintsprocessor.generic_failure=cause=Internal error in Blueprint Processor run time.,action=Contact CDS administrator team. +org.onap.ccsdk.cds.blueprintsprocessor.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Self Service API +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.generic_failure=cause=Internal error in Self Service API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.generic_process_failure=cause=Internal error while processing REST call to the Self Service API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Designer API +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.generic_failure=cause=Internal error while processing REST call to the Designer API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Resource API +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.generic_failure=cause=Internal error while processing REST call to the Resource API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + + +# Configs API +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.generic_failure=cause=Internal error while processing REST call to the Configs API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. + +# Python Executor +org.onap.ccsdk.cds.blueprintsprocessor.functions.python.executor.generic_failure=cause=Internal error in Blueprint Processor run time.,action=Contact CDS administrator team. + +# Resource resolution +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.resource_not_found=cause=No response was found for this resolution in CDS.,action=Verify definition of the resource in CBA. +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.internal_error=cause=Internal error while processing Resource Resolution.,action=Verify the payload. + +org.onap.ccsdk.cds.sdclistener.generic_failure=cause=Internal error in SDC Listener.,action=Contact CDS administrator team. diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/ErrorCatalogTestConfiguration.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/ErrorCatalogTestConfiguration.kt new file mode 100644 index 000000000..c9d55c18a --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/ErrorCatalogTestConfiguration.kt @@ -0,0 +1,28 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.uat + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan( + basePackages = ["org.onap.ccsdk.cds.error.catalog"] +) +@EnableAutoConfiguration +open class ErrorCatalogTestConfiguration diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt index aebda8c07..4e7d4ce40 100644 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/UatServicesTest.kt @@ -30,6 +30,8 @@ import com.github.tomakehurst.wiremock.client.WireMock.equalToJson import com.github.tomakehurst.wiremock.client.WireMock.request import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig +import com.github.tomakehurst.wiremock.http.HttpHeader +import com.github.tomakehurst.wiremock.http.HttpHeaders import org.apache.http.HttpStatus import org.apache.http.client.methods.HttpPost import org.apache.http.entity.ContentType @@ -55,8 +57,6 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.web.server.LocalServerPort import org.springframework.core.env.ConfigurableEnvironment import org.springframework.core.env.MapPropertySource -import org.springframework.http.HttpHeaders -import org.springframework.http.MediaType import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.support.TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME import org.yaml.snakeyaml.Yaml @@ -211,7 +211,6 @@ class UatServicesTest : BaseUatTest() { service.expectations.forEach { expectation -> val request = expectation.request - val response = expectation.response // WebTestClient always use absolute path, prefixing with "/" if necessary val urlPattern = urlEqualTo(request.path.prefixIfNot("/")) val mappingBuilder: MappingBuilder = request(request.method, urlPattern) @@ -222,15 +221,19 @@ class UatServicesTest : BaseUatTest() { mappingBuilder.withRequestBody(equalToJson(mapper.writeValueAsString(request.body), true, true)) } - val responseDefinitionBuilder: ResponseDefinitionBuilder = aResponse() - .withStatus(response.status) - if (response.body != null) { - responseDefinitionBuilder.withBody(mapper.writeValueAsBytes(response.body)) - .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + for (response in expectation.responses) { + val responseDefinitionBuilder: ResponseDefinitionBuilder = aResponse() + .withStatus(response.status) + if (response.body != null) { + responseDefinitionBuilder.withBody(mapper.writeValueAsBytes(response.body)) + .withHeaders(HttpHeaders( + response.headers.entries.map { e -> HttpHeader(e.key, e.value) })) + } + + // TODO: MockServer verification for multiple responses should be done using Wiremock scenarios + mappingBuilder.willReturn(responseDefinitionBuilder) } - mappingBuilder.willReturn(responseDefinitionBuilder) - mockServer.stubFor(mappingBuilder) } return mockServer diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/error/ErrorCatalogServiceTest.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/error/ErrorCatalogServiceTest.kt new file mode 100644 index 000000000..4f7ef0ec2 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/uat/error/ErrorCatalogServiceTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.uat.error + +import org.junit.runner.RunWith +import org.onap.ccsdk.cds.blueprintsprocessor.uat.ErrorCatalogTestConfiguration +import org.onap.ccsdk.cds.controllerblueprints.core.grpcProcessorException +import org.onap.ccsdk.cds.controllerblueprints.core.httpProcessorException +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalog +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalogCodes +import org.onap.ccsdk.cds.error.catalog.core.ErrorMessage +import org.onap.ccsdk.cds.error.catalog.core.ErrorPayload +import org.onap.ccsdk.cds.error.catalog.services.ErrorCatalogService +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.junit4.SpringRunner +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertTrue + +@RunWith(SpringRunner::class) +@ContextConfiguration( + classes = [ErrorCatalogTestConfiguration::class] +) +@TestPropertySource(locations = ["classpath:application-test.properties"]) +class ErrorCatalogServiceTest { + @Autowired + lateinit var errorCatalogService: ErrorCatalogService + + private val domain = "org.onap.ccsdk.cds.blueprintsprocessor" + private lateinit var errorType: String + private lateinit var errorCatalogHttp: ErrorCatalog + private lateinit var errorCatalogGrpc: ErrorCatalog + private lateinit var errorPayloadHttp: ErrorPayload + private lateinit var errorPayloadGrpc: ErrorPayload + + @BeforeTest + fun setup() { + errorType = ErrorCatalogCodes.GENERIC_FAILURE + errorCatalogHttp = ErrorCatalog(errorType, domain, 500, + "Contact CDS administrator team.", "Internal error in Blueprint Processor run time.") + errorCatalogGrpc = ErrorCatalog(errorType, domain, 2, + "Contact CDS administrator team.", "Internal error in Blueprint Processor run time.") + + errorPayloadHttp = ErrorPayload(500, ErrorCatalogCodes.GENERIC_FAILURE, + "Cause: Internal error in Blueprint Processor run time. \n Action : Contact CDS administrator team.", + errorMessage = ErrorMessage("org.onap.ccsdk.cds.blueprintsprocessor", + "Internal error in Blueprint Processor run time.", "")) + errorPayloadGrpc = ErrorPayload(2, ErrorCatalogCodes.GENERIC_FAILURE, + "Cause: Internal error in Blueprint Processor run time. \n Action : Contact CDS administrator team.", + errorMessage = ErrorMessage("org.onap.ccsdk.cds.blueprintsprocessor", + "Internal error in Blueprint Processor run time.", "")) + } + + @Test + fun errorPayloadHttp() { + val errorPayload = errorCatalogService.errorPayload(httpProcessorException(errorType, domain, + "Internal error in Blueprint Processor run time.")) + assertTrue { errorPayload.isEqualTo(errorPayloadHttp) } + } + + @Test + fun errorPayloadGrpc() { + val errorPayload = errorCatalogService.errorPayload(grpcProcessorException(errorType, domain, + "Internal error in Blueprint Processor run time.")) + assertTrue { errorPayload.isEqualTo(errorPayloadGrpc) } + } + + @Test + fun getErrorCatalogHttp() { + val errorCatalog = errorCatalogService.getErrorCatalog(httpProcessorException(errorType, domain, + "Internal error in Blueprint Processor run time.")) + assertTrue { errorCatalog == errorCatalogHttp } + } + + @Test + fun getErrorCatalogGrpc() { + val errorCatalog = errorCatalogService.getErrorCatalog(grpcProcessorException(errorType, domain, + "Internal error in Blueprint Processor run time.")) + assertTrue { errorCatalog == errorCatalogGrpc } + } +} diff --git a/ms/blueprintsprocessor/application/src/test/resources/application-test.properties b/ms/blueprintsprocessor/application/src/test/resources/application-test.properties index 1d2565be3..d2170c7c1 100644 --- a/ms/blueprintsprocessor/application/src/test/resources/application-test.properties +++ b/ms/blueprintsprocessor/application/src/test/resources/application-test.properties @@ -16,6 +16,11 @@ spring.http.log-request-details=true +# Error Managements +error.catalog.applicationId=cds +error.catalog.type=properties +error.catalog.errorDefinitionDir=./src/test/resources/ + blueprintsprocessor.httpPort=0 blueprintsprocessor.grpcEnable=true blueprintsprocessor.grpcPort=0 @@ -61,4 +66,3 @@ blueprintprocessor.healthcheck.mapping-service-name-with-service-link=[Execution #BaseUrls for health check Cds Listener services cdslistener.healthcheck.baseUrl=http://cds-sdc-listener:8080/ cdslistener.healthcheck.mapping-service-name-with-service-link=[SDC Listener service,/api/v1/sdclistener/healthcheck] - diff --git a/ms/blueprintsprocessor/application/src/test/resources/application.properties b/ms/blueprintsprocessor/application/src/test/resources/application.properties index ea14c493a..cb3419397 100644 --- a/ms/blueprintsprocessor/application/src/test/resources/application.properties +++ b/ms/blueprintsprocessor/application/src/test/resources/application.properties @@ -61,6 +61,11 @@ blueprints.processor.functions.python.executor.modulePaths=/opt/app/onap/scripts security.user.password:{bcrypt}$2a$10$duaUzVUVW0YPQCSIbGEkQOXwafZGwQ/b32/Ys4R1iwSSawFgz7QNu security.user.name:ccsdkapps +# Error Managements +error.catalog.applicationId=cds +error.catalog.type=properties +error.catalog.errorDefinitionDir=./src/test/resources/ + # Executor Options blueprintsprocessor.resourceResolution.enabled=true blueprintsprocessor.netconfExecutor.enabled=true diff --git a/ms/blueprintsprocessor/application/src/test/resources/error-messages_en.properties b/ms/blueprintsprocessor/application/src/test/resources/error-messages_en.properties new file mode 100644 index 000000000..71196ce16 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/resources/error-messages_en.properties @@ -0,0 +1,91 @@ +# +# Copyright © 2020 IBM, Bell Canada +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.onap.ccsdk.cds.blueprintsprocessor.generic_failure=cause=Internal error in Blueprint Processor run time.,action=Contact CDS administrator team. +org.onap.ccsdk.cds.blueprintsprocessor.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Self Service API +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.generic_failure=cause=Internal error in Self Service API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.generic_process_failure=cause=Internal error while processing REST call to the Self Service API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Designer API +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.generic_failure=cause=Internal error while processing REST call to the Designer API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Resource API +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.generic_failure=cause=Internal error while processing REST call to the Resource API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + + +# Configs API +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.generic_failure=cause=Internal error while processing REST call to the Configs API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. + +# Python Executor +org.onap.ccsdk.cds.blueprintsprocessor.functions.python.executor.generic_failure=cause=Internal error in Blueprint Processor run time.,action=Contact CDS administrator team. + +# Resource resolution +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.resource_not_found=cause=No response was found for this resolution in CDS.,action=Verify definition of the resource in CBA. +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.internal_error=cause=Internal error while processing Resource Resolution.,action=Verify the payload. + +org.onap.ccsdk.cds.sdclistener.generic_failure=cause=Internal error in SDC Listener.,action=Contact CDS administrator team. diff --git a/ms/blueprintsprocessor/functions/message-prioritizaion/pom.xml b/ms/blueprintsprocessor/functions/message-prioritizaion/pom.xml index ac46b3635..c7dbaf174 100644 --- a/ms/blueprintsprocessor/functions/message-prioritizaion/pom.xml +++ b/ms/blueprintsprocessor/functions/message-prioritizaion/pom.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> diff --git a/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/ComponentNetconfExecutor.kt b/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/ComponentNetconfExecutor.kt index 1262e8500..307e73e6b 100644 --- a/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/ComponentNetconfExecutor.kt +++ b/ms/blueprintsprocessor/functions/netconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/netconf/executor/ComponentNetconfExecutor.kt @@ -65,6 +65,9 @@ open class ComponentNetconfExecutor(private var componentFunctionScriptingServic // Handles both script processing and error handling scriptComponent.executeScript(executionServiceInput) + + componentFunctionScriptingService.cleanupInstance(bluePrintRuntimeService.bluePrintContext(), + scriptType) } override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { diff --git a/ms/blueprintsprocessor/functions/pom.xml b/ms/blueprintsprocessor/functions/pom.xml index abd186bcf..3097c1b98 100755 --- a/ms/blueprintsprocessor/functions/pom.xml +++ b/ms/blueprintsprocessor/functions/pom.xml @@ -33,7 +33,7 @@ <modules> <module>resource-resolution</module> - <module>nrm-restful</module> + <module>restful-executor</module> <module>ansible-awx-executor</module> <module>python-executor</module> <module>netconf-executor</module> diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponent.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponent.kt index db0a6f0ed..3c95ea7bb 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponent.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponent.kt @@ -43,6 +43,7 @@ open class ResourceResolutionComponent(private val resourceResolutionService: Re const val INPUT_RESOURCE_TYPE = "resource-type" const val INPUT_ARTIFACT_PREFIX_NAMES = "artifact-prefix-names" const val INPUT_RESOLUTION_KEY = "resolution-key" + const val INPUT_RESOLUTION_SUMMARY = "resolution-summary" const val INPUT_STORE_RESULT = "store-result" const val INPUT_OCCURRENCE = "occurrence" @@ -64,6 +65,8 @@ open class ResourceResolutionComponent(private val resourceResolutionService: Re val resourceType = getOptionalOperationInput(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE)?.returnNullIfMissing()?.textValue() ?: "" + val resolutionSummary = + getOptionalOperationInput(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY)?.asBoolean() ?: false val properties: MutableMap<String, Any> = mutableMapOf() properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] = storeResult @@ -71,6 +74,7 @@ open class ResourceResolutionComponent(private val resourceResolutionService: Re properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = resourceId properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = resourceType properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = occurrence + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = resolutionSummary val jsonResponse = JsonNodeFactory.instance.objectNode() // Initialize Output Attribute to empty JSON diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSL.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSL.kt index 6573d0e9a..fd104d3ad 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSL.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSL.kt @@ -82,6 +82,11 @@ fun BluePrintTypes.nodeTypeComponentResourceResolution(): NodeType { ) property( + ResourceResolutionComponent.INPUT_RESOLUTION_SUMMARY, BluePrintConstants.DATA_TYPE_BOOLEAN, + false, "Enables ResolutionSummary output" + ) + + property( ResourceResolutionComponent.INPUT_OCCURRENCE, BluePrintConstants.DATA_TYPE_INTEGER, false, "Number of time to perform the resolution." ) { @@ -176,6 +181,12 @@ class ComponentResourceResolutionNodeTemplateBuilder(id: String, description: St property(ResourceResolutionComponent.INPUT_RESOLUTION_KEY, resolutionKey) } + fun resolutionSummary(resolutionSummary: Boolean) = resolutionSummary(resolutionSummary.asJsonPrimitive()) + + fun resolutionSummary(resolutionSummary: JsonNode) { + property(ResourceResolutionComponent.INPUT_RESOLUTION_SUMMARY, resolutionSummary) + } + fun dynamicProperties(dynamicProperties: String) = dynamicProperties(dynamicProperties.asJsonType()) fun dynamicProperties(dynamicProperties: JsonNode) { diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionConstants.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionConstants.kt index c39933dc8..8f6069160 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionConstants.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionConstants.kt @@ -29,4 +29,5 @@ object ResourceResolutionConstants { const val RESOURCE_RESOLUTION_INPUT_OCCURRENCE = "occurrence" const val RESOURCE_RESOLUTION_INPUT_RESOURCE_ID = "resource-id" const val RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE = "resource-type" + const val RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY = "resolution-summary" } diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionService.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionService.kt index 7272a3d63..dff00c7eb 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionService.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionService.kt @@ -147,7 +147,7 @@ open class ResourceResolutionServiceImpl( properties: Map<String, Any> ): String { - // Velocity Artifact Definition Name + // Template Artifact Definition Name val artifactTemplate = "$artifactPrefix-template" // Resource Assignment Artifact Definition Name val artifactMapping = "$artifactPrefix-mapping" @@ -185,22 +185,28 @@ open class ResourceResolutionServiceImpl( properties ) + val resolutionSummary = properties.getOrDefault(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY, false) as Boolean val resolvedParamJsonContent = ResourceAssignmentUtils.generateResourceDataForAssignments(resourceAssignments.toList()) - val artifactTemplateDefinition = bluePrintRuntimeService.bluePrintContext().checkNodeTemplateArtifact(nodeTemplateName, artifactTemplate) - val resolvedContent = if (artifactTemplateDefinition != null) { - blueprintTemplateService.generateContent( - bluePrintRuntimeService, nodeTemplateName, - artifactTemplate, resolvedParamJsonContent, false, - mutableMapOf( - ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE to - properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive() + val resolvedContent = when { + artifactTemplateDefinition != null -> { + blueprintTemplateService.generateContent( + bluePrintRuntimeService, nodeTemplateName, + artifactTemplate, resolvedParamJsonContent, false, + mutableMapOf( + ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE to + properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive() + ) ) - ) - } else { - resolvedParamJsonContent + } + resolutionSummary -> { + ResourceAssignmentUtils.generateResolutionSummaryData(resourceAssignments, resourceDefinitions) + } + else -> { + resolvedParamJsonContent + } } if (isToStore(properties)) { diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBService.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBService.kt index f8bf7bd09..dc1553747 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBService.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBService.kt @@ -198,4 +198,27 @@ class ResourceResolutionDBService(private val resourceResolutionRepository: Reso throw BluePrintException("Failed to store resource resolution result.", ex) } } + + /** + * This is a deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey method to delete resources + * associated to a specific resolution-key + * + * @param blueprintName name of the CBA + * @param blueprintVersion version of the CBA + * @param artifactName name of the artifact + * @param resolutionKey value of the resolution-key + */ + suspend fun deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey( + blueprintName: String, + blueprintVersion: String, + artifactName: String, + resolutionKey: String + ) { + resourceResolutionRepository.deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey( + blueprintName, + blueprintVersion, + artifactName, + resolutionKey + ) + } } diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionRepository.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionRepository.kt index a2a3a753b..c2d630e5e 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionRepository.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionRepository.kt @@ -17,6 +17,7 @@ package org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository +import javax.transaction.Transactional @Repository interface ResourceResolutionRepository : JpaRepository<ResourceResolution, String> { @@ -59,4 +60,12 @@ interface ResourceResolutionRepository : JpaRepository<ResourceResolution, Strin resourceType: String, occurrence: Int ): List<ResourceResolution> + + @Transactional + fun deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey( + blueprintName: String?, + blueprintVersion: String?, + artifactName: String, + resolutionKey: String + ) } diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessor.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessor.kt index feef4c2fe..868f919c1 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessor.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessor.kt @@ -81,6 +81,8 @@ open class CapabilityResourceResolutionProcessor(private var componentFunctionSc // Invoke componentResourceAssignmentProcessor componentResourceAssignmentProcessor!!.executeScript(resourceAssignment) + + componentFunctionScriptingService.cleanupInstance(raRuntimeService.bluePrintContext(), scriptType) } } diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt index e43b45e7d..0bfd7e4e2 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt @@ -28,6 +28,7 @@ import org.onap.ccsdk.cds.controllerblueprints.core.checkNotEmpty import org.onap.ccsdk.cds.controllerblueprints.core.isNotEmpty import org.onap.ccsdk.cds.controllerblueprints.core.nullToEmpty import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.KeyIdentifier import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDictionaryConstants import org.slf4j.LoggerFactory @@ -91,6 +92,11 @@ open class DatabaseResourceAssignmentProcessor( "failed to get input-key-mappings for $dName under $dSource properties" } + sourceProperties.inputKeyMapping + ?.mapValues { raRuntimeService.getDictionaryStore(it.value) } + ?.map { KeyIdentifier(it.key, it.value) } + ?.let { resourceAssignment.keyIdentifiers.addAll(it) } + logger.info( "DatabaseResource ($dSource) dictionary information: " + "Query:($sql), input-key-mapping:($inputKeyMapping), output-key-mapping:(${sourceProperties.outputKeyMapping})" diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt index 2ff5c441e..5d9226876 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt @@ -27,6 +27,7 @@ import org.onap.ccsdk.cds.controllerblueprints.core.checkNotEmpty import org.onap.ccsdk.cds.controllerblueprints.core.isNotEmpty import org.onap.ccsdk.cds.controllerblueprints.core.nullToEmpty import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.KeyIdentifier import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment import org.slf4j.LoggerFactory import org.springframework.beans.factory.config.ConfigurableBeanFactory @@ -75,8 +76,14 @@ open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyS checkNotNull(sourceProperties.inputKeyMapping) { "failed to get input-key-mappings for $dName under $dSource properties" } val resolvedInputKeyMapping = resolveInputKeyMappingVariables(inputKeyMapping).toMutableMap() + sourceProperties.inputKeyMapping + ?.mapValues { raRuntimeService.getDictionaryStore(it.value) } + ?.map { KeyIdentifier(it.key, it.value) } + ?.let { resourceAssignment.keyIdentifiers.addAll(it) } + // Resolving content Variables val payload = resolveFromInputKeyMapping(nullToEmpty(sourceProperties.payload), resolvedInputKeyMapping) + resourceSourceProperties["resolved-payload"] = JacksonUtils.jsonNode(payload) val urlPath = resolveFromInputKeyMapping(checkNotNull(sourceProperties.urlPath), resolvedInputKeyMapping) val verb = resolveFromInputKeyMapping(nullToEmpty(sourceProperties.verb), resolvedInputKeyMapping) diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt index 7ffc6db39..7bb757b8e 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt @@ -42,6 +42,9 @@ import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeServ import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonReactorUtils import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils.Companion.hasLogProtect +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.DictionaryMetadataEntry +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.KeyIdentifier +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResolutionSummary import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition import org.slf4j.LoggerFactory @@ -196,6 +199,29 @@ class ResourceAssignmentUtils { return data } + fun generateResolutionSummaryData( + resourceAssignments: List<ResourceAssignment>, + resourceDefinitions: Map<String, ResourceDefinition> + ): String { + val resolutionSummaryList = resourceAssignments.map { + val definition = resourceDefinitions[it.name] + val payload = definition?.sources?.get(it.dictionarySource) + ?.properties?.get("resolved-payload") + val metadata = definition?.property?.metadata + ?.map { e -> DictionaryMetadataEntry(e.key, e.value) } + ?.toMutableList() ?: mutableListOf() + val description = definition?.property?.description + ResolutionSummary( + it.name, it.property?.value, it.property?.required, it.property?.type, + it.keyIdentifiers, description, metadata, it.dictionaryName, + it.dictionarySource, payload, it.status, it.message + ) + } + // Wrapper needed for integration with SDNC + val data = mapOf("resolution-summary" to resolutionSummaryList) + return JacksonUtils.getJson(data, includeNull = true) + } + private fun useDefaultValueIfNull( resourceAssignment: ResourceAssignment, resourceAssignmentName: String @@ -263,7 +289,7 @@ class ResourceAssignmentUtils { return when (type) { in BluePrintTypes.validPrimitiveTypes() -> { // Primitive Types - parseResponseNodeForPrimitiveTypes(responseNode, outputKeyMapping) + parseResponseNodeForPrimitiveTypes(responseNode, resourceAssignment, outputKeyMapping) } in BluePrintTypes.validCollectionTypes() -> { // Array Types @@ -282,6 +308,7 @@ class ResourceAssignmentUtils { private fun parseResponseNodeForPrimitiveTypes( responseNode: JsonNode, + resourceAssignment: ResourceAssignment, outputKeyMapping: MutableMap<String, String> ): JsonNode { // Return responseNode if is not a Complex Type @@ -306,11 +333,16 @@ class ResourceAssignmentUtils { if (returnNode.isNullOrMissing() || returnNode!!.isComplexType() && !returnNode.has(outputKeyMapping[outputKey])) { throw BluePrintProcessorException("Fail to find output key mapping ($outputKey) in the responseNode.") } - return if (returnNode.isComplexType()) { + + val returnValue = if (returnNode.isComplexType()) { returnNode[outputKeyMapping[outputKey]] } else { returnNode } + + outputKey?.let { KeyIdentifier(it, returnValue) } + ?.let { resourceAssignment.keyIdentifiers.add(it) } + return returnValue } private fun parseResponseNodeForCollection( @@ -337,7 +369,7 @@ class ResourceAssignmentUtils { val responseArrayNode = responseNode.toList() for (responseSingleJsonNode in responseArrayNode) { val arrayChildNode = parseSingleElementOfArrayResponseNode( - entrySchemaType, + entrySchemaType, resourceAssignment, outputKeyMapping, raRuntimeService, responseSingleJsonNode, metadata ) arrayNode.add(arrayChildNode) @@ -347,7 +379,10 @@ class ResourceAssignmentUtils { is ObjectNode -> { val responseArrayNode = responseNode.rootFieldsToMap() resultNode = - parseObjectResponseNode(entrySchemaType, outputKeyMapping, responseArrayNode, metadata) + parseObjectResponseNode( + resourceAssignment, entrySchemaType, outputKeyMapping, + responseArrayNode, metadata + ) } else -> { throw BluePrintProcessorException("Key-value response expected to match the responseNode.") @@ -387,6 +422,7 @@ class ResourceAssignmentUtils { private fun parseSingleElementOfArrayResponseNode( entrySchemaType: String, + resourceAssignment: ResourceAssignment, outputKeyMapping: MutableMap<String, String>, raRuntimeService: ResourceAssignmentRuntimeService, responseNode: JsonNode, @@ -397,7 +433,13 @@ class ResourceAssignmentUtils { in BluePrintTypes.validPrimitiveTypes() -> { if (outputKeyMappingHasOnlyOneElement) { val outputKeyMap = outputKeyMapping.entries.first() + if (resourceAssignment.keyIdentifiers.none { it.name == outputKeyMap.key }) { + resourceAssignment.keyIdentifiers.add( + KeyIdentifier(outputKeyMap.key, JacksonUtils.objectMapper.createArrayNode()) + ) + } return parseSingleElementNodeWithOneOutputKeyMapping( + resourceAssignment, responseNode, outputKeyMap.key, outputKeyMap.value, @@ -416,6 +458,7 @@ class ResourceAssignmentUtils { raRuntimeService ) -> { parseSingleElementNodeWithAllOutputKeyMapping( + resourceAssignment, responseNode, outputKeyMapping, entrySchemaType, @@ -425,6 +468,7 @@ class ResourceAssignmentUtils { outputKeyMappingHasOnlyOneElement -> { val outputKeyMap = outputKeyMapping.entries.first() parseSingleElementNodeWithOneOutputKeyMapping( + resourceAssignment, responseNode, outputKeyMap.key, outputKeyMap.value, @@ -441,6 +485,7 @@ class ResourceAssignmentUtils { } private fun parseObjectResponseNode( + resourceAssignment: ResourceAssignment, entrySchemaType: String, outputKeyMapping: MutableMap<String, String>, responseArrayNode: MutableMap<String, JsonNode>, @@ -449,19 +494,21 @@ class ResourceAssignmentUtils { val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping) if (outputKeyMappingHasOnlyOneElement) { val outputKeyMap = outputKeyMapping.entries.first() - return parseObjectResponseNodeWithOneOutputKeyMapping( + val returnValue = parseObjectResponseNodeWithOneOutputKeyMapping( responseArrayNode, outputKeyMap.key, outputKeyMap.value, entrySchemaType, metadata ) + resourceAssignment.keyIdentifiers.add(KeyIdentifier(outputKeyMap.key, returnValue)) + return returnValue } else { throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType") } } private fun parseSingleElementNodeWithOneOutputKeyMapping( + resourceAssignment: ResourceAssignment, responseSingleJsonNode: JsonNode, - outputKeyMappingKey: - String, + outputKeyMappingKey: String, outputKeyMappingValue: String, type: String, metadata: MutableMap<String, String>? @@ -476,11 +523,19 @@ class ResourceAssignmentUtils { logKeyValueResolvedResource(metadata, outputKeyMappingKey, responseKeyValue, type) JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, responseKeyValue, type, arrayChildNode) - + resourceAssignment.keyIdentifiers.find { it.name == outputKeyMappingKey && it.value.isArray } + .let { + if (it != null) + (it.value as ArrayNode).add(responseKeyValue) + else + resourceAssignment.keyIdentifiers.add( + KeyIdentifier(outputKeyMappingKey, responseKeyValue)) + } return arrayChildNode } private fun parseSingleElementNodeWithAllOutputKeyMapping( + resourceAssignment: ResourceAssignment, responseSingleJsonNode: JsonNode, outputKeyMapping: MutableMap<String, String>, type: String, @@ -496,6 +551,7 @@ class ResourceAssignmentUtils { logKeyValueResolvedResource(metadata, it.key, responseKeyValue, type) JacksonUtils.populateJsonNodeValues(it.key, responseKeyValue, type, arrayChildNode) + resourceAssignment.keyIdentifiers.add(KeyIdentifier(it.key, responseKeyValue)) } return arrayChildNode } @@ -541,6 +597,7 @@ class ResourceAssignmentUtils { raRuntimeService ) -> { parseSingleElementNodeWithAllOutputKeyMapping( + resourceAssignment, responseNode, outputKeyMapping, entrySchemaType, @@ -550,8 +607,8 @@ class ResourceAssignmentUtils { outputKeyMappingHasOnlyOneElement -> { val outputKeyMap = outputKeyMapping.entries.first() parseSingleElementNodeWithOneOutputKeyMapping( - responseNode, outputKeyMap.key, outputKeyMap.value, - entrySchemaType, metadata + resourceAssignment, responseNode, outputKeyMap.key, + outputKeyMap.value, entrySchemaType, metadata ) } else -> { diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSLTest.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSLTest.kt index ae9b4208f..d1347113a 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSLTest.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponentDSLTest.kt @@ -41,6 +41,7 @@ class ResourceResolutionComponentDSLTest { occurrence(2) resourceType("vnf") storeResult(false) + resolutionSummary(true) artifactPrefixNames(arrayListOf("template1", "template2")) dynamicProperties( """{ diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionServiceTest.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionServiceTest.kt index 2f338a3a1..d5c43184e 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionServiceTest.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionServiceTest.kt @@ -36,6 +36,7 @@ import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintContext import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResolutionSummary import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.EnableAutoConfiguration @@ -44,6 +45,7 @@ import org.springframework.context.annotation.ComponentScan import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.TestPropertySource import org.springframework.test.context.junit4.SpringRunner +import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -79,6 +81,7 @@ class ResourceResolutionServiceTest { props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] = resourceId props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] = resourceType props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = occurrence + props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = false } @Test @@ -214,6 +217,101 @@ class ResourceResolutionServiceTest { } @Test + @Throws(Exception::class) + fun testResolveResourcesResolutionSummary() { + runBlocking { + props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY] = true + Assert.assertNotNull("failed to create ResourceResolutionService", resourceResolutionService) + + val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime( + "1234", + "./../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration" + ) + + val executionServiceInput = + JacksonUtils.readValueFromClassPathFile( + "payload/requests/sample-resourceresolution-request.json", + ExecutionServiceInput::class.java + )!! + + val resourceAssignmentRuntimeService = + ResourceAssignmentUtils.transformToRARuntimeService( + bluePrintRuntimeService, + "testResolveResourcesWithMappingAndTemplate" + ) + + val artifactPrefix = "notemplate" + + // Prepare Inputs + PayloadUtils.prepareInputsFromWorkflowPayload( + bluePrintRuntimeService, + executionServiceInput.payload, + "resource-assignment" + ) + + resourceResolutionService.resolveResources( + resourceAssignmentRuntimeService, + "resource-assignment", + artifactPrefix, + props + ) + }.let { + val summaries = JacksonUtils.jsonNode(it)["resolution-summary"] + val list = JacksonUtils.getListFromJsonNode(summaries, ResolutionSummary::class.java) + assertEquals(list.size, 3) + } + } + + @Test + @Throws(Exception::class) + fun testResolveResourcesWithoutTemplate() { + runBlocking { + Assert.assertNotNull("failed to create ResourceResolutionService", resourceResolutionService) + + val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime( + "1234", + "./../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration" + ) + + val executionServiceInput = + JacksonUtils.readValueFromClassPathFile( + "payload/requests/sample-resourceresolution-request.json", + ExecutionServiceInput::class.java + )!! + + val resourceAssignmentRuntimeService = + ResourceAssignmentUtils.transformToRARuntimeService( + bluePrintRuntimeService, + "testResolveResourcesWithMappingAndTemplate" + ) + + val artifactPrefix = "notemplate" + + // Prepare Inputs + PayloadUtils.prepareInputsFromWorkflowPayload( + bluePrintRuntimeService, + executionServiceInput.payload, + "resource-assignment" + ) + + resourceResolutionService.resolveResources( + resourceAssignmentRuntimeService, + "resource-assignment", + artifactPrefix, + props + ) + }.let { + assertEquals(""" + { + "service-instance-id" : "siid_1234", + "vnf-id" : "vnf_1234", + "vnf_name" : "temp_vnf" + } + """.trimIndent(), it) + } + } + + @Test fun testResolveResourcesWithResourceIdAndResourceType() { props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] = "" runBlocking { diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBServiceTest.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBServiceTest.kt index 4f864a49c..e667cd16f 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBServiceTest.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBServiceTest.kt @@ -223,4 +223,16 @@ open class ResourceResolutionDBServiceTest { assertEquals(resourceResolution, res) } } + + @Test + fun deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKeyTest() { + every { + resourceResolutionRepository.deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey(any(), any(), any(), any()) + } returns Unit + runBlocking { + val res = resourceResolutionDBService.deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey( + blueprintName, blueprintVersion, artifactPrefix, resolutionKey) + assertEquals(Unit, res) + } + } } diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessorTest.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessorTest.kt index d84488d76..5fbe32e07 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessorTest.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/CapabilityResourceResolutionProcessorTest.kt @@ -52,6 +52,10 @@ class CapabilityResourceResolutionProcessorTest { .scriptInstance<ResourceAssignmentProcessor>(any(), any(), any()) } returns MockCapabilityScriptRA() + coEvery { + componentFunctionScriptingService.cleanupInstance(any(), any()) + } returns mockk() + val raRuntimeService = mockk<ResourceAssignmentRuntimeService>() every { raRuntimeService.bluePrintContext() } returns mockk<BluePrintContext>() every { raRuntimeService.getInputValue("test-property") } returns NullNode.getInstance() @@ -96,6 +100,10 @@ class CapabilityResourceResolutionProcessorTest { .scriptInstance<ResourceAssignmentProcessor>(any(), BluePrintConstants.SCRIPT_JYTHON, any()) } returns MockCapabilityScriptRA() + coEvery { + componentFunctionScriptingService.cleanupInstance(any(), any()) + } returns mockk() + val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService("1234", bluePrintContext) val capabilityResourceResolutionProcessor = diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtilsTest.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtilsTest.kt index 3251dcacb..9df8fb7d7 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtilsTest.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtilsTest.kt @@ -33,10 +33,12 @@ import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive import org.onap.ccsdk.cds.controllerblueprints.core.asJsonType import org.onap.ccsdk.cds.controllerblueprints.core.data.DataType import org.onap.ccsdk.cds.controllerblueprints.core.data.EntrySchema +import org.onap.ccsdk.cds.controllerblueprints.core.data.NodeTemplate import org.onap.ccsdk.cds.controllerblueprints.core.data.PropertyDefinition import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment +import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition import kotlin.test.assertEquals data class IpAddress(val port: String, val ip: String) @@ -46,6 +48,7 @@ data class ExpectedResponseIpAddress(val ipAddress: IpAddress) class ResourceAssignmentUtilsTest { private lateinit var resourceAssignmentRuntimeService: ResourceAssignmentRuntimeService + private lateinit var resourceAssignment: ResourceAssignment private lateinit var inputMapToTestPrimitiveTypeWithValue: JsonNode private lateinit var inputMapToTestPrimitiveTypeWithKeyValue: JsonNode @@ -156,6 +159,46 @@ class ResourceAssignmentUtilsTest { assertEquals(expected, outcome.replace("\r\n", "\n"), "unexpected outcome generated") } + @Test + fun generate() { + val resourceAssignment = createResourceAssignmentForTest(null) + val resourceDefinition = ResourceDefinition() + val nodeTemplate = NodeTemplate().apply { + properties = mutableMapOf("resolved-payload" to JacksonUtils.jsonNode("{\"mock\": true}")) + } + resourceDefinition.sources = mutableMapOf("input" to nodeTemplate) + resourceDefinition.property = PropertyDefinition().apply { + this.description = "pnf-id" + this.metadata = mutableMapOf("aai-path" to "//path/in/aai") + } + + val result = ResourceAssignmentUtils.generateResolutionSummaryData( + listOf(resourceAssignment), mapOf("pnf-id" to resourceDefinition)) + + assertEquals(""" + { + "resolution-summary":[ + { + "name":"pnf-id", + "value":null, + "required":null, + "type":"string", + "key-identifiers":[], + "dictionary-description":"pnf-id", + "dictionary-metadata":[ + {"name":"aai-path","value":"//path/in/aai"} + ], + "dictionary-name":"pnf-id", + "dictionary-source":"input", + "request-payload":{"mock":true}, + "status":null, + "message":null + } + ] + } + """.replace("\n|\\s".toRegex(), ""), result) + } + private fun createResourceAssignmentForTest(resourceValue: String?): ResourceAssignment { val valueForTest = if (resourceValue == null) null else TextNode(resourceValue) val resourceAssignmentForTest = ResourceAssignment().apply { @@ -181,6 +224,7 @@ class ResourceAssignmentUtilsTest { outcome, "Unexpected outcome returned for primitive type of simple String" ) + assertEquals(0, resourceAssignment.keyIdentifiers.size) outcome = prepareResponseNodeForTest( "sample-key-value", "string", "", @@ -191,6 +235,10 @@ class ResourceAssignmentUtilsTest { outcome, "Unexpected outcome returned for primitive type of key-value String" ) + assertEquals( + expectedValueToTestPrimitiveType, + resourceAssignment.keyIdentifiers[0].value + ) } @Test @@ -204,6 +252,13 @@ class ResourceAssignmentUtilsTest { outcome, "unexpected outcome returned for list of String" ) + + val expectedKeyIdentifierValue = JacksonUtils.getJsonNode(outcome.map { it["ip"] }) + assertEquals( + expectedKeyIdentifierValue, + resourceAssignment.keyIdentifiers[0].value + ) + // FIXME("Map is not collection type, It is known complex type") // outcome = prepareResponseNodeForTest( // "mapOfString", "map", "string", @@ -250,6 +305,9 @@ class ResourceAssignmentUtilsTest { outcome, "Unexpected outcome returned for complex type" ) + assertEquals( + expectedValueToTestComplexTypeWithOneOutputKeyMapping["host"], + resourceAssignment.keyIdentifiers[0].value) } @Test @@ -263,6 +321,16 @@ class ResourceAssignmentUtilsTest { outcome, "Unexpected outcome returned for complex type" ) + assertEquals(2, resourceAssignment.keyIdentifiers.size) + assertEquals( + expectedValueToTestComplexTypeWithAllOutputKeyMapping["name"], + resourceAssignment.keyIdentifiers[0].value + ) + + assertEquals( + expectedValueToTestComplexTypeWithAllOutputKeyMapping["ipAddress"], + resourceAssignment.keyIdentifiers[1].value + ) } private fun initInputMapAndExpectedValuesForPrimitiveType() { @@ -359,7 +427,7 @@ class ResourceAssignmentUtilsTest { response: Any ): JsonNode { - val resourceAssignment = when (sourceType) { + resourceAssignment = when (sourceType) { "list" -> { prepareRADataDictionaryCollection(dictionary_source, sourceType, entrySchema) } diff --git a/ms/blueprintsprocessor/functions/restconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restconf/executor/RestconfExecutorExtensions.kt b/ms/blueprintsprocessor/functions/restconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restconf/executor/RestconfExecutorExtensions.kt index 906bef9a4..408eaf45b 100644 --- a/ms/blueprintsprocessor/functions/restconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restconf/executor/RestconfExecutorExtensions.kt +++ b/ms/blueprintsprocessor/functions/restconf-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restconf/executor/RestconfExecutorExtensions.kt @@ -45,12 +45,12 @@ suspend fun AbstractScriptComponentFunction.restconfMountDevice( headers: Map<String, String> = mutableMapOf("Content-Type" to "application/xml") ) { - val mountUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId" + val mountUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId" log.info("sending mount request, url: $mountUrl") webClientService.exchangeResource("PUT", mountUrl, payload as String, headers) /** Check device has mounted */ - val mountCheckUrl = "restconf/operational/network-topology:network-topology/topology/topology-netconf/node/$deviceId" + val mountCheckUrl = "/restconf/operational/network-topology:network-topology/topology/topology-netconf/node/$deviceId" val expectedResult = """"netconf-node-topology:connection-status":"connected"""" val mountCheckExecutionBlock: suspend (Int) -> String = { tryCount: Int -> @@ -69,6 +69,7 @@ suspend fun AbstractScriptComponentFunction.restconfMountDevice( /** * Generic Configure function + * @return The WebClientResponse from the request */ suspend fun AbstractScriptComponentFunction.restconfApplyDeviceConfig( webClientService: BlueprintWebClientService, @@ -76,14 +77,12 @@ suspend fun AbstractScriptComponentFunction.restconfApplyDeviceConfig( configletResourcePath: String, configletToApply: Any, additionalHeaders: Map<String, String> = mutableMapOf("Content-Type" to "application/yang.patch+xml") -) { - +): BlueprintWebClientService.WebClientResponse<String> { log.debug("headers: $additionalHeaders") log.info("configuring device: $deviceId, Configlet: $configletToApply") - val applyConfigUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/" + + val applyConfigUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/" + "$deviceId/$configletResourcePath" - val result: Any = webClientService.exchangeResource("PATCH", applyConfigUrl, configletToApply as String, additionalHeaders) - log.info("Configuration application result: $result") + return webClientService.exchangeResource("PATCH", applyConfigUrl, configletToApply as String, additionalHeaders) } suspend fun AbstractScriptComponentFunction.restconfDeviceConfig( @@ -93,7 +92,7 @@ suspend fun AbstractScriptComponentFunction.restconfDeviceConfig( ): BlueprintWebClientService.WebClientResponse<String> { - val configPathUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/" + + val configPathUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/" + "$deviceId/$configletResourcePath" log.debug("sending GET request, url: $configPathUrl") return webClientService.exchangeResource("GET", configPathUrl, "") @@ -107,7 +106,7 @@ suspend fun AbstractScriptComponentFunction.restconfUnMountDevice( deviceId: String, payload: String ) { - val unMountUrl = "restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId" + val unMountUrl = "/restconf/config/network-topology:network-topology/topology/topology-netconf/node/$deviceId" log.info("sending unMount request, url: $unMountUrl") webClientService.exchangeResource("DELETE", unMountUrl, "") } diff --git a/ms/blueprintsprocessor/functions/nrm-restful/pom.xml b/ms/blueprintsprocessor/functions/restful-executor/pom.xml index 337e71e15..4ac52c4b8 100644 --- a/ms/blueprintsprocessor/functions/nrm-restful/pom.xml +++ b/ms/blueprintsprocessor/functions/restful-executor/pom.xml @@ -24,11 +24,10 @@ </parent> <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId> - <artifactId>nrm-restful</artifactId> - <packaging>jar</packaging> + <artifactId>restful-executor</artifactId> - <name>Blueprints Processor Function - NRM Restful</name> - <description>Blueprints Processor Function - NRM Restful</description> + <name>Blueprints Processor Function - NRM Restful executor</name> + <description>Blueprints Processor Function - NRM Restful executor</description> <dependencies> <dependency> @@ -48,5 +47,10 @@ <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/internal/scripts/TestRestfulConfigure.kt b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/internal/scripts/TestRestfulConfigure.kt new file mode 100644 index 000000000..5f867b93f --- /dev/null +++ b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/internal/scripts/TestRestfulConfigure.kt @@ -0,0 +1,42 @@ +/* + * Copyright © 2020 Huawei. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("unused") + +package internal.scripts + +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.functions.restful.executor.RestfulCMComponentFunction +import org.slf4j.LoggerFactory + +/** + * This is for used for Testing only + */ +open class TestRestfulConfigure : RestfulCMComponentFunction() { + + val log = LoggerFactory.getLogger(TestRestfulConfigure::class.java)!! + + override fun getName(): String { + return "TestRestfulConfigure" + } + + override suspend fun processNB(executionRequest: ExecutionServiceInput) { + log.info("processing request..") + } + + override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { + log.info("recovering..") + } +} diff --git a/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/ComponentRestfulExecutor.kt b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/ComponentRestfulExecutor.kt new file mode 100644 index 000000000..e1643b576 --- /dev/null +++ b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/ComponentRestfulExecutor.kt @@ -0,0 +1,69 @@ +/* + * Copyright © 2020 Huawei Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.restful.executor + +import com.fasterxml.jackson.databind.node.ArrayNode +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractComponentFunction +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.ComponentFunctionScriptingService +import org.onap.ccsdk.cds.controllerblueprints.core.getAsString +import org.springframework.beans.factory.config.ConfigurableBeanFactory +import org.springframework.context.annotation.Scope +import org.springframework.stereotype.Component + +@Component("component-restful-executor") +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +open class ComponentRestfulExecutor(private var componentFunctionScriptingService: ComponentFunctionScriptingService) : AbstractComponentFunction() { + + lateinit var scriptComponent: RestfulCMComponentFunction + + companion object { + const val SCRIPT_TYPE = "script-type" + const val SCRIPT_CLASS_REFERENCE = "script-class-reference" + const val INSTANCE_DEPENDENCIES = "instance-dependencies" + } + + override suspend fun processNB(executionRequest: ExecutionServiceInput) { + + val scriptType = operationInputs.getAsString(SCRIPT_TYPE) + val scriptClassReference = operationInputs.getAsString(SCRIPT_CLASS_REFERENCE) + val instanceDependenciesNode = operationInputs.get(INSTANCE_DEPENDENCIES) as? ArrayNode + + val scriptDependencies: MutableList<String> = arrayListOf() + scriptDependencies.add(RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY) + + instanceDependenciesNode?.forEach { instanceName -> + scriptDependencies.add(instanceName.textValue()) + } + /** + * Populate the Script Instance based on the Type + */ + scriptComponent = componentFunctionScriptingService + .scriptInstance<RestfulCMComponentFunction>(this, scriptType, + scriptClassReference, scriptDependencies) + + checkNotNull(scriptComponent) { "failed to get restfulCM script component" } + + // Handles both script processing and error handling + scriptComponent.executeScript(executionServiceInput) + } + + override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { + bluePrintRuntimeService.getBluePrintError() + .addError("Failed in ComponentRestfulExecutor : ${runtimeException.message}") + } +} diff --git a/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/RestfulCMComponentFunction.kt b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/RestfulCMComponentFunction.kt new file mode 100644 index 000000000..46fec3126 --- /dev/null +++ b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/RestfulCMComponentFunction.kt @@ -0,0 +1,126 @@ +/* + * Copyright © 2020 Huawei. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.functions.restful.executor + +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.AbstractScriptComponentFunction +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.databind.node.ArrayNode +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService +import org.onap.ccsdk.cds.blueprintsprocessor.functions.restful.executor.nrmfunction.RestfulNRMServiceClient +import org.slf4j.LoggerFactory + +abstract class RestfulCMComponentFunction : AbstractScriptComponentFunction() { + + private val log = LoggerFactory.getLogger(RestfulCMComponentFunction::class.java) + override suspend fun processNB(executionRequest: ExecutionServiceInput) { + throw BluePrintException("Not Implemented required") + } + + override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { + throw BluePrintException("Not Implemented required") + } + + open fun bluePrintRestLibPropertyService(): BluePrintRestLibPropertyService = + functionDependencyInstanceAsType(RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY) + + fun restClientService(clientInfo: JsonNode): BlueprintWebClientService { + return bluePrintRestLibPropertyService().blueprintWebClientService(clientInfo) + } + + fun processNRM(executionRequest: ExecutionServiceInput, input_params: ArrayNode): String { + // process the managed object instances + log.info("Processing NRM Object") + operationInputs = executionServiceInput.stepData!!.properties + val dynamic_properties = operationInputs.get("dynamic-properties") + // instantiate one restClientService instance + val hostname = dynamic_properties?.get("hostname").toString().replace("\"", "") + val port = dynamic_properties?.get("port").toString().replace("\"", "") + val username = dynamic_properties?.get("username").toString().replace("\"", "") + val password = dynamic_properties?.get("password").toString().replace("\"", "") + val url = "http://" + hostname + ":" + port + val RestInfo: String = "{\n" + + " \"type\" : \"basic-auth\",\n" + + " \"url\" : \"" + url + "\",\n" + + " \"username\" : \"" + username + "\",\n" + + " \"password\" : \"" + password + "\"\n" + + "}" + val mapper = ObjectMapper() + val jsonRestInfo: JsonNode = mapper.readTree(RestInfo) + val web_client_service = restClientService(jsonRestInfo) + val managed_object_instances = input_params + var response = JacksonUtils.jsonNode("{}") as ObjectNode + // Invoke the corresponding function according to the workflowname + when (this.workflowName) { + "config-deploy" -> { + for (managed_object_instance in managed_object_instances) { + // invoke createMOI for each managed-object-instance + log.info("invoke createMOI for each managed-object-instance") + var NRM_Restful_client = RestfulNRMServiceClient() + val MOI_id = NRM_Restful_client.generateMOIid() + var httpresponse = NRM_Restful_client.createMOI(web_client_service, MOI_id, managed_object_instance) + var MOIname = managed_object_instance.get("className").toString().replace("\"", "") + response.put("/$MOIname/$MOI_id", httpresponse) + } + } + "config-get" -> { + for (managed_object_instance in managed_object_instances) { + // invoke getMOIAttributes for each managed-object-instance + log.info("invoke getMOIAttributes for each managed-object-instance") + var NRM_Restful_client = RestfulNRMServiceClient() + val MOI_id = managed_object_instance.get("id").toString().replace("\"", "") + var httpresponse = NRM_Restful_client.getMOIAttributes(web_client_service, MOI_id, managed_object_instance) + var MOIname = managed_object_instance.get("className").toString().replace("\"", "") + response.put("/$MOIname/$MOI_id", httpresponse) + } + } + "config-modify" -> { + for (managed_object_instance in managed_object_instances) { + // invoke modifyMOIAttributes for each managed-object-instance + log.info("invoke modifyMOIAttributes for each managed-object-instance") + var NRM_Restful_client = RestfulNRMServiceClient() + val MOI_id = managed_object_instance.get("id").toString().replace("\"", "") + var httpresponse = NRM_Restful_client.modifyMOIAttributes(web_client_service, MOI_id, managed_object_instance) + var MOIname = managed_object_instance.get("className").toString().replace("\"", "") + response.put("/$MOIname/$MOI_id", httpresponse) + } + } + "config-delete" -> { + for (managed_object_instance in managed_object_instances) { + // invoke deleteMOI for each managed-object-instance + log.info("invoke deleteMOI for each managed-object-instance") + var NRM_Restful_client = RestfulNRMServiceClient() + val MOI_id = managed_object_instance.get("id").toString().replace("\"", "") + var httpresponse = NRM_Restful_client.deleteMOI(web_client_service, MOI_id, managed_object_instance) + var MOIname = managed_object_instance.get("className").toString().replace("\"", "") + response.put("/$MOIname/$MOI_id", httpresponse) + } + } + else -> { + print("not Implemented") + response.put(this.workflowName, "Not Implemented") + } + } + val strresponse = "$response" + return strresponse + } +} diff --git a/ms/blueprintsprocessor/functions/nrm-restful/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/nrm/restful/RestfulNRMServiceClient.kt b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/nrmfunction/RestfulNRMServiceClient.kt index eb14d255b..31ad377c9 100644 --- a/ms/blueprintsprocessor/functions/nrm-restful/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/nrm/restful/RestfulNRMServiceClient.kt +++ b/ms/blueprintsprocessor/functions/restful-executor/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/restful/executor/nrmfunction/RestfulNRMServiceClient.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onap.ccsdk.cds.blueprintsprocessor.functions.nrm.restful +package org.onap.ccsdk.cds.blueprintsprocessor.functions.restful.executor.nrmfunction import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper diff --git a/ms/blueprintsprocessor/functions/restful-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/restful/executor/ComponentRestfulExecutorTest.kt b/ms/blueprintsprocessor/functions/restful-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/restful/executor/ComponentRestfulExecutorTest.kt new file mode 100644 index 000000000..ad70ac021 --- /dev/null +++ b/ms/blueprintsprocessor/functions/restful-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/restful/executor/ComponentRestfulExecutorTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright © 2020 Huawei Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.functions.restful.executor + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node.ObjectNode +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ActionIdentifiers +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.CommonHeader +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput +import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.StepData +import org.onap.ccsdk.cds.blueprintsprocessor.services.execution.ComponentFunctionScriptingService +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants +import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive +import org.onap.ccsdk.cds.controllerblueprints.core.data.Implementation +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintContext +import org.onap.ccsdk.cds.controllerblueprints.core.service.DefaultBluePrintRuntimeService +import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils +import org.springframework.context.ApplicationContext + +class ComponentRestfulExecutorTest { + + @Test + fun testComponentRestfulExecutor() { + runBlocking { + + val applicationContext = mockk<ApplicationContext>() + every { applicationContext.getBean(any()) } returns mockk() + + val componentFunctionScriptingService = ComponentFunctionScriptingService(applicationContext, mockk()) + + val componentRestfulExecutor = ComponentRestfulExecutor(componentFunctionScriptingService) + + val executionServiceInput = ExecutionServiceInput().apply { + commonHeader = CommonHeader().apply { + requestId = "1234" + } + actionIdentifiers = ActionIdentifiers().apply { + actionName = "config-deploy" + } + payload = JacksonUtils.jsonNode("{}") as ObjectNode + } + + val blueprintContext = mockk<BluePrintContext>() + every { + blueprintContext.nodeTemplateOperationImplementation( + any(), any(), any() + ) + } returns Implementation() + + val bluePrintRuntime = mockk<DefaultBluePrintRuntimeService>("1234") + every { bluePrintRuntime.bluePrintContext() } returns blueprintContext + + componentRestfulExecutor.bluePrintRuntimeService = bluePrintRuntime + componentRestfulExecutor.stepName = "sample-step" + + val operationInputs = hashMapOf<String, JsonNode>() + operationInputs[BluePrintConstants.PROPERTY_CURRENT_NODE_TEMPLATE] = "config-deploy-process".asJsonPrimitive() + operationInputs[BluePrintConstants.PROPERTY_CURRENT_INTERFACE] = "interfaceName".asJsonPrimitive() + operationInputs[BluePrintConstants.PROPERTY_CURRENT_OPERATION] = "operationName".asJsonPrimitive() + operationInputs["script-type"] = BluePrintConstants.SCRIPT_INTERNAL.asJsonPrimitive() + operationInputs["script-class-reference"] = "internal.scripts.TestRestfulConfigure".asJsonPrimitive() + + val stepInputData = StepData().apply { + name = "call-config-deploy-process" + properties = operationInputs + } + executionServiceInput.stepData = stepInputData + + every { + bluePrintRuntime.resolveNodeTemplateInterfaceOperationInputs( + "config-deploy-process", + "interfaceName", "operationName" + ) + } returns operationInputs + + val operationOutputs = hashMapOf<String, JsonNode>() + every { + bluePrintRuntime.resolveNodeTemplateInterfaceOperationOutputs( + "config-deploy-process", + "interfaceName", "operationName" + ) + } returns operationOutputs + + componentRestfulExecutor.applyNB(executionServiceInput) + } + } +} diff --git a/ms/blueprintsprocessor/functions/nrm-restful/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/nrm/restful/RestfulNRMServiceClientTest.kt b/ms/blueprintsprocessor/functions/restful-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/restful/executor/nrmfunction/RestfulNRMServiceClientTest.kt index 8dcb7975d..e7f04a5d4 100644 --- a/ms/blueprintsprocessor/functions/nrm-restful/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/nrm/restful/RestfulNRMServiceClientTest.kt +++ b/ms/blueprintsprocessor/functions/restful-executor/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/restful/executor/nrmfunction/RestfulNRMServiceClientTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.onap.ccsdk.cds.blueprintsprocessor.functions.nrm.restful +package org.onap.ccsdk.cds.blueprintsprocessor.functions.restful.executor.nrmfunction import com.fasterxml.jackson.databind.node.ObjectNode import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/pom.xml b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/pom.xml index 4700c6337..fb2daab3a 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/pom.xml +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/pom.xml @@ -76,6 +76,12 @@ <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>com.hubspot.jinjava</groupId> @@ -85,6 +91,10 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + </dependency> <!--Testing dependencies--> <dependency> <groupId>org.jetbrains.kotlin</groupId> diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt index 20aef3498..e26af2b66 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintConstants.kt @@ -218,9 +218,6 @@ object BluePrintConstants { const val DEFAULT_STEP_OPERATION = "process" const val DEFAULT_STEP_INTERFACE = "ComponentInterface" - const val ARTIFACT_VELOCITY_TYPE_NAME = "artifact-template-velocity" - const val ARTIFACT_JINJA_TYPE_NAME = "artifact-template-jinja" - const val MODEL_TYPE_ARTIFACT_TEMPLATE_VELOCITY = "artifact-template-velocity" const val MODEL_TYPE_ARTIFACT_TEMPLATE_JINJA = "artifact-template-jinja" const val MODEL_TYPE_ARTIFACT_MAPPING_RESOURCE = "artifact-mapping-resource" @@ -229,6 +226,8 @@ object BluePrintConstants { const val MODEL_TYPE_ARTIFACT_DIRECTED_GRAPH = "artifact-directed-graph" const val MODEL_TYPE_ARTIFACT_COMPONENT_JAR = "artifact-component-jar" + const val TOSCA_SPEC = "TOSCA" + val USE_SCRIPT_COMPILE_CACHE: Boolean = (System.getenv("USE_SCRIPT_COMPILE_CACHE") ?: "true").toBoolean() const val LOG_PROTECT: String = "log-protect" diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintException.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintException.kt index a2435da13..74e6bb6bd 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintException.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintException.kt @@ -1,5 +1,6 @@ /* * Copyright © 2017-2018 AT&T Intellectual Property. + * Modifications Copyright © 2018 - 2020 IBM, Bell Canada. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,29 +22,13 @@ package org.onap.ccsdk.cds.controllerblueprints.core * * @author Brinda Santh */ -class BluePrintException : Exception { - - var code: Int = 100 +class BluePrintException : BluePrintProcessorException { constructor(cause: Throwable) : super(cause) constructor(message: String) : super(message) constructor(message: String, cause: Throwable) : super(message, cause) - constructor(cause: Throwable, message: String, vararg args: Any?) : super(String.format(message, *args), cause) - - constructor(code: Int, cause: Throwable) : super(cause) { - this.code = code - } - - constructor(code: Int, message: String) : super(message) { - this.code = code - } - - constructor(code: Int, message: String, cause: Throwable) : super(message, cause) { - this.code = code - } - - constructor(code: Int, cause: Throwable, message: String, vararg args: Any?) : - super(String.format(message, *args), cause) { - this.code = code - } + constructor(cause: Throwable, message: String, vararg args: Any?) : super(cause, message, args) + constructor(code: Int, cause: Throwable) : super(code, cause) + constructor(code: Int, message: String) : super(code, message) + constructor(code: Int, message: String, cause: Throwable) : super(code, message, cause) } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintProcessorException.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintProcessorException.kt index b0b217051..acb158d89 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintProcessorException.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/BluePrintProcessorException.kt @@ -1,6 +1,6 @@ /* * Copyright © 2017-2018 AT&T Intellectual Property. - * Modifications Copyright © 2018 IBM. + * Modifications Copyright © 2018 - 2020 IBM, Bell Canada. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,35 +17,55 @@ package org.onap.ccsdk.cds.controllerblueprints.core +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalogException +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalogExceptionFluent +import org.onap.ccsdk.cds.error.catalog.core.ErrorMessage + /** * * * @author Brinda Santh */ -class BluePrintProcessorException : RuntimeException { - - var code: Int = 100 +open class BluePrintProcessorException : ErrorCatalogException, ErrorCatalogExceptionFluent<BluePrintProcessorException> { constructor(message: String, cause: Throwable) : super(message, cause) constructor(message: String) : super(message) constructor(cause: Throwable) : super(cause) - constructor(cause: Throwable, message: String, vararg args: Any?) : super(format(message, *args), cause) + constructor(cause: Throwable, message: String, vararg args: Any?) : super(cause, message, args) + constructor(code: Int, cause: Throwable) : super(code, cause) + constructor(code: Int, message: String) : super(code, message) + constructor(code: Int, message: String, cause: Throwable) : super(code, message, cause) + + override fun code(code: Int): BluePrintProcessorException { + return this.updateCode(code) + } + + override fun domain(domain: String): BluePrintProcessorException { + return this.updateDomain(domain) + } + + override fun action(action: String): BluePrintProcessorException { + return this.updateAction(action) + } - constructor(code: Int, cause: Throwable) : super(cause) { - this.code = code + override fun http(type: String): BluePrintProcessorException { + return this.updateHttp(type) } - constructor(code: Int, message: String) : super(message) { - this.code = code + override fun grpc(type: String): BluePrintProcessorException { + return this.updateGrpc(type) } - constructor(code: Int, message: String, cause: Throwable) : super(message, cause) { - this.code = code + override fun payloadMessage(message: String): BluePrintProcessorException { + return this.updatePayloadMessage(message) } - constructor(code: Int, cause: Throwable, message: String, vararg args: Any?) : - super(String.format(message, *args), cause) { - this.code = code + override fun addErrorPayloadMessage(message: String): BluePrintProcessorException { + return this.updateErrorPayloadMessage(message) + } + + override fun addSubError(errorMessage: ErrorMessage): BluePrintProcessorException { + return this.updateSubError(errorMessage) } } @@ -55,3 +75,65 @@ class BluePrintRetryException : RuntimeException { constructor(cause: Throwable) : super(cause) constructor(cause: Throwable, message: String, vararg args: Any?) : super(format(message, *args), cause) } + +/** Extension Functions */ + +fun processorException(message: String): BluePrintProcessorException { + return BluePrintProcessorException(message) +} + +fun processorException(code: Int, message: String): BluePrintProcessorException { + return processorException(message).code(code) +} + +fun httpProcessorException(type: String, message: String): BluePrintProcessorException { + return processorException(message).http(type) +} + +fun grpcProcessorException(type: String, message: String): BluePrintProcessorException { + return processorException(message).grpc(type) +} + +fun httpProcessorException(type: String, domain: String, message: String): BluePrintProcessorException { + val bluePrintProcessorException = processorException(message).http(type) + return bluePrintProcessorException.addDomainAndErrorMessage(domain, message) +} + +fun grpcProcessorException(type: String, domain: String, message: String): BluePrintProcessorException { + val bluePrintProcessorException = processorException(message).grpc(type) + return bluePrintProcessorException.addDomainAndErrorMessage(domain, message) +} + +fun httpProcessorException(type: String, domain: String, message: String, cause: Throwable): + BluePrintProcessorException { + val bluePrintProcessorException = processorException(message).http(type) + return bluePrintProcessorException.addDomainAndErrorMessage(domain, message, cause) +} + +fun grpcProcessorException(type: String, domain: String, message: String, cause: Throwable): + BluePrintProcessorException { + val bluePrintProcessorException = processorException(message).grpc(type) + return bluePrintProcessorException.addDomainAndErrorMessage(domain, message, cause) +} + +fun BluePrintProcessorException.updateErrorMessage(domain: String, message: String, cause: Throwable): + BluePrintProcessorException { + return this.addDomainAndErrorMessage(domain, message, cause).domain(domain) + .addErrorPayloadMessage(message) + .payloadMessage(message) +} + +fun BluePrintProcessorException.updateErrorMessage(domain: String, message: String): BluePrintProcessorException { + return this.addDomainAndErrorMessage(domain, message).domain(domain) + .addErrorPayloadMessage(message) + .payloadMessage(message) +} + +private fun BluePrintProcessorException.addDomainAndErrorMessage( + domain: String, + message: String, + cause: Throwable = Throwable() +): BluePrintProcessorException { + return this.addSubError(ErrorMessage(domain, message, cause.message + ?: "")).domain(domain) +} diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/interfaces/BluePrintScriptsService.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/interfaces/BluePrintScriptsService.kt index aa61b0c4d..0f7cb79f2 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/interfaces/BluePrintScriptsService.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/interfaces/BluePrintScriptsService.kt @@ -36,4 +36,6 @@ interface BluePrintScriptsService { suspend fun <T> scriptInstance(cacheKey: String, scriptClassName: String): T suspend fun <T> scriptInstance(scriptClassName: String): T + + suspend fun cleanupInstance(blueprintBasePath: String) } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintCompileService.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintCompileService.kt index a8c630387..a9684a14d 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintCompileService.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintCompileService.kt @@ -25,11 +25,9 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler import org.jetbrains.kotlin.config.Services -import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException import org.onap.ccsdk.cds.controllerblueprints.core.checkFileExists import org.onap.ccsdk.cds.controllerblueprints.core.logger -import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintFileUtils import java.io.File import java.net.URLClassLoader import java.util.ArrayList @@ -58,13 +56,7 @@ open class BluePrintCompileService { compile(bluePrintSourceCode) } - val classLoaderWithDependencies = if (BluePrintConstants.USE_SCRIPT_COMPILE_CACHE) { - /** Get the class loader with compiled jar from cache */ - BluePrintCompileCache.classLoader(bluePrintSourceCode.cacheKey) - } else { - /** Get the class loader with compiled jar from disk */ - BluePrintFileUtils.getURLClassLoaderFromDirectory(bluePrintSourceCode.cacheKey) - } + val classLoaderWithDependencies = BluePrintCompileCache.classLoader(bluePrintSourceCode.cacheKey) /** Create the instance from the class loader */ return instance(classLoaderWithDependencies, kClassName, args) @@ -126,9 +118,6 @@ open class BluePrintCompileService { .single().newInstance(*args.toArray()) } ?: throw BluePrintException("failed to create class($kClassName) instance for constructor argument($args).") - if (!BluePrintConstants.USE_SCRIPT_COMPILE_CACHE) { - classLoader.close() - } return instance as T } } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintScriptsServiceImpl.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintScriptsServiceImpl.kt index f3eb1a2b9..fa8ca2719 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintScriptsServiceImpl.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/scripts/BluePrintScriptsServiceImpl.kt @@ -79,4 +79,11 @@ open class BluePrintScriptsServiceImpl : BluePrintScriptsService { return Thread.currentThread().contextClassLoader.loadClass(scriptClassName).constructors .single().newInstance(*args.toArray()) as T } + + override suspend fun cleanupInstance(blueprintBasePath: String) { + if (!BluePrintConstants.USE_SCRIPT_COMPILE_CACHE) { + log.info("Invalidating compile cache for blueprint ($blueprintBasePath)") + BluePrintCompileCache.cleanClassLoader(blueprintBasePath) + } + } } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/service/BluePrintTemplateService.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/service/BluePrintTemplateService.kt index db733bda1..51a6e10ee 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/service/BluePrintTemplateService.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/service/BluePrintTemplateService.kt @@ -40,7 +40,7 @@ class BluePrintTemplateService(private val bluePrintLoadConfiguration: BluePrint val template = bluePrintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, artifactName) return when (templateType) { - BluePrintConstants.ARTIFACT_JINJA_TYPE_NAME -> { + BluePrintConstants.MODEL_TYPE_ARTIFACT_TEMPLATE_JINJA -> { BluePrintJinjaTemplateService.generateContent( template, jsonData, @@ -51,13 +51,13 @@ class BluePrintTemplateService(private val bluePrintLoadConfiguration: BluePrint bluePrintRuntimeService.bluePrintContext().version() ) } - BluePrintConstants.ARTIFACT_VELOCITY_TYPE_NAME -> { + BluePrintConstants.MODEL_TYPE_ARTIFACT_TEMPLATE_VELOCITY -> { BluePrintVelocityTemplateService.generateContent(template, jsonData, ignoreJsonNull, additionalContext) } else -> { throw BluePrintProcessorException( - "Unknown Artifact type, expecting ${BluePrintConstants.ARTIFACT_JINJA_TYPE_NAME}" + - "or ${BluePrintConstants.ARTIFACT_VELOCITY_TYPE_NAME}" + "Unknown Artifact type, expecting ${BluePrintConstants.MODEL_TYPE_ARTIFACT_TEMPLATE_JINJA}" + + "or ${BluePrintConstants.MODEL_TYPE_ARTIFACT_TEMPLATE_VELOCITY}" ) } } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/BluePrintArchiveUtils.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/BluePrintArchiveUtils.kt index 9ccf856b5..595dbce6b 100755 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/BluePrintArchiveUtils.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/BluePrintArchiveUtils.kt @@ -22,22 +22,39 @@ import com.google.common.base.Predicates import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException import org.slf4j.LoggerFactory import java.io.BufferedInputStream +import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream +import java.io.Closeable import java.io.File import java.io.FileOutputStream +import java.io.InputStream +import java.io.InputStreamReader import java.io.IOException import java.io.OutputStream -import java.nio.charset.Charset import java.nio.file.FileVisitResult import java.nio.file.Files import java.nio.file.Path import java.nio.file.SimpleFileVisitor import java.nio.file.attribute.BasicFileAttributes import java.util.function.Predicate +import org.apache.commons.compress.archivers.ArchiveEntry +import org.apache.commons.compress.archivers.ArchiveInputStream +import org.apache.commons.compress.archivers.ArchiveOutputStream +import org.apache.commons.compress.archivers.tar.TarArchiveEntry +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import org.apache.commons.compress.archivers.zip.ZipFile +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream +import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream +import java.util.Enumeration import java.util.zip.Deflater -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream + +enum class ArchiveType { + TarGz, + Zip +} class BluePrintArchiveUtils { @@ -51,7 +68,7 @@ class BluePrintArchiveUtils { * @param destination the output filename * @return True if OK */ - fun compress(source: File, destination: File): Boolean { + fun compress(source: File, destination: File, archiveType: ArchiveType = ArchiveType.Zip): Boolean { try { if (!destination.parentFile.exists()) { destination.parentFile.mkdirs() @@ -59,7 +76,7 @@ class BluePrintArchiveUtils { destination.createNewFile() val ignoreZipFiles = Predicate<Path> { path -> !path.endsWith(".zip") && !path.endsWith(".ZIP") } FileOutputStream(destination).use { out -> - compressFolder(source.toPath(), out, pathFilter = ignoreZipFiles) + compressFolder(source.toPath(), out, archiveType, pathFilter = ignoreZipFiles) } } catch (e: Exception) { log.error("Fail to compress folder($source) to path(${destination.path})", e) @@ -71,8 +88,12 @@ class BluePrintArchiveUtils { /** * In-memory compress an entire folder. */ - fun compressToBytes(baseDir: Path, compressionLevel: Int = Deflater.NO_COMPRESSION): ByteArray { - return compressFolder(baseDir, ByteArrayOutputStream(), compressionLevel = compressionLevel) + fun compressToBytes( + baseDir: Path, + archiveType: ArchiveType = ArchiveType.Zip, + compressionLevel: Int = Deflater.NO_COMPRESSION + ): ByteArray { + return compressFolder(baseDir, ByteArrayOutputStream(), archiveType, compressionLevel = compressionLevel) .toByteArray() } @@ -89,38 +110,51 @@ class BluePrintArchiveUtils { private fun <T> compressFolder( baseDir: Path, output: T, + archiveType: ArchiveType, pathFilter: Predicate<Path> = Predicates.alwaysTrue(), compressionLevel: Int = Deflater.DEFAULT_COMPRESSION, fixedModificationTime: Long? = null ): T where T : OutputStream { - ZipOutputStream(output) - .apply { setLevel(compressionLevel) } - .use { zos -> + val stream: ArchiveOutputStream = if (archiveType == ArchiveType.Zip) + ZipArchiveOutputStream(output).apply { setLevel(compressionLevel) } + else + TarArchiveOutputStream(GzipCompressorOutputStream(output)) + stream + .use { aos -> Files.walkFileTree(baseDir, object : SimpleFileVisitor<Path>() { @Throws(IOException::class) override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { if (pathFilter.test(file)) { - val zipEntry = ZipEntry(baseDir.relativize(file).toString()) - fixedModificationTime?.let { - zipEntry.time = it + var archiveEntry: ArchiveEntry = aos.createArchiveEntry(file.toFile(), + baseDir.relativize(file).toString()) + if (archiveType == ArchiveType.Zip) { + val entry = archiveEntry as ZipArchiveEntry + fixedModificationTime?.let { + entry.time = it + } + entry.time = 0 } - zipEntry.time = 0 - zos.putNextEntry(zipEntry) - Files.copy(file, zos) - zos.closeEntry() + aos.putArchiveEntry(archiveEntry) + Files.copy(file, aos) + aos.closeArchiveEntry() } return FileVisitResult.CONTINUE } @Throws(IOException::class) override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult { - val zipEntry = ZipEntry(baseDir.relativize(dir).toString() + "/") - fixedModificationTime?.let { - zipEntry.time = it - } - zos.putNextEntry(zipEntry) - zos.closeEntry() + var archiveEntry: ArchiveEntry? + if (archiveType == ArchiveType.Zip) { + val entry = ZipArchiveEntry(baseDir.relativize(dir).toString() + "/") + fixedModificationTime?.let { + entry.time = it + } + archiveEntry = entry + } else + archiveEntry = TarArchiveEntry(baseDir.relativize(dir).toString() + "/") + aos.putArchiveEntry(archiveEntry) + aos.closeArchiveEntry() return FileVisitResult.CONTINUE } }) @@ -128,31 +162,111 @@ class BluePrintArchiveUtils { return output } - fun deCompress(zipFile: File, targetPath: String): File { - val zip = ZipFile(zipFile, Charset.defaultCharset()) - val enumeration = zip.entries() - while (enumeration.hasMoreElements()) { - val entry = enumeration.nextElement() - val destFilePath = File(targetPath, entry.name) - destFilePath.parentFile.mkdirs() + private fun getDefaultEncoding(): String? { + val bytes = byteArrayOf('D'.toByte()) + val inputStream: InputStream = ByteArrayInputStream(bytes) + val reader = InputStreamReader(inputStream) + return reader.encoding + } - if (entry.isDirectory) - continue + fun deCompress(archiveFile: File, targetPath: String, archiveType: ArchiveType = ArchiveType.Zip): File { + var enumeration: ArchiveEnumerator? = null + if (archiveType == ArchiveType.Zip) { + val zipArchive = ZipFile(archiveFile, getDefaultEncoding()) + enumeration = ArchiveEnumerator(zipArchive) + } else { // Tar Gz + var tarGzArchiveIs: InputStream = BufferedInputStream(archiveFile.inputStream()) + tarGzArchiveIs = GzipCompressorInputStream(tarGzArchiveIs) + val tarGzArchive: ArchiveInputStream = TarArchiveInputStream(tarGzArchiveIs) + enumeration = ArchiveEnumerator(tarGzArchive) + } + + enumeration.use { + while (enumeration!!.hasMoreElements()) { + val entry: ArchiveEntry? = enumeration.nextElement() + val destFilePath = File(targetPath, entry!!.name) + destFilePath.parentFile.mkdirs() - val bufferedIs = BufferedInputStream(zip.getInputStream(entry)) - bufferedIs.use { + if (entry!!.isDirectory) + continue + + val bufferedIs = BufferedInputStream(enumeration.getInputStream(entry)) destFilePath.outputStream().buffered(1024).use { bos -> bufferedIs.copyTo(bos) } + + if (!enumeration.getHasSharedEntryInputStream()) + bufferedIs.close() } } val destinationDir = File(targetPath) check(destinationDir.isDirectory && destinationDir.exists()) { - throw BluePrintProcessorException("failed to decompress blueprint(${zipFile.absolutePath}) to ($targetPath) ") + throw BluePrintProcessorException("failed to decompress blueprint(${archiveFile.absolutePath}) to ($targetPath) ") } return destinationDir } } + + class ArchiveEnumerator : Enumeration<ArchiveEntry>, Closeable { + private val zipArchive: ZipFile? + private val zipEnumeration: Enumeration<ZipArchiveEntry>? + private val archiveStream: ArchiveInputStream? + private var nextEntry: ArchiveEntry? = null + private val hasSharedEntryInputStream: Boolean + + constructor(zipFile: ZipFile) { + zipArchive = zipFile + zipEnumeration = zipFile.entries + archiveStream = null + hasSharedEntryInputStream = false + } + + constructor(archiveStream: ArchiveInputStream) { + this.archiveStream = archiveStream + zipArchive = null + zipEnumeration = null + hasSharedEntryInputStream = true + } + + fun getHasSharedEntryInputStream(): Boolean { + return hasSharedEntryInputStream + } + + fun getInputStream(entry: ArchiveEntry): InputStream? { + return if (zipArchive != null) + zipArchive?.getInputStream(entry as ZipArchiveEntry?) + else + archiveStream + } + + override fun hasMoreElements(): Boolean { + if (zipEnumeration != null) + return zipEnumeration?.hasMoreElements() + else if (archiveStream != null) { + nextEntry = archiveStream.nextEntry + if (nextEntry != null && !archiveStream.canReadEntryData(nextEntry)) + return hasMoreElements() + return nextEntry != null + } + return false + } + + override fun nextElement(): ArchiveEntry? { + if (zipEnumeration != null) + nextEntry = zipEnumeration.nextElement() + else if (archiveStream != null) { + if (nextEntry == null) + nextEntry = archiveStream.nextEntry + } + return nextEntry + } + + override fun close() { + if (zipArchive != null) + zipArchive.close() + else archiveStream?.close() + } + } } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/JacksonUtils.kt b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/JacksonUtils.kt index 3db1f84cd..573fc17d2 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/JacksonUtils.kt +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/utils/JacksonUtils.kt @@ -133,9 +133,13 @@ class JacksonUtils { return getJson(wrapperMap, pretty) } - fun getJson(any: kotlin.Any, pretty: Boolean = false): String { + fun getJson(any: kotlin.Any, pretty: Boolean = false, includeNull: Boolean = false): String { val objectMapper = jacksonObjectMapper() - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL) + if (includeNull) { + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS) + } else { + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL) + } if (pretty) { objectMapper.enable(SerializationFeature.INDENT_OUTPUT) } diff --git a/ms/blueprintsprocessor/modules/blueprints/blueprint-proto/pom.xml b/ms/blueprintsprocessor/modules/blueprints/blueprint-proto/pom.xml index b208cbda5..1e90cca76 100644 --- a/ms/blueprintsprocessor/modules/blueprints/blueprint-proto/pom.xml +++ b/ms/blueprintsprocessor/modules/blueprints/blueprint-proto/pom.xml @@ -28,6 +28,10 @@ <name>Controller Blueprints Proto</name> <description>Controller Blueprints Proto</description> + <properties> + <sonar.skip>true</sonar.skip> + </properties> + <dependencies> <dependency> <groupId>com.github.marcoferrer.krotoplus</groupId> diff --git a/ms/blueprintsprocessor/modules/blueprints/pom.xml b/ms/blueprintsprocessor/modules/blueprints/pom.xml index cd6a17e88..527a5bf98 100644 --- a/ms/blueprintsprocessor/modules/blueprints/pom.xml +++ b/ms/blueprintsprocessor/modules/blueprints/pom.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> diff --git a/ms/blueprintsprocessor/modules/blueprints/resource-dict/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/resource/dict/ResourceDefinition.kt b/ms/blueprintsprocessor/modules/blueprints/resource-dict/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/resource/dict/ResourceDefinition.kt index 4ed98ddd0..77025c5a4 100644 --- a/ms/blueprintsprocessor/modules/blueprints/resource-dict/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/resource/dict/ResourceDefinition.kt +++ b/ms/blueprintsprocessor/modules/blueprints/resource-dict/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/resource/dict/ResourceDefinition.kt @@ -19,6 +19,7 @@ package org.onap.ccsdk.cds.controllerblueprints.resource.dict import com.fasterxml.jackson.annotation.JsonFormat import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.JsonNode import org.onap.ccsdk.cds.controllerblueprints.core.data.NodeTemplate import org.onap.ccsdk.cds.controllerblueprints.core.data.PropertyDefinition import java.io.Serializable @@ -87,6 +88,9 @@ open class ResourceAssignment { @JsonProperty("updated-by") var updatedBy: String? = null + /** input & output key-mapping with their resolved values **/ + var keyIdentifiers: MutableList<KeyIdentifier> = mutableListOf() + override fun toString(): String { return """ [ @@ -101,6 +105,33 @@ open class ResourceAssignment { } } +data class KeyIdentifier(val name: String, val value: JsonNode) +data class DictionaryMetadataEntry(val name: String, val value: String) +/** + * Data class for exposing summary of resource resolution + */ +data class ResolutionSummary( + val name: String, + val value: JsonNode?, + val required: Boolean?, + val type: String?, + @JsonProperty("key-identifiers") + val keyIdentifiers: MutableList<KeyIdentifier>, + @JsonProperty("dictionary-description") + val dictionaryDescription: String?, + @JsonProperty("dictionary-metadata") + val dictionaryMetadata: MutableList<DictionaryMetadataEntry>, + @JsonProperty("dictionary-name") + val dictionaryName: String?, + @JsonProperty("dictionary-source") + val dictionarySource: String?, + @JsonProperty("request-payload") + val requestPayload: JsonNode?, + @JsonProperty("status") + val status: String?, + @JsonProperty("message") + val message: String? +) /** * Interface for Source Definitions (ex Input Source, * Default Source, Database Source, Rest Sources, etc) diff --git a/ms/blueprintsprocessor/modules/commons/nats-lib/pom.xml b/ms/blueprintsprocessor/modules/commons/nats-lib/pom.xml index f4e7f4d98..f28a7132e 100644 --- a/ms/blueprintsprocessor/modules/commons/nats-lib/pom.xml +++ b/ms/blueprintsprocessor/modules/commons/nats-lib/pom.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterService.kt b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterService.kt index 6be3334bb..a58c077fa 100644 --- a/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterService.kt +++ b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterService.kt @@ -243,8 +243,11 @@ open class ClusterLockImpl(private val hazelcast: HazelcastInstance, private val } override suspend fun unLock() { - distributedLock.unlock() - log.trace("Cluster unlock(${name()}) successfully..") + // Added condition to avoid failures like - "Current thread is not owner of the lock!" + if (distributedLock.isLockedByCurrentThread) { + distributedLock.unlock() + log.trace("Cluster unlock(${name()}) successfully..") + } } override fun isLocked(): Boolean { diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterServiceTest.kt b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterServiceTest.kt index b298eacae..80cf41558 100644 --- a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterServiceTest.kt +++ b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/cluster/HazlecastClusterServiceTest.kt @@ -113,7 +113,7 @@ class HazlecastClusterServiceTest { } else { ClusterInfo( id = "test-cluster", nodeId = nodeId, joinAsClient = false, - configFile = "./src/test/resources/hazelcast/hazelcast-$port.yaml", + configFile = "./src/test/resources/hazelcast/hazelcast-cluster.yaml", properties = properties ) } @@ -162,7 +162,7 @@ class HazlecastClusterServiceTest { executeLock(bluePrintClusterServices[0], "first", lockName) } val deferred2 = async { - executeLock(bluePrintClusterServices[0], "second", lockName) + executeLock(bluePrintClusterServices[1], "second", lockName) } val deferred3 = async { executeLock(bluePrintClusterServices[2], "third", lockName) diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5679.yaml b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5679.yaml deleted file mode 100644 index e7ac273ed..000000000 --- a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5679.yaml +++ /dev/null @@ -1,21 +0,0 @@ -hazelcast: - cluster-name: ${CLUSTER_ID} - instance-name: node-5679 - lite-member: - enabled: false - cp-subsystem: - cp-member-count: 3 - group-size: 3 - session-time-to-live-seconds: 60 - session-heartbeat-interval-seconds: 5 - missing-cp-member-auto-removal-seconds: 120 -# network: -# join: -# multicast: -# enabled: false -# kubernetes: -# enabled: true -# namespace: MY-KUBERNETES-NAMESPACE -# service-name: MY-SERVICE-NAME -# service-label-name: MY-SERVICE-LABEL-NAME -# service-label-value: MY-SERVICE-LABEL-VALUE
\ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5680.yaml b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5680.yaml deleted file mode 100644 index cb493d169..000000000 --- a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5680.yaml +++ /dev/null @@ -1,21 +0,0 @@ -hazelcast: - cluster-name: ${CLUSTER_ID} - instance-name: node-5680 - lite-member: - enabled: false - cp-subsystem: - cp-member-count: 3 - group-size: 3 - session-time-to-live-seconds: 60 - session-heartbeat-interval-seconds: 5 - missing-cp-member-auto-removal-seconds: 120 -# network: -# join: -# multicast: -# enabled: false -# kubernetes: -# enabled: true -# namespace: MY-KUBERNETES-NAMESPACE -# service-name: MY-SERVICE-NAME -# service-label-name: MY-SERVICE-LABEL-NAME -# service-label-value: MY-SERVICE-LABEL-VALUE
\ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5681.yaml b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5681.yaml deleted file mode 100644 index e60b0c506..000000000 --- a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5681.yaml +++ /dev/null @@ -1,21 +0,0 @@ -hazelcast: - cluster-name: ${CLUSTER_ID} - instance-name: node-5681 - lite-member: - enabled: false - cp-subsystem: - cp-member-count: 3 - group-size: 3 - session-time-to-live-seconds: 60 - session-heartbeat-interval-seconds: 5 - missing-cp-member-auto-removal-seconds: 120 -# network: -# join: -# multicast: -# enabled: false -# kubernetes: -# enabled: true -# namespace: MY-KUBERNETES-NAMESPACE -# service-name: MY-SERVICE-NAME -# service-label-name: MY-SERVICE-LABEL-NAME -# service-label-value: MY-SERVICE-LABEL-VALUE
\ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5682.yaml b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5682.yaml index 3cb10a08b..859ea3328 100644 --- a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5682.yaml +++ b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-5682.yaml @@ -9,10 +9,11 @@ hazelcast: session-time-to-live-seconds: 60 session-heartbeat-interval-seconds: 5 missing-cp-member-auto-removal-seconds: 120 -# network: -# join: -# multicast: -# enabled: false + network: + join: + multicast: + enabled: true + multicast-group: 224.0.0.1 # kubernetes: # enabled: true # namespace: MY-KUBERNETES-NAMESPACE diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-cluster.yaml b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-cluster.yaml new file mode 100644 index 000000000..de6047a90 --- /dev/null +++ b/ms/blueprintsprocessor/modules/commons/processor-core/src/test/resources/hazelcast/hazelcast-cluster.yaml @@ -0,0 +1,23 @@ +hazelcast: + lite-member: + enabled: false + cp-subsystem: + cp-member-count: 3 + group-size: 3 + session-time-to-live-seconds: 60 + session-heartbeat-interval-seconds: 5 + missing-cp-member-auto-removal-seconds: 120 + network: + join: + multicast: + enabled: true + # Specify 224.0.0.1 instead of default 224.2.2.3 since there's some issue + # on macOs with docker installed and multicast address different than 224.0.0.1 + # https://stackoverflow.com/questions/46341715/hazelcast-multicast-does-not-work-because-of-vboxnet-which-is-used-by-docker-mac + multicast-group: 224.0.0.1 + # kubernetes: + # enabled: true + # namespace: MY-KUBERNETES-NAMESPACE + # service-name: MY-SERVICE-NAME + # service-label-name: MY-SERVICE-LABEL-NAME + # service-label-value: MY-SERVICE-LABEL-VALUE diff --git a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt index 540b3d9ad..bfc0a5cd4 100644 --- a/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt +++ b/ms/blueprintsprocessor/modules/commons/rest-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/rest/service/BasicAuthRestClientService.kt @@ -20,6 +20,7 @@ import org.apache.http.message.BasicHeader import org.onap.ccsdk.cds.blueprintsprocessor.rest.BasicAuthRestClientProperties import org.springframework.http.HttpHeaders import org.springframework.http.MediaType +import java.net.URI import java.nio.charset.Charset import java.util.Base64 @@ -43,7 +44,8 @@ class BasicAuthRestClientService( } override fun host(uri: String): String { - return restClientProperties.url + uri + val uri: URI = URI.create(restClientProperties.url + uri) + return uri.resolve(uri).toString() } override fun convertToBasicHeaders(headers: Map<String, String>): diff --git a/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/BlueprintModelController.kt b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/BlueprintModelController.kt index bb824ce4d..1f01d1ce3 100644 --- a/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/BlueprintModelController.kt +++ b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/BlueprintModelController.kt @@ -26,6 +26,7 @@ import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.handler.BluePrintMode import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.utils.BlueprintSortByOption import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.mdcWebCoroutineScope import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException +import org.onap.ccsdk.cds.controllerblueprints.core.asJsonString import org.springframework.core.io.Resource import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest @@ -208,4 +209,42 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint ) = mdcWebCoroutineScope { bluePrintModelHandler.deleteBlueprintModel(name, version) } + + @PostMapping( + path = arrayOf("/workflow-spec"), produces = arrayOf( + MediaType + .APPLICATION_JSON_VALUE + ), + consumes = arrayOf(MediaType.APPLICATION_JSON_VALUE) + ) + @ResponseBody + @Throws(BluePrintException::class) + @PreAuthorize("hasRole('USER')") + suspend fun workflowSpec(@RequestBody workFlowSpecReq: WorkFlowSpecRequest): + ResponseEntity<String> = mdcWebCoroutineScope { + var json = bluePrintModelHandler.prepareWorkFlowSpec(workFlowSpecReq) + .asJsonString() + ResponseEntity(json, HttpStatus.OK) + } + + @GetMapping( + path = arrayOf( + "/workflows/blueprint-name/{name}/version/{version" + + "}" + ), + produces = arrayOf(MediaType.APPLICATION_JSON_VALUE) + ) + @ResponseBody + @Throws(BluePrintException::class) + @PreAuthorize("hasRole('USER')") + suspend fun getWorkflowList( + @ApiParam(value = "Name of the CBA.", required = true) + @PathVariable(value = "name") name: String, + @ApiParam(value = "Version of the CBA.", required = true) + @PathVariable(value = "version") version: String + ): ResponseEntity<String> = mdcWebCoroutineScope { + var json = bluePrintModelHandler.getWorkflowNames(name, version) + .asJsonString() + ResponseEntity(json, HttpStatus.OK) + } } diff --git a/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/DesignerApiData.kt b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/DesignerApiData.kt index d0cb67315..369844445 100644 --- a/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/DesignerApiData.kt +++ b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/DesignerApiData.kt @@ -22,6 +22,11 @@ import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonTypeName import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.domain.ResourceDictionary +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.DATA_TYPE_JSON +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.DEFAULT_VERSION_NUMBER +import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.TOSCA_SPEC +import org.onap.ccsdk.cds.controllerblueprints.core.data.DataType +import org.onap.ccsdk.cds.controllerblueprints.core.data.PropertyDefinition import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment import java.io.Serializable import java.util.Date @@ -32,6 +37,33 @@ class BootstrapRequest { var loadCBA: Boolean = false } +class WorkFlowsResponse { + lateinit var blueprintName: String + var version: String = DEFAULT_VERSION_NUMBER + var workflows: MutableSet<String> = mutableSetOf() +} + +class WorkFlowSpecRequest { + lateinit var blueprintName: String + var version: String = DEFAULT_VERSION_NUMBER + var returnContent: String = DATA_TYPE_JSON + lateinit var workflowName: String + var specType: String = TOSCA_SPEC +} + +class WorkFlowSpecResponse { + lateinit var blueprintName: String + var version: String = DEFAULT_VERSION_NUMBER + lateinit var workFlowData: WorkFlowData + var dataTypes: MutableMap<String, DataType>? = mutableMapOf() +} + +class WorkFlowData { + lateinit var workFlowName: String + var inputs: MutableMap<String, PropertyDefinition>? = null + var outputs: MutableMap<String, PropertyDefinition>? = null +} + /** * ArtifactRequest.java Purpose: Provide Configuration Generator ArtifactRequest Model * diff --git a/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/ErrorHandling.kt b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/ErrorHandling.kt new file mode 100644 index 000000000..ae91246fe --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/ErrorHandling.kt @@ -0,0 +1,40 @@ +/* + * Copyright © 2018-2019 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.designer.api + +object DesignerApiDomains { + // Designer Api Domains Constants + const val DESIGNER_API = "org.onap.ccsdk.cds.blueprintsprocessor.designer.api" + const val DESIGNER_API_ENHANCER = "org.onap.ccsdk.cds.blueprintsprocessor.designer.api.enhancer" + const val DESIGNER_API_HANDLER = "org.onap.ccsdk.cds.blueprintsprocessor.designer.api.handler" + const val DESIGNER_API_LOAD = "org.onap.ccsdk.cds.blueprintsprocessor.designer.api.load" + const val DESIGNER_API_SERVICE = "org.onap.ccsdk.cds.blueprintsprocessor.designer.api.service" +} + +object DesignerApiHttpErrorCodes { + init { + // Register HttpErrorCodes + // HttpErrorCodes.register("", 200) + } +} + +object DesignerGrpcErrorCodes { + init { + // Register GrpcErrorCodes + // GrpcErrorCodes.register("", 3) + } +} diff --git a/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/handler/BluePrintModelHandler.kt b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/handler/BluePrintModelHandler.kt index 274650ae4..e9839328b 100644 --- a/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/handler/BluePrintModelHandler.kt +++ b/ms/blueprintsprocessor/modules/inbounds/designer-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/designer/api/handler/BluePrintModelHandler.kt @@ -25,10 +25,15 @@ import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.repository.BlueprintMod import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.repository.BlueprintModelRepository import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.repository.BlueprintModelSearchRepository import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.BootstrapRequest +import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.WorkFlowData +import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.WorkFlowSpecRequest +import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.WorkFlowSpecResponse +import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.WorkFlowsResponse import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.load.BluePrintDatabaseLoadService import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.utils.BluePrintEnhancerUtils import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException import org.onap.ccsdk.cds.controllerblueprints.core.config.BluePrintLoadConfiguration +import org.onap.ccsdk.cds.controllerblueprints.core.data.DataType import org.onap.ccsdk.cds.controllerblueprints.core.data.ErrorCode import org.onap.ccsdk.cds.controllerblueprints.core.deleteNBDir import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintCatalogService @@ -37,7 +42,9 @@ import org.onap.ccsdk.cds.controllerblueprints.core.logger import org.onap.ccsdk.cds.controllerblueprints.core.normalizedFile import org.onap.ccsdk.cds.controllerblueprints.core.normalizedPathName import org.onap.ccsdk.cds.controllerblueprints.core.scripts.BluePrintCompileCache +import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintContext import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintFileUtils +import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintMetadataUtils import org.springframework.core.io.ByteArrayResource import org.springframework.core.io.Resource import org.springframework.data.domain.Page @@ -89,6 +96,68 @@ open class BluePrintModelHandler( } } + @Throws(BluePrintException::class) + open suspend fun prepareWorkFlowSpec(req: WorkFlowSpecRequest): + WorkFlowSpecResponse { + val basePath = blueprintsProcessorCatalogService.getFromDatabase(req + .blueprintName, req.version) + log.info("blueprint base path $basePath") + + val blueprintContext = BluePrintMetadataUtils.getBluePrintContext(basePath.toString()) + val workFlow = blueprintContext.workflowByName(req.workflowName) + + val wfRes = WorkFlowSpecResponse() + wfRes.blueprintName = req.blueprintName + wfRes.version = req.version + + val workFlowData = WorkFlowData() + workFlowData.workFlowName = req.workflowName + workFlowData.inputs = workFlow.inputs + workFlowData.outputs = workFlow.outputs + + for ((k, v) in workFlow.inputs!!) { + addDataType(v.type, blueprintContext, wfRes) + } + + for ((k, v) in workFlow.outputs!!) { + addDataType(v.type, blueprintContext, wfRes) + } + wfRes.workFlowData = workFlowData + return wfRes + } + + private fun addDataType(name: String, ctx: BluePrintContext, res: WorkFlowSpecResponse) { + var data = ctx.dataTypeByName(name) + if (data != null) { + res.dataTypes?.put(name, data) + addParentDataType(data, ctx, res) + } + } + + private fun addParentDataType(data: DataType, ctx: BluePrintContext, res: WorkFlowSpecResponse) { + for ((k, v) in data.properties!!) { + addDataType(v.type, ctx, res) + } + } + + @Throws(BluePrintException::class) + open suspend fun getWorkflowNames(name: String, version: String): WorkFlowsResponse { + val basePath = blueprintsProcessorCatalogService.getFromDatabase( + name, version) + log.info("blueprint base path $basePath") + + var res = WorkFlowsResponse() + res.blueprintName = name + res.version = version + + val blueprintContext = BluePrintMetadataUtils.getBluePrintContext( + basePath.toString()) + if (blueprintContext.workflows() != null) { + res.workflows = blueprintContext.workflows()!!.keys + } + return res + } + /** * This is a getAllBlueprintModel method to retrieve all the BlueprintModel in Database * diff --git a/ms/blueprintsprocessor/modules/inbounds/resource-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/resource/api/ErrorHandling.kt b/ms/blueprintsprocessor/modules/inbounds/resource-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/resource/api/ErrorHandling.kt new file mode 100644 index 000000000..b37cd0eda --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/resource-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/resource/api/ErrorHandling.kt @@ -0,0 +1,36 @@ +/* + * Copyright © 2018-2019 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.resource.api + +object ResourceApiDomains { + // Resource Api Domains Constants + const val RESOURCE_API = "org.onap.ccsdk.cds.blueprintsprocessor.resource.api" +} + +object ResourceApiHttpErrorCodes { + init { + // Register HttpErrorCodes + // HttpErrorCodes.register("", 200) + } +} + +object ResourceGrpcErrorCodes { + init { + // Register GrpcErrorCodes + // GrpcErrorCodes.register("", 3) + } +} diff --git a/ms/blueprintsprocessor/modules/inbounds/resource-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/resource/api/ResourceController.kt b/ms/blueprintsprocessor/modules/inbounds/resource-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/resource/api/ResourceController.kt index b49ca68ed..264cd23ff 100644 --- a/ms/blueprintsprocessor/modules/inbounds/resource-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/resource/api/ResourceController.kt +++ b/ms/blueprintsprocessor/modules/inbounds/resource-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/resource/api/ResourceController.kt @@ -103,6 +103,28 @@ open class ResourceController(private var resourceResolutionDBService: ResourceR } @RequestMapping( + path = [""], + method = [RequestMethod.DELETE], produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @ApiOperation(value = "Delete resources using resolution key", + notes = "Delete all the resources associated to a resolution-key using blueprint metadata, artifact name and the resolution-key.", + produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasRole('USER')") + fun deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey( + @ApiParam(value = "Name of the CBA.", required = true) + @RequestParam(value = "bpName", required = true) bpName: String, + @ApiParam(value = "Version of the CBA.", required = true) + @RequestParam(value = "bpVersion", required = true) bpVersion: String, + @ApiParam(value = "Artifact name for which to retrieve a resolved resource.", required = true) + @RequestParam(value = "artifactName", required = false, defaultValue = "") artifactName: String, + @ApiParam(value = "Resolution Key associated with the resolution.", required = true) + @RequestParam(value = "resolutionKey", required = true) resolutionKey: String + ) = runBlocking { + ResponseEntity.ok() + .body(resourceResolutionDBService.deleteByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKey(bpName, bpVersion, artifactName, resolutionKey)) + } + + @RequestMapping( path = ["/resource"], method = [RequestMethod.GET], produces = [MediaType.APPLICATION_JSON_VALUE] diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ErrorHandling.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ErrorHandling.kt new file mode 100644 index 000000000..b76bc263a --- /dev/null +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ErrorHandling.kt @@ -0,0 +1,44 @@ +/* + * Copyright © 2018-2019 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api + +object SelfServiceApiDomains { + // SelfServiceApi Domains Constants + const val BLUEPRINT_PROCESSOR = "org.onap.ccsdk.cds.blueprintsprocessor" + const val SELF_SERVICE_API = "org.onap.ccsdk.cds.blueprintsprocessor.resource.api" + const val SELF_SERVICE_API_VALIDATOR = "org.onap.ccsdk.cds.blueprintsprocessor.resource.api.validator" + const val NETCONF_EXECUTOR = "org.onap.ccsdk.cds.blueprintsprocessor.functions.netconf.executor" + const val RESOURCE_RESOLUTION = "org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution" + const val RESTCONF_EXECUTOR = "org.onap.ccsdk.cds.blueprintsprocessor.functions.restconf.executor" + const val CLI_EXECUTOR = "org.onap.ccsdk.cds.blueprintsprocessor.functions.cli.executor" + const val PYTHON_EXECUTOR = "org.onap.ccsdk.cds.blueprintsprocessor.functions.python.executor" + const val SDC_LISTENER = "org.onap.ccsdk.cds.sdclistener" +} + +object SelfServiceApiHttpErrorCodes { + init { + // Register HttpErrorCodes + // HttpErrorCodes.register("", 200) + } +} + +object SelfServiceGrpcErrorCodes { + init { + // Register GrpcErrorCodes + // GrpcErrorCodes.register("", 3) + } +} diff --git a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceController.kt b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceController.kt index 8b268d6f8..e5daecede 100644 --- a/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceController.kt +++ b/ms/blueprintsprocessor/modules/inbounds/selfservice-api/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/selfservice/api/ExecutionServiceController.kt @@ -27,7 +27,9 @@ import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutp import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.mdcWebCoroutineScope import org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.utils.determineHttpStatusCode import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive +import org.onap.ccsdk.cds.controllerblueprints.core.httpProcessorException import org.onap.ccsdk.cds.controllerblueprints.core.logger +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalogCodes import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.MediaType import org.springframework.http.ResponseEntity @@ -81,7 +83,9 @@ open class ExecutionServiceController { ): ResponseEntity<ExecutionServiceOutput> = mdcWebCoroutineScope { if (executionServiceInput.actionIdentifiers.mode == ACTION_MODE_ASYNC) { - throw IllegalStateException("Can't process async request through the REST endpoint. Use gRPC for async processing.") + throw httpProcessorException(ErrorCatalogCodes.GENERIC_FAILURE, + SelfServiceApiDomains.BLUEPRINT_PROCESSOR, + "Can't process async request through the REST endpoint. Use gRPC for async processing.") } ph.register() val processResult = executionServiceHandler.doProcess(executionServiceInput) diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentFunctionScriptingService.kt b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentFunctionScriptingService.kt index 4c7d5d0ec..34b18091f 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentFunctionScriptingService.kt +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentFunctionScriptingService.kt @@ -101,4 +101,10 @@ class ComponentFunctionScriptingService( } return scriptComponent } + + suspend fun cleanupInstance(bluePrintContext: BluePrintContext, scriptType: String) { + if (scriptType == BluePrintConstants.SCRIPT_KOTLIN) { + BluePrintScriptsServiceImpl().cleanupInstance(bluePrintContext.rootPath) + } + } } diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentScriptExecutor.kt b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentScriptExecutor.kt index 09eee6717..2a63297be 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentScriptExecutor.kt +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/ComponentScriptExecutor.kt @@ -60,6 +60,8 @@ open class ComponentScriptExecutor(private var componentFunctionScriptingService // Handles both script processing and error handling scriptComponentFunction.executeScript(executionServiceInput) + + componentFunctionScriptingService.cleanupInstance(bluePrintRuntimeService.bluePrintContext(), scriptType) } override suspend fun recoverNB(runtimeException: RuntimeException, executionRequest: ExecutionServiceInput) { diff --git a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintSvcLogicService.kt b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintSvcLogicService.kt index 9cc325d94..4efe9f12d 100644 --- a/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintSvcLogicService.kt +++ b/ms/blueprintsprocessor/modules/services/workflow-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/workflow/BlueprintSvcLogicService.kt @@ -49,6 +49,10 @@ interface BlueprintSvcLogicService : SvcLogicServiceBase { suspend fun execute(graph: SvcLogicGraph, bluePrintRuntimeService: BluePrintRuntimeService<*>, input: Any): Any + override fun execute(module: String, rpc: String, version: String, mode: String, ctx: SvcLogicContext): SvcLogicContext { + TODO("not implemented") + } + @Deprecated("Populate Graph Dynamically from Blueprints, No need to get from Database Store ") override fun getStore(): SvcLogicStore { TODO("not implemented") diff --git a/ms/blueprintsprocessor/parent/pom.xml b/ms/blueprintsprocessor/parent/pom.xml index 4ee3f3656..ce78c55fa 100755 --- a/ms/blueprintsprocessor/parent/pom.xml +++ b/ms/blueprintsprocessor/parent/pom.xml @@ -35,6 +35,7 @@ <sli.version>${ccsdk.sli.core.version}</sli.version> <!-- Override CDS version from parent to be project.version --> <ccsdk.cds.version>${project.version}</ccsdk.cds.version> + <error.catalog.version>${project.version}</error.catalog.version> <dmaap.client.version>1.1.5</dmaap.client.version> <!-- Should be using released artifact as soon as available: --> <!-- https://github.com/springfox/springfox/milestone/44 --> @@ -55,14 +56,12 @@ <jslt.version>0.1.8</jslt.version> <jython.version>2.7.1</jython.version> <jinja.version>2.5.1</jinja.version> - <velocity.version>1.7</velocity.version> <guava.version>27.0.1-jre</guava.version> - <jsoup.version>1.10.3</jsoup.version> <json-patch.version>1.9</json-patch.version> <json-smart.version>2.3</json-smart.version> <commons-io-version>2.6</commons-io-version> - <commons-lang3-version>3.2.1</commons-lang3-version> + <commons-compress-version>1.20</commons-compress-version> <commons-collections-version>3.2.2</commons-collections-version> </properties> @@ -78,12 +77,6 @@ </dependency> <dependency> - <groupId>org.jsoup</groupId> - <artifactId>jsoup</artifactId> - <version>${jsoup.version}</version> - </dependency> - - <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>${spring-kafka.version}</version> @@ -129,11 +122,6 @@ <!-- Common Utils Dependencies --> <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <version>${commons-lang3-version}</version> - </dependency> - <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>${commons-collections-version}</version> @@ -144,15 +132,9 @@ <version>${commons-io-version}</version> </dependency> <dependency> - <groupId>org.apache.velocity</groupId> - <artifactId>velocity</artifactId> - <version>${velocity.version}</version> - <exclusions> - <exclusion> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </exclusion> - </exclusions> + <groupId>org.apache.commons</groupId> + <artifactId>commons-compress</artifactId> + <version>${commons-compress-version}</version> </dependency> <dependency> <groupId>com.hubspot.jinjava</groupId> @@ -251,47 +233,6 @@ <version>${kotlin.version}</version> </dependency> - <!-- GRPC Dependencies --> - <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-core</artifactId> - <version>${grpc.version}</version> - </dependency> - <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-netty</artifactId> - <version>${grpc.version}</version> - </dependency> - <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-protobuf</artifactId> - <version>${grpc.version}</version> - </dependency> - <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-stub</artifactId> - <version>${grpc.version}</version> - </dependency> - <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-netty-shaded</artifactId> - <version>${grpc.version}</version> - </dependency> - <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-grpclb</artifactId> - <version>${grpc.version}</version> - </dependency> - <dependency> - <groupId>com.google.protobuf</groupId> - <artifactId>protobuf-java</artifactId> - <version>${protobuff.java.version}</version> - </dependency> - <dependency> - <groupId>com.google.protobuf</groupId> - <artifactId>protobuf-java-util</artifactId> - <version>${protobuff.java.utils.version}</version> - </dependency> <dependency> <groupId>com.github.marcoferrer.krotoplus</groupId> <artifactId>kroto-plus-coroutines</artifactId> @@ -351,6 +292,30 @@ <version>${kafka.version}</version> </dependency> + <!-- Error Catalog --> + <dependency> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog-core</artifactId> + <version>${error.catalog.version}</version> + <exclusions> + <exclusion> + <groupId>*</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog-services</artifactId> + <version>${error.catalog.version}</version> + <exclusions> + <exclusion> + <groupId>*</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- SLI Version --> <dependency> <groupId>org.onap.ccsdk.sli.core</groupId> @@ -485,7 +450,7 @@ </dependency> <dependency> <groupId>org.onap.ccsdk.cds.blueprintsprocessor.functions</groupId> - <artifactId>nrm-restful</artifactId> + <artifactId>restful-executor</artifactId> <version>${ccsdk.cds.version}</version> </dependency> <dependency> @@ -614,13 +579,6 @@ <scope>test</scope> </dependency> - <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-testing</artifactId> - <version>${grpc.version}</version> - <scope>test</scope> - </dependency> - <!-- Spring Kafka --> <dependency> <groupId>org.springframework.kafka</groupId> @@ -628,7 +586,6 @@ <version>${spring-kafka.version}</version> <scope>test</scope> </dependency> - </dependencies> </dependencyManagement> <dependencies> @@ -757,6 +714,11 @@ <groupId>io.netty</groupId> <artifactId>netty-tcnative-boringssl-static</artifactId> </dependency> + + <dependency> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog-core</artifactId> + </dependency> </dependencies> <repositories> diff --git a/ms/error-catalog/README.md b/ms/error-catalog/README.md new file mode 100755 index 000000000..1df859574 --- /dev/null +++ b/ms/error-catalog/README.md @@ -0,0 +1,40 @@ +## How to use library + +##### 1. Set Error Catalog service type (Database or properties file service) in application.properties file + +``` +##### Error Managements ##### +## For database service type ## +# error.catalog.type=DB +## For database service type ## +# error.catalog.type=properties +error.catalog.applicationId=cds +error.catalog.type=properties +error.catalog.errorDefinitionDir=/opt/app/onap/config +``` + +##### 2. Generate exception + +- HTTP Error Exception +``` +errorCatalogException: ErrorCatalogException = httpProcessorException(ErrorCatalogCodes.ERROR_TYPE, +"Error message here...") +``` + +- GRPC Error Exception +``` +errorCatalogException: ErrorCatalogException = grpcProcessorException(ErrorCatalogCodes.ERROR_TYPE, +"Error message here...") +``` + +##### 3. Update an existing exception +``` +e = errorCatalogException.code(500) +e = errorCatalogException.action("message") +... +``` + +##### 4. Add a HTTP REST Exception handler +@RestControllerAdvice("domain.here") +open class ExceptionHandler(private val errorCatalogService: ErrorCatalogService) : + ErrorCatalogExceptionHandler(errorCatalogService)
\ No newline at end of file diff --git a/ms/error-catalog/application/pom.xml b/ms/error-catalog/application/pom.xml new file mode 100644 index 000000000..4e1a6f3f1 --- /dev/null +++ b/ms/error-catalog/application/pom.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright © 2018-2019 AT&T Intellectual Property. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog</artifactId> + <version>0.7.0-SNAPSHOT</version> + </parent> + + <artifactId>error-catalog-application</artifactId> + + <name>Error Catalog Application</name> + <description>Error Catalog Application</description> +</project> diff --git a/ms/error-catalog/core/pom.xml b/ms/error-catalog/core/pom.xml new file mode 100644 index 000000000..f117aa83f --- /dev/null +++ b/ms/error-catalog/core/pom.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright © 2018-2019 AT&T Intellectual Property. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog</artifactId> + <version>0.7.0-SNAPSHOT</version> + </parent> + + <artifactId>error-catalog-core</artifactId> + + <name>Error Catalog Core</name> + <description>Error Catalog Core</description> +</project> diff --git a/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCatalogException.kt b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCatalogException.kt new file mode 100644 index 000000000..032feb62c --- /dev/null +++ b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCatalogException.kt @@ -0,0 +1,112 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * Modifications Copyright © 2019-2020 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.core + +interface ErrorCatalogExceptionFluent<T> { + fun code(code: Int): T + fun domain(domain: String): T + fun action(action: String): T + fun http(type: String): T + fun grpc(type: String): T + fun payloadMessage(message: String): T + fun addErrorPayloadMessage(message: String): T + fun addSubError(errorMessage: ErrorMessage): T +} + +open class ErrorCatalogException : RuntimeException { + var code: Int = -1 + var domain: String = "" + var name: String = ErrorCatalogCodes.GENERIC_FAILURE + var action: String = "" + var errorPayload: ErrorPayload? = null + var protocol: String = "" + var errorPayloadMessages: MutableList<String>? = null + + val messageSeparator = "${System.lineSeparator()} -> " + + constructor(message: String, cause: Throwable) : super(message, cause) + constructor(message: String) : super(message) + constructor(cause: Throwable) : super(cause) + constructor(cause: Throwable, message: String, vararg args: Any?) : super(format(message, *args), cause) + + constructor(code: Int, cause: Throwable) : super(cause) { + this.code = code + } + + constructor(code: Int, message: String) : super(message) { + this.code = code + } + + constructor(code: Int, message: String, cause: Throwable) : super(message, cause) { + this.code = code + } + + constructor(code: Int, cause: Throwable, message: String, vararg args: Any?) : + super(String.format(message, *args), cause) { + this.code = code + } + + open fun <T : ErrorCatalogException> updateCode(code: Int): T { + this.code = code + return this as T + } + + open fun <T : ErrorCatalogException> updateDomain(domain: String): T { + this.domain = domain + return this as T + } + + open fun <T : ErrorCatalogException> updateAction(action: String): T { + this.action = action + return this as T + } + + fun <T : ErrorCatalogException> updateHttp(type: String): T { + this.protocol = ErrorMessageLibConstants.ERROR_CATALOG_PROTOCOL_HTTP + this.code = HttpErrorCodes.code(type) + return this as T + } + + fun <T : ErrorCatalogException> updateGrpc(type: String): T { + this.protocol = ErrorMessageLibConstants.ERROR_CATALOG_PROTOCOL_GRPC + this.code = GrpcErrorCodes.code(type) + return this as T + } + + fun <T : ErrorCatalogException> updatePayloadMessage(message: String): T { + if (this.errorPayloadMessages == null) this.errorPayloadMessages = arrayListOf() + this.errorPayloadMessages!!.add(message) + return this as T + } + + fun <T : ErrorCatalogException> updateErrorPayloadMessage(message: String): T { + if (errorPayload == null) { + errorPayload = ErrorPayload() + } + errorPayload!!.message = "${errorPayload!!.message} $messageSeparator $message" + return this as T + } + + fun <T : ErrorCatalogException> updateSubError(errorMessage: ErrorMessage): T { + if (errorPayload == null) { + errorPayload = ErrorPayload() + } + errorPayload!!.subErrors.add(errorMessage) + return this as T + } +} diff --git a/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCatalogExtensions.kt b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCatalogExtensions.kt new file mode 100644 index 000000000..65c547245 --- /dev/null +++ b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCatalogExtensions.kt @@ -0,0 +1,32 @@ +/* + * Copyright © 2018-2019 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.core + +import org.slf4j.LoggerFactory +import org.slf4j.helpers.MessageFormatter +import kotlin.reflect.KClass + +fun <T : Any> logger(clazz: T) = LoggerFactory.getLogger(clazz.javaClass)!! + +fun <T : KClass<*>> logger(clazz: T) = LoggerFactory.getLogger(clazz.java)!! + +fun format(message: String, vararg args: Any?): String { + if (args != null && args.isNotEmpty()) { + return MessageFormatter.arrayFormat(message, args).message + } + return message +} diff --git a/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCodes.kt b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCodes.kt new file mode 100644 index 000000000..86483e3e9 --- /dev/null +++ b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorCodes.kt @@ -0,0 +1,88 @@ +/* + * Copyright © 2018-2019 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.core + +object ErrorCatalogCodes { + const val GENERIC_FAILURE = "GENERIC_FAILURE" + const val GENERIC_PROCESS_FAILURE = "GENERIC_PROCESS_FAILURE" + const val INVALID_FILE_EXTENSION = "INVALID_FILE_EXTENSION" + const val RESOURCE_NOT_FOUND = "RESOURCE_NOT_FOUND" + const val RESOURCE_PATH_MISSING = "RESOURCE_PATH_MISSING" + const val RESOURCE_WRITING_FAIL = "RESOURCE_WRITING_FAIL" + const val IO_FILE_INTERRUPT = "IO_FILE_INTERRUPT" + const val INVALID_REQUEST_FORMAT = "INVALID_REQUEST_FORMAT" + const val UNAUTHORIZED_REQUEST = "UNAUTHORIZED_REQUEST" + const val REQUEST_NOT_FOUND = "REQUEST_NOT_FOUND" + const val CONFLICT_ADDING_RESOURCE = "CONFLICT_ADDING_RESOURCE" + const val DUPLICATE_DATA = "DUPLICATE_DATA" +} + +object HttpErrorCodes { + private val store: MutableMap<String, Int> = mutableMapOf() + + init { + store[ErrorCatalogCodes.GENERIC_FAILURE] = 500 + store[ErrorCatalogCodes.GENERIC_PROCESS_FAILURE] = 500 + store[ErrorCatalogCodes.INVALID_FILE_EXTENSION] = 415 + store[ErrorCatalogCodes.RESOURCE_NOT_FOUND] = 404 + store[ErrorCatalogCodes.RESOURCE_PATH_MISSING] = 503 + store[ErrorCatalogCodes.RESOURCE_WRITING_FAIL] = 503 + store[ErrorCatalogCodes.IO_FILE_INTERRUPT] = 503 + store[ErrorCatalogCodes.INVALID_REQUEST_FORMAT] = 400 + store[ErrorCatalogCodes.UNAUTHORIZED_REQUEST] = 401 + store[ErrorCatalogCodes.REQUEST_NOT_FOUND] = 404 + store[ErrorCatalogCodes.CONFLICT_ADDING_RESOURCE] = 409 + store[ErrorCatalogCodes.DUPLICATE_DATA] = 409 + } + + fun register(type: String, code: Int) { + store[type] = code + } + + fun code(type: String): Int { + // FIXME("Return Default Error Code , If missing") + return store[type]!! + } +} + +object GrpcErrorCodes { + private val store: MutableMap<String, Int> = mutableMapOf() + + init { + store[ErrorCatalogCodes.GENERIC_FAILURE] = 2 + store[ErrorCatalogCodes.GENERIC_PROCESS_FAILURE] = 2 + store[ErrorCatalogCodes.INVALID_FILE_EXTENSION] = 3 + store[ErrorCatalogCodes.RESOURCE_NOT_FOUND] = 5 + store[ErrorCatalogCodes.RESOURCE_PATH_MISSING] = 3 + store[ErrorCatalogCodes.RESOURCE_WRITING_FAIL] = 9 + store[ErrorCatalogCodes.IO_FILE_INTERRUPT] = 3 + store[ErrorCatalogCodes.INVALID_REQUEST_FORMAT] = 3 + store[ErrorCatalogCodes.UNAUTHORIZED_REQUEST] = 16 + store[ErrorCatalogCodes.REQUEST_NOT_FOUND] = 8 + store[ErrorCatalogCodes.CONFLICT_ADDING_RESOURCE] = 10 + store[ErrorCatalogCodes.DUPLICATE_DATA] = 11 + } + + fun register(type: String, code: Int) { + store[type] = code + } + + fun code(type: String): Int { + // FIXME("Return Default Error Code , If missing") + return store[type]!! + } +} diff --git a/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorLibData.kt b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorLibData.kt new file mode 100644 index 000000000..2c0772e31 --- /dev/null +++ b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorLibData.kt @@ -0,0 +1,94 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.core + +import com.fasterxml.jackson.annotation.JsonFormat +import org.slf4j.event.Level +import org.onap.ccsdk.cds.error.catalog.core.ErrorMessageLibConstants.ERROR_CATALOG_DEFAULT_ERROR_CODE +import java.time.LocalDateTime + +open class ErrorPayload { + var code: Int = ERROR_CATALOG_DEFAULT_ERROR_CODE + var status: String = "" + @get:JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + var timestamp: LocalDateTime = LocalDateTime.now() + var message: String = "" + var debugMessage: String = "" + var logLevel: String = Level.ERROR.name + val subErrors: ArrayList<ErrorMessage> = ArrayList() + + constructor() + + constructor( + code: Int = ERROR_CATALOG_DEFAULT_ERROR_CODE, + status: String, + message: String, + logLevel: String = Level.ERROR.name, + debugMessage: String = "" + ) { + this.code = code + this.status = status + this.message = message + this.logLevel = logLevel + this.debugMessage = debugMessage + } + + constructor( + code: Int = ERROR_CATALOG_DEFAULT_ERROR_CODE, + status: String, + message: String, + logLevel: String = Level.ERROR.name, + debugMessage: String = "", + errorMessage: ErrorMessage + ) { + this.code = code + this.status = status + this.message = message + this.logLevel = logLevel + this.debugMessage = debugMessage + this.subErrors.add(errorMessage) + } + + fun isEqualTo(errorPayload: ErrorPayload): Boolean { + return (this.code == errorPayload.code && this.status == errorPayload.status && this.message == errorPayload.message && + this.logLevel == errorPayload.logLevel && this.debugMessage == errorPayload.debugMessage && + this.subErrors == errorPayload.subErrors) + } +} + +/** + * + * + * @author Steve Siani + */ +data class ErrorMessage( + val domainId: String, + val message: String, + val cause: String +) + +data class ErrorCatalog( + val errorId: String, + val domainId: String, + val code: Int, + val action: String, + val cause: String +) { + fun getMessage(): String { + return "Cause: $cause ${System.lineSeparator()} Action : $action" + } +} diff --git a/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorMessageLibConstants.kt b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorMessageLibConstants.kt new file mode 100644 index 000000000..6570e3e66 --- /dev/null +++ b/ms/error-catalog/core/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/core/ErrorMessageLibConstants.kt @@ -0,0 +1,31 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.core + +object ErrorMessageLibConstants { + const val ERROR_CATALOG_DOMAIN = "org.onap.ccsdk.cds.error.catalog" + const val ERROR_CATALOG_TYPE = "error.catalog.type" + const val ERROR_CATALOG_TYPE_PROPERTIES = "properties" + const val ERROR_CATALOG_TYPE_DB = "DB" + const val ERROR_CATALOG_PROPERTIES_FILENAME = "error-messages_en.properties" + const val ERROR_CATALOG_PROPERTIES_DIRECTORY = "/opt/app/onap/config/" + const val ERROR_CATALOG_MODELS = "org.onap.ccsdk.cds.error.catalog.domain" + const val ERROR_CATALOG_REPOSITORY = "org.onap.ccsdk.cds.error.catalog.repository" + const val ERROR_CATALOG_DEFAULT_ERROR_CODE = 500 + const val ERROR_CATALOG_PROTOCOL_HTTP = "http" + const val ERROR_CATALOG_PROTOCOL_GRPC = "grpc" +} diff --git a/ms/error-catalog/pom.xml b/ms/error-catalog/pom.xml new file mode 100644 index 000000000..1e6707a75 --- /dev/null +++ b/ms/error-catalog/pom.xml @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright © 2018-2019 AT&T Intellectual Property. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onap.ccsdk.cds</groupId> + <artifactId>ms</artifactId> + <version>0.7.0-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog</artifactId> + <packaging>pom</packaging> + + <name>Error Catalog Lib</name> + <description>Error Catalog Lib for ONAP Components</description> + + <modules> + <module>application</module> + <module>core</module> + <module>services</module> + </modules> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog-services</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + </dependencyManagement> + <dependencies> + <!-- Kotlin Dependencies --> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib-common</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-script-util</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib-jdk8</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlinx</groupId> + <artifactId>kotlinx-coroutines-core</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlinx</groupId> + <artifactId>kotlinx-coroutines-reactor</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.module</groupId> + <artifactId>jackson-module-kotlin</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-compiler-embeddable</artifactId> + </dependency> + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-scripting-jvm-host</artifactId> + <!--Use kotlin-compiler-embeddable as koltin-compiler wrap--> + <!--guava dependency creating classpath issues at runtime--> + <exclusions> + <exclusion> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-compiler</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-webflux</artifactId> + </dependency> + <dependency> + <groupId>javax.persistence</groupId> + <artifactId>javax.persistence-api</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.projectreactor</groupId> + <artifactId>reactor-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-maven-plugin</artifactId> + <version>${kotlin.maven.version}</version> + <executions> + <execution> + <id>compile</id> + <goals> + <goal>compile</goal> + </goals> + <configuration> + <sourceDirs> + <sourceDir>src/main/kotlin</sourceDir> + </sourceDirs> + </configuration> + </execution> + <execution> + <id>test-compile</id> + <goals> + <goal>test-compile</goal> + </goals> + <configuration> + <sourceDirs> + <sourceDir>${project.basedir}/src/test/kotlin</sourceDir> + </sourceDirs> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/ms/error-catalog/services/pom.xml b/ms/error-catalog/services/pom.xml new file mode 100644 index 000000000..6fcf158f5 --- /dev/null +++ b/ms/error-catalog/services/pom.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright © 2018-2019 AT&T Intellectual Property. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog</artifactId> + <version>0.7.0-SNAPSHOT</version> + </parent> + + <artifactId>error-catalog-services</artifactId> + + <name>Error Catalog Service</name> + <description>Error Catalog Service</description> + + <dependencies> + <dependency> + <groupId>org.onap.ccsdk.cds.error.catalog</groupId> + <artifactId>error-catalog-core</artifactId> + </dependency> + </dependencies> +</project> diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogConfiguration.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogConfiguration.kt new file mode 100644 index 000000000..f0a75e087 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogConfiguration.kt @@ -0,0 +1,34 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * Modifications Copyright © 2019-2020 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.error.catalog.services + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Configuration +import org.springframework.stereotype.Component + +@Configuration +@EnableConfigurationProperties(ErrorCatalogProperties::class) +open class ErrorCatalogConfiguration + +@Component +@ConfigurationProperties(prefix = "error.catalog") +open class ErrorCatalogProperties { + lateinit var type: String + lateinit var applicationId: String + var errorDefinitionDir: String? = null +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogDBService.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogDBService.kt new file mode 100644 index 000000000..95b44e683 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogDBService.kt @@ -0,0 +1,98 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * Modifications Copyright © 2019-2020 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services + +import org.onap.ccsdk.cds.error.catalog.core.ErrorMessageLibConstants +import org.onap.ccsdk.cds.error.catalog.services.domain.Domain +import org.onap.ccsdk.cds.error.catalog.services.domain.ErrorMessageModel +import org.onap.ccsdk.cds.error.catalog.services.repository.DomainRepository +import org.onap.ccsdk.cds.error.catalog.services.repository.ErrorMessageModelRepository +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable +import org.springframework.stereotype.Service + +@Service +@ConditionalOnProperty( + name = [ErrorMessageLibConstants.ERROR_CATALOG_TYPE], + havingValue = ErrorMessageLibConstants.ERROR_CATALOG_TYPE_DB +) +open class ErrorCatalogDBService( + private val domainRepository: DomainRepository, + private val errorMessageModelRepository: ErrorMessageModelRepository +) { + + /** + * This is a getAllDomains method to retrieve all the Domain in Error Catalog Database by pages + * + * @return Page<Domain> list of the domains by page + </Domain> */ + open fun getAllDomains(pageRequest: Pageable): Page<Domain> { + return domainRepository.findAll(pageRequest) + } + + /** + * This is a getAllDomains method to retrieve all the Domain in Error Catalog Database + * + * @return List<Domain> list of the domains + </Domain> */ + open fun getAllDomains(): List<Domain> { + return domainRepository.findAll() + } + + /** + * This is a getAllDomainsByApplication method to retrieve all the Domain that belong to an application in Error Catalog Database + * + * @return List<Domain> list of the domains + </Domain> */ + open fun getAllDomainsByApplication(applicationId: String): List<Domain> { + return domainRepository.findAllByApplicationId(applicationId) + } + + /** + * This is a getAllErrorMessagesByApplication method to retrieve all the Messages that belong to an application in Error Catalog Database + * + * @return MutableMap<String, ErrorCode> list of the abstractErrorModel + </Domain> */ + open fun getAllErrorMessagesByApplication(applicationId: String): MutableMap<String, ErrorMessageModel> { + val domains = domainRepository.findAllByApplicationId(applicationId) + val errorMessages = mutableMapOf<String, ErrorMessageModel>() + for (domain in domains) { + val errorMessagesFound = errorMessageModelRepository.findByDomainsId(domain.id) + for (errorMessageFound in errorMessagesFound) { + errorMessages["${domain.name}$MESSAGE_KEY_SEPARATOR${errorMessageFound.messageID}"] = errorMessageFound + } + } + return errorMessages + } + + open fun saveDomain( + domain: String, + applicationId: String, + description: String = "", + errorMessageList: List<ErrorMessageModel> + ) { + val domainModel = Domain(domain, applicationId, description) + domainModel.errorMessages.addAll(errorMessageList) + domainRepository.saveAndFlush(domainModel) + } + + companion object { + private const val MESSAGE_KEY_SEPARATOR = "." + } +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogExceptionHandler.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogExceptionHandler.kt new file mode 100644 index 000000000..88e2f4522 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogExceptionHandler.kt @@ -0,0 +1,31 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services + +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalogException +import org.onap.ccsdk.cds.error.catalog.core.ErrorPayload +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.ExceptionHandler + +abstract class ErrorCatalogExceptionHandler(private val errorCatalogService: ErrorCatalogService) { + + @ExceptionHandler(ErrorCatalogException::class) + fun errorCatalogException(e: ErrorCatalogException): ResponseEntity<ErrorPayload> { + val errorPayload = errorCatalogService.errorPayload(e) + return errorPayload.toResponseEntity() + } +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogLoadService.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogLoadService.kt new file mode 100644 index 000000000..ca7d72b50 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogLoadService.kt @@ -0,0 +1,147 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * Modifications Copyright © 2019-2020 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services + +import org.onap.ccsdk.cds.error.catalog.core.ErrorMessageLibConstants +import org.onap.ccsdk.cds.error.catalog.core.logger +import org.onap.ccsdk.cds.error.catalog.services.domain.ErrorMessageModel +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Service +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream +import java.nio.file.Paths +import java.util.Properties + +interface ErrorCatalogLoadService { + + suspend fun loadErrorCatalog() + + fun getErrorMessage(domain: String, key: String): String? + + fun getErrorMessage(attribute: String): String? +} + +/** + * Representation of Blueprint Error Message lib from database service to load the properties + */ +@Service +@ConditionalOnBean(ErrorCatalogDBService::class) +open class ErrorCatalogLoadDBService( + private var errorCatalogProperties: ErrorCatalogProperties, + private var errorCatalogDBService: ErrorCatalogDBService +) : ErrorCatalogLoadService { + + private var log = logger(ErrorCatalogLoadDBService::class) + + lateinit var errorMessageInDB: Map<String, ErrorMessageModel> + + override suspend fun loadErrorCatalog() { + log.info("Application ID: ${errorCatalogProperties.applicationId} > Initializing error catalog message from database...") + errorMessageInDB = getErrorMessagesFromDB() + } + + override fun getErrorMessage(domain: String, key: String): String? { + return getErrorMessage("$domain.${key.toLowerCase()}") + } + + override fun getErrorMessage(attribute: String): String? { + val errorMessage = findErrorMessage(attribute) + return prepareErrorMessage(errorMessage) + } + + /** + * Parses the error-messages.properties file which contains error messages + */ + private suspend fun getErrorMessagesFromDB(): Map<String, ErrorMessageModel> { + return errorCatalogDBService.getAllErrorMessagesByApplication(errorCatalogProperties.applicationId) + } + + private fun findErrorMessage(attribute: String): ErrorMessageModel? { + return if (errorMessageInDB.containsKey(attribute)) { + errorMessageInDB[attribute] + } else { + null + } + } + + private fun prepareErrorMessage(errorMessage: ErrorMessageModel?): String? { + return if (errorMessage != null) { + "cause=${errorMessage.cause}, action=${errorMessage.action}" + } else { + null + } + } +} + +/** + * Representation of Blueprint Error Message lib property service to load the properties + */ +@Service +@ConditionalOnProperty( + name = [ErrorMessageLibConstants.ERROR_CATALOG_TYPE], + havingValue = ErrorMessageLibConstants.ERROR_CATALOG_TYPE_PROPERTIES +) +open class ErrorCatalogLoadPropertyService(private var errorCatalogProperties: ErrorCatalogProperties) : + ErrorCatalogLoadService { + + private val propertyFileName = ErrorMessageLibConstants.ERROR_CATALOG_PROPERTIES_FILENAME + private lateinit var propertyFile: File + + private var log = logger(ErrorCatalogLoadPropertyService::class) + + lateinit var properties: Properties + + override suspend fun loadErrorCatalog() { + log.info("Application ID: ${errorCatalogProperties.applicationId} > Initializing error catalog message from properties...") + val propertyDir = errorCatalogProperties.errorDefinitionDir ?: ErrorMessageLibConstants.ERROR_CATALOG_PROPERTIES_DIRECTORY + propertyFile = Paths.get(propertyDir, propertyFileName).toFile().normalize() + properties = parseErrorMessagesProps() + } + + override fun getErrorMessage(domain: String, key: String): String? { + return getErrorMessage("$domain.${key.toLowerCase()}") + } + + override fun getErrorMessage(attribute: String): String? { + return properties.getProperty(attribute) + } + + /** + * Parses the error-messages.properties file which contains error messages + */ + private fun parseErrorMessagesProps(): Properties { + var inputStream: InputStream? = null + val props = Properties() + try { + inputStream = propertyFile.inputStream() + props.load(inputStream) + } catch (e: FileNotFoundException) { + log.error("Application ID: ${errorCatalogProperties.applicationId} > Property File '$propertyFileName' " + + "not found in the application directory.") + } catch (e: IOException) { + log.error("Application ID: ${errorCatalogProperties.applicationId} > Fail to load property file " + + "'$propertyFileName' for message errors.") + } finally { + inputStream!!.close() + } + return props + } +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogService.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogService.kt new file mode 100644 index 000000000..91f817133 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogService.kt @@ -0,0 +1,91 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * Modifications Copyright © 2019-2020 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services + +import kotlinx.coroutines.runBlocking +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalog +import org.onap.ccsdk.cds.error.catalog.core.ErrorCatalogException +import org.onap.ccsdk.cds.error.catalog.core.ErrorMessageLibConstants +import org.onap.ccsdk.cds.error.catalog.core.ErrorPayload +import org.onap.ccsdk.cds.error.catalog.core.GrpcErrorCodes +import org.onap.ccsdk.cds.error.catalog.core.HttpErrorCodes +import org.onap.ccsdk.cds.error.catalog.services.utils.ErrorCatalogUtils +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.stereotype.Service +import javax.annotation.PostConstruct + +@Service +@ConditionalOnBean(ErrorCatalogLoadService::class) +open class ErrorCatalogService(private var errorCatalogLoadService: ErrorCatalogLoadService) { + + @PostConstruct + open fun init() = runBlocking { + errorCatalogLoadService.loadErrorCatalog() + } + + fun errorPayload(errorCatalogException: ErrorCatalogException): ErrorPayload { + val errorCatalog = getErrorCatalog(errorCatalogException) + val errorPayload = ErrorPayload(errorCatalog.code, errorCatalog.errorId, errorCatalog.getMessage()) + errorPayload.subErrors.addAll(errorCatalogException.errorPayload!!.subErrors) + if (errorCatalogException.cause != null) { + errorPayload.debugMessage = errorCatalogException.cause!!.printStackTrace().toString() + } + return errorPayload + } + + fun getErrorCatalog(errorCatalogException: ErrorCatalogException): ErrorCatalog { + val errorMessage = getMessage(errorCatalogException.domain, errorCatalogException.name) + val errorCode = + if (errorCatalogException.code == -1) { + getProtocolErrorCode( + errorCatalogException.protocol, + errorCatalogException.name + ) + } else { + errorCatalogException.code + } + val action: String + val errorCause: String + if (errorMessage.isNullOrEmpty()) { + action = errorCatalogException.action + errorCause = errorCatalogException.message ?: "" + } else { + action = ErrorCatalogUtils.readErrorActionFromMessage(errorMessage) + errorCause = errorCatalogException.message ?: ErrorCatalogUtils.readErrorCauseFromMessage(errorMessage) + } + + return ErrorCatalog( + errorCatalogException.name, + errorCatalogException.domain, + errorCode, + action, + errorCause + ) + } + + private fun getProtocolErrorCode(protocol: String, type: String): Int { + return when (protocol) { + ErrorMessageLibConstants.ERROR_CATALOG_PROTOCOL_GRPC -> GrpcErrorCodes.code(type) + else -> HttpErrorCodes.code(type) + } + } + + private fun getMessage(domain: String, key: String): String? { + return errorCatalogLoadService.getErrorMessage(domain, key) + } +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogServiceExtensions.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogServiceExtensions.kt new file mode 100644 index 000000000..bb4a8f15e --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/ErrorCatalogServiceExtensions.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2018-2019 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services + +import org.onap.ccsdk.cds.error.catalog.core.ErrorPayload +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity + +fun ErrorPayload.toResponseEntity(): ResponseEntity<ErrorPayload> { + return ResponseEntity(this, HttpStatus.resolve(this.code)!!) +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/domain/Domain.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/domain/Domain.kt new file mode 100755 index 000000000..7ade1c2a2 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/domain/Domain.kt @@ -0,0 +1,76 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services.domain + +import java.io.Serializable +import javax.persistence.CascadeType +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.Id +import javax.persistence.Lob +import javax.persistence.ManyToMany +import javax.persistence.Table +import javax.persistence.UniqueConstraint +import java.util.UUID +import javax.persistence.JoinTable +import javax.persistence.JoinColumn + +/** + * Provide ErrorCode Entity + * + * @author Steve Siani + * @version 1.0 + */ + +@Entity +@Table(name = "ERROR_DOMAINS", uniqueConstraints = [UniqueConstraint(columnNames = ["name", "application_id"])]) +class Domain : Serializable { + @Id + var id: String = UUID.randomUUID().toString() + + @Column(name = "name") + lateinit var name: String + + @Column(name = "application_id") + lateinit var applicationId: String + + @Lob + @Column(name = "description") + var description: String = "" + + @ManyToMany(fetch = FetchType.EAGER, cascade = [CascadeType.ALL]) + @JoinTable( + name = "ERROR_DOMAINS_ERROR_MESSAGES", + joinColumns = [JoinColumn(name = "domain_id", referencedColumnName = "id")], + inverseJoinColumns = [JoinColumn(name = "message_id", referencedColumnName = "id")] + ) + @Column(name = "error_msg") + val errorMessages: MutableSet<ErrorMessageModel> = mutableSetOf() + + constructor() + + constructor(name: String, applicationId: String, description: String) { + this.name = name + this.description = description + this.applicationId = applicationId + } + + companion object { + private const val serialVersionUID = 1L + } +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/domain/ErrorMessageModel.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/domain/ErrorMessageModel.kt new file mode 100644 index 000000000..a4e92af48 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/domain/ErrorMessageModel.kt @@ -0,0 +1,69 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services.domain + +import java.io.Serializable +import java.util.UUID +import javax.persistence.CascadeType +import javax.persistence.Column +import javax.persistence.Entity +import javax.persistence.FetchType +import javax.persistence.Id +import javax.persistence.Lob +import javax.persistence.ManyToMany +import javax.persistence.Table +import javax.persistence.UniqueConstraint + +/** + * Provide Error Message Model Entity + * + * @author Steve Siani + * @version 1.0 + */ +@Entity +@Table(name = "ERROR_MESSAGES", uniqueConstraints = [UniqueConstraint(columnNames = ["message_id"])]) +class ErrorMessageModel : Serializable { + + @Id + var id: String = UUID.randomUUID().toString() + + @Column(name = "message_id", nullable = false) + lateinit var messageID: String + + @Lob + @Column(name = "cause") + var cause: String = "" + + @Lob + @Column(name = "action") + lateinit var action: String + + @ManyToMany(mappedBy = "errorMessages", fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST]) + val domains: MutableSet<Domain> = mutableSetOf() + + companion object { + private const val serialVersionUID = 1L + } + + constructor() + + constructor(messageId: String, cause: String, action: String) { + this.messageID = messageId + this.cause = cause + this.action = action + } +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/repository/DomainRepository.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/repository/DomainRepository.kt new file mode 100644 index 000000000..197e385e0 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/repository/DomainRepository.kt @@ -0,0 +1,45 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services.repository + +import org.onap.ccsdk.cds.error.catalog.services.domain.Domain +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +/** + * @param <T> Model + */ +@Repository +interface DomainRepository : JpaRepository<Domain, String> { + + /** + * This is a findByNameAndApplicationId method + * + * @param name name + * @param applicationId applicationId + * @return Optional<T> + */ + fun findByNameAndApplicationId(name: String, applicationId: String): Domain? + + /** + * This is a findAllByApplicationId method + * + * @param applicationId applicationId + * @return List<T> + */ + fun findAllByApplicationId(applicationId: String): List<Domain> +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/repository/ErrorMessageModelRepository.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/repository/ErrorMessageModelRepository.kt new file mode 100644 index 000000000..4a0f78672 --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/repository/ErrorMessageModelRepository.kt @@ -0,0 +1,36 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.error.catalog.services.repository + +import org.onap.ccsdk.cds.error.catalog.services.domain.ErrorMessageModel +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +/** + * @param <T> Model + */ +@Repository +interface ErrorMessageModelRepository : JpaRepository<ErrorMessageModel, String> { + + /** + * This is a findByDomains method + * + * @param domainId domainId + * @return List<T> + */ + fun findByDomainsId(domainId: String): List<ErrorMessageModel> +} diff --git a/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/utils/ErrorCatalogUtils.kt b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/utils/ErrorCatalogUtils.kt new file mode 100644 index 000000000..967d3560c --- /dev/null +++ b/ms/error-catalog/services/src/main/kotlin/org/onap/ccsdk/cds/error/catalog/services/utils/ErrorCatalogUtils.kt @@ -0,0 +1,39 @@ +/* + * Copyright © 2020 IBM, Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.error.catalog.services.utils + +object ErrorCatalogUtils { + private const val REGEX_PATTERN = "^cause=(.*),action=(.*)" + private val regex = REGEX_PATTERN.toRegex() + + fun readErrorCauseFromMessage(message: String): String { + val matchResults = regex.matchEntire(message) + return matchResults!!.groupValues[1] + } + + fun readErrorActionFromMessage(message: String): String { + val matchResults = regex.matchEntire(message) + return matchResults!!.groupValues[2] + } +} + +fun Exception.errorCauseOrDefault(): Throwable { + return this.cause ?: Throwable() +} + +fun Exception.errorMessageOrDefault(): String { + return this.message ?: "" +} diff --git a/ms/pom.xml b/ms/pom.xml index 7ec3f39a2..7a0168f5b 100644 --- a/ms/pom.xml +++ b/ms/pom.xml @@ -32,9 +32,29 @@ <description>Micro-services</description> <modules> + <module>error-catalog</module> <module>blueprintsprocessor</module> <module>py-executor</module> <module>command-executor</module> <module>sdclistener</module> </modules> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>pl.project13.maven</groupId> + <artifactId>git-commit-id-plugin</artifactId> + <version>4.0.0</version> + <configuration> + <includeOnlyProperties> + <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty> + <includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty> + </includeOnlyProperties> + <commitIdGenerationMode>full</commitIdGenerationMode> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> </project> diff --git a/ms/py-executor/pom.xml b/ms/py-executor/pom.xml index 9ede1b21b..5c8ac17cf 100644 --- a/ms/py-executor/pom.xml +++ b/ms/py-executor/pom.xml @@ -36,6 +36,7 @@ <ccsdk.project.version>${project.version}</ccsdk.project.version> <ccsdk.build.timestamp>${maven.build.timestamp}</ccsdk.build.timestamp> <maven.build.timestamp.format>yyyyMMdd'T'HHmmss'Z'</maven.build.timestamp.format> + <sonar.skip>true</sonar.skip> </properties> <build> diff --git a/ms/py-executor/resource_resolution/README b/ms/py-executor/resource_resolution/README index 222dae499..353600445 100644 --- a/ms/py-executor/resource_resolution/README +++ b/ms/py-executor/resource_resolution/README @@ -77,4 +77,67 @@ if __name__ == "__main__": for response in client.process(generate_messages()): print(response) +``` + +### Authorizarion header + +``` +from proto.BluePrintCommon_pb2 import ActionIdentifiers, CommonHeader +from proto.BluePrintProcessing_pb2 import ExecutionServiceInput +from resource_resolution.client import Client as ResourceResolutionClient + + +def generate_messages(): + commonHeader = CommonHeader() + commonHeader.requestId = "1234" + commonHeader.subRequestId = "1234-1" + commonHeader.originatorId = "CDS" + + actionIdentifiers = ActionIdentifiers() + actionIdentifiers.blueprintName = "sample-cba" + actionIdentifiers.blueprintVersion = "1.0.0" + actionIdentifiers.actionName = "SampleScript" + + input = ExecutionServiceInput(commonHeader=commonHeader, actionIdentifiers=actionIdentifiers) + + commonHeader2 = CommonHeader() + commonHeader2.requestId = "1235" + commonHeader2.subRequestId = "1234-2" + commonHeader2.originatorId = "CDS" + + input2 = ExecutionServiceInput(commonHeader=commonHeader2, actionIdentifiers=actionIdentifiers) + + yield from [input, input2] + + +if __name__ == "__main__": + with ResourceResolutionClient("127.0.0.1:9111", use_header_auth=True, header_auth_token="Token test") as client: + for response in client.process(generate_messages()): + print(response) + +``` + +# ResourceResoulution helper class + +## How to use examples + +### Insecure channel + +``` +from resource_resolution.resource_resolution import ResourceResolution, WorkflowExecution, WorkflowExecutionResult + + +if __name__ == "__main__": + with ResourceResolution(use_header_auth=True, header_auth_token="Basic token") as rr: + for response in rr.execute_workflows( # type: WorkflowExecutionResult + WorkflowExecution( + blueprint_name="blueprintName", + blueprint_version="1.0", + workflow_name="resource-assignment" + ) + ): + if response.has_error: + print(response.error_message) + else: + print(response.payload) ```
\ No newline at end of file diff --git a/ms/py-executor/resource_resolution/authorization.py b/ms/py-executor/resource_resolution/authorization.py new file mode 100644 index 000000000..ae5954ecc --- /dev/null +++ b/ms/py-executor/resource_resolution/authorization.py @@ -0,0 +1,64 @@ +"""Copyright 2020 Deutsche Telekom. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from collections import namedtuple +from typing import Any, Callable, List + +from grpc import ClientCallDetails, StreamStreamClientInterceptor + + +class NewClientCallDetails( + namedtuple("_ClientCallDetails", ("method", "timeout", "metadata", "credentials")), ClientCallDetails +): + """Namedtuple class to store metadata. + + It's impossible to change original metadata in ClientCallDetails object + passed as a parameter to intercept method, so this class is going to get + original metadata tuple and add the authorization one. + """ + + pass + + +class AuthTokenInterceptor(StreamStreamClientInterceptor): + """Interceptor class to set authorization header. + + Set authorization header (but it can be any header also) for a gRPC call. + """ + + def __init__(self, token: str, header: str = "authorization") -> None: + """Initialize interceptor. + + Set token and header which should be set into call. By default header is "authorization". + Header have to be lowercase. + + Args: + token (str): Token value to be set. + header (str, optional): Header name. It must be lowercase. Defaults to "authorization". + """ + self.token: str = token + if not header.islower(): + raise ValueError("Header must be lowercase.") + self.header: str = header + + def intercept_stream_stream( + self, continuation: Callable, client_call_details: ClientCallDetails, request_iterator: Any + ) -> Any: + """Add header into metadata.""" + metadata: List = list(client_call_details.metadata) if client_call_details.metadata is not None else [] + metadata.append((self.header, self.token,)) + new_client_call_details: NewClientCallDetails = NewClientCallDetails( + client_call_details.method, client_call_details.timeout, metadata, client_call_details.credentials + ) + return continuation(new_client_call_details, request_iterator) diff --git a/ms/py-executor/resource_resolution/client.py b/ms/py-executor/resource_resolution/client.py index 89087745c..fee168628 100644 --- a/ms/py-executor/resource_resolution/client.py +++ b/ms/py-executor/resource_resolution/client.py @@ -14,12 +14,20 @@ limitations under the License. """ from logging import Logger, getLogger from types import TracebackType -from typing import Iterable, List, Optional, Type - -from grpc import Channel, insecure_channel, secure_channel, ssl_channel_credentials +from typing import Iterable, Optional, Type + +from grpc import ( + Channel, + insecure_channel, + intercept_channel, + secure_channel, + ssl_channel_credentials, +) from proto.BluePrintProcessing_pb2 import ExecutionServiceInput, ExecutionServiceOutput from proto.BluePrintProcessing_pb2_grpc import BluePrintProcessingServiceStub +from .authorization import AuthTokenInterceptor + class Client: """Resource resoulution client class.""" @@ -28,20 +36,29 @@ class Client: self, server_address: str, *, + # TLS/SSL configuration use_ssl: bool = False, root_certificates: bytes = None, private_key: bytes = None, certificate_chain: bytes = None, + # Authentication header configuration + use_header_auth: bool = False, + header_auth_token: str = None, ) -> None: """Client class initialization. :param server_address: Address to server to connect. :param use_ssl: Boolean flag to determine if secure channel should be created or not. Keyword argument. :param root_certificates: The PEM-encoded root certificates. None if it shouldn't be used. Keyword argument. - :param private_key: The PEM-encoded private key as a byte string, or None if no private key should be used. Keyword argument. - :param certificate_chain: The PEM-encoded certificate chain as a byte string to use or or None if no certificate chain should be used. Keyword argument. + :param private_key: The PEM-encoded private key as a byte string, or None if no private key should be used. + Keyword argument. + :param certificate_chain: The PEM-encoded certificate chain as a byte string to use or or None if + no certificate chain should be used. Keyword argument. + :param use_header_auth: Boolean flag to determine if authorization headed shoud be added for every call or not. + Keyword argument. + :param header_auth_token: Authorization token value. Keyword argument. """ - self.logger = getLogger(__name__) + self.logger: Logger = getLogger(__name__) if use_ssl: self.channel: Channel = secure_channel( server_address, ssl_channel_credentials(root_certificates, private_key, certificate_chain) @@ -50,6 +67,8 @@ class Client: else: self.channel: Channel = insecure_channel(server_address) self.logger.debug(f"Create insecure channel to connect to {server_address}") + if use_header_auth: + self.channel: Channel = intercept_channel(self.channel, AuthTokenInterceptor(header_auth_token)) self.stub: BluePrintProcessingServiceStub = BluePrintProcessingServiceStub(self.channel) def close(self) -> None: diff --git a/ms/py-executor/resource_resolution/resource_resolution.py b/ms/py-executor/resource_resolution/resource_resolution.py new file mode 100644 index 000000000..e4f162f8f --- /dev/null +++ b/ms/py-executor/resource_resolution/resource_resolution.py @@ -0,0 +1,294 @@ +"""Copyright 2020 Deutsche Telekom. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from enum import Enum, unique +from logging import Logger, getLogger +from types import TracebackType +from typing import Any, Dict, Generator, Optional, Type + +from google.protobuf import json_format + +from proto.BluePrintProcessing_pb2 import ExecutionServiceInput, ExecutionServiceOutput + +from .client import Client + + +@unique +class WorkflowMode(Enum): + """Workflow mode enumerator. + + Workflow can be executed in two modes: synchronously and asynchronously. + This enumerator stores valid values to set the mode: SYNC for synchronously mode and ASYNC for asynchronously. + """ + + SYNC = "sync" + ASYNC = "async" + + +class WorkflowExecution: + """Wokflow execution class. + + Describes workflow to call. Set blueprint name and version and workflow name to execute. + Workflow inputs are optional, by default set to empty directory. + Workflow mode is also optional. It is set by default to call workflow synchronously. + """ + + def __init__( + self, + blueprint_name: str, + blueprint_version: str, + workflow_name: str, + workflow_inputs: Dict[str, Any] = None, + workflow_mode: WorkflowMode = WorkflowMode.SYNC, + ) -> None: + """Initialize workflow execution. + + Get all needed information to execute workflow. + + Args: + blueprint_name (str): Blueprint name to execute workflow from. + blueprint_version (str): Blueprint version. + workflow_name (str): Name of the workflow to execute + workflow_inputs (Dict[str, Any], optional): Key-value workflow inputs. Defaults to None. + workflow_mode (WorkflowMode, optional): Workflow execution mode. It can be run synchronously or + asynchronously. Defaults to WorkflowMode.SYNC. + """ + self.blueprint_name: str = blueprint_name + self.blueprint_version: str = blueprint_version + self.workflow_name: str = workflow_name + if workflow_inputs is None: + workflow_inputs = {} + self.workflow_inputs: Dict[str, Any] = workflow_inputs + self.workflow_mode: WorkflowMode = workflow_mode + + @property + def message(self) -> ExecutionServiceInput: + """Workflow execution protobuf message. + + This message is going to be sent to gRPC server to execute workflow. + + Returns: + ExecutionServiceInput: Properly filled protobuf message. + """ + execution_msg: ExecutionServiceInput = ExecutionServiceInput() + execution_msg.actionIdentifiers.mode = self.workflow_mode.value + execution_msg.actionIdentifiers.blueprintName = self.blueprint_name + execution_msg.actionIdentifiers.blueprintVersion = self.blueprint_version + execution_msg.actionIdentifiers.actionName = self.workflow_name + execution_msg.payload.update({f"{self.workflow_name}-request": self.workflow_inputs}) + return execution_msg + + +class WorkflowExecutionResult: + """Result of workflow execution. + + Store both workflow data and the result returns by server. + """ + + def __init__(self, workflow_execution: WorkflowExecution, execution_output: ExecutionServiceOutput) -> None: + """Initialize workflow execution result object. + + Stores workflow execution data and execution result. + + Args: + workflow_execution (WorkflowExecution): WorkflowExecution object which was used to call request. + execution_output (ExecutionServiceOutput): gRPC server response. + """ + self.workflow_execution: WorkflowExecution = workflow_execution + self.execution_output: ExecutionServiceOutput = execution_output + + @property + def blueprint_name(self) -> str: + """Name of blueprint used to call workflow. + + This value is taken from server response not request (should be the same). + + Returns: + str: Blueprint name + """ + return self.execution_output.actionIdentifiers.blueprintName + + @property + def blueprint_version(self) -> str: + """Blueprint version. + + This value is taken from server response not request (should be the same). + + Returns: + str: Blueprint version + """ + return self.execution_output.actionIdentifiers.blueprintVersion + + @property + def workflow_name(self) -> str: + """Workflow name. + + This value is taken from server response not request (should be the same). + + Returns: + str: Workflow name + """ + return self.execution_output.actionIdentifiers.actionName + + @property + def has_error(self) -> bool: + """Returns bool if request returns error or not. + + Returns: + bool: True if response has status code different than 200 + """ + return self.execution_output.status.code != 200 + + @property + def error_message(self) -> str: + """Error message. + + This property is available only if response has error. Otherwise AttributeError will be raised. + + Raises: + AttributeError: Response has 200 response code and hasn't error message. + + Returns: + str: Error message returned by server + """ + if self.has_error: + return self.execution_output.status.errorMessage + raise AttributeError("Execution does not finish with error") + + @property + def payload(self) -> dict: + """Response payload. + + Payload retured by the server is migrated to Python dict. + + Returns: + dict: Response's payload. + """ + return json_format.MessageToDict(self.execution_output.payload) + + +class ResourceResolution: + """Resource resolution class. + + Helper class to connect to blueprintprocessor's gRPC server, send request to execute workflow and parse responses. + Blueprint with workflow must be deployed before workflow request. + It's possible to create both secre or unsecure connection (without SSL/TLS). + """ + + def __init__( + self, + *, + server_address: str = "127.0.0.1", + server_port: int = "9111", + use_ssl: bool = False, + root_certificates: bytes = None, + private_key: bytes = None, + certificate_chain: bytes = None, + # Authentication header configuration + use_header_auth: bool = False, + header_auth_token: str = None, + ) -> None: + """Resource resolution object initialization. + + Args: + server_address (str, optional): gRPC server address. Defaults to "127.0.0.1". + server_port (int, optional): gRPC server address port. Defaults to "9111". + use_ssl (bool, optional): Boolean flag to determine if secure channel should be created or not. + Defaults to False. + root_certificates (bytes, optional): The PEM-encoded root certificates. None if it shouldn't be used. + Defaults to None. + private_key (bytes, optional): The PEM-encoded private key as a byte string, or None if no private key + should be used. Defaults to None. + certificate_chain (bytes, optional): The PEM-encoded certificate chain as a byte string to use or or None if + no certificate chain should be used. Defaults to None. + use_header_auth (bool, optional): Boolean flag to determine if authorization headed shoud be added for + every call or not. Defaults to False. + header_auth_token (str, optional): Authorization token value. Defaults to None. + """ + # Logger + self.logger: Logger = getLogger(__name__) + # Client settings + self.client_server_address: str = server_address + self.client_server_port: str = server_port + self.client_use_ssl: bool = use_ssl + self.client_root_certificates: bytes = root_certificates + self.client_private_key: bytes = private_key + self.client_certificate_chain: bytes = certificate_chain + self.client_use_header_auth: bool = use_header_auth + self.client_header_auth_token: str = header_auth_token + self.client: Client = None + + def __enter__(self) -> "ResourceResolution": + """Enter ResourceResolution instance context. + + Client connection is created. + """ + self.client = Client( + server_address=f"{self.client_server_address}:{self.client_server_port}", + use_ssl=self.client_use_ssl, + root_certificates=self.client_root_certificates, + private_key=self.client_private_key, + certificate_chain=self.client_certificate_chain, + use_header_auth=self.client_use_header_auth, + header_auth_token=self.client_header_auth_token, + ) + return self + + def __exit__( + self, + unused_exc_type: Optional[Type[BaseException]], + unused_exc_value: Optional[BaseException], + unused_traceback: Optional[TracebackType], + ) -> None: + """Exit ResourceResolution instance context. + + Client connection is closed. + """ + self.client.close() + + def execute_workflows(self, *workflows: WorkflowExecution) -> Generator[WorkflowExecutionResult, None, None]: + """Execute provided workflows. + + Workflows are going to be execured using one gRPC API call. Depends of implementation that may has + some consequences. In some cases if any request fails all requests after that won't be called. + + Responses and zipped with workflows and WorkflowExecutionResult object is initialized and yielded. + + Raises: + AttributeError: Raises if client object is not created. It occurs only if you not uses context manager. + Then user have to create client instance for ResourceResolution object by himself calling: + ``` + resource_resoulution.client = Client( + server_address=f"{resource_resoulution.client_server_address}:{resource_resoulution.client_server_port}", + use_ssl=resource_resoulution.client_use_ssl, + root_certificates=resource_resoulution.client_root_certificates, + private_key=resource_resoulution.client_private_key, + certificate_chain=resource_resoulution.client_certificate_chain, + use_header_auth=resource_resoulution.client_use_header_auth, + header_auth_token=resource_resoulution.client_header_auth_token, + ) + ``` + Remeber also to close client connection. + + Returns: + Generator[WorkflowExecutionResult, None, None]: WorkflowExecutionResult object + with both WorkflowExection object and server response for it's request. + """ + self.logger.debug("Execute workflows") + if not self.client: + raise AttributeError("gRPC client not connected") + + for response, workflow in zip(self.client.process((workflow.message for workflow in workflows)), workflows): + yield WorkflowExecutionResult(workflow, response) diff --git a/ms/py-executor/resource_resolution/tests/authorization_interceptor_test.py b/ms/py-executor/resource_resolution/tests/authorization_interceptor_test.py new file mode 100644 index 000000000..4b03f0b36 --- /dev/null +++ b/ms/py-executor/resource_resolution/tests/authorization_interceptor_test.py @@ -0,0 +1,50 @@ +"""Copyright 2020 Deutsche Telekom. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from unittest.mock import MagicMock, _Call + +import pytest + +from resource_resolution.authorization import AuthTokenInterceptor, NewClientCallDetails + + +def test_resource_resolution_auth_token_interceptor(): + """Test AuthTokenInterceptor class. + + - Checks if it's correctly set default value. + - Checks if it's correctly set passed values. + - Checks if it's correctly checked if all header characters are lowercase. + - Checks if continuation function is called with headers setted + """ + interceptor: AuthTokenInterceptor = AuthTokenInterceptor("test_token", header="header") + assert interceptor.token == "test_token" + assert interceptor.header == "header" + + interceptor: AuthTokenInterceptor = AuthTokenInterceptor("test_token") + assert interceptor.token == "test_token" + assert interceptor.header == "authorization" + + with pytest.raises(ValueError): + AuthTokenInterceptor("test_token", header="Auth") + + continuation_mock: MagicMock = MagicMock() + client_call_details: MagicMock = MagicMock() + request_iterator: MagicMock = MagicMock() + + interceptor.intercept_stream_stream(continuation_mock, client_call_details, request_iterator) + continuation_mock.assert_called_once() + client_call_details_argument: _Call = continuation_mock.call_args_list[0][0][0] # Get NewClientCallDetails instance + assert isinstance(client_call_details_argument, NewClientCallDetails) + assert client_call_details_argument.metadata[0] == (interceptor.header, interceptor.token) diff --git a/ms/py-executor/resource_resolution/tests/resource_resolution_test.py b/ms/py-executor/resource_resolution/tests/resource_resolution_test.py new file mode 100644 index 000000000..8a41357e6 --- /dev/null +++ b/ms/py-executor/resource_resolution/tests/resource_resolution_test.py @@ -0,0 +1,105 @@ +"""Copyright 2020 Deutsche Telekom. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from google.protobuf import json_format +from pytest import raises + +from resource_resolution.resource_resolution import ( + ExecutionServiceInput, + ExecutionServiceOutput, + WorkflowExecution, + WorkflowExecutionResult, + WorkflowMode, +) + + +def test_workflow_execution_class(): + """Workflow execution class tests. + + - Test initialization and default values + - Test request message formatting + """ + # Without inputs + workflow_execution: WorkflowExecution = WorkflowExecution("test blueprint", "test version", "test workflow") + assert workflow_execution.blueprint_name == "test blueprint" + assert workflow_execution.blueprint_version == "test version" + assert workflow_execution.workflow_name == "test workflow" + assert workflow_execution.workflow_inputs == {} + assert workflow_execution.workflow_mode == WorkflowMode.SYNC + + msg: ExecutionServiceInput = workflow_execution.message + msg_dict: dict = json_format.MessageToDict(msg) + assert msg_dict["actionIdentifiers"]["blueprintName"] == "test blueprint" + assert msg_dict["actionIdentifiers"]["blueprintVersion"] == "test version" + assert msg_dict["actionIdentifiers"]["actionName"] == "test workflow" + assert msg_dict["actionIdentifiers"]["mode"] == "sync" + assert list(msg_dict["payload"].keys())[0] == "test workflow-request" + assert msg_dict["payload"]["test workflow-request"] == {} + + # With inputs + workflow_execution: WorkflowExecution = WorkflowExecution( + "test blueprint2", + "test version2", + "test workflow2", + workflow_inputs={"test": "test"}, + workflow_mode=WorkflowMode.ASYNC, + ) + assert workflow_execution.blueprint_name == "test blueprint2" + assert workflow_execution.blueprint_version == "test version2" + assert workflow_execution.workflow_name == "test workflow2" + assert workflow_execution.workflow_inputs == {"test": "test"} + assert workflow_execution.workflow_mode == WorkflowMode.ASYNC + + msg: ExecutionServiceInput = workflow_execution.message + msg_dict: dict = json_format.MessageToDict(msg) + assert msg_dict["actionIdentifiers"]["blueprintName"] == "test blueprint2" + assert msg_dict["actionIdentifiers"]["blueprintVersion"] == "test version2" + assert msg_dict["actionIdentifiers"]["actionName"] == "test workflow2" + assert msg_dict["actionIdentifiers"]["mode"] == "async" + assert list(msg_dict["payload"].keys())[0] == "test workflow2-request" + assert msg_dict["payload"]["test workflow2-request"] == {"test": "test"} + + +def test_workflow_execution_result_class(): + """Workflow execution result class tests. + + - Test initizalization and default values + - Test `has_error` property + - Test `error_message` property + - Test payload formatting + """ + workflow_execution: WorkflowExecution = WorkflowExecution("test blueprint", "test version", "test workflow") + execution_output: ExecutionServiceOutput = ExecutionServiceOutput() + execution_output.actionIdentifiers.blueprintName = "test blueprint" + execution_output.actionIdentifiers.blueprintVersion = "test version" + execution_output.actionIdentifiers.actionName = "test workflow" + execution_output.status.code = 200 + + execution_result: WorkflowExecutionResult = WorkflowExecutionResult(workflow_execution, execution_output) + assert not execution_result.has_error + with raises(AttributeError): + execution_result.error_message + assert execution_result.payload == {} + assert execution_result.blueprint_name == "test blueprint" + assert execution_result.blueprint_version == "test version" + assert execution_result.workflow_name == "test workflow" + + execution_output.payload.update({"test_key": "test_value"}) + execution_result: WorkflowExecutionResult = WorkflowExecutionResult(workflow_execution, execution_output) + assert execution_result.payload == {"test_key": "test_value"} + + execution_output.status.code = 500 + assert execution_result.has_error + assert execution_result.error_message == "" diff --git a/ms/sdclistener/application/pom.xml b/ms/sdclistener/application/pom.xml index 88d8d1b2f..72f552cdd 100644 --- a/ms/sdclistener/application/pom.xml +++ b/ms/sdclistener/application/pom.xml @@ -63,7 +63,6 @@ <dependency> <groupId>org.onap.sdc.sdc-distribution-client</groupId> <artifactId>sdc-distribution-client</artifactId> - <version>1.3.0</version> </dependency> <dependency> @@ -112,6 +111,11 @@ </dependency> <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + + <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <scope>compile</scope> diff --git a/ms/sdclistener/application/src/main/resources/application.yaml b/ms/sdclistener/application/src/main/resources/application.yaml index 424f0a5c0..d07d8ae61 100644 --- a/ms/sdclistener/application/src/main/resources/application.yaml +++ b/ms/sdclistener/application/src/main/resources/application.yaml @@ -2,7 +2,7 @@ listenerservice: config: asdcAddress: ${asdcAddress:localhost:8443} messageBusAddress: ${messageBusAddress:localhost} - user: ${sdcusername:vid} + user: ${sdcusername:cds} password: ${password:Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U} pollingInterval: ${pollingInterval:15} pollingTimeout: ${pollingTimeout:60} diff --git a/ms/sdclistener/application/src/test/java/org/onap/ccsdk/cds/sdclistener/SdcListenerConfigurationTest.java b/ms/sdclistener/application/src/test/java/org/onap/ccsdk/cds/sdclistener/SdcListenerConfigurationTest.java index 26757a657..8275bc084 100644 --- a/ms/sdclistener/application/src/test/java/org/onap/ccsdk/cds/sdclistener/SdcListenerConfigurationTest.java +++ b/ms/sdclistener/application/src/test/java/org/onap/ccsdk/cds/sdclistener/SdcListenerConfigurationTest.java @@ -37,7 +37,7 @@ public class SdcListenerConfigurationTest { public void testCdsSdcListenerConfiguration() { assertEquals(listenerConfiguration.getAsdcAddress(), "localhost:8443"); assertEquals(listenerConfiguration.getMsgBusAddress().stream().findFirst().get(), "localhost"); - assertEquals(listenerConfiguration.getUser(), "vid"); + assertEquals(listenerConfiguration.getUser(), "cds"); assertEquals(listenerConfiguration.getPassword(), "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U"); assertEquals(listenerConfiguration.getPollingInterval(), 15); assertEquals(listenerConfiguration.getPollingTimeout(), 60); diff --git a/ms/sdclistener/parent/pom.xml b/ms/sdclistener/parent/pom.xml index eaa1d60f4..2cbc79dcc 100755 --- a/ms/sdclistener/parent/pom.xml +++ b/ms/sdclistener/parent/pom.xml @@ -40,7 +40,7 @@ <mockk.version>1.9</mockk.version> <dmaap.client.version>1.1.5</dmaap.client.version> <mockkserver.version>5.5.1</mockkserver.version> - <sdc-distribution-client.version>1.3.0</sdc-distribution-client.version> + <sdc-distribution-client.version>1.4.0</sdc-distribution-client.version> <jmockit.version>1.19</jmockit.version> <reactorcore.version>3.2.6.RELEASE</reactorcore.version> </properties> @@ -22,7 +22,7 @@ limitations under the License. <parent> <groupId>org.onap.ccsdk.parent</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>1.5.2-SNAPSHOT</version> + <version>1.5.2</version> <relativePath/> </parent> @@ -111,8 +111,7 @@ limitations under the License. <phase>validate</phase> <configuration> <target name="ktlint"> - <java taskname="ktlint" dir="${project.basedir}" fork="true" failonerror="true" - classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath"> + <java taskname="ktlint" dir="${project.basedir}" fork="true" failonerror="true" classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath"> <arg value="src/**/*.kt"/> </java> </target> @@ -128,8 +127,7 @@ limitations under the License. <phase>process-sources</phase> <configuration> <target name="ktlint"> - <java taskname="ktlint" dir="${project.basedir}" fork="true" failonerror="true" - classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath"> + <java taskname="ktlint" dir="${project.basedir}" fork="true" failonerror="true" classname="com.pinterest.ktlint.Main" classpathref="maven.plugin.classpath"> <arg value="-F"/> <arg value="src/**/*.kt"/> </java> |