diff options
36 files changed, 1533 insertions, 160 deletions
diff --git a/cds-ui/client/JointJS-POC-In-Progress/app-routing.module.ts b/cds-ui/client/JointJS-POC-In-Progress/app-routing.module.ts new file mode 100644 index 000000000..d425c6f56 --- /dev/null +++ b/cds-ui/client/JointJS-POC-In-Progress/app-routing.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = []; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/cds-ui/client/JointJS-POC-In-Progress/app.component.html b/cds-ui/client/JointJS-POC-In-Progress/app.component.html new file mode 100644 index 000000000..da05dd563 --- /dev/null +++ b/cds-ui/client/JointJS-POC-In-Progress/app.component.html @@ -0,0 +1,31 @@ +<!-- <div id="myholder"></div> --> +<div style="background-color: black; color: white; height: 75px; width:100%; font-size: 2em; margin-bottom: 3px"> + CDS DESIGNER POC + <button (click)="convertGraphToJson()">Save</button> +</div> +<div style="display: flex;flex-direction:row; height: 700PX"> + <div id="stencil" style="width: 30%;height: 100%;border: 1px solid; border-radius:0.5em; background-color: gainsboro"></div> + <div style="width: 70%; height: 100%;"> + <div style="position: fixed;width: 200px;background-color: gainsboro;height: 36px;z-index: 2;"> + <button *ngIf="!sourceMode" style="border-radius: 0.5em;width: 6em;height: 2.5em;border: 2px solid black;" (click)="convertGraphToJson()">Source</button> + <button *ngIf="sourceMode" style="border-radius: 0.5em;width: 6em;height: 2.5em;border: 2px solid black;" (click)="convertJsonToGraph()">Graph</button> + <i (click) ="zoomIn()" class="fa fa-search-plus" aria-hidden="true"></i> + <i (click) ="zoomOut()" class="fa fa-search-minus" aria-hidden="true"></i> + </div> + <div [hidden]="sourceMode" id="paper" style="height: 100%;"></div> + <div id="sourceJson" [hidden]="!sourceMode"style="width: 100%; height: 100%;"><textarea style="margin-top: 4em; + width: 100%; + height: 100%;">{{grapJson}}</textarea></div> + </div> + <div id="propertyPanel" style="width: 20%; height: 100%; background-color: white"> + <div style="background-color: gainsboro;height:28px">Attributes</div> + <div style="height: 100px;padding: 10px"> + Action Name<br/> + <input type="text" [(ngModel)]="selectedElement.attributes.attrs.label.text" (change)="setNewValue($event)"/> + </div> + <div> + <div style="background-color: gainsboro;height:28px">Input</div> + + </div> + </div> +</div> diff --git a/cds-ui/client/JointJS-POC-In-Progress/app.component.scss b/cds-ui/client/JointJS-POC-In-Progress/app.component.scss new file mode 100644 index 000000000..68d06fbe7 --- /dev/null +++ b/cds-ui/client/JointJS-POC-In-Progress/app.component.scss @@ -0,0 +1,81 @@ +#stencil { + position: relative; + border: 1px solid gray; + display: inline-block; + background: transparent; + overflow: hidden; +} +#stencil svg { + background: transparent; +} +#stencil svg .link { + z-index: 2; +} +.html-element { + position: absolute; + background: #3498DB; + /* Make sure events are propagated to the JointJS element so, e.g. dragging works.*/ + pointer-events: none; + -webkit-user-select: none; + border-radius: 4px; + border: 2px solid #2980B9; + box-shadow: inset 0 0 5px black, 2px 2px 1px gray; + padding: 5px; + box-sizing: border-box; + z-index: 2; +} +.html-element select, +.html-element input, +.html-element button { + /* Enable interacting with inputs only. */ + pointer-events: auto; +} +.html-element button.delete { + color: white; + border: none; + background-color: #C0392B; + border-radius: 20px; + width: 15px; + height: 15px; + line-height: 15px; + text-align: middle; + position: absolute; + top: -15px; + left: -15px; + padding: 0; + margin: 0; + font-weight: bold; + cursor: pointer; +} +.html-element button.delete:hover { + width: 20px; + height: 20px; + line-height: 20px; +} +.html-element select { + position: absolute; + right: 2px; + bottom: 28px; +} +.html-element input { + position: absolute; + bottom: 0; + left: 0; + right: 0; + border: none; + color: #333; + padding: 5px; + height: 16px; +} +.html-element label { + color: #333; + text-shadow: 1px 0 0 lightgray; + font-weight: bold; +} +.html-element span { + position: absolute; + top: 2px; + right: 9px; + color: white; + font-size: 10px; +}
\ No newline at end of file diff --git a/cds-ui/client/JointJS-POC-In-Progress/app.component.spec.ts b/cds-ui/client/JointJS-POC-In-Progress/app.component.spec.ts new file mode 100644 index 000000000..8de310a78 --- /dev/null +++ b/cds-ui/client/JointJS-POC-In-Progress/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'jointJS-designer'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('jointJS-designer'); + }); + + it('should render title in a h1 tag', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('Welcome to jointJS-designer!'); + }); +}); diff --git a/cds-ui/client/JointJS-POC-In-Progress/app.component.ts b/cds-ui/client/JointJS-POC-In-Progress/app.component.ts new file mode 100644 index 000000000..289a57766 --- /dev/null +++ b/cds-ui/client/JointJS-POC-In-Progress/app.component.ts @@ -0,0 +1,869 @@ +declare var require: any +import { Component, OnInit } from '@angular/core'; +import * as $ from 'jquery'; +import * as _ from 'lodash'; +const joint = require('../../node_modules/jointjs/dist/joint.js'); + + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent implements OnInit { + title = 'jointJS-designer'; + + public graph: any; + public paper: any; + public rect: any; + public rect2: any; + public link: any; + + public stencilGraph: any; + public stencilPaper: any; + + public selectedElement = { + attributes: { + attrs: { + label: { + text: 'abc' + } + } + } + }; + + grapJson: any; + sourceMode = false; + + constructor() { + + } + + ngOnInit() { + // this.creatElementWithPorts(); + // this.dragCopyPlainElements(); + this.sourceMode = false; + this.dragCopyElementsWithPort(); + // this.newDragCopy(); + + this.createContainerElements(); + } + + creatElementWithPorts() { + // working code + + // create graph + let elementList: any[] = []; + // this.graph = new joint.dia.Graph; + + // // create paper + // this.paper = new joint.dia.Paper({ + // el: document.getElementById('paper'), + // width: 1000, + // height: 1000, + // model: this.graph, + // gridSize: 2, + // drawGrid: true + // }); + + // this.paper.setGrid({ + // name: 'dot', + // args: + // { color: 'black', thickness: 2, scaleFactor: 8 } + + // }).drawGrid(); + + // create element + this.rect = new joint.shapes.basic.Rect({ + position: { x: 100, y: 30 }, + size: { width: 100, height: 100 }, + attrs: { rect: { fill: 'white' }, text: { text: 'my box', fill: 'white' } } + }); + this.rect.translate(100, 50); + elementList.push(this.rect); + + this.rect.position(10, 10); + // clone element + // this.rect2 = this.rect.clone(); + // this.rect2.translate(180); + // elementList.push(this.rect2); + + // Create link + // this.link = new joint.dia.Link({ + // source: { id: this.rect.id }, + // target: { id: this.rect2.id } + // }); + + + // create circle + var circle = new joint.shapes.standard.Circle(); + circle.resize(100, 100); + circle.position(180, 10); + circle.attr('root/title', 'joint.shapes.standard.Circle'); + circle.attr('label/text', 'Circle'); + circle.attr('body/fill', 'lightblue'); + elementList.push(circle); + + // create link + var ellipse = new joint.shapes.standard.Ellipse(); + ellipse.resize(150, 100); + ellipse.position(180, 150); + ellipse.attr('root/title', 'joint.shapes.standard.Ellipse'); + ellipse.attr('label/text', 'Ellipse'); + ellipse.attr('body/fill', 'lightblue'); + elementList.push(ellipse); + + // rectangle with header + var headeredRectangle = new joint.shapes.standard.HeaderedRectangle(); + headeredRectangle.resize(150, 100); + headeredRectangle.position(10, 280); + headeredRectangle.attr('root/title', 'joint.shapes.standard.HeaderedRectangle'); + headeredRectangle.attr('header/fill', 'lightgray'); + headeredRectangle.attr('headerText/text', 'Header'); + headeredRectangle.attr('bodyText/text', 'Headered\nRectangle'); + elementList.push(headeredRectangle); + + let m1 = new joint.shapes.devs.Model({ + position: { x: 200, y: 280 }, + size: { width: 90, height: 90 }, + inPorts: ['in1', 'in2'], + outPorts: ['out', 'out2'], + ports: { + groups: { + 'in': { + attrs: { + '.port-body': { + fill: '#16A085' + } + } + }, + 'out': { + attrs: { + '.port-body': { + fill: '#E74C3C' + } + } + } + } + }, + attrs: { + '.label': { text: 'Model', 'ref-x': .5, 'ref-y': .2 }, + rect: { fill: 'white' } + } + }); + elementList.push(m1); + + //container element + let c1 = new joint.shapes.devs.Coupled({ + position: { + x: 150, + y: 470 + }, + size: { + width: 200, + height: 200 + } + }); + + c1.set('inPorts', ['in']); + c1.set('outPorts', ['out 1', 'out 2']); + c1.attr({ + '.body': { + 'rx': 6, + 'ry': 6 + } +}); + elementList.push(c1); + // circle.position(10, 150); + var a1 = new joint.shapes.devs.Atomic({ + position: { + x: 10, + y: 150 + }, + inPorts: ['xy'], + outPorts: ['x', 'y'] + }); + + a1.attr({ + '.body': { + 'rx': 6, + 'ry': 6 + } + }); + + elementList.push(a1); + + + return elementList; + } + + dragCopyPlainElements() { + // Canvas where sape are dropped + this.graph = new joint.dia.Graph, + this.paper = new joint.dia.Paper({ + el: $('#paper'), + model: this.graph + }); + + // Canvas from which you take shapes + this.stencilGraph = new joint.dia.Graph, + this.stencilPaper = new joint.dia.Paper({ + el: $('#stencil'), + height: 60, + model: this.stencilGraph, + interactive: false + }); + + var r1 = new joint.shapes.basic.Rect({ + position: { + x: 10, + y: 10 + }, + size: { + width: 100, + height: 40 + }, + attrs: { + text: { + text: 'Rect1' + } + } + }); + var r2 = new joint.shapes.basic.Rect({ + position: { + x: 120, + y: 10 + }, + size: { + width: 100, + height: 40 + }, + attrs: { + text: { + text: 'Rect2' + } + } + }); + this.stencilGraph.addCells([r1, r2]); + + let _this = this; + + this.stencilPaper.on('cell:pointerdown', function (cellView, e, x, y) { + $('body').append('<div id="flyPaper" style="position:fixed;z-index:100;opacity:.7;pointer-event:none;"></div>'); + var flyGraph = new joint.dia.Graph, + flyPaper = new joint.dia.Paper({ + el: $('#flyPaper'), + model: flyGraph, + interactive: false + }), + flyShape = cellView.model.clone(), + pos = cellView.model.position(), + offset = { + x: x - pos.x, + y: y - pos.y + }; + + flyShape.position(0, 0); + flyGraph.addCell(flyShape); + $("#flyPaper").offset({ + left: e.pageX - offset.x, + top: e.pageY - offset.y + }); + $('body').on('mousemove.fly', function (e) { + $("#flyPaper").offset({ + left: e.pageX - offset.x, + top: e.pageY - offset.y + }); + }); + $('body').on('mouseup.fly', function (e) { + var x = e.pageX, + y = e.pageY, + target = _this.paper.$el.offset(); + + // Dropped over paper ? + if (x > target.left && x < target.left + _this.paper.$el.width() && y > target.top && y < target.top + _this.paper.$el.height()) { + var s = flyShape.clone(); + s.position(x - target.left - offset.x, y - target.top - offset.y); + _this.graph.addCell(s); + } + $('body').off('mousemove.fly').off('mouseup.fly'); + flyShape.remove(); + $('#flyPaper').remove(); + }); + }) + } + + dragCopyElementsWithPort() { + // Canvas where sape are dropped + + this.graph = new joint.dia.Graph, + this.paper = new joint.dia.Paper({ + el: $('#paper'), + model: this.graph, + height: 700, + width: 1000, + gridSize: 2, + drawGrid: true + }); + + // create paper + // this.paper = new joint.dia.Paper({ + // el: document.getElementById('paper'), + // width: 1000, + // height: 1000, + // model: this.graph, + // gridSize: 2, + // drawGrid: true + // }); + + this.paper.setGrid({ + name: 'dot', + args: + { color: 'black', thickness: 2, scaleFactor: 8 } + + }).drawGrid(); + + // Canvas from which you take shapes + this.stencilGraph = new joint.dia.Graph, + this.stencilPaper = new joint.dia.Paper({ + el: $('#stencil'), + height: 700, + width: 382, + model: this.stencilGraph, + interactive: false + }); + + let elementWithPort = this.creatElementWithPorts(); + // let elementWithPort = this.createCustomElement(); + // let elementWithPort = this.myCustomElementGenerator(); + + + + elementWithPort.forEach(element => { + this.stencilGraph.addCell(element); + }); + + let _this = this; + this.stencilPaperEventListeners(_this); + this.drawAreapaperEventListerners(); + } + + resetAll(paper) { + this.paper.drawBackground({ + color: 'white' + }) + + var elements = this.paper.model.getElements(); + for (var i = 0, ii = elements.length; i < ii; i++) { + var currentElement = elements[i]; + currentElement.attr('body/stroke', 'black'); + } + + var links = this.paper.model.getLinks(); + for (var j = 0, jj = links.length; j < jj; j++) { + var currentLink = links[j]; + currentLink.attr('line/stroke', 'black'); + currentLink.label(0, { + attrs: { + body: { + stroke: 'black' + } + } + }) + } + } + + onDrag(evt) { + // transform client to paper coordinates + var p = evt.data.paper.snapToGrid({ + x: evt.clientX, + y: evt.clientY + }); + // manually execute the linkView mousemove handler + evt.data.view.pointermove(evt, p.x, p.y); + } + + onDragEnd(evt) { + // manually execute the linkView mouseup handler + evt.data.view.pointerup(evt); + $(document).off('.example'); + } + + stencilPaperEventListeners(_this) { + this.stencilPaper.on('cell:pointerdown', function (cellView, e, x, y) { + $('body').append('<div id="flyPaper" style="position:fixed;z-index:100;opacity:.7;pointer-event:none;"></div>'); + var flyGraph = new joint.dia.Graph, + flyPaper = new joint.dia.Paper({ + el: $('#flyPaper'), + model: flyGraph, + interactive: true + }), + flyShape = cellView.model.clone(), + pos = cellView.model.position(), + offset = { + x: x - pos.x, + y: y - pos.y + }; + + flyShape.position(0, 0); + flyGraph.addCell(flyShape); + $("#flyPaper").offset({ + left: e.pageX - offset.x, + top: e.pageY - offset.y + }); + $('body').on('mousemove.fly', function (e) { + $("#flyPaper").offset({ + left: e.pageX - offset.x, + top: e.pageY - offset.y + }); + }); + let elementabove, elementBelow; + $('body').on('mouseup.fly', function (e) { + console.log(this); + var x = e.pageX, + y = e.pageY, + target = _this.paper.$el.offset(); + + // Dropped over paper ? + if (x > target.left && x < target.left + _this.paper.$el.width() && y > target.top && y < target.top + _this.paper.$el.height()) { + var s = flyShape.clone(); + + // var coordinates = new g.Point(x, y); + // elementabove = s; + // elementBelow = _this.paper.model.findModelsFromPoint(coordinates).find(function(el) { + // return (el.id !== elementabove.id); + // }); + // elementBelow =_this.paper.findModelsFromPoint(coordinates).find(function(el) { + // return (el.id !== elementabove.id); + // }); + // elementBelow.embed(elementabove); + + s.position(x - target.left - offset.x, y - target.top - offset.y); + _this.graph.addCell(s); + // let elementssss = (_this.graph.getElements()); + // console.log("elementsss", elementssss); + // let elementBelow = elementssss[0]; + // let elementAbove; + // if(elementssss[1]) { + // elementAbove = elementssss[1]; + // elementBelow.embed(elementabove); + // } + } + $('body').off('mousemove.fly').off('mouseup.fly'); + flyShape.remove(); + $('#flyPaper').remove(); + }); + _this.paper.on('mouse') + }) + } + + drawAreapaperEventListerners() { + // create event listerners + let _this = this; + this.paper.on('element:pointerdblclick', function (elementView) { + _this.resetAll(this); + _this.selectedElement = elementView.model; + var currentElement = elementView.model; + currentElement.attr('body/stroke', 'orange'); + // currentElement.attr('label/text', "abc"); + }); + + this.paper.on('blank:pointerdblclick', function () { + _this.resetAll(this); + + this.drawBackground({ + color: 'orange' + }); + }); + + this.paper.on('link:pointerclick', function (linkView) { + _this.resetAll(this); + let currentElement = linkView.model; + currentElement.appendLabel({ + attrs: { + text: { + text: "Hello to new link!" + } + } + }); + + }); + + + this.paper.on('blank:pointerdown', function (evt, x, y) { + let linkView = this.getDefaultLink() + .set({ + 'source': { x: x, y: y }, + 'target': { x: x, y: y } + }) + .addTo(this.model) + .findView(this); + linkView.startArrowheadMove('target'); + + $(document).on({ + 'mousemove.example': _this.onDrag, + 'mouseup.example': _this.onDragEnd + }, { + // shared data between listeners + view: linkView, + paper: this + }); + }); + + this.paper.on({ + // 'element:pointerdown': function(elementView, evt) { + + // evt.data = elementView.model.position(); + // }, + 'element:pointerup': function(elementView, evt, x, y) { + var coordinates = new g.Point(x, y); + var elementAbove = elementView.model; + var elementBelow = this.model.findModelsFromPoint(coordinates).find(function(el) { + return (el.id !== elementAbove.id); + }); + if(elementBelow) elementBelow.embed(elementAbove); + } + + }); //end of my event + } + + createCustomElement() { + joint.shapes.html = {}; + joint.shapes.html.Element = joint.shapes.basic.Rect.extend({ + defaults: joint.util.deepSupplement({ + type: 'html.Element', + attrs: { + rect: { stroke: 'none', 'fill-opacity': 0 } + } + }, joint.shapes.basic.Rect.prototype.defaults) + }); + + // / Create a custom view for that element that displays an HTML div above it. + // ------------------------------------------------------------------------- + + joint.shapes.html.ElementView = joint.dia.ElementView.extend({ + + template: [ + '<div class="html-element">', + '<button class="delete">x</button>', + '<label></label>', + '<span></span>', '<br/>', + '<select><option>--</option><option>one</option><option>two</option></select>', + '<input type="text" value="I\'m HTML input" />', + '</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 is an example of reacting on the input change and storing the input data in the cell model. + this.$box.find('input').on('change', _.bind(function(evt) { + this.model.set('input', $(evt.target).val()); + }, this)); + this.$box.find('select').on('change', _.bind(function(evt) { + this.model.set('select', $(evt.target).val()); + }, this)); + this.$box.find('select').val(this.model.get('select')); + this.$box.find('.delete').on('click', _.bind(this.model.remove, this.model)); + // Update the box position whenever the underlying model changes. + this.model.on('change', this.updateBox, this); + // Remove the box when the model gets removed from the graph. + this.model.on('remove', this.removeBox, 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(); + // Example of updating the HTML with a data stored in the cell model. + this.$box.find('label').text(this.model.get('label')); + this.$box.find('span').text(this.model.get('select')); + this.$box.css({ + width: bbox.width, + height: bbox.height, + left: bbox.x, + top: bbox.y, + transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' + }); + }, + + removeBox: function(evt) { + this.$box.remove(); + } + }); + + // Create JointJS elements and add them to the graph as usual. + // ----------------------------------------------------------- + + var el1 = new joint.shapes.html.Element({ + position: { x: 10, y: 10 }, + size: { width: 170, height: 100 }, + label: 'I am HTML', + select: 'one' + }); + + var el2 = new joint.shapes.html.Element({ + position: { x: 370, y: 160 }, + size: { width: 170, height: 100 }, + label: 'Me too', + select: 'two' + }); + var l = new joint.dia.Link({ + source: { id: el1.id }, + target: { id: el2.id }, + attrs: { '.connection': { 'stroke-width': 5, stroke: '#34495E' }} + }); + + let elementArray : any[] = []; + elementArray.push(el1, el2); + return elementArray; + } + + myCustomElementGenerator() { + var Ellipse = joint.dia.Element.define('examples.Ellipse', { + // default attributes + markup: [{ + tagName: 'ellipse', + selector: 'ellipse' // not necessary but faster + }], + attrs: { + ellipse: { + fill: 'white', + stroke: 'black', + strokeWidth: 4, + refRx: .5, + refRy: .5, + refCx: .5, + refCy: .5 + } + } + }); + + var Rectangle = joint.shapes.standard.Rectangle.define('examples.CustomRectangle', { + markup: [{ + tagName: 'body', + selector: 'body' // not necessary but faster + }, + { + tagName: 'label', + selector: 'label' + }], + attrs: { + body: { + rx: 10, // add a corner radius + ry: 10, + strokeWidth: 1, + fill: 'cornflowerblue' + }, + label: { + textAnchor: 'left', // align text to left + refX: 10, // offset text from right edge of model bbox + fill: 'white', + fontSize: 18, + text: 'mad mad mad' + } + } + }) + + + var customElement = (new joint.dia.Element.examples.Ellipse()).position(100, 100).size(120, 50); + let elementsArray = []; + elementsArray.push(customElement); + + var customRect = new Rectangle().position(100, 200).size(120, 120); + elementsArray.push(customRect); + return elementsArray; + } + + convertGraphToJson() { + this.grapJson = JSON.stringify(this.graph.toJSON()); + // this.grapJson = this.graph.toJSON(); + console.log(this.graph.toJSON()); + console.log(this.grapJson); + this.sourceMode = true; + } + + setNewValue(event) { + // this.selectedElement.attr('label/text', event.currentTarget.value); + } + + convertJsonToGraph() { + this.sourceMode = false; + } + + zoomIn() { + var graphScale = 1; + graphScale += 0.1; + this.paper.scale(graphScale, graphScale); + } + + zoomOut() { + var graphScale = 1; + graphScale -= 0.1; + this.paper.scale(graphScale, graphScale); + } + + createContainerElements() { + this.graph = new joint.dia.Graph; + + this.paper = new joint.dia.Paper({ + + el: document.getElementById('paper'), + width: 800, + height: 400, + gridSize: 1, + model: this.graph, + snapLinks: true, + linkPinning: false, + embeddingMode: true, + clickThreshold: 5, + defaultConnectionPoint: { name: 'boundary' }, + highlighting: { + 'default': { + name: 'stroke', + options: { + padding: 6 + } + }, + 'embedding': { + name: 'addClass', + options: { + className: 'highlighted-parent' + } + } + }, + + validateEmbedding: function(childView, parentView) { + return parentView.model instanceof joint.shapes.devs.Coupled; + }, + + validateConnection: function(sourceView, sourceMagnet, targetView, targetMagnet) { + return sourceMagnet != targetMagnet; + } + }); + + let c1 = new joint.shapes.devs.Coupled({ + position: { + x: 230, + y: 50 + }, + size: { + width: 300, + height: 300 + } +}); + +c1.set('inPorts', ['in']); +c1.set('outPorts', ['out 1', 'out 2']); + +var a1 = new joint.shapes.devs.Atomic({ + position: { + x: 360, + y: 260 + }, + inPorts: ['xy'], + outPorts: ['x', 'y'] +}); + +var a2 = new joint.shapes.devs.Atomic({ + position: { + x: 50, + y: 160 + }, + outPorts: ['out'] +}); + +var a3 = new joint.shapes.devs.Atomic({ + position: { + x: 650, + y: 50 + }, + size: { + width: 100, + height: 300 + }, + inPorts: ['a', 'b'] +}); + +[c1, a1, a2, a3].forEach(function(element) { + element.attr({ + '.body': { + 'rx': 6, + 'ry': 6 + } + }); +}); + + + +this.graph.addCells([c1, a1, a2, a3]); + +c1.embed(a1); + +this.connect(a2, 'out', c1, 'in'); +this.connect(c1, 'in', a1, 'xy'); +this.connect(a1, 'x', c1, 'out 1'); +this.connect(a1, 'y', c1, 'out 2'); +this.connect(c1, 'out 1', a3, 'a'); +this.connect(c1, 'out 2', a3, 'b'); + +var strokeDasharrayPath = '.body/strokeDasharray'; +let _this = this; + +this.paper.on('element:pointerdblclick', function(elementView) { + var element = elementView.model; + if (element.get('type') === 'devs.Atomic') { + _this.toggleDelegation(element); + } +}); + +this.paper.setInteractivity(function(elementView) { + return { + stopDelegation: !elementView.model.attr(strokeDasharrayPath) + }; +}); + + +} // function end + + // function + connect(source, sourcePort, target, targetPort) { + + var link = new joint.shapes.devs.Link({ + source: { + id: source.id, + port: sourcePort + }, + target: { + id: target.id, + port: targetPort + } + }); + + link.addTo(this.graph).reparent(); + } + + toggleDelegation(element) { + var strokeDasharrayPath = '.body/strokeDasharray'; + element.attr(strokeDasharrayPath, element.attr(strokeDasharrayPath) ? '' : '15,1'); +} + +createContainerElemnetsByDragDrop() { + + +} +} diff --git a/cds-ui/client/JointJS-POC-In-Progress/app.module.ts b/cds-ui/client/JointJS-POC-In-Progress/app.module.ts new file mode 100644 index 000000000..15eb6d5fc --- /dev/null +++ b/cds-ui/client/JointJS-POC-In-Progress/app.module.ts @@ -0,0 +1,24 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { DragCopyComponent } from './drag-copy/drag-copy.component'; + +@NgModule({ + declarations: [ + AppComponent, + DragCopyComponent + ], + imports: [ + BrowserModule, + AppRoutingModule, + FormsModule, + CommonModule + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/cds-ui/client/src/app/feature-modules/blueprint/modify-template/editor/editor.service.ts b/cds-ui/client/src/app/feature-modules/blueprint/modify-template/editor/editor.service.ts index f1d1d148c..ceaace3b9 100644 --- a/cds-ui/client/src/app/feature-modules/blueprint/modify-template/editor/editor.service.ts +++ b/cds-ui/client/src/app/feature-modules/blueprint/modify-template/editor/editor.service.ts @@ -49,11 +49,11 @@ export class EditorService { } saveBlueprint(body: any | null, options?: any): Observable<any> { - return this.api.post(BlueprintURLs.save, body, options); + return this.api.post(BlueprintURLs.save, body, { responseType: 'text' }); } publishBlueprint(body: any | null, options?: any): Observable<any> { - return this.api.post(BlueprintURLs.publish, body, options); + return this.api.post(BlueprintURLs.publish, body, { responseType: 'text' }); } deployPost(body: any | null, options?: any): Observable<any> { diff --git a/cds-ui/client/src/app/feature-modules/blueprint/modify-template/modify-template.component.html b/cds-ui/client/src/app/feature-modules/blueprint/modify-template/modify-template.component.html index a6612d537..3740ced70 100644 --- a/cds-ui/client/src/app/feature-modules/blueprint/modify-template/modify-template.component.html +++ b/cds-ui/client/src/app/feature-modules/blueprint/modify-template/modify-template.component.html @@ -38,7 +38,7 @@ limitations under the License. <button mat-menu-item (click)="saveToBlueprintProcessor()">Deploy</button> <!-- <button mat-menu-item (click)="processBlueprint()">Test</button> --> </mat-menu> - <button class="btn-active" (click)="publishToControllerBlueprint()">Publish</button> + <!-- <button class="btn-active" (click)="publishToControllerBlueprint()">Publish</button> --> <button class="btn-active" (click)="saveToControllerBlueprint()">Save</button> <button class="btn-active" (click)="getEnriched()">Enrich</button> diff --git a/cds-ui/server/src/config/app-config.ts b/cds-ui/server/src/config/app-config.ts index dcca629f2..a2cebe59d 100644 --- a/cds-ui/server/src/config/app-config.ts +++ b/cds-ui/server/src/config/app-config.ts @@ -21,12 +21,12 @@ export const appConfig = Object.freeze({ }) }); -export const controllerApiConfig = Object.freeze({ - http: Object.freeze({ - url: process.env.API_BLUEPRINT_PROCESSOR_HTTP_BASE_URL || "http://localhost:8081/api/v1", - authToken: process.env.API_BLUEPRINT_PROCESSOR_HTTP_AUTH_TOKEN || "Basic Y2NzZGthcHBzOmNjc2RrYXBwcw==" - }) -}); +// export const controllerApiConfig = Object.freeze({ +// http: Object.freeze({ +// url: process.env.API_BLUEPRINT_PROCESSOR_HTTP_BASE_URL || "http://localhost:8081/api/v1", +// authToken: process.env.API_BLUEPRINT_PROCESSOR_HTTP_AUTH_TOKEN || "Basic Y2NzZGthcHBzOmNjc2RrYXBwcw==" +// }) +// }); export const processorApiConfig = Object.freeze({ http: Object.freeze({ diff --git a/cds-ui/server/src/controllers/blueprint-rest.controller.ts b/cds-ui/server/src/controllers/blueprint-rest.controller.ts index c73f7a6f8..789589837 100644 --- a/cds-ui/server/src/controllers/blueprint-rest.controller.ts +++ b/cds-ui/server/src/controllers/blueprint-rest.controller.ts @@ -48,7 +48,7 @@ import { BlueprintService } from '../services'; import * as fs from 'fs'; import * as multiparty from 'multiparty'; import * as request_lib from 'request'; -import { controllerApiConfig, processorApiConfig, appConfig } from '../config/app-config'; +import { processorApiConfig, appConfig } from '../config/app-config'; import { bluePrintManagementServiceGrpcClient } from '../clients/blueprint-management-service-grpc-client'; export class BlueprintRestController { @@ -220,7 +220,7 @@ export class BlueprintRestController { } async uploadFileToBlueprintController(file: multiparty.File, uri: string, response: Response): Promise<Response> { - return this.uploadFileToBlueprintService(file, controllerApiConfig.http.url + uri, controllerApiConfig.http.authToken, response); + return this.uploadFileToBlueprintService(file, processorApiConfig.http.url + uri, processorApiConfig.http.authToken, response); } async uploadFileToBlueprintProcessor(file: multiparty.File, uri: string, response: Response): Promise<Response> { @@ -267,7 +267,7 @@ export class BlueprintRestController { } async downloadFileFromBlueprintController(uri: string, response: Response): Promise<Response> { - return this.downloadFileFromBlueprintService(controllerApiConfig.http.url + uri, controllerApiConfig.http.authToken, response); + return this.downloadFileFromBlueprintService(processorApiConfig.http.url + uri, processorApiConfig.http.authToken, response); } async downloadFileFromBlueprintService(url: string, authToken: string, response: Response): Promise<Response> { diff --git a/cds-ui/server/src/datasources/blueprint.datasource-template.ts b/cds-ui/server/src/datasources/blueprint.datasource-template.ts index 1188d9bcf..85e0aa342 100644 --- a/cds-ui/server/src/datasources/blueprint.datasource-template.ts +++ b/cds-ui/server/src/datasources/blueprint.datasource-template.ts @@ -1,19 +1,19 @@ -import {controllerApiConfig} from '../config/app-config'; +import {processorApiConfig} from '../config/app-config'; export default { "name": "blueprint", "connector": "rest", - "baseURL": controllerApiConfig.http.url, + "baseURL": processorApiConfig.http.url, "crud": false, "debug": true, "operations": [{ "template": { "method": "GET", - "url": controllerApiConfig.http.url + "/blueprint-model/", + "url": processorApiConfig.http.url + "/blueprint-model/", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "responsePath": "$.*" }, @@ -25,11 +25,11 @@ export default { { "template": { "method": "GET", - "url": controllerApiConfig.http.url + "/blueprint-model/search/{tags}", + "url": processorApiConfig.http.url + "/blueprint-model/search/{tags}", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "responsePath": "$.*" }, diff --git a/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts b/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts index 9f9d754d1..f839bb225 100644 --- a/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts +++ b/cds-ui/server/src/datasources/resource-dictionary.datasource-template.ts @@ -1,19 +1,19 @@ -import {controllerApiConfig} from '../config/app-config'; +import {processorApiConfig} from '../config/app-config'; export default { "name": "resourceDictionary", "connector": "rest", - "baseURL": controllerApiConfig.http.url + "/dictionary", + "baseURL": processorApiConfig.http.url + "/dictionary", "crud": false, "debug": true, "operations": [{ "template": { "method": "GET", - "url": controllerApiConfig.http.url + "/dictionary/{name}", + "url": processorApiConfig.http.url + "/dictionary/{name}", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "responsePath": "$.*" }, @@ -25,11 +25,11 @@ export default { { "template": { "method": "GET", - "url": controllerApiConfig.http.url + "/dictionary/source-mapping", + "url": processorApiConfig.http.url + "/dictionary/source-mapping", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "responsePath": "$.*" }, @@ -41,11 +41,11 @@ export default { { "template": { "method": "GET", - "url": controllerApiConfig.http.url + "/dictionary/search/{tags}", + "url": processorApiConfig.http.url + "/dictionary/search/{tags}", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "responsePath": "$.*" }, @@ -57,11 +57,11 @@ export default { { "template": { "method": "POST", - "url": controllerApiConfig.http.url + "/dictionary", + "url": processorApiConfig.http.url + "/dictionary", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "body": "{resourceDictionary}", "responsePath": "$.*" @@ -74,11 +74,11 @@ export default { { "template": { "method": "POST", - "url": controllerApiConfig.http.url + "/dictionary/by-names", + "url": processorApiConfig.http.url + "/dictionary/by-names", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "body": "{resourceDictionaryList}", "responsePath": "$.*" @@ -91,11 +91,11 @@ export default { { "template": { "method": "GET", - "url": controllerApiConfig.http.url + "/model-type/{source}", + "url": processorApiConfig.http.url + "/model-type/{source}", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "responsePath": "$.*" }, @@ -107,11 +107,11 @@ export default { { "template": { "method": "GET", - "url": controllerApiConfig.http.url + "/model-type/by-definition/data_type", + "url": processorApiConfig.http.url + "/model-type/by-definition/data_type", "headers": { "accepts": "application/json", "content-type": "application/json", - "authorization": controllerApiConfig.http.authToken + "authorization": processorApiConfig.http.authToken }, "responsePath": "$.*" }, diff --git a/components/model-catalog/blueprint-model/service-blueprint/vFW/Definitions/resources_definition_types.json b/components/model-catalog/blueprint-model/service-blueprint/vFW/Definitions/resources_definition_types.json index 426b91d34..a35974c83 100644 --- a/components/model-catalog/blueprint-model/service-blueprint/vFW/Definitions/resources_definition_types.json +++ b/components/model-catalog/blueprint-model/service-blueprint/vFW/Definitions/resources_definition_types.json @@ -876,22 +876,6 @@ "default" : { "type" : "source-default", "properties" : { } - }, - "sdnc" : { - "type" : "source-rest", - "properties" : { - "type" : "JSON", - "url-path" : "config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-id/vnf-data/vnf-topology/vnf-parameters-data/param/vnf-id", - "path" : "/param/0/value", - "input-key-mapping" : { - "service-instance-id" : "service-instance-id", - "vnf-id" : "vnf-id" - }, - "output-key-mapping" : { - "vnf-id" : "value" - }, - "key-dependencies" : [ "service-instance-id", "vnf-id" ] - } } } }, @@ -930,16 +914,16 @@ "type" : "source-rest", "properties" : { "type" : "JSON", - "url-path" : "config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-name/vnf-data/vnf-topology/vnf-parameters-data/param/vnf-name", + "url-path" : "config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-id/vnf-data/vnf-topology/vnf-parameters-data/param/vnf-name", "path" : "/param/0/value", "input-key-mapping" : { "service-instance-id" : "service-instance-id", - "vnf-name" : "vnf-name" + "vnf-id" : "vnf-id" }, "output-key-mapping" : { "vnf-name" : "value" }, - "key-dependencies" : [ "service-instance-id", "vnf-name" ] + "key-dependencies" : [ "service-instance-id", "vnf-id" ] } }, "aai-data" : { @@ -1184,4 +1168,4 @@ } } } -}
\ No newline at end of file +} diff --git a/components/model-catalog/blueprint-model/service-blueprint/vLB/Definitions/resources_definition_types.json b/components/model-catalog/blueprint-model/service-blueprint/vLB/Definitions/resources_definition_types.json index aa49e1ee9..f7da40904 100644 --- a/components/model-catalog/blueprint-model/service-blueprint/vLB/Definitions/resources_definition_types.json +++ b/components/model-catalog/blueprint-model/service-blueprint/vLB/Definitions/resources_definition_types.json @@ -1760,23 +1760,6 @@ "default" : { "type" : "source-default", "properties" : { } - }, - "sdnc" : { - "type" : "source-rest", - "properties" : { - "verb" : "GET", - "type" : "JSON", - "url-path" : "/restconf/config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-id/vnf-data/vnf-topology/vnf-parameters-data/param/vnf-id", - "path" : "/param/0/value", - "input-key-mapping" : { - "service-instance-id" : "service-instance-id", - "vnf-id" : "vnf-id" - }, - "output-key-mapping" : { - "vnf-id" : "value" - }, - "key-dependencies" : [ "service-instance-id", "vnf-id" ] - } } } }, diff --git a/components/model-catalog/blueprint-model/test-blueprint/capability_cli/Scripts/kotlin/cba/capability/cli/CapabilityCli.kt b/components/model-catalog/blueprint-model/test-blueprint/capability_cli/Scripts/kotlin/cba/capability/cli/CapabilityCli.kt index d29abb0d2..9552b6136 100644 --- a/components/model-catalog/blueprint-model/test-blueprint/capability_cli/Scripts/kotlin/cba/capability/cli/CapabilityCli.kt +++ b/components/model-catalog/blueprint-model/test-blueprint/capability_cli/Scripts/kotlin/cba/capability/cli/CapabilityCli.kt @@ -35,7 +35,8 @@ open class Check : AbstractScriptComponentFunction() { override suspend fun processNB(executionRequest: ExecutionServiceInput) { log.info("Executing process : ${executionRequest.payload}") - val data = executionRequest.payload.at("/check-request/data") + val data = requestPayloadActionProperty("data")?.first() + ?: throw BluePrintProcessorException("Failed to load payload data properties.") log.info("Data : ${data.asJsonString()}") diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/vnf-id.json b/components/model-catalog/resource-dictionary/starter-dictionary/vnf-id.json index 8445d29f4..a71bef975 100755 --- a/components/model-catalog/resource-dictionary/starter-dictionary/vnf-id.json +++ b/components/model-catalog/resource-dictionary/starter-dictionary/vnf-id.json @@ -13,25 +13,6 @@ "default": { "type": "source-default", "properties": {} - }, - "sdnc": { - "type": "source-rest", - "properties": { - "type": "JSON", - "url-path": "config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-id/vnf-data/vnf-topology/vnf-parameters-data/param/vnf-id", - "path": "/param/0/value", - "input-key-mapping": { - "service-instance-id": "service-instance-id", - "vnf-id": "vnf-id" - }, - "output-key-mapping": { - "vnf-id": "value" - }, - "key-dependencies": [ - "service-instance-id", - "vnf-id" - ] - } } } } diff --git a/components/model-catalog/resource-dictionary/starter-dictionary/vnf-name.json b/components/model-catalog/resource-dictionary/starter-dictionary/vnf-name.json index 675b754ee..7d6511935 100755 --- a/components/model-catalog/resource-dictionary/starter-dictionary/vnf-name.json +++ b/components/model-catalog/resource-dictionary/starter-dictionary/vnf-name.json @@ -18,18 +18,18 @@ "type": "source-rest", "properties": { "type": "JSON", - "url-path": "config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-name/vnf-data/vnf-topology/vnf-parameters-data/param/vnf-name", + "url-path": "config/GENERIC-RESOURCE-API:services/service/$service-instance-id/service-data/vnfs/vnf/$vnf-id/vnf-data/vnf-topology/vnf-parameters-data/param/vnf-name", "path": "/param/0/value", "input-key-mapping": { "service-instance-id": "service-instance-id", - "vnf-name": "vnf-name" + "vnf-id": "vnf-id" }, "output-key-mapping": { "vnf-name": "value" }, "key-dependencies": [ "service-instance-id", - "vnf-name" + "vnf-id" ] } }, diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/LoggingWebFilter.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/LoggingWebFilter.kt new file mode 100644 index 000000000..5ed5ff450 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/LoggingWebFilter.kt @@ -0,0 +1,39 @@ +/* + * 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.blueprintsprocessor.core.service.LoggingService +import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext +import org.springframework.stereotype.Component +import org.springframework.web.server.ServerWebExchange +import org.springframework.web.server.WebFilter +import org.springframework.web.server.WebFilterChain +import reactor.core.publisher.Mono +import reactor.util.context.Context + +@Component +open class LoggingWebFilter : WebFilter { + override fun filter(serverWebExchange: ServerWebExchange, webFilterChain: WebFilterChain): Mono<Void> { + + val loggingService = LoggingService() + loggingService.entering(serverWebExchange.request) + val filterChain = webFilterChain.filter(serverWebExchange).subscriberContext( + Context.of(MDCContext, MDCContext())) + loggingService.exiting(serverWebExchange.request, serverWebExchange.response) + return filterChain + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties b/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties index e40ccba09..faabb80e7 100755 --- a/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties +++ b/ms/blueprintsprocessor/application/src/main/resources/application-dev.properties @@ -36,7 +36,7 @@ blueprintsprocessor.blueprintArchivePath=blueprints/archive blueprintsprocessor.blueprintWorkingPath=blueprints/work # Controller Blueprint Load Configurations # blueprints.load.initial-data may be overridden by ENV variables -blueprintsprocessor.loadInitialData=true +blueprintsprocessor.loadInitialData=false blueprintsprocessor.loadBluePrint=false blueprintsprocessor.loadBluePrintPaths=./../../../components/model-catalog/blueprint-model/service-blueprint blueprintsprocessor.loadModelType=true diff --git a/ms/blueprintsprocessor/application/src/main/resources/logback.xml b/ms/blueprintsprocessor/application/src/main/resources/logback.xml index 6f917096d..9d2b82f25 100644 --- a/ms/blueprintsprocessor/application/src/main/resources/logback.xml +++ b/ms/blueprintsprocessor/application/src/main/resources/logback.xml @@ -15,11 +15,16 @@ --> <configuration> + + <property name="localPattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/> + <property name="defaultPattern" + value="%date{ISO8601,UTC}|%X{RequestID}|%X{InvocationID}|%thread|%X{ServiceName}|%X{ClientIPAddress}|%logger{50}| %msg%n"/> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> - <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> + <pattern>${defaultPattern}</pattern> </encoder> </appender> diff --git a/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/BluePrintMessageLibData.kt b/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/BluePrintMessageLibData.kt index ab04054fe..1cd8a2af7 100644 --- a/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/BluePrintMessageLibData.kt +++ b/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/BluePrintMessageLibData.kt @@ -36,6 +36,7 @@ open class KafkaMessageConsumerProperties : MessageConsumerProperties() { var clientId: String? = null var topic: String? = null var pollMillSec: Long = 1000 + var pollRecords: Int = -1 } open class KafkaBasicAuthMessageConsumerProperties : KafkaMessageConsumerProperties() diff --git a/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageConsumerService.kt b/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageConsumerService.kt index 5a9e61bfd..b5d444a49 100644 --- a/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageConsumerService.kt +++ b/ms/blueprintsprocessor/modules/commons/message-lib/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/KafkaBasicAuthMessageConsumerService.kt @@ -51,6 +51,10 @@ class KafkaBasicAuthMessageConsumerService( if (messageConsumerProperties.clientId != null) { configProperties[ConsumerConfig.CLIENT_ID_CONFIG] = messageConsumerProperties.clientId!! } + /** To handle Back pressure, Get only configured record for processing */ + if (messageConsumerProperties.pollRecords > 0) { + configProperties[ConsumerConfig.MAX_POLL_RECORDS_CONFIG] = messageConsumerProperties.pollRecords + } // TODO("Security Implementation based on type") /** add or override already set properties */ additionalConfig?.let { configProperties.putAll(it) } @@ -84,6 +88,7 @@ class KafkaBasicAuthMessageConsumerService( kafkaConsumer!!.use { kc -> while (keepGoing) { val consumerRecords = kc.poll(Duration.ofMillis(messageConsumerProperties.pollMillSec)) + log.info("Consumed Records : ${consumerRecords.count()}") runBlocking { consumerRecords?.forEach { consumerRecord -> /** execute the command block */ diff --git a/ms/blueprintsprocessor/modules/commons/message-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/BlueprintMessageConsumerServiceTest.kt b/ms/blueprintsprocessor/modules/commons/message-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/BlueprintMessageConsumerServiceTest.kt index 2b84eaa78..f4e85a94b 100644 --- a/ms/blueprintsprocessor/modules/commons/message-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/BlueprintMessageConsumerServiceTest.kt +++ b/ms/blueprintsprocessor/modules/commons/message-lib/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/message/service/BlueprintMessageConsumerServiceTest.kt @@ -52,6 +52,7 @@ import kotlin.test.assertTrue "blueprintsprocessor.messageconsumer.sample.topic=default-topic", "blueprintsprocessor.messageconsumer.sample.clientId=default-client-id", "blueprintsprocessor.messageconsumer.sample.pollMillSec=10", + "blueprintsprocessor.messageconsumer.sample.pollRecords=1", "blueprintsprocessor.messageproducer.sample.type=kafka-basic-auth", "blueprintsprocessor.messageproducer.sample.bootstrapServers=127.0.0.1:9092", @@ -129,11 +130,11 @@ open class BlueprintMessageConsumerServiceTest { .blueprintMessageProducerService("sample") as KafkaBasicAuthMessageProducerService launch { repeat(5) { - delay(1000) + delay(100) blueprintMessageProducerService.sendMessage("this is my message($it)") } } - delay(10000) + delay(5000) blueprintMessageConsumerService.shutDown() } } diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/LoggerExtensions.kt b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/LoggerExtensions.kt new file mode 100644 index 000000000..cdf6ce195 --- /dev/null +++ b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/LoggerExtensions.kt @@ -0,0 +1,45 @@ +/* + * 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.core + +import kotlinx.coroutines.* +import kotlinx.coroutines.reactor.ReactorContext +import kotlinx.coroutines.reactor.asCoroutineContext +import org.onap.ccsdk.cds.blueprintsprocessor.core.service.MonoMDCCoroutine +import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext +import reactor.core.publisher.Mono +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +/** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */ +@UseExperimental(InternalCoroutinesApi::class) +fun <T> monoMdc(context: CoroutineContext = EmptyCoroutineContext, + block: suspend CoroutineScope.() -> T?): Mono<T> = Mono.create { sink -> + + val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext()) + ?: sink.currentContext()).asCoroutineContext() + /** Populate MDC context only if present in Reactor Context */ + val newContext = if (!reactorContext.context.isEmpty + && reactorContext.context.hasKey(MDCContext)) { + val mdcContext = reactorContext.context.get<MDCContext>(MDCContext) + GlobalScope.newCoroutineContext(context + reactorContext + mdcContext) + } else GlobalScope.newCoroutineContext(context + reactorContext) + + val coroutine = MonoMDCCoroutine(newContext, sink) + sink.onDispose(coroutine) + coroutine.start(CoroutineStart.DEFAULT, coroutine, block) +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/service/BluePrintProcessorLoggingService.kt b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/service/BluePrintProcessorLoggingService.kt new file mode 100644 index 000000000..4da7dcd0e --- /dev/null +++ b/ms/blueprintsprocessor/modules/commons/processor-core/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/core/service/BluePrintProcessorLoggingService.kt @@ -0,0 +1,109 @@ +/* + * 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.core.service + +import kotlinx.coroutines.AbstractCoroutine +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.handleCoroutineException +import org.onap.ccsdk.cds.controllerblueprints.core.logger +import org.slf4j.MDC +import org.springframework.http.server.reactive.ServerHttpRequest +import org.springframework.http.server.reactive.ServerHttpResponse +import reactor.core.Disposable +import reactor.core.publisher.MonoSink +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.util.* +import kotlin.coroutines.CoroutineContext + +class LoggingService { + private val log = logger(LoggingService::class) + + companion object { + const val ONAP_REQUEST_ID = "X-ONAP-RequestID" + const val ONAP_INVOCATION_ID = "X-ONAP-InvocationID" + const val ONAP_PARTNER_NAME = "X-ONAP-PartnerName" + } + + fun entering(request: ServerHttpRequest) { + val headers = request.headers + val requestID = defaultToUUID(headers.getFirst(ONAP_REQUEST_ID)) + val invocationID = defaultToUUID(headers.getFirst(ONAP_INVOCATION_ID)) + val partnerName = defaultToEmpty(headers.getFirst(ONAP_PARTNER_NAME)) + MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT)) + MDC.put("RequestID", requestID) + MDC.put("InvocationID", invocationID) + MDC.put("PartnerName", partnerName) + MDC.put("ClientIPAddress", defaultToEmpty(request.remoteAddress?.address?.hostAddress)) + MDC.put("ServerFQDN", defaultToEmpty(request.remoteAddress?.hostString)) + if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) { + MDC.put("ServiceName", request.uri.path) + } + } + + fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) { + try { + val reqHeaders = request.headers + val resHeaders = response.headers + resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID") + resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID") + } catch (e: Exception) { + log.warn("couldn't set response headers", e) + } finally { + MDC.clear() + } + } + + private fun defaultToEmpty(input: Any?): String { + return input?.toString() ?: "" + } + + private fun defaultToUUID(input: String?): String { + return input ?: UUID.randomUUID().toString() + } +} + + +@InternalCoroutinesApi +class MonoMDCCoroutine<in T>( + parentContext: CoroutineContext, + private val sink: MonoSink<T> +) : AbstractCoroutine<T>(parentContext, true), Disposable { + private var disposed = false + + override fun onCompleted(value: T) { + if (!disposed) { + if (value == null) sink.success() else sink.success(value) + } + } + + override fun onCancelled(cause: Throwable, handled: Boolean) { + if (!disposed) { + sink.error(cause) + } else if (!handled) { + handleCoroutineException(context, cause) + } + } + + override fun dispose() { + disposed = true + cancel() + } + + override fun isDisposed(): Boolean = disposed +} 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 4d13486c3..a6bff7051 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 @@ -19,7 +19,7 @@ package org.onap.ccsdk.cds.blueprintsprocessor.designer.api import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiParam -import kotlinx.coroutines.runBlocking +import org.onap.ccsdk.cds.blueprintsprocessor.core.monoMdc import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.domain.BlueprintModelSearch import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.handler.BluePrintModelHandler import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException @@ -29,6 +29,7 @@ import org.springframework.http.ResponseEntity import org.springframework.http.codec.multipart.FilePart import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* +import reactor.core.publisher.Mono /** * BlueprintModelController Purpose: Handle controllerBlueprint API request @@ -44,7 +45,7 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint @ResponseBody @Throws(BluePrintException::class) @PreAuthorize("hasRole('USER')") - fun saveBlueprint(@RequestPart("file") filePart: FilePart): BlueprintModelSearch = runBlocking { + fun saveBlueprint(@RequestPart("file") filePart: FilePart): Mono<BlueprintModelSearch> = monoMdc { bluePrintModelHandler.saveBlueprintModel(filePart) } @@ -67,8 +68,9 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint @Throws(BluePrintException::class) @PreAuthorize("hasRole('USER')") fun getBlueprintByNameAndVersion(@PathVariable(value = "name") name: String, - @PathVariable(value = "version") version: String): BlueprintModelSearch { - return this.bluePrintModelHandler.getBlueprintModelSearchByNameAndVersion(name, version) + @PathVariable(value = "version") version: String) + : Mono<BlueprintModelSearch> = monoMdc { + bluePrintModelHandler.getBlueprintModelSearchByNameAndVersion(name, version) } @GetMapping("/download/by-name/{name}/version/{version}", produces = [MediaType.APPLICATION_JSON_VALUE]) @@ -76,8 +78,9 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint @Throws(BluePrintException::class) @PreAuthorize("hasRole('USER')") fun downloadBlueprintByNameAndVersion(@PathVariable(value = "name") name: String, - @PathVariable(value = "version") version: String): ResponseEntity<Resource> { - return this.bluePrintModelHandler.downloadBlueprintModelFileByNameAndVersion(name, version) + @PathVariable(value = "version") version: String) + : Mono<ResponseEntity<Resource>> = monoMdc { + bluePrintModelHandler.downloadBlueprintModelFileByNameAndVersion(name, version) } @GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE]) @@ -92,8 +95,8 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint @ResponseBody @Throws(BluePrintException::class) @PreAuthorize("hasRole('USER')") - fun downloadBluePrint(@PathVariable(value = "id") id: String): ResponseEntity<Resource> { - return this.bluePrintModelHandler.downloadBlueprintModelFile(id) + fun downloadBluePrint(@PathVariable(value = "id") id: String): Mono<ResponseEntity<Resource>> = monoMdc { + bluePrintModelHandler.downloadBlueprintModelFile(id) } @PostMapping("/enrich", produces = [MediaType.APPLICATION_JSON_VALUE], consumes = [MediaType @@ -101,7 +104,7 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint @ResponseBody @Throws(BluePrintException::class) @PreAuthorize("hasRole('USER')") - fun enrichBlueprint(@RequestPart("file") file: FilePart): ResponseEntity<Resource> = runBlocking { + fun enrichBlueprint(@RequestPart("file") file: FilePart): Mono<ResponseEntity<Resource>> = monoMdc { bluePrintModelHandler.enrichBlueprint(file) } @@ -109,7 +112,7 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint @ResponseBody @Throws(BluePrintException::class) @PreAuthorize("hasRole('USER')") - fun publishBlueprint(@RequestPart("file") file: FilePart): BlueprintModelSearch = runBlocking { + fun publishBlueprint(@RequestPart("file") file: FilePart): Mono<BlueprintModelSearch> = monoMdc { bluePrintModelHandler.publishBlueprint(file) } @@ -128,7 +131,7 @@ open class BlueprintModelController(private val bluePrintModelHandler: BluePrint fun deleteBlueprint(@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) = runBlocking { + @PathVariable(value = "version") version: String) = monoMdc { bluePrintModelHandler.deleteBlueprintModel(name, version) } } 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 4441d2b4b..f14f61e60 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 @@ -17,27 +17,29 @@ package org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api -import com.fasterxml.jackson.databind.JsonNode import io.swagger.annotations.Api import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiParam -import kotlinx.coroutines.runBlocking import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ACTION_MODE_ASYNC import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceInput import org.onap.ccsdk.cds.blueprintsprocessor.core.api.data.ExecutionServiceOutput +import org.onap.ccsdk.cds.blueprintsprocessor.core.monoMdc 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.logger import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.* +import reactor.core.publisher.Mono @RestController @RequestMapping("/api/v1/execution-service") @Api(value = "/api/v1/execution-service", description = "Interaction with CBA.") open class ExecutionServiceController { + val log = logger(ExecutionServiceController::class) @Autowired lateinit var executionServiceHandler: ExecutionServiceHandler @@ -47,7 +49,8 @@ open class ExecutionServiceController { produces = [MediaType.APPLICATION_JSON_VALUE]) @ResponseBody @ApiOperation(value = "Health Check", hidden = true) - fun executionServiceControllerHealthCheck(): JsonNode = runBlocking { + fun executionServiceControllerHealthCheck() = monoMdc { + log.info("Health check success...") "Success".asJsonPrimitive() } @@ -59,12 +62,13 @@ open class ExecutionServiceController { @ResponseBody @PreAuthorize("hasRole('USER')") fun process(@ApiParam(value = "ExecutionServiceInput payload.", required = true) - @RequestBody executionServiceInput: ExecutionServiceInput): ResponseEntity<ExecutionServiceOutput> = - runBlocking { - if (executionServiceInput.actionIdentifiers.mode == ACTION_MODE_ASYNC) { - throw IllegalStateException("Can't process async request through the REST endpoint. Use gRPC for async processing.") - } - val processResult = executionServiceHandler.doProcess(executionServiceInput) - ResponseEntity(processResult, determineHttpStatusCode(processResult.status.code)) - } + @RequestBody executionServiceInput: ExecutionServiceInput) + : Mono<ResponseEntity<ExecutionServiceOutput>> = monoMdc { + + if (executionServiceInput.actionIdentifiers.mode == ACTION_MODE_ASYNC) { + throw IllegalStateException("Can't process async request through the REST endpoint. Use gRPC for async processing.") + } + val processResult = executionServiceHandler.doProcess(executionServiceInput) + ResponseEntity(processResult, determineHttpStatusCode(processResult.status.code)) + } } diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/AbstractComponentFunction.kt b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/AbstractComponentFunction.kt index 5163a93ac..bee919249 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/AbstractComponentFunction.kt +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/AbstractComponentFunction.kt @@ -167,9 +167,9 @@ abstract class AbstractComponentFunction : BlueprintFunctionNode<ExecutionServic */ fun requestPayloadActionProperty(expression: String?): JsonNode? { val requestExpression = if (expression.isNullOrBlank()) { - "$operationName-request" + "$workflowName-request" } else { - "$operationName-request.$expression" + "$workflowName-request.$expression" } return executionServiceInput.payload.jsonPathParse(".$requestExpression") } 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 a16c52069..062d370bc 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 @@ -54,6 +54,7 @@ class ComponentFunctionScriptingService(private val applicationContext: Applicat scriptComponent.operationName = componentFunction.operationName scriptComponent.nodeTemplateName = componentFunction.nodeTemplateName scriptComponent.operationInputs = componentFunction.operationInputs + scriptComponent.executionServiceInput = componentFunction.executionServiceInput scriptComponent.scriptType = scriptType // Populate Instance Properties diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionService.kt b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionService.kt index 07848164e..adb1d67d2 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionService.kt +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionService.kt @@ -20,15 +20,14 @@ import com.fasterxml.jackson.databind.JsonNode import com.github.marcoferrer.krotoplus.coroutines.client.ClientBidiCallChannel import com.github.marcoferrer.krotoplus.coroutines.client.clientCallBidiStreaming import io.grpc.ManagedChannel -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.launch import org.onap.ccsdk.cds.blueprintsprocessor.grpc.GrpcClientProperties import org.onap.ccsdk.cds.blueprintsprocessor.grpc.service.BluePrintGrpcClientService import org.onap.ccsdk.cds.blueprintsprocessor.grpc.service.BluePrintGrpcLibPropertyService +import org.onap.ccsdk.cds.controllerblueprints.common.api.EventType import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException import org.onap.ccsdk.cds.controllerblueprints.core.logger import org.onap.ccsdk.cds.controllerblueprints.processing.api.BluePrintProcessingServiceGrpc @@ -39,11 +38,13 @@ import org.springframework.stereotype.Service interface StreamingRemoteExecutionService<ReqT, ResT> { - suspend fun openSubscription(selector: Any, requestId: String): Flow<ResT> + suspend fun openSubscription(selector: Any, txId: String): Flow<ResT> - suspend fun send(input: ReqT) + suspend fun sendNonInteractive(selector: Any, txId: String, input: ReqT, timeOutMill: Long): ResT - suspend fun cancelSubscription(requestId: String) + suspend fun send(txId: String, input: ReqT) + + suspend fun cancelSubscription(txId: String) suspend fun closeChannel(selector: Any) } @@ -63,60 +64,90 @@ class StreamingRemoteExecutionServiceImpl(private val bluePrintGrpcLibPropertySe /** - * Open new channel to send and receive for grpc properties [selector] for [requestId], + * Open new channel to send and receive for grpc properties [selector] for [txId], * Create the only one GRPC channel per host port and reuse for further communication. * Create request communication channel to send and receive requests and responses. - * We can send multiple request with same requestId with unique subRequestId. + * We can send multiple request with same txId. * Consume the flow for responses, * Client should cancel the subscription for the request Id once no longer response is needed. * */ @FlowPreview - override suspend fun openSubscription(selector: Any, requestId: String): Flow<ExecutionServiceOutput> { + override suspend fun openSubscription(selector: Any, txId: String): Flow<ExecutionServiceOutput> { - if (!commChannels.containsKey(requestId)) { + if (!commChannels.containsKey(txId)) { /** Get GRPC Channel*/ val grpcChannel = grpcChannel(selector) /** Get Send and Receive Channel for bidirectional process method*/ val channels = clientCallBidiStreaming(BluePrintProcessingServiceGrpc.getProcessMethod(), grpcChannel) - commChannels[requestId] = channels + commChannels[txId] = channels } - val commChannel = commChannels[requestId] - ?: throw BluePrintException("failed to create response subscription for request($requestId) channel") + val commChannel = commChannels[txId] + ?: throw BluePrintException("failed to create response subscription for transactionId($txId) channel") - log.info("created subscription for request($requestId)") + log.info("created subscription for transactionId($txId)") return commChannel.responseChannel.consumeAsFlow() } /** - * Send the [input]request, by reusing same GRPC channel and Communication channel + * Send the [input] request, by reusing same GRPC channel and Communication channel * for the request Id. */ - override suspend fun send(input: ExecutionServiceInput) { - val requestId = input.commonHeader.requestId - val subRequestId = input.commonHeader.subRequestId - val sendChannel = commChannels[requestId]?.requestChannel - ?: throw BluePrintException("failed to get request($requestId) send channel") + override suspend fun send(txId: String, input: ExecutionServiceInput) { + val sendChannel = commChannels[txId]?.requestChannel + ?: throw BluePrintException("failed to get transactionId($txId) send channel") coroutineScope { launch { sendChannel.send(input) - log.trace("Message sent for request($requestId) : subRequest($subRequestId)") + log.trace("Message sent for transactionId($txId)") + } + } + } + + /** + * Simplified version of Streaming calls, Use this API only listing for actual response for [input] + * for the GRPC [selector] with execution [timeOutMill]. + * Other state of the request will be skipped. + * The assumption here is you can call this API with same request Id and unique subrequest Id, + * so the correlation is sub request id to receive the response. + */ + @ExperimentalCoroutinesApi + override suspend fun sendNonInteractive(selector: Any, txId: String, input: ExecutionServiceInput, timeOutMill: Long) + : ExecutionServiceOutput { + + var output: ExecutionServiceOutput? = null + val flow = openSubscription(selector, txId) + + /** Send the request */ + val sendChannel = commChannels[txId]?.requestChannel + ?: throw BluePrintException("failed to get transactionId($txId) send channel") + sendChannel.send(input) + + /** Receive the response with timeout */ + withTimeout(timeOutMill) { + flow.collect { + log.trace("Received non-interactive transactionId($txId) response : ${it.status.eventType}") + if (it.status.eventType == EventType.EVENT_COMPONENT_EXECUTED) { + output = it + cancelSubscription(txId) + } } } + return output!! } - /** Cancel the Subscription for the [requestId], This closes communication channel **/ + /** Cancel the Subscription for the [txId], This closes communication channel **/ @ExperimentalCoroutinesApi - override suspend fun cancelSubscription(requestId: String) { - commChannels[requestId]?.let { + override suspend fun cancelSubscription(txId: String) { + commChannels[txId]?.let { if (!it.requestChannel.isClosedForSend) it.requestChannel.close() /** If receive channel has to close immediately, once the subscription has cancelled, then enable this */ //it.responseChannel.cancel(CancellationException("subscription cancelled")) - commChannels.remove(requestId) - log.info("closed subscription for request($requestId)") + commChannels.remove(txId) + log.info("closed subscription for transactionId($txId)") } } diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionServiceTest.kt b/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionServiceTest.kt index c9ff23573..29d24c6ad 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionServiceTest.kt +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/StreamingRemoteExecutionServiceTest.kt @@ -36,6 +36,8 @@ import org.onap.ccsdk.cds.controllerblueprints.common.api.EventType import org.onap.ccsdk.cds.controllerblueprints.core.logger import org.onap.ccsdk.cds.controllerblueprints.processing.api.ExecutionServiceInput import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull class StreamingRemoteExecutionServiceTest { @@ -69,25 +71,48 @@ class StreamingRemoteExecutionServiceTest { val spyStreamingRemoteExecutionService = spyk(streamingRemoteExecutionService) /** To test with real server, uncomment below line */ - coEvery { spyStreamingRemoteExecutionService.createGrpcChannel(any()) } returns channel + coEvery() { spyStreamingRemoteExecutionService.createGrpcChannel(any()) } returns channel + + /** Test Send and Receive non interactive transaction */ + val nonInteractiveDeferred = arrayListOf<Deferred<*>>() + repeat(2) { count -> + val requestId = "1234-$count" + val request = getRequest(requestId) + val invocationId = request.commonHeader.subRequestId + val deferred = async { + val response = spyStreamingRemoteExecutionService.sendNonInteractive(tokenAuthGrpcClientProperties, + invocationId, request, 1000L) + assertNotNull(response, "failed to get non interactive response") + assertEquals(response.commonHeader.requestId, requestId, + "failed to match non interactive response id") + assertEquals(response.status.eventType, EventType.EVENT_COMPONENT_EXECUTED, + "failed to match non interactive response type") + } + nonInteractiveDeferred.add(deferred) - val responseFlowsDeferred = arrayListOf<Deferred<*>>() + } + nonInteractiveDeferred.awaitAll() - repeat(1) { count -> + /** Test Send and Receive interactive transaction */ + val responseFlowsDeferred = arrayListOf<Deferred<*>>() + repeat(2) { count -> val requestId = "12345-$count" - val responseFlow = spyStreamingRemoteExecutionService.openSubscription(tokenAuthGrpcClientProperties, requestId) + val request = getRequest(requestId) + val invocationId = request.commonHeader.requestId + val responseFlow = spyStreamingRemoteExecutionService + .openSubscription(tokenAuthGrpcClientProperties, invocationId) val deferred = async { responseFlow.collect { - log.info("Received $count-response (${it.commonHeader.subRequestId}) : ${it.status.eventType}") + log.info("Received $count-response ($invocationId) : ${it.status.eventType}") if (it.status.eventType == EventType.EVENT_COMPONENT_EXECUTED) { - spyStreamingRemoteExecutionService.cancelSubscription(it.commonHeader.requestId) + spyStreamingRemoteExecutionService.cancelSubscription(invocationId) } } } responseFlowsDeferred.add(deferred) /** Sending Multiple messages with same requestId and different subRequestId */ - spyStreamingRemoteExecutionService.send(getRequest(requestId)) + spyStreamingRemoteExecutionService.send(invocationId, request) } responseFlowsDeferred.awaitAll() streamingRemoteExecutionService.closeChannel(tokenAuthGrpcClientProperties) diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/scripts/AbstractComponentFunctionTest.kt b/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/scripts/AbstractComponentFunctionTest.kt index 16e4e611b..2fc9a993b 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/scripts/AbstractComponentFunctionTest.kt +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/services/execution/scripts/AbstractComponentFunctionTest.kt @@ -95,7 +95,7 @@ class AbstractComponentFunctionTest { @Test fun testComponentFunctionPayload() { val sampleComponent = SampleComponent() - sampleComponent.operationName = "sample-action" + sampleComponent.workflowName = "sample-action" sampleComponent.executionServiceInput = JacksonUtils.readValueFromClassPathFile( "payload/requests/sample-execution-request.json", ExecutionServiceInput::class.java)!! val payload = sampleComponent.requestPayload() diff --git a/ms/blueprintsprocessor/modules/services/execution-service/src/test/resources/logback-test.xml b/ms/blueprintsprocessor/modules/services/execution-service/src/test/resources/logback-test.xml index 703a52642..afe10b39d 100644 --- a/ms/blueprintsprocessor/modules/services/execution-service/src/test/resources/logback-test.xml +++ b/ms/blueprintsprocessor/modules/services/execution-service/src/test/resources/logback-test.xml @@ -19,7 +19,7 @@ <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> - <pattern>%d{HH:mm:ss.SSS} %-5level %logger{100} - %msg%n</pattern> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> diff --git a/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/MDCContext.kt b/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/MDCContext.kt new file mode 100644 index 000000000..001ec751e --- /dev/null +++ b/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/MDCContext.kt @@ -0,0 +1,50 @@ +/* + * 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.controllerblueprints.core + +import kotlinx.coroutines.ThreadContextElement +import org.slf4j.MDC +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +typealias MDCContextMap = Map<String, String>? + +class MDCContext(private val contextMap: MDCContextMap = MDC.getCopyOfContextMap()) : + ThreadContextElement<MDCContextMap>, AbstractCoroutineContextElement(Key) { + /** + * Key of [MDCContext] in [CoroutineContext]. + */ + companion object Key : CoroutineContext.Key<MDCContext> + + override fun updateThreadContext(context: CoroutineContext): MDCContextMap { + val oldState = MDC.getCopyOfContextMap() + setCurrent(contextMap) + return oldState + } + + override fun restoreThreadContext(context: CoroutineContext, oldState: MDCContextMap) { + setCurrent(oldState) + } + + private fun setCurrent(contextMap: MDCContextMap) { + if (contextMap == null) { + MDC.clear() + } else { + MDC.setContextMap(contextMap) + } + } +}
\ No newline at end of file diff --git a/ms/controllerblueprints/modules/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/MDCContextTest.kt b/ms/controllerblueprints/modules/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/MDCContextTest.kt new file mode 100644 index 000000000..2ddb4503d --- /dev/null +++ b/ms/controllerblueprints/modules/blueprint-core/src/test/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/MDCContextTest.kt @@ -0,0 +1,56 @@ +/* + * 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.controllerblueprints.core + +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Before +import org.slf4j.MDC +import kotlin.test.Test +import kotlin.test.assertEquals + +class MDCContextTest { + val log = logger(MDCContextTest::class) + @Before + fun setup() { + MDC.clear() + } + + @After + fun tearDow() { + MDC.clear() + } + + @Test + fun testContextCanBePassedBetweenCoroutines() { + MDC.put(BluePrintConstants.RESPONSE_HEADER_TRANSACTION_ID, "12345") + runBlocking { + GlobalScope.launch { + assertEquals(null, MDC.get(BluePrintConstants.RESPONSE_HEADER_TRANSACTION_ID)) + } + launch(MDCContext()) { + assertEquals("12345", MDC.get(BluePrintConstants.RESPONSE_HEADER_TRANSACTION_ID), + "couldn't get request id") + + MDC.put("client_id", "client-1") + assertEquals("client-1", MDC.get("client_id"), "couldn't get client id") + } + } + } +}
\ No newline at end of file diff --git a/ms/py-executor/requirements.txt b/ms/py-executor/requirements.txt index 6051e04c1..b55636918 100644 --- a/ms/py-executor/requirements.txt +++ b/ms/py-executor/requirements.txt @@ -2,5 +2,4 @@ grpcio==1.23.0 grpcio-tools==1.23.0 configparser==4.0.2 requests==2.22.0 -ncclient==0.6.6 -ciscoconfparse==1.4.7
\ No newline at end of file +ncclient==0.6.6
\ No newline at end of file |