blob: 52100e7abfad29c49e5debe29ec71424bcda565e [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as _ from 'lodash';
import {Graph} from 'graphlib';
import {IXosModelStoreService} from '../../datasources/stores/model.store';
import {IXosDebouncer} from '../../core/services/helpers/debounce.helper';
import {Subscription} from 'rxjs/Subscription';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import {IXosBaseModel, IXosSgLink, IXosSgNode} from '../interfaces';
export interface IXosGraphStore {
get(): Observable<Graph>;
nodesFromGraph(graph: Graph): IXosSgNode[];
linksFromGraph(graph: Graph): IXosSgLink[];
toggleServiceInstances(): Graph;
toggleInstances(): Graph;
toggleNetwork(): Graph;
}
export class XosGraphStore implements IXosGraphStore {
static $inject = [
'$log',
'XosModelStore',
'XosDebouncer'
];
// state
private serviceInstanceShown: boolean = false;
private instanceShown: boolean = false;
private networkShown: boolean = false;
// graphs
private serviceGraph: any;
private ServiceGraphSubject: BehaviorSubject<any>;
// datastore
private InstanceSubscription: Subscription;
private NetworkSubscription: Subscription;
private PortSubscription: Subscription;
private ServiceSubscription: Subscription;
private ServiceDependencySubscription: Subscription;
private ServiceInstanceSubscription: Subscription;
private ServiceInstanceLinkSubscription: Subscription;
private TenantWithContainerSubscription: Subscription;
// debounced
private efficientNext = this.XosDebouncer.debounce(this.callNext, 500, this, false);
constructor (
private $log: ng.ILogService,
private XosModelStore: IXosModelStoreService,
private XosDebouncer: IXosDebouncer
) {
this.$log.info('[XosGraphStore] Setup');
this.serviceGraph = new Graph();
this.ServiceGraphSubject = new BehaviorSubject(this.serviceGraph);
this.loadData();
}
$onDestroy() {
this.ServiceSubscription.unsubscribe();
this.ServiceDependencySubscription.unsubscribe();
}
public nodesFromGraph(graph: Graph): IXosSgNode[] {
return _.map(graph.nodes(), (n: string) => {
const nodeData = graph.node(n);
return {
id: n,
type: this.getModelType(nodeData),
data: nodeData
};
});
}
public linksFromGraph(graph: Graph): IXosSgLink[] {
const nodes = this.nodesFromGraph(graph);
return _.map(graph.edges(), l => {
const link = graph.edge(l);
const linkType = this.getModelType(link);
let sourceId, targetId;
switch (linkType) {
case 'servicedependency':
sourceId = this.getServiceId(link.subscriber_service_id);
targetId = this.getServiceId(link.provider_service_id);
break;
case 'serviceinstancelink':
// NOTE ServiceInstanceLink can actually also connect to a service and a network
sourceId = this.getServiceInstanceId(link.subscriber_service_instance_id);
targetId = this.getServiceInstanceId(link.provider_service_instance_id);
break;
case 'ownership':
sourceId = this.getServiceId(link.service);
targetId = this.getServiceInstanceId(link.service_instance);
break;
case 'instance_ownership':
sourceId = this.getServiceInstanceId(link.id);
targetId = this.getInstanceId(link.instance_id);
break;
case 'port':
sourceId = this.getInstanceId(link.instance_id);
targetId = this.getNetworkId(link.network_id);
break;
}
// NOTE help while debugging
if (!sourceId || !targetId) {
this.$log.warn(`Link ${l.v}-${l.w} has missing source or target:`, l, link);
// TODO return null and then filter out so that we don't break the rendering
}
return {
id: `${l.v}-${l.w}`,
type: this.getModelType(link),
source: _.findIndex(nodes, {id: sourceId}),
target: _.findIndex(nodes, {id: targetId}),
data: link
};
});
}
public toggleServiceInstances(): Graph {
if (this.serviceInstanceShown) {
// NOTE remove subscriptions
this.ServiceInstanceSubscription.unsubscribe();
this.ServiceInstanceLinkSubscription.unsubscribe();
// remove nodes from the graph
this.removeElementsFromGraph('serviceinstance'); // NOTE links are automatically removed by the graph library
if (this.instanceShown) {
// NOTE if we remove ServiceInstances we also need to remove Instances
this.removeElementsFromGraph('instance');
this.instanceShown = false;
}
if (this.networkShown) {
// NOTE if we remove ServiceInstances we also need to remove Networks
this.removeElementsFromGraph('network');
this.networkShown = false;
}
}
else {
// NOTE subscribe to ServiceInstance and ServiceInstanceLink observables
this.loadServiceInstances();
this.loadServiceInstanceLinks();
}
this.serviceInstanceShown = !this.serviceInstanceShown;
return this.serviceGraph;
}
public toggleInstances(): Graph {
if (this.instanceShown) {
this.InstanceSubscription.unsubscribe();
this.TenantWithContainerSubscription.unsubscribe();
this.removeElementsFromGraph('instance'); // NOTE links are automatically removed by the graph library
if (this.networkShown) {
// NOTE if we remove Instances we also need to remove Networks
this.removeElementsFromGraph('network');
this.networkShown = false;
}
}
else {
this.loadInstances();
this.loadInstanceLinks();
}
this.instanceShown = !this.instanceShown;
return this.serviceGraph;
}
public toggleNetwork() {
if (this.networkShown) {
this.NetworkSubscription.unsubscribe();
this.PortSubscription.unsubscribe();
this.removeElementsFromGraph('network');
}
else {
this.loadNetworks();
this.loadPorts(); // Ports define the connection of an Instance to a Network
}
this.networkShown = !this.networkShown;
return this.serviceGraph;
}
public get(): Observable<Graph> {
return this.ServiceGraphSubject.asObservable();
}
private loadData() {
this.loadServices();
this.loadServiceDependencies();
}
// graph operations
private addNode(node: IXosBaseModel) {
const nodeId = this.getNodeId(node);
this.serviceGraph.setNode(nodeId, node);
const nodeType = this.getModelType(node);
if (nodeType === 'serviceinstance') {
// NOTE adding owner link
this.addOwnershipEdge({
service: node.owner_id,
service_instance: node.id,
type: 'ownership'
});
}
}
private addEdge(link: IXosBaseModel) {
const linkType = this.getModelType(link);
if (linkType === 'servicedependency') {
const sourceId = this.getServiceId(link.subscriber_service_id);
const targetId = this.getServiceId(link.provider_service_id);
this.serviceGraph.setEdge(sourceId, targetId, link);
}
if (linkType === 'serviceinstancelink') {
// NOTE serviceinstancelink can point also to services, networks, ...
const sourceId = this.getServiceInstanceId(link.provider_service_instance_id);
if (angular.isDefined(link.subscriber_service_instance_id)) {
const targetId = this.getServiceInstanceId(link.subscriber_service_instance_id);
this.serviceGraph.setEdge(sourceId, targetId, link);
}
}
}
private addOwnershipEdge(link: any) {
const sourceId = this.getServiceInstanceId(link.service_instance);
const targetId = this.getServiceId(link.service);
this.serviceGraph.setEdge(sourceId, targetId, link);
}
private addInstanceOwner(tenantWithContainer: any) {
// NOTE some TenantWithContainer don't have an instance
if (tenantWithContainer.instance_id) {
const sourceId = this.getServiceInstanceId(tenantWithContainer.id);
const targetId = this.getInstanceId(tenantWithContainer.instance_id);
this.serviceGraph.setEdge(sourceId, targetId, angular.merge(tenantWithContainer, {type: 'instance_ownership'}));
}
}
private addNetworkLink(port: any) {
// ports are connected to 1 Instance and 1 Network
const sourceId = this.getInstanceId(port.instance_id);
const targetId = this.getNetworkId(port.network_id);
this.serviceGraph.setEdge(sourceId, targetId, angular.merge(port, {type: 'port'}));
}
private removeElementsFromGraph(type: string) {
_.forEach(this.serviceGraph.nodes(), (n: string) => {
const node = this.serviceGraph.node(n);
const nodeType = this.getModelType(node);
if (nodeType === type) {
this.serviceGraph.removeNode(n);
}
});
// NOTE update the observable
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
// helpers
private getModelType(node: IXosBaseModel): string {
if (node.type) {
// NOTE handling "ownership" links
return node.type;
}
return node.class_names.split(',')[0].toLowerCase();
}
private getServiceId(id: number): string {
return `service~${id}`;
}
private getServiceInstanceId(id: number): string {
return `serviceinstance~${id}`;
}
private getInstanceId(id: number): string {
return `instance~${id}`;
}
private getNetworkId(id: number): string {
return `network~${id}`;
}
private getNodeId(node: IXosBaseModel): string {
const nodeType = this.getModelType(node);
switch (nodeType) {
case 'service':
return this.getServiceId(node.id);
case 'serviceinstance':
return this.getServiceInstanceId(node.id);
case 'instance':
return this.getInstanceId(node.id);
case 'network':
return this.getNetworkId(node.id);
}
}
// data loaders
private loadServices() {
this.ServiceSubscription = this.XosModelStore.query('Service', '/core/services')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, n => {
this.addNode(n);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
}
);
}
private loadServiceDependencies() {
this.ServiceDependencySubscription = this.XosModelStore.query('ServiceDependency', '/core/servicedependencys')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, l => {
this.addEdge(l);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] Service Observable: `, err);
}
);
}
private loadServiceInstances() {
this.ServiceInstanceSubscription = this.XosModelStore.query('ServiceInstance', '/core/serviceinstances')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, n => {
this.addNode(n);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] ServiceInstance Observable: `, err);
}
);
}
private loadServiceInstanceLinks() {
this.ServiceInstanceLinkSubscription = this.XosModelStore.query('ServiceInstanceLink', '/core/serviceinstancelinks')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, l => {
this.addEdge(l);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] ServiceInstanceLinks Observable: `, err);
}
);
}
private loadInstances() {
this.InstanceSubscription = this.XosModelStore.query('Instance', '/core/instances')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, n => {
this.addNode(n);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
}
);
}
private loadInstanceLinks() {
this.TenantWithContainerSubscription = this.XosModelStore.query('TnantWithContainer', '/core/tenantwithcontainers')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, n => {
this.addInstanceOwner(n);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] Instance Observable: `, err);
}
);
}
private loadNetworks() {
this.NetworkSubscription = this.XosModelStore.query('Network', '/core/networks')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, n => {
this.addNode(n);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
}
);
}
private loadPorts() {
this.PortSubscription = this.XosModelStore.query('Port', '/core/ports')
.subscribe(
(res) => {
if (res.length > 0) {
_.forEach(res, n => {
this.addNetworkLink(n);
});
this.efficientNext(this.ServiceGraphSubject, this.serviceGraph);
}
},
(err) => {
this.$log.error(`[XosServiceGraphStore] Network Observable: `, err);
}
);
}
private callNext(subject: BehaviorSubject<any>, data: any) {
subject.next(data);
}
}