| /* |
| * 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); |
| } |
| } |