From ebda3c95e2ff975b34033de0640f0a137b8a1fa9 Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Fri, 14 Feb 2020 15:37:17 +0000 Subject: Support Tosca DependsOn root node relationship​ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tosca.nodes.Root supports the use of a DependsOn relationship to indicate a general dependency relationship between two nodes. SDC currently disallows this relationship to be created in the VF/Service composition view. Change-Id: Ie5f1f5d2e5173b59f878986ee625b27aaa81e4f6 Issue-ID: SDC-2768 Signed-off-by: andre.schmid --- .../composition/graph/composition-graph.module.ts | 2 +- .../graph/utils/composition-graph-general-utils.ts | 2 +- .../graph/utils/composition-graph-links-utils.ts | 2 +- .../graph/utils/composition-graph-palette-utils.ts | 2 +- .../app/ng2/pages/composition/graph/utils/index.ts | 2 +- .../match-capability-requierment-utils.spec.ts | 342 -------------------- .../utils/match-capability-requierment-utils.ts | 196 ------------ .../match-capability-requirement-utils.spec.ts | 345 +++++++++++++++++++++ .../utils/match-capability-requirement-utils.ts | 195 ++++++++++++ 9 files changed, 545 insertions(+), 543 deletions(-) delete mode 100644 catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts delete mode 100644 catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts create mode 100644 catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.spec.ts create mode 100644 catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.ts diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.module.ts index e58d160c4d..1a20b53bed 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.module.ts @@ -8,7 +8,7 @@ import {CommonGraphUtils} from "./common/common-graph-utils"; import {LinksFactory} from "app/models/graph/graph-links/links-factory"; import {NodesFactory} from "app/models/graph/nodes/nodes-factory"; import {ImageCreatorService} from "./common/image-creator.service"; -import {MatchCapabilitiesRequirementsUtils} from "./utils/match-capability-requierment-utils"; +import {MatchCapabilitiesRequirementsUtils} from "./utils/match-capability-requirement-utils"; import {CompositionGraphNodesUtils} from "./utils/composition-graph-nodes-utils"; import {ConnectionWizardService} from "app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service"; import {CompositionGraphPaletteUtils} from "./utils/composition-graph-palette-utils"; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts index bc8bd691c9..8b4a29414b 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts @@ -21,7 +21,7 @@ import * as _ from "lodash"; import {ComponentInstance, Match, CompositionCiLinkBase, CompositionCiNodeUcpeCp} from "app/models"; import {Dictionary, GraphUIObjects} from "app/utils"; -import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requirement-utils"; import {CommonGraphUtils} from "../common/common-graph-utils"; import {Injectable} from "@angular/core"; import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils"; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts index 6035d05b7f..ba35a98332 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts @@ -40,7 +40,7 @@ import { } from "app/models"; import {CommonGraphUtils} from "../common/common-graph-utils"; import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils"; -import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requirement-utils"; import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link"; import {Injectable} from "@angular/core"; import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils"; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts index 1776c2f9b9..922f19cb7f 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts @@ -34,7 +34,7 @@ import {CompositionService} from "../../composition.service"; import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service"; import { QueueServiceUtils } from "app/ng2/utils/queue-service-utils"; import {ComponentGenericResponse} from "../../../../services/responses/component-generic-response"; -import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requirement-utils"; import {CompositionGraphNodesUtils} from "./index"; @Injectable() diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts index e7f11af248..e1621d1a4c 100644 --- a/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts @@ -11,7 +11,7 @@ import {CompositionGraphGeneralUtils} from './composition-graph-general-utils'; import {CompositionGraphNodesUtils} from './composition-graph-nodes-utils'; -import {MatchCapabilitiesRequirementsUtils} from './match-capability-requierment-utils' +import {MatchCapabilitiesRequirementsUtils} from './match-capability-requirement-utils' import {CompositionGraphPaletteUtils} from './composition-graph-palette-utils'; import {CompositionGraphZoneUtils} from './composition-graph-zone-utils'; import {ServicePathGraphUtils} from './composition-graph-service-path-utils'; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts deleted file mode 100644 index dbfc3e7219..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Mock } from 'ts-mockery'; -import { - CapabilitiesGroup, - Capability, ComponentInstance, CompositionCiLinkBase, CompositionCiNodeBase, CompositionCiNodeCp, - CompositionCiNodeVf, CompositionCiNodeVl, - Requirement, RequirementsGroup -} from '../../../../../models'; -import { MatchCapabilitiesRequirementsUtils } from './match-capability-requierment-utils'; - -describe('match capability requirements utils service ', () => { - - const bindableReq = Mock.of({ - capability : 'tosca.capabilities.network.Bindable', - name: 'virtualBinding', - relationship: 'tosca.relationships.network.BindsTo', - uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualBinding', - ownerId : 'extcp0', - ownerName : 's' - }); - - const virtualLinkReq = Mock.of({ - capability: 'tosca.capabilities.network.Linkable', - name: 'virtualLink', - relationship: 'tosca.relationships.network.LinksTo', - uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualLink', - ownerId : '', - ownerName : 's' - }); - - const storeAttachmentReq = Mock.of({ - capability: 'tosca.capabilities.Attachment', - name: 'local_storage', - relationship: 'tosca.relationships.AttachesTo', - uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.local_storage', - node: 'tosca.nodes.BlockStorage', - ownerId : '', - ownerName : 's' - }); - - const vlAttachmentReq = Mock.of({ - capability: 'tosca.capabilities.Attachment', - name: 'local_storage', - relationship: 'tosca.relationships.AttachesTo', - uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.local_storage', - node: 'tosca.nodes.BlockStorage', - ownerId : '', - ownerName : 's' - }); - - const extVirtualLinkReq = Mock.of({ - capability: 'tosca.capabilities.network.Linkable', - name: 'external_virtualLink', - relationship: 'tosca.relationships.network.LinksTo', - uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.external_virtualLink' - }); - - const dependencyReq = Mock.of({ - capability: 'tosca.capabilities.Node', - name: 'dependency', - relationship: 'tosca.relationships.DependsOn', - uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.dependency' - }); - - const featureCap = Mock.of({ - type: 'tosca.capabilities.Node', - name: 'feature', - uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.feature', - maxOccurrences: 'UNBOUNDED', - minOccurrences: '1' - }); - - const internalConnPointCap = Mock.of({ - type: 'tosca.capabilities.Node', - name: 'internal_connectionPoint', - capabilitySources : ['org.openecomp.resource.cp.extCP'], - uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.internal_connectionPoint', - maxOccurrences: 'UNBOUNDED', - minOccurrences: '1' - }); - - const blockStoreAttachmentCap = Mock.of({ - type: 'tosca.capabilities.Attachment', - name: 'attachment', - capabilitySources: ['tosca.nodes.BlockStorage'], - uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.attachment', - maxOccurrences: 'UNBOUNDED', - minOccurrences: '1' - }); - - const bindingCap = Mock.of({ - type: 'tosca.capabilities.network.Bindable', - name: 'binding', - capabilitySources: ['tosca.nodes.Compute'], - uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.binding', - maxOccurrences: 'UNBOUNDED', - minOccurrences: '1', - }); - - const linkableCap = Mock.of({ - type: 'tosca.capabilities.network.Linkable', - capabilitySources: ['org.openecomp.resource.vl.extVL'], - uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.virtual_linkable', - maxOccurrences: 'UNBOUNDED', - minOccurrences: '1' - }); - - const nodeCompute = Mock.of({ - name: 'Compute 0', - componentInstance: Mock.of({ - componentName: 'Compute', - uniqueId : 'compute0', - requirements: Mock.of({ - 'tosca.capabilities.Node' : [ dependencyReq ], - 'tosca.capabilities.Attachment' : [ storeAttachmentReq ] - }), - capabilities: Mock.of({ - 'tosca.capabilities.network.Bindable' : [ bindingCap ], - 'tosca.capabilities.Node' : [ featureCap ] - }) - }) - }); - - const nodeBlockStorage = Mock.of({ - name: 'BlockStorage 0', - componentInstance: Mock.of({ - componentName: 'BlockStorage', - uniqueId : 'blockstorage0', - requirements: Mock.of({ - 'tosca.capabilities.Node' : [ dependencyReq ] - }), - capabilities: Mock.of({ - 'tosca.capabilities.Attachment' : [ blockStoreAttachmentCap ], - 'tosca.capabilities.Node' : [ featureCap ] - }) - }) - }); - - const nodeVl = Mock.of({ - name: 'ExtVL 0', - componentInstance: Mock.of({ - componentName: 'BlockStorage', - uniqueId : 'extvl0', - requirements: Mock.of({ - 'tosca.capabilities.Node' : [ dependencyReq ] - }), - capabilities: Mock.of({ - 'tosca.capabilities.network.Linkable' : [ linkableCap ], - 'tosca.capabilities.Node' : [ featureCap ] - }) - }) - }); - - const nodeCp = Mock.of({ - name: 'ExtCP 0', - componentInstance: Mock.of({ - componentName: 'ExtCP', - uniqueId : 'extcp0', - requirements: Mock.of({ - 'tosca.capabilities.network.Linkable' : [ virtualLinkReq ], - 'tosca.capabilities.network.Bindable' : [ bindableReq ] - }), - capabilities: Mock.of({ - 'tosca.capabilities.Node' : [ featureCap ] - }) - }) - }); - - let service: MatchCapabilitiesRequirementsUtils; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [], - providers: [MatchCapabilitiesRequirementsUtils] - }); - - service = TestBed.get(MatchCapabilitiesRequirementsUtils); - }); - - it('match capability requirements utils should be defined', () => { - console.log(JSON.stringify(service)); - expect(service).toBeDefined(); - }); - - describe('isMatch function ', () => { - - it('capability type not equal to requirement capability, match is false', () => { - const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable11'}); - const capability = Mock.of({type: 'tosca.capabilities.network.Linkable'}); - expect(service.isMatch(requirement, capability)).toBeFalsy(); - }); - - it('capability type equal to requirement capability and requirement node not exist, match is true', () => { - const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable'}); - const capability = Mock.of({type: 'tosca.capabilities.network.Linkable'}); - expect(service.isMatch(requirement, capability)).toBeTruthy(); - }); - - it('is match - capability type equal to requirement capability and requirement node exist and includes in capability sources, match is true', () => { - const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable', node: 'node1'}); - const capability = Mock.of({ - type: 'tosca.capabilities.network.Linkable', - capabilitySources: ['node1', 'node2', 'node3'] - }); - expect(service.isMatch(requirement, capability)).toBeTruthy(); - }); - - it('no match - capability type equal to requirement capability and requirement node but not includes in capability sources, match is false', () => { - const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable', node: 'node4'}); - const capability = Mock.of({ - type: 'tosca.capabilities.network.Linkable', - capabilitySources: ['node1', 'node2', 'node3'] - }); - expect(service.isMatch(requirement, capability)).toBeFalsy(); - }); - }); - - describe('hasUnfulfilledRequirementContainingMatch function ', () => { - - it('node have no componentInstance, return false', () => { - const node = Mock.of({componentInstance: undefined}); - expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); - }); - - it('node have componentInstance data but no unfulfilled requirements, return false', () => { - const node = Mock.of({componentInstance: Mock.of()}); - jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]); - expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); - }); - - it('node have componentInstance data and unfulfilled requirements but no match found, return false', () => { - const node = Mock.of({componentInstance: Mock.of()}); - jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); - jest.spyOn(service, 'containsMatch').mockReturnValue(false); - expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); - }); - - it('node have componentInstance data with unfulfilled requirements and match found, return true', () => { - const node = Mock.of({componentInstance: Mock.of()}); - jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); - jest.spyOn(service, 'containsMatch').mockReturnValue(true); - expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeTruthy(); - }); - }); - - describe('getMatches function ', () => { - let fromId: string; - let toId: string; - - beforeEach(() => { - fromId = 'from_id'; - toId = 'to_id'; - }); - - it('node have no unfulfilled requirements, return empty match array', () => { - jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]); - expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); - }); - - it('node have unfulfilled requirements but no capabilities, return empty match array', () => { - jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); - expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); - }); - - it('node have unfulfilled requirements and capabilities but no match found, return empty match array', () => { - jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); - jest.spyOn(service, 'isMatch').mockReturnValue(false); - expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); - }); - - it('node have 2 unfulfilled requirements and 2 capabilities and match found, return 4 matches', () => { - jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); - const capabilities = {aaa: Mock.of(), bbb: Mock.of()}; - jest.spyOn(service, 'isMatch').mockReturnValue(true); - expect(service.getMatches({}, capabilities, [], fromId, toId, true)).toHaveLength(4); - }); - }); - - describe('Find matching nodes ===>', () => { - - it('should find matching nodes with component instance', () => { - const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ]; - let matchingNodes: any; - - // Compute can connect to Block Store - matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []); - expect(matchingNodes).toHaveLength(1); - expect(matchingNodes).toContain(nodeBlockStorage); - - // Block Storage can connect to Compute - matchingNodes = service.findMatchingNodesToComponentInstance(nodeBlockStorage.componentInstance, nodes, []); - expect(matchingNodes).toHaveLength(1); - expect(matchingNodes).toContain(nodeCompute); - - // Vl has no matches - matchingNodes = service.findMatchingNodesToComponentInstance(nodeVl.componentInstance, nodes, []); - expect(matchingNodes).toHaveLength(0); - - // CP should be able to connect to VL and Compute - matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, []); - expect(matchingNodes).toHaveLength(2); - expect(matchingNodes).toContain(nodeCompute); - expect(matchingNodes).toContain(nodeVl); - }); - - it('try with empty list of nodes', () => { - const nodes = [ ]; - let matchingNodes: any; - - // Compute can connect to Block Store - matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []); - expect(matchingNodes).toHaveLength(0); - }); - - it('should detect fulfilled connection with compute node', () => { - const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ]; - let matchingNodes: any; - const link = { - relation: { - fromNode: 'extcp0', - toNode: 'compute0', - relationships: [{ - relation: { - requirementOwnerId: 'extcp0', - requirement: 'virtualBinding', - relationship: { - type: 'tosca.relationships.network.BindsTo' - } - - } - }] - } - }; - - const links = [link]; - // CP should be able to connect to VL only since it already has a link with compute - matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, links as CompositionCiLinkBase[]); - expect(matchingNodes).toHaveLength(1); - expect(matchingNodes).toContain(nodeVl); - }); - }); -}); diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts deleted file mode 100644 index c3a1286a97..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts +++ /dev/null @@ -1,196 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============LICENSE_END========================================================= - */ -import { Injectable } from '@angular/core'; -import { - CapabilitiesGroup, Capability, ComponentInstance, CompositionCiLinkBase, - CompositionCiNodeBase, Match, Requirement, RequirementsGroup -} from 'app/models'; -import * as _ from 'lodash'; - -/** - * Created by obarda on 1/1/2017. - */ -@Injectable() -export class MatchCapabilitiesRequirementsUtils { - - /** - * Shows + icon in corner of each node passed in - * @param filteredNodesData - * @param cy - */ - public highlightMatchingComponents(filteredNodesData, cy: Cy.Instance) { - _.each(filteredNodesData, (data: any) => { - const node = cy.getElementById(data.id); - cy.emit('showhandle', [node]); - }); - } - - /** - * Adds opacity to each node that cannot be linked to hovered node - * @param filteredNodesData - * @param nodesData - * @param cy - * @param hoveredNodeData - */ - public fadeNonMachingComponents(filteredNodesData, nodesData, cy: Cy.Instance, hoveredNodeData?) { - const fadeNodes = _.xorWith(nodesData, filteredNodesData, (node1, node2) => { - return node1.id === node2.id; - }); - if (hoveredNodeData) { - _.remove(fadeNodes, hoveredNodeData); - } - cy.batch(() => { - _.each(fadeNodes, (node) => { - cy.getElementById(node.id).style({'background-image-opacity': 0.4}); - }); - }); - } - - /** - * Resets all nodes to regular opacity - * @param cy - */ - public resetFadedNodes(cy: Cy.Instance) { - cy.batch(() => { - cy.nodes().style({'background-image-opacity': 1}); - }); - } - - public getMatchedRequirementsCapabilities(fromComponentInstance: ComponentInstance, - toComponentInstance: ComponentInstance, - links: CompositionCiLinkBase[]): Match[] { - const fromToMatches: Match[] = this.getMatches(fromComponentInstance.requirements, - toComponentInstance.capabilities, - links, - fromComponentInstance.uniqueId, - toComponentInstance.uniqueId, true); - const toFromMatches: Match[] = this.getMatches(toComponentInstance.requirements, - fromComponentInstance.capabilities, - links, - toComponentInstance.uniqueId, - fromComponentInstance.uniqueId, false); - - return fromToMatches.concat(toFromMatches); - } - - /***** REFACTORED FUNCTIONS START HERE *****/ - - public getMatches(requirements: RequirementsGroup, capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[], - fromId: string, toId: string, isFromTo: boolean): Match[] { - const matches: Match[] = []; - const unfulfilledReqs = this.getUnfulfilledRequirements(fromId, requirements, links); - _.forEach(unfulfilledReqs, (req) => { - _.forEach(_.flatten(_.values(capabilities)), (capability: Capability) => { - if (this.isMatch(req, capability)) { - if (isFromTo) { - matches.push(new Match(req, capability, isFromTo, fromId, toId)); - } else { - matches.push(new Match(req, capability, isFromTo, toId, fromId)); - } - } - }); - }); - return matches; - } - - public getUnfulfilledRequirements = (fromNodeId: string, requirements: RequirementsGroup, links: CompositionCiLinkBase[]): Requirement[] => { - const requirementArray: Requirement[] = []; - _.forEach(_.flatten(_.values(requirements)), (requirement: Requirement) => { - const reqFulfilled = this.isRequirementFulfilled(fromNodeId, requirement, links); - if (requirement.name !== 'dependency' && requirement.parentName !== 'dependency' && !reqFulfilled) { - requirementArray.push(requirement); - } - }); - return requirementArray; - } - - /** - * Returns true if there is a match between the capabilities and requirements that are passed in - * @param requirements - * @param capabilities - * @returns {boolean} - */ - public containsMatch = (requirements: Requirement[], capabilities: CapabilitiesGroup): boolean => { - return _.some(requirements, (req: Requirement) => { - return _.some(_.flatten(_.values(capabilities)), (capability: Capability) => { - return this.isMatch(req, capability); - }); - }); - } - - public hasUnfulfilledRequirementContainingMatch = (node: CompositionCiNodeBase, componentRequirements: Requirement[], capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[]) => { - if (node && node.componentInstance) { - // Check if node has unfulfilled requirement that can be filled by component (#2) - const nodeRequirements: Requirement[] = this.getUnfulfilledRequirements(node.componentInstance.uniqueId, node.componentInstance.requirements, links); - if (!nodeRequirements.length) { - return false; - } - if (this.containsMatch(nodeRequirements, capabilities)) { - return true; - } - } - } - - /** - * Returns array of nodes that can connect to the component. - * In order to connect, one of the following conditions must be met: - * 1. component has an unfulfilled requirement that matches a node's capabilities - * 2. node has an unfulfilled requirement that matches the component's capabilities - * 3. vl is passed in which has the capability to fulfill requirement from component and requirement on node. - */ - public findMatchingNodesToComponentInstance(componentInstance: ComponentInstance, nodeDataArray: CompositionCiNodeBase[], links: CompositionCiLinkBase[]): any[] { - return _.filter(nodeDataArray, (node: CompositionCiNodeBase) => { - const matchedRequirementsCapabilities = this.getMatchedRequirementsCapabilities(node.componentInstance, componentInstance, links); - return matchedRequirementsCapabilities && matchedRequirementsCapabilities.length > 0; - }); - } - - public isMatch(requirement: Requirement, capability: Capability): boolean { - if (capability.type === requirement.capability) { - if (requirement.node) { - if (_.includes(capability.capabilitySources, requirement.node)) { - return true; - } - } else { - return true; - } - } - return false; - } - - private isRequirementFulfilled(fromNodeId: string, requirement: any, links: CompositionCiLinkBase[]): boolean { - return _.some(links, { - relation: { - fromNode: fromNodeId, - relationships: [{ - relation: { - requirementOwnerId: requirement.ownerId, - requirement: requirement.name, - relationship: { - type: requirement.relationship - } - - } - }] - } - }); - } - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.spec.ts new file mode 100644 index 0000000000..10b26fc939 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.spec.ts @@ -0,0 +1,345 @@ +import { TestBed } from '@angular/core/testing'; +import { Mock } from 'ts-mockery'; +import { + CapabilitiesGroup, + Capability, + ComponentInstance, + CompositionCiLinkBase, + CompositionCiNodeCp, + CompositionCiNodeVf, + CompositionCiNodeVl, + Requirement, + RequirementsGroup +} from '../../../../../models'; +import { MatchCapabilitiesRequirementsUtils } from './match-capability-requirement-utils'; + +describe('match capability requirements utils service ', () => { + + const bindableReq = Mock.of({ + capability : 'tosca.capabilities.network.Bindable', + name: 'virtualBinding', + relationship: 'tosca.relationships.network.BindsTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualBinding', + ownerId : 'extcp0', + ownerName : 's' + }); + + const virtualLinkReq = Mock.of({ + capability: 'tosca.capabilities.network.Linkable', + name: 'virtualLink', + relationship: 'tosca.relationships.network.LinksTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualLink', + ownerId : '', + ownerName : 's' + }); + + const storeAttachmentReq = Mock.of({ + capability: 'tosca.capabilities.Attachment', + name: 'local_storage', + relationship: 'tosca.relationships.AttachesTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.local_storage', + node: 'tosca.nodes.BlockStorage', + ownerId : '', + ownerName : 's' + }); + + const dependencyReq = Mock.of({ + capability: 'tosca.capabilities.Node', + name: 'dependency', + node: 'tosca.nodes.Root', + relationship: 'tosca.relationships.DependsOn', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.dependency', + minOccurrences: 0, + maxOccurrences: 'UNBOUNDED' + }); + + const featureCap = Mock.of({ + type: 'tosca.capabilities.Node', + name: 'feature', + capabilitySources: ['tosca.nodes.Root'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.feature', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1' + }); + + const blockStoreAttachmentCap = Mock.of({ + type: 'tosca.capabilities.Attachment', + name: 'attachment', + capabilitySources: ['tosca.nodes.BlockStorage'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.attachment', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1' + }); + + const bindingCap = Mock.of({ + type: 'tosca.capabilities.network.Bindable', + name: 'binding', + capabilitySources: ['tosca.nodes.Compute'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.binding', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1', + }); + + const linkableCap = Mock.of({ + type: 'tosca.capabilities.network.Linkable', + capabilitySources: ['org.openecomp.resource.vl.extVL'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.virtual_linkable', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1' + }); + + const nodeCompute = Mock.of({ + name: 'Compute 0', + componentInstance: Mock.of({ + componentName: 'Compute', + uniqueId : 'compute0', + requirements: Mock.of({ + 'tosca.capabilities.Attachment' : [ storeAttachmentReq ] + }), + capabilities: Mock.of({ + 'tosca.capabilities.network.Bindable' : [ bindingCap ], + }) + }) + }); + + const nodeBlockStorage = Mock.of({ + name: 'BlockStorage 0', + componentInstance: Mock.of({ + componentName: 'BlockStorage', + uniqueId : 'blockstorage0', + capabilities: Mock.of({ + 'tosca.capabilities.Attachment' : [ blockStoreAttachmentCap ], + }) + }) + }); + + const nodeVl = Mock.of({ + name: 'ExtVL 0', + componentInstance: Mock.of({ + componentName: 'BlockStorage', + uniqueId : 'extvl0', + capabilities: Mock.of({ + 'tosca.capabilities.network.Linkable' : [ linkableCap ], + }) + }) + }); + + const nodeCp = Mock.of({ + name: 'ExtCP 0', + componentInstance: Mock.of({ + componentName: 'ExtCP', + uniqueId : 'extcp0', + requirements: Mock.of({ + 'tosca.capabilities.network.Linkable' : [ virtualLinkReq ], + 'tosca.capabilities.network.Bindable' : [ bindableReq ] + }), + }) + }); + + let service: MatchCapabilitiesRequirementsUtils; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [MatchCapabilitiesRequirementsUtils] + }); + + service = TestBed.get(MatchCapabilitiesRequirementsUtils); + }); + + it('match capability requirements utils should be defined', () => { + console.log(JSON.stringify(service)); + expect(service).toBeDefined(); + }); + + describe('isMatch function ', () => { + + it('capability type not equal to requirement capability, match is false', () => { + const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable11'}); + const capability = Mock.of({type: 'tosca.capabilities.network.Linkable'}); + expect(service.isMatch(requirement, capability)).toBeFalsy(); + }); + + it('capability type equal to requirement capability and requirement node not exist, match is true', () => { + const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable'}); + const capability = Mock.of({type: 'tosca.capabilities.network.Linkable'}); + expect(service.isMatch(requirement, capability)).toBeTruthy(); + }); + + it('is match - capability type equal to requirement capability and requirement node exist and includes in capability sources, match is true', () => { + const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable', node: 'node1'}); + const capability = Mock.of({ + type: 'tosca.capabilities.network.Linkable', + capabilitySources: ['node1', 'node2', 'node3'] + }); + expect(service.isMatch(requirement, capability)).toBeTruthy(); + }); + + it('no match - capability type equal to requirement capability and requirement node but not includes in capability sources, match is false', () => { + const requirement = Mock.of({capability: 'tosca.capabilities.network.Linkable', node: 'node4'}); + const capability = Mock.of({ + type: 'tosca.capabilities.network.Linkable', + capabilitySources: ['node1', 'node2', 'node3'] + }); + expect(service.isMatch(requirement, capability)).toBeFalsy(); + }); + }); + + describe('hasUnfulfilledRequirementContainingMatch function ', () => { + + it('node have no componentInstance, return false', () => { + const node = Mock.of({componentInstance: undefined}); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); + }); + + it('node have componentInstance data but no unfulfilled requirements, return false', () => { + const node = Mock.of({componentInstance: Mock.of()}); + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); + }); + + it('node have componentInstance data and unfulfilled requirements but no match found, return false', () => { + const node = Mock.of({componentInstance: Mock.of()}); + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); + jest.spyOn(service, 'containsMatch').mockReturnValue(false); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); + }); + + it('node have componentInstance data with unfulfilled requirements and match found, return true', () => { + const node = Mock.of({componentInstance: Mock.of()}); + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); + jest.spyOn(service, 'containsMatch').mockReturnValue(true); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeTruthy(); + }); + }); + + describe('getMatches function ', () => { + let fromId: string; + let toId: string; + + beforeEach(() => { + fromId = 'from_id'; + toId = 'to_id'; + }); + + it('node have no unfulfilled requirements, return empty match array', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]); + expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); + }); + + it('node have unfulfilled requirements but no capabilities, return empty match array', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); + expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); + }); + + it('node have unfulfilled requirements and capabilities but no match found, return empty match array', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); + jest.spyOn(service, 'isMatch').mockReturnValue(false); + expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); + }); + + it('node have 2 unfulfilled requirements and 2 capabilities and match found, return 4 matches', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of(), Mock.of()]); + const capabilities = {aaa: Mock.of(), bbb: Mock.of()}; + jest.spyOn(service, 'isMatch').mockReturnValue(true); + expect(service.getMatches({}, capabilities, [], fromId, toId, true)).toHaveLength(4); + }); + + it('node have 3 unfulfilled requirements: DependsOn tosca.nodes.Root, BindsTo and LinksTo;' + + ' and DependsOn and BindsTo matching capabilities, ' + + 'should return 2 matches', () => { + const dependencyList = [dependencyReq, bindableReq, virtualLinkReq]; + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue(dependencyList); + const capabilities = { + cap1Match: featureCap, cap2NotMatch: Mock.of(), cap3Match: bindingCap + }; + const expectedMatchDependsOn = { + requirement: dependencyReq, + capability: featureCap, + isFromTo: true, + fromNode: fromId, + toNode: toId + }; + const expectedMatchBindsTo = { + requirement: bindableReq, + capability: bindingCap, + isFromTo: true, + fromNode: fromId, + toNode: toId + }; + const matches = service.getMatches({}, capabilities, [], fromId, toId, true); + expect(matches).toHaveLength(2); + expect(matches).toEqual( + expect.arrayContaining([ + expect.objectContaining(expectedMatchDependsOn), + expect.objectContaining(expectedMatchBindsTo) + ]) + ); + + }); + }); + + describe('Find matching nodes ===>', () => { + + it('should find matching nodes with component instance', () => { + const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ]; + let matchingNodes: any; + + // Compute can connect to Block Store + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(1); + expect(matchingNodes).toContain(nodeBlockStorage); + + // Block Storage can connect to Compute + matchingNodes = service.findMatchingNodesToComponentInstance(nodeBlockStorage.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(1); + expect(matchingNodes).toContain(nodeCompute); + + // Vl has no matches + matchingNodes = service.findMatchingNodesToComponentInstance(nodeVl.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(0); + + // CP should be able to connect to VL and Compute + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(2); + expect(matchingNodes).toContain(nodeCompute); + expect(matchingNodes).toContain(nodeVl); + }); + + it('try with empty list of nodes', () => { + const nodes = [ ]; + let matchingNodes: any; + + // Compute can connect to Block Store + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(0); + }); + + it('should detect fulfilled connection with compute node', () => { + const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ]; + let matchingNodes: any; + const link = { + relation: { + fromNode: 'extcp0', + toNode: 'compute0', + relationships: [{ + relation: { + requirementOwnerId: 'extcp0', + requirement: 'virtualBinding', + relationship: { + type: 'tosca.relationships.network.BindsTo' + } + + } + }] + } + }; + + const links = [link]; + // CP should be able to connect to VL only since it already has a link with compute + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, links as CompositionCiLinkBase[]); + expect(matchingNodes).toHaveLength(1); + expect(matchingNodes).toContain(nodeVl); + }); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.ts new file mode 100644 index 0000000000..ec7f8d1ab6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requirement-utils.ts @@ -0,0 +1,195 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +import { Injectable } from '@angular/core'; +import { + CapabilitiesGroup, Capability, ComponentInstance, CompositionCiLinkBase, + CompositionCiNodeBase, Match, Requirement, RequirementsGroup +} from 'app/models'; +import * as _ from 'lodash'; + +/** + * Created by obarda on 1/1/2017. + */ +@Injectable() +export class MatchCapabilitiesRequirementsUtils { + + /** + * Shows + icon in corner of each node passed in + * @param filteredNodesData + * @param cy + */ + public highlightMatchingComponents(filteredNodesData, cy: Cy.Instance) { + _.each(filteredNodesData, (data: any) => { + const node = cy.getElementById(data.id); + cy.emit('showhandle', [node]); + }); + } + + /** + * Adds opacity to each node that cannot be linked to hovered node + * @param filteredNodesData + * @param nodesData + * @param cy + * @param hoveredNodeData + */ + public fadeNonMachingComponents(filteredNodesData, nodesData, cy: Cy.Instance, hoveredNodeData?) { + const fadeNodes = _.xorWith(nodesData, filteredNodesData, (node1, node2) => { + return node1.id === node2.id; + }); + if (hoveredNodeData) { + _.remove(fadeNodes, hoveredNodeData); + } + cy.batch(() => { + _.each(fadeNodes, (node) => { + cy.getElementById(node.id).style({'background-image-opacity': 0.4}); + }); + }); + } + + /** + * Resets all nodes to regular opacity + * @param cy + */ + public resetFadedNodes(cy: Cy.Instance) { + cy.batch(() => { + cy.nodes().style({'background-image-opacity': 1}); + }); + } + + public getMatchedRequirementsCapabilities(fromComponentInstance: ComponentInstance, + toComponentInstance: ComponentInstance, + links: CompositionCiLinkBase[]): Match[] { + const fromToMatches: Match[] = this.getMatches(fromComponentInstance.requirements, + toComponentInstance.capabilities, + links, + fromComponentInstance.uniqueId, + toComponentInstance.uniqueId, true); + const toFromMatches: Match[] = this.getMatches(toComponentInstance.requirements, + fromComponentInstance.capabilities, + links, + toComponentInstance.uniqueId, + fromComponentInstance.uniqueId, false); + + return fromToMatches.concat(toFromMatches); + } + + /***** REFACTORED FUNCTIONS START HERE *****/ + + public getMatches(requirements: RequirementsGroup, capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[], + fromId: string, toId: string, isFromTo: boolean): Match[] { + const matches: Match[] = []; + const unfulfilledReqs = this.getUnfulfilledRequirements(fromId, requirements, links); + _.forEach(unfulfilledReqs, (req) => { + _.forEach(_.flatten(_.values(capabilities)), (capability: Capability) => { + if (this.isMatch(req, capability)) { + if (isFromTo) { + matches.push(new Match(req, capability, isFromTo, fromId, toId)); + } else { + matches.push(new Match(req, capability, isFromTo, toId, fromId)); + } + } + }); + }); + return matches; + } + + public getUnfulfilledRequirements = (fromNodeId: string, requirements: RequirementsGroup, links: CompositionCiLinkBase[]): Requirement[] => { + const requirementArray: Requirement[] = []; + _.forEach(_.flatten(_.values(requirements)), (requirement: Requirement) => { + if (!this.isRequirementFulfilled(fromNodeId, requirement, links)) { + requirementArray.push(requirement); + } + }); + return requirementArray; + }; + + /** + * Returns true if there is a match between the capabilities and requirements that are passed in + * @param requirements + * @param capabilities + * @returns {boolean} + */ + public containsMatch = (requirements: Requirement[], capabilities: CapabilitiesGroup): boolean => { + return _.some(requirements, (req: Requirement) => { + return _.some(_.flatten(_.values(capabilities)), (capability: Capability) => { + return this.isMatch(req, capability); + }); + }); + } + + public hasUnfulfilledRequirementContainingMatch = (node: CompositionCiNodeBase, componentRequirements: Requirement[], capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[]) => { + if (node && node.componentInstance) { + // Check if node has unfulfilled requirement that can be filled by component (#2) + const nodeRequirements: Requirement[] = this.getUnfulfilledRequirements(node.componentInstance.uniqueId, node.componentInstance.requirements, links); + if (!nodeRequirements.length) { + return false; + } + if (this.containsMatch(nodeRequirements, capabilities)) { + return true; + } + } + } + + /** + * Returns array of nodes that can connect to the component. + * In order to connect, one of the following conditions must be met: + * 1. component has an unfulfilled requirement that matches a node's capabilities + * 2. node has an unfulfilled requirement that matches the component's capabilities + * 3. vl is passed in which has the capability to fulfill requirement from component and requirement on node. + */ + public findMatchingNodesToComponentInstance(componentInstance: ComponentInstance, nodeDataArray: CompositionCiNodeBase[], links: CompositionCiLinkBase[]): any[] { + return _.filter(nodeDataArray, (node: CompositionCiNodeBase) => { + const matchedRequirementsCapabilities = this.getMatchedRequirementsCapabilities(node.componentInstance, componentInstance, links); + return matchedRequirementsCapabilities && matchedRequirementsCapabilities.length > 0; + }); + } + + public isMatch(requirement: Requirement, capability: Capability): boolean { + if (capability.type === requirement.capability) { + if (requirement.node) { + if (_.includes(capability.capabilitySources, requirement.node)) { + return true; + } + } else { + return true; + } + } + return false; + } + + private isRequirementFulfilled(fromNodeId: string, requirement: any, links: CompositionCiLinkBase[]): boolean { + return _.some(links, { + relation: { + fromNode: fromNodeId, + relationships: [{ + relation: { + requirementOwnerId: requirement.ownerId, + requirement: requirement.name, + relationship: { + type: requirement.relationship + } + + } + }] + } + }); + } + +} -- cgit 1.2.3-korg