[CORD-2742] Remove elements from the service graph
Change-Id: Ibcb9fac4428f0b168ff66fab3219b8357e732dc5
diff --git a/conf/browsersync.conf.js b/conf/browsersync.conf.js
index ff3bb4f..23dee02 100644
--- a/conf/browsersync.conf.js
+++ b/conf/browsersync.conf.js
@@ -18,6 +18,7 @@
const conf = require('./gulp.conf');
const proxy = require('./proxy').proxy;
+const wsProxy = require('./proxy').wsProxy;
module.exports = function () {
return {
@@ -29,11 +30,15 @@
middleware: function(req, res, next) {
if (
req.url.indexOf('xosapi') !== -1
- || req.url.indexOf('socket.io') !== -1
|| req.url.indexOf('extensions') !== -1
) {
proxy.web(req, res);
}
+ else if (
+ req.url.indexOf('socket.io') !== -1
+ ) {
+ wsProxy.web(req, res);
+ }
else {
next();
}
diff --git a/conf/proxy.js b/conf/proxy.js
index ad94635..387bd57 100644
--- a/conf/proxy.js
+++ b/conf/proxy.js
@@ -19,17 +19,27 @@
const httpProxy = require('http-proxy');
const target = process.env.PROXY || '127.0.0.1:9101';
+const wsTarget = process.env.WS || '127.0.0.1:3000';
const proxy = httpProxy.createProxyServer({
target: `http://${target}`,
ws: true
});
-
proxy.on('error', function(error, req, res) {
res.writeHead(500, {'Content-Type': 'text/plain'});
console.error('[Proxy]', error);
});
+const wsProxy = httpProxy.createProxyServer({
+ target: `http://${wsTarget}`,
+ ws: true
+});
+wsProxy.on('error', function(error, req, res) {
+ res.writeHead(500, {'Content-Type': 'text/plain'});
+ console.error('[Proxy]', error);
+});
+
module.exports = {
- proxy
+ proxy,
+ wsProxy
};
\ No newline at end of file
diff --git a/src/app/datasources/stores/model.store.ts b/src/app/datasources/stores/model.store.ts
index 09bd715..4f430e6 100644
--- a/src/app/datasources/stores/model.store.ts
+++ b/src/app/datasources/stores/model.store.ts
@@ -68,7 +68,7 @@
this.efficientNext(this._collections[modelName]);
}
- // NOTE do we need to subscriber every time we query?
+ // NOTE do we need to subscribe every time we query?
this.webSocket.list()
.filter((e: IWSEvent) => e.model === modelName)
.subscribe(
diff --git a/src/app/service-graph/services/graph.store.spec.ts b/src/app/service-graph/services/graph.store.spec.ts
index 53f24fc..ad67f12 100644
--- a/src/app/service-graph/services/graph.store.spec.ts
+++ b/src/app/service-graph/services/graph.store.spec.ts
@@ -22,6 +22,7 @@
import {Subject} from 'rxjs/Subject';
import {Graph} from 'graphlib';
import {XosDebouncer} from '../../core/services/helpers/debounce.helper';
+import {IWSEvent} from '../../datasources/websocket/global';
interface ITestXosGraphStore extends IXosGraphStore {
@@ -94,6 +95,7 @@
const subject_servicedependency = new Subject();
const subject_serviceinstances = new Subject();
const subject_serviceinstancelinks = new Subject();
+const subject_websocket = new Subject();
let MockModelStore = {
query: jasmine.createSpy('XosModelStore.query')
@@ -113,6 +115,11 @@
})
};
+let MockWebSocket = {
+ list: jasmine.createSpy('WebSocket.list')
+ .and.returnValue(subject_websocket)
+};
+
describe('The XosGraphStore service', () => {
@@ -120,6 +127,7 @@
angular.module('XosGraphStore', [])
.service('XosGraphStore', XosGraphStore)
.value('XosModelStore', MockModelStore)
+ .value('WebSocket', MockWebSocket)
.service('XosDebouncer', XosDebouncer);
angular.mock.module('XosGraphStore');
@@ -134,25 +142,128 @@
}));
- it('should load services and service-dependency and add nodes to the graph', (done) => {
- let event = 0;
- service.get().subscribe(
- (graph: Graph) => {
- if (event === 1) {
- expect(graph.nodes().length).toBe(services.length);
- expect(graph.nodes()).toEqual(['service~1', 'service~2']);
- expect(graph.edges().length).toBe(servicedependencies.length);
- expect(graph.edges()).toEqual([{v: 'service~1', w: 'service~2'}]);
- done();
+ describe('when started', () => {
+
+ let subscription;
+
+ it('should load services and service-dependency and add nodes to the graph', (done) => {
+ let event = 0;
+ subscription = service.get().subscribe(
+ (graph: Graph) => {
+ if (event === 1) {
+ expect(graph.nodes().length).toBe(services.length);
+ expect(graph.nodes()).toEqual(['service~1', 'service~2']);
+ expect(graph.edges().length).toBe(servicedependencies.length);
+ expect(graph.edges()).toEqual([{v: 'service~1', w: 'service~2'}]);
+ done();
+ }
+ else {
+ event = event + 1;
+ }
}
- else {
- event = event + 1;
+ );
+ subject_services.next(services);
+ subject_servicedependency.next(servicedependencies);
+ scope.$apply();
+ });
+
+ afterEach(() => {
+ subscription.unsubscribe();
+ });
+ });
+
+ describe('when an observed model is removed', () => {
+
+ let subscription;
+
+ beforeEach(() => {
+ subject_services.next([]);
+ subject_servicedependency.next([]);
+ subject_services.next(services);
+ subject_servicedependency.next(servicedependencies);
+ service.addServiceInstances();
+ subject_serviceinstances.next([serviceInstances[0]]);
+ });
+
+ it('should be removed from the graph', (done) => {
+ let event = 0;
+ subscription = service.get().subscribe(
+ (graph: Graph) => {
+ if (event === 1) {
+ expect(graph.nodes().length).toBe(0);
+ expect(graph.nodes()).toEqual([]);
+ expect(graph.edges().length).toBe(0);
+ expect(graph.edges()).toEqual([]);
+ done();
+ }
+ else {
+ event = event + 1;
+ }
}
- }
- );
- subject_services.next(services);
- subject_servicedependency.next(servicedependencies);
- scope.$apply();
+ );
+
+ const removeService1: IWSEvent = {
+ model: 'Service',
+ deleted: true,
+ msg: {
+ changed_fields: [],
+ pk: 1,
+ object: {
+ id: 1,
+ class_names: 'Service, XOSBase'
+ }
+ }
+ };
+
+ const removeService2: IWSEvent = {
+ model: 'Service',
+ deleted: true,
+ msg: {
+ changed_fields: [],
+ pk: 2,
+ object: {
+ id: 2,
+ class_names: 'Service, XOSBase'
+ }
+ }
+ };
+
+ const removeServiceDependency1: IWSEvent = {
+ model: 'ServiceDependency',
+ deleted: true,
+ msg: {
+ changed_fields: [],
+ pk: 1,
+ object: {
+ id: 1,
+ class_names: 'ServiceDependency, XOSBase'
+ }
+ }
+ };
+
+ const removeServiceInstnace1: IWSEvent = {
+ model: 'VSGServiceInstance',
+ deleted: true,
+ msg: {
+ changed_fields: [],
+ pk: 1,
+ object: {
+ id: 1,
+ class_names: 'VSGServiceInstance,TenantWithContainer,ServiceInstance'
+ }
+ }
+ };
+
+ subject_websocket.next(removeService1);
+ subject_websocket.next(removeService2);
+ subject_websocket.next(removeServiceDependency1);
+ subject_websocket.next(removeServiceInstnace1);
+ scope.$apply();
+ });
+
+ afterEach(() => {
+ subscription.unsubscribe();
+ });
});
describe(`the getModelType`, () => {
diff --git a/src/app/service-graph/services/graph.store.ts b/src/app/service-graph/services/graph.store.ts
index 0dd6653..0edba0f 100644
--- a/src/app/service-graph/services/graph.store.ts
+++ b/src/app/service-graph/services/graph.store.ts
@@ -23,6 +23,7 @@
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import {IXosBaseModel, IXosSgLink, IXosSgNode} from '../interfaces';
+import {IWSEvent, IWSEventService} from '../../datasources/websocket/global';
export interface IXosGraphStore {
@@ -41,7 +42,8 @@
static $inject = [
'$log',
'XosModelStore',
- 'XosDebouncer'
+ 'XosDebouncer',
+ 'WebSocket'
];
// graphs
@@ -64,7 +66,8 @@
constructor (
private $log: ng.ILogService,
private XosModelStore: IXosModelStoreService,
- private XosDebouncer: IXosDebouncer
+ private XosDebouncer: IXosDebouncer,
+ private webSocket: IWSEventService,
) {
this.$log.info('[XosGraphStore] Setup');
@@ -72,6 +75,34 @@
this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
this.loadData();
+
+ // handle model deletion
+ this.webSocket.list()
+ .filter((e: IWSEvent) => {
+
+ const model = this.getModelType(e.msg.object);
+
+ switch (model) {
+ case 'service':
+ case 'serviceinstance':
+ case 'instance':
+ case 'network':
+ return true;
+ case 'servicedependency':
+ case 'serviceinstanceLink':
+ // NOTE ServiceDependency are considered links, they are automatically removed by the graph library
+ return false;
+ default:
+ return false;
+ }
+ })
+ .subscribe((event: IWSEvent) => {
+ if (event.deleted) {
+ const nodeId = this.getNodeId(event.msg.object);
+ this.serviceGraph.removeNode(nodeId);
+ this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
+ }
+ });
}
$onDestroy() {