From c87449a93fea4da141f8c11af15ea11fcef42138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miles=20St=C3=B6tzner?= Date: Sat, 9 Nov 2024 22:26:59 +0100 Subject: [PATCH] managed node template --- .../variability4tosca/specification/index.md | 11 +--- src/controller/utils/scenarios.ts | 2 +- src/enricher/conditions.ts | 2 +- src/enricher/constraints.ts | 3 +- src/enricher/elements.ts | 66 +++++++++---------- src/graph/artifact.ts | 4 +- src/graph/node.ts | 29 +++++--- src/graph/options.ts | 31 +-------- src/graph/populator.ts | 4 +- src/normalizer/index.ts | 1 - src/resolver/transformer.ts | 1 + src/specification/node-template.ts | 1 + src/technologies/plugins/rules/index.ts | 2 +- src/technologies/utils.ts | 2 +- src/utils/utils.ts | 4 ++ .../generate/variability/test.template.ejs | 2 +- .../implied-default/fixed-left/template.yaml | 1 + .../implied-default/manual-left/template.yaml | 1 + .../implied-host/broken/template.yaml | 1 + .../implied-host/fixed-left/template.yaml | 1 + .../implied-host/manual-left/template.yaml | 1 + .../inputs/pruning-consumed-v3/expected.yaml | 4 -- .../inputs/pruning-consumed-v3/template.yaml | 10 +-- .../template.yaml | 6 +- .../template.yaml | 1 + .../technologies/optimization/template.yaml | 1 + .../technologies/throw-required/template.yaml | 1 + 27 files changed, 82 insertions(+), 111 deletions(-) diff --git a/docs/docs/variability4tosca/specification/index.md b/docs/docs/variability4tosca/specification/index.md index 12589a3be4..53039ca840 100644 --- a/docs/docs/variability4tosca/specification/index.md +++ b/docs/docs/variability4tosca/specification/index.md @@ -259,14 +259,6 @@ The following options are used to configure the solver. | optimization_technologies_unique | false | Boolean | false | Enable check for unique results considering technologies. | | optimization_technologies_mode | false | count | weight | weight-count | count | Configure optimization mode considering technologies. | -### Normalization Options - -The following options are used to configure the normalizer. - -| Keyname | Mandatory | Type | Default | Description | -|----------------------|-----------|-------------------------------|---------|-----------------------------------------------------------| -| technology_required | false | Boolean | false | Enable if a technology is required by default for a node. | - ### Enricher Options @@ -329,7 +321,6 @@ optimization_technologies_mode: weight-count technology_constraint: true hosting_stack_constraint: true relation_default_implied: true -technology_required: false unconsumed_input_check: false unproduced_output_check: false enrich_technologies: true @@ -357,7 +348,6 @@ unique_input_constraint: true unique_output_constraint: true required_artifact_constraint: true relation_default_implied: true -technology_required: true checks: false enrich_technologies: true enrich_implementations: true @@ -561,6 +551,7 @@ A node template can also hold conditional types, artifact, and properties. | weight | false | Boolean | Non-Negative Number | Configure the weight of this element used during optimization (default is 1). | | implies | false | List(Tuple(Target: VariabilityCondition, Condition?: VariabilityCondition)) | An optional list of implications following the pattern `element implies target` or `(element and condition) implies target`. | | technology | false | String | List(Map(String, TechnologyTemplate){single}) | An optional conditional assignment of deployment technologies. | +| managed | false | Boolean | Enable if node is managed (default is true). | For example, the following node template has a variability condition assigned. diff --git a/src/controller/utils/scenarios.ts b/src/controller/utils/scenarios.ts index 117d5a0a2a..6bfec64120 100644 --- a/src/controller/utils/scenarios.ts +++ b/src/controller/utils/scenarios.ts @@ -83,7 +83,7 @@ export default async function (options: ScenariosOptions) { rules = rules.filter(rule => { assert.isDefined(rule.hosting) - if (check.isTrue(options.hosting)) return !utils.isEmpty(rule.hosting) + if (check.isTrue(options.hosting)) return utils.isPopulated(rule.hosting) if (check.isFalse(options.hosting)) return utils.isEmpty(rule.hosting) assert.isArray(options.hosting) diff --git a/src/enricher/conditions.ts b/src/enricher/conditions.ts index f89f15e8e9..679b6dc5a3 100644 --- a/src/enricher/conditions.ts +++ b/src/enricher/conditions.ts @@ -85,7 +85,7 @@ export class ConditionEnricher { return false }) - if (!utils.isEmpty(selected)) { + if (utils.isPopulated(selected)) { conditions.unshift( generatify( simplify( diff --git a/src/enricher/constraints.ts b/src/enricher/constraints.ts index 3f0ee31eba..9df406456d 100644 --- a/src/enricher/constraints.ts +++ b/src/enricher/constraints.ts @@ -162,8 +162,9 @@ export class ConstraintEnricher { /** * Ensure that technology exists (required and single) */ + // .filter(it => utils.isPopulated(it.technologies) if (this.graph.options.constraints.technology) { - for (const node of this.graph.nodes.filter(it => !utils.isEmpty(it.technologies))) { + for (const node of this.graph.nodes.filter(it => it.managed)) { const consequence = node.technologies.length === 1 ? node.technologies[0].id : {exo: node.technologies.map(it => it.id)} this.graph.addConstraint({implies: [node.id, consequence]}) diff --git a/src/enricher/elements.ts b/src/enricher/elements.ts index d0876bcf51..b24e35a44e 100644 --- a/src/enricher/elements.ts +++ b/src/enricher/elements.ts @@ -41,55 +41,51 @@ export class ElementEnricher { // for backwards compatibility and testing purposed, continue if, e.g., no rules at all exists if (utils.isEmpty(this.graph.plugins.technology)) return - for (const node of this.graph.nodes) { - if (!utils.isEmpty(node.technologies)) { - // Do not override manual assigned technologies but enrich them with an implementation - const enriched: TechnologyTemplateMap[] = [] + for (const node of this.graph.nodes.filter(it => it.managed).filter(it => utils.isPopulated(it.technologies))) { + // Do not override manual assigned technologies but enrich them with an implementation + const enriched: TechnologyTemplateMap[] = [] - node.technologies.forEach(technology => { - // TODO: super inefficient but we need copies for each since the same technology might be defined multiple times - const candidates = this.getTechnologyCandidates(node) - const selected = candidates.filter(map => utils.firstKey(map) === technology.name) + node.technologies.forEach(technology => { + // TODO: super inefficient but we need copies for each since the same technology might be defined multiple times + const candidates = this.getTechnologyCandidates(node) + const selected = candidates.filter(map => utils.firstKey(map) === technology.name) - for (const map of selected) { - const template = utils.firstValue(map) - if (!utils.isEmpty(technology.conditions)) { - // TODO: improve plugin typing! - // assert.isArray(template.conditions) - assert.isDefined(template.conditions) - template.conditions = [ - ...technology.conditions, - ...(check.isArray(template.conditions) ? template.conditions : [template.conditions]), - ] - } + for (const map of selected) { + const template = utils.firstValue(map) + if (utils.isPopulated(technology.conditions)) { + // TODO: improve plugin typing! + // assert.isArray(template.conditions) + assert.isDefined(template.conditions) + template.conditions = [ + ...technology.conditions, + ...(check.isArray(template.conditions) ? template.conditions : [template.conditions]), + ] } + } - if (utils.isEmpty(selected)) - throw new Error(`${node.Display} has no implementation for ${technology.display}`) + if (utils.isEmpty(selected)) + throw new Error(`${node.Display} has no implementation for ${technology.display}`) - enriched.push(...selected) - }) - this.graph.replaceTechnologies(node, enriched) - } + enriched.push(...selected) + }) + this.graph.replaceTechnologies(node, enriched) } } private enrichTechnologies() { - for (const node of this.graph.nodes) { + for (const node of this.graph.nodes.filter(it => it.managed).filter(it => utils.isEmpty(it.technologies))) { // Get all possible technology assignments const candidates = this.getTechnologyCandidates(node) - if (utils.isEmpty(node.technologies)) { - // Throw if technology is required but no candidate has been found - if (this.graph.options.checks.requiredTechnology) { - if (node.technologyRequired && utils.isEmpty(candidates)) { - throw new Error(`${node.Display} has no technology candidates`) - } + // Throw if technology is required but no candidate has been found + if (this.graph.options.checks.requiredTechnology) { + if (utils.isEmpty(candidates)) { + throw new Error(`${node.Display} has no technology candidates`) } - - // Assign each possible technology assignment - candidates.forEach(it => this.graph.addTechnology(node, it)) } + + // Assign each possible technology assignment + candidates.forEach(it => this.graph.addTechnology(node, it)) } } } diff --git a/src/graph/artifact.ts b/src/graph/artifact.ts index dd9481250b..197ed1f691 100644 --- a/src/graph/artifact.ts +++ b/src/graph/artifact.ts @@ -107,11 +107,11 @@ export default class Artifact extends Element { const wrappers: ConditionsWrapper[] = [] - if (!utils.isEmpty(consistencies)) { + if (utils.isPopulated(consistencies)) { wrappers.push({conditions: andify(consistencies), consistency: true, semantic: false}) } - if (!utils.isEmpty(semantics)) { + if (utils.isPopulated(semantics)) { wrappers.push({conditions: andify(semantics), consistency: false, semantic: true}) } diff --git a/src/graph/node.ts b/src/graph/node.ts index d2fcd9ab51..4092e9430f 100644 --- a/src/graph/node.ts +++ b/src/graph/node.ts @@ -66,14 +66,27 @@ export default class Node extends Element { return this.name } - // TODO: NEXT: adapt this to external component /** - * Technology is required if type is not abstract + * Checks if component is managed, i.e., requires a technology. */ - get technologyRequired() { + // TODO: write input validations in populator that ensures that raw.manged does not conflict eg with modeled technologies? + get managed() { + // Component is (un)managed if manually specified + if (check.isDefined(this.raw.managed)) { + assert.isBoolean(this.raw.managed) + return this.raw.managed + } + + // Component is managed, if technologies are assigned (e.g., manually) + if (utils.isPopulated(this.technologies)) return true + + // Component is unmanaged if type is abstract const type = this.graph.inheritance.getNodeType(this.getType().name) assert.isDefined(type, `Node type "${this.getType().name}" does not exist`) - return !isAbstract(type) + if (isAbstract(type)) return false + + // Component is managed by default + return true } get persistent() { @@ -105,19 +118,19 @@ export default class Node extends Element { } get hasHost() { - return !utils.isEmpty(this.hosts) + return utils.isPopulated(this.hosts) } get isTarget() { - return !utils.isEmpty(this.ingoing) + return utils.isPopulated(this.ingoing) } get isSource() { - return !utils.isEmpty(this.outgoing) + return utils.isPopulated(this.outgoing) } get hasArtifact() { - return !utils.isEmpty(this.artifacts) + return utils.isPopulated(this.artifacts) } getTypeSilent() { diff --git a/src/graph/options.ts b/src/graph/options.ts index ba23468fdb..5cec85cbe0 100644 --- a/src/graph/options.ts +++ b/src/graph/options.ts @@ -650,19 +650,8 @@ class ChecksOptions extends BaseOptions { this.ambiguousTechnology = this.raw.ambiguous_technology_check ?? this.consistency assert.isBoolean(this.ambiguousTechnology) - if (this.v1 || this.v2) { - /** - * Case: tosca_simple_yaml_1_3, tosca_variability_1_0, tosca_variability_1_0_rc_1, tosca_variability_1_0_rc_2 - */ - this.requiredTechnology = this.raw.required_technology_check ?? this.semantic - assert.isBoolean(this.expectedTechnology) - } else { - /** - * Case: tosca_variability_1_0_rc_3 - */ - this.requiredTechnology = this.raw.required_technology_check ?? true - assert.isBoolean(this.expectedTechnology) - } + this.requiredTechnology = this.raw.required_technology_check ?? this.semantic + assert.isBoolean(this.expectedTechnology) this.ambiguousRelation = this.raw.ambiguous_relation_check ?? this.consistency assert.isBoolean(this.ambiguousRelation) @@ -940,24 +929,8 @@ class ConstraintsOptions extends BaseOptions { } export class NormalizationOptions extends BaseOptions { - readonly technologyRequired: boolean - constructor(serviceTemplate: ServiceTemplate) { super(serviceTemplate) - - if (this.v1 || this.v2) { - /** - * Case: tosca_simple_yaml_1_3, tosca_variability_1_0, tosca_variability_1_0_rc_1, tosca_variability_1_0_rc_2 - */ - this.technologyRequired = this.raw.technology_required ?? false - assert.isBoolean(this.technologyRequired) - } else { - /** - * Case: tosca_variability_1_0_rc_3 - */ - this.technologyRequired = this.raw.technology_required ?? true - assert.isBoolean(this.technologyRequired) - } } } diff --git a/src/graph/populator.ts b/src/graph/populator.ts index 556c247234..d803baa66b 100644 --- a/src/graph/populator.ts +++ b/src/graph/populator.ts @@ -574,14 +574,14 @@ export class Populator { // GUESS: relation is referenced const relations = node.outgoing.filter(it => it.name === parsed.propertyContainer).map(it => it.target) - if (!utils.isEmpty(relations)) { + if (utils.isPopulated(relations)) { property.consuming.push(...relations) continue } // GUESS: artifact is referenced const artifacts = node.artifactsMap.get(parsed.propertyContainer) ?? [] - if (!utils.isEmpty(artifacts)) { + if (utils.isPopulated(artifacts)) { property.consuming.push(...artifacts) continue } diff --git a/src/normalizer/index.ts b/src/normalizer/index.ts index 5b984374d5..7a77596e18 100644 --- a/src/normalizer/index.ts +++ b/src/normalizer/index.ts @@ -307,7 +307,6 @@ export default class Normalizer { assert.isString(rule.component) if (check.isUndefined(rule.hosting)) rule.hosting = [] - // TODO: dont allow short form assert.isArray(rule.hosting) rule.hosting.forEach(it => assert.isString(it)) diff --git a/src/resolver/transformer.ts b/src/resolver/transformer.ts index b70e97614c..7d68d086fc 100644 --- a/src/resolver/transformer.ts +++ b/src/resolver/transformer.ts @@ -68,6 +68,7 @@ export default class Transformer { delete raw.weight delete raw.implies delete raw.technology + delete raw.managed } private transformNodes() { diff --git a/src/specification/node-template.ts b/src/specification/node-template.ts index 07bda52a08..af020903fe 100644 --- a/src/specification/node-template.ts +++ b/src/specification/node-template.ts @@ -25,6 +25,7 @@ export type NodeTemplate = { weight?: number | boolean persistent?: boolean technology?: TechnologyAssignment + managed?: boolean } & VariabilityAlternative & { default_condition_mode?: NodeDefaultConditionMode } diff --git a/src/technologies/plugins/rules/index.ts b/src/technologies/plugins/rules/index.ts index 73ca201781..3620bd13ea 100644 --- a/src/technologies/plugins/rules/index.ts +++ b/src/technologies/plugins/rules/index.ts @@ -113,7 +113,7 @@ export class TechnologyRulePlugin implements TechnologyPlugin { assert.isDefined(rule.artifact) return it.getType().isA(rule.artifact) }) - const hasArtifactInTemplate = !utils.isEmpty(artifactsByTemplate) + const hasArtifactInTemplate = utils.isPopulated(artifactsByTemplate) // Check for artifact in type const hasArtifactInType = this.graph.inheritance.hasArtifactDefinition( diff --git a/src/technologies/utils.ts b/src/technologies/utils.ts index 927c3a1d73..7ca11f8825 100644 --- a/src/technologies/utils.ts +++ b/src/technologies/utils.ts @@ -73,7 +73,7 @@ export function constructRuleName(data: TechnologyRule, options: {technology?: b output += '::' + data.technology } - if (check.isDefined(data.hosting) && !utils.isEmpty(data.hosting)) { + if (check.isDefined(data.hosting) && utils.isPopulated(data.hosting)) { output += '@' + data.hosting.join('->') } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 1afdebff5d..71477900a7 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -57,6 +57,10 @@ export function isEmpty(obj: any) { throw new Error(`Cannot check if object ${pretty(obj)} is empty`) } +export function isPopulated(obj: any) { + return !isEmpty(obj) +} + export function first(array: T[]) { return array[0] } diff --git a/tasks/docs/generate/variability/test.template.ejs b/tasks/docs/generate/variability/test.template.ejs index 37e459be46..19f55ca5bf 100644 --- a/tasks/docs/generate/variability/test.template.ejs +++ b/tasks/docs/generate/variability/test.template.ejs @@ -17,7 +17,7 @@ The variability of the following variable service template shall be resolved. {% endraw %} ``` -<% if (!utils.isEmpty(test.inputs)) { -%> +<% if (utils.isPopulated(test.inputs)) { -%> ## Variability Inputs When resolving variability, the following variability inputs shall be used. diff --git a/tests/conformance/implied-default/fixed-left/template.yaml b/tests/conformance/implied-default/fixed-left/template.yaml index d823d8f136..13114dfb09 100644 --- a/tests/conformance/implied-default/fixed-left/template.yaml +++ b/tests/conformance/implied-default/fixed-left/template.yaml @@ -14,6 +14,7 @@ topology_template: expected_incoming_relation_check: false required_incoming_relation_constraint: false enrich_technologies: false + technology_constraint: false node_templates: application: diff --git a/tests/conformance/implied-default/manual-left/template.yaml b/tests/conformance/implied-default/manual-left/template.yaml index 3b31254d6f..8f52b45aba 100644 --- a/tests/conformance/implied-default/manual-left/template.yaml +++ b/tests/conformance/implied-default/manual-left/template.yaml @@ -14,6 +14,7 @@ topology_template: expected_incoming_relation_check: false required_incoming_relation_constraint: false enrich_technologies: false + technology_constraint: false node_templates: application: diff --git a/tests/conformance/implied-host/broken/template.yaml b/tests/conformance/implied-host/broken/template.yaml index fecd30cfb7..50512796a9 100644 --- a/tests/conformance/implied-host/broken/template.yaml +++ b/tests/conformance/implied-host/broken/template.yaml @@ -12,6 +12,7 @@ topology_template: options: enrich_technologies: false + technology_constraint: false node_templates: application: diff --git a/tests/conformance/implied-host/fixed-left/template.yaml b/tests/conformance/implied-host/fixed-left/template.yaml index 0029c2dbac..357671eb8f 100644 --- a/tests/conformance/implied-host/fixed-left/template.yaml +++ b/tests/conformance/implied-host/fixed-left/template.yaml @@ -12,6 +12,7 @@ topology_template: options: enrich_technologies: false + technology_constraint: false node_templates: application: diff --git a/tests/conformance/implied-host/manual-left/template.yaml b/tests/conformance/implied-host/manual-left/template.yaml index 91666657a7..19a1e7fca3 100644 --- a/tests/conformance/implied-host/manual-left/template.yaml +++ b/tests/conformance/implied-host/manual-left/template.yaml @@ -12,6 +12,7 @@ topology_template: options: enrich_technologies: false + technology_constraint: false node_templates: application: diff --git a/tests/conformance/inputs/pruning-consumed-v3/expected.yaml b/tests/conformance/inputs/pruning-consumed-v3/expected.yaml index 902ae27bba..c8c53eab66 100644 --- a/tests/conformance/inputs/pruning-consumed-v3/expected.yaml +++ b/tests/conformance/inputs/pruning-consumed-v3/expected.yaml @@ -1,9 +1,5 @@ tosca_definitions_version: tosca_simple_yaml_1_3 -node_types: - container: - derived_from: tosca.nodes.Root - topology_template: inputs: some_input: diff --git a/tests/conformance/inputs/pruning-consumed-v3/template.yaml b/tests/conformance/inputs/pruning-consumed-v3/template.yaml index 2b15875049..276d3cb26d 100644 --- a/tests/conformance/inputs/pruning-consumed-v3/template.yaml +++ b/tests/conformance/inputs/pruning-consumed-v3/template.yaml @@ -1,15 +1,6 @@ tosca_definitions_version: tosca_variability_1_0_rc_3 -node_types: - container: - derived_from: tosca.nodes.Root - topology_template: - variability: - options: - enrich_implementations: false - enrich_technologies: false - inputs: some_input: type: string @@ -18,5 +9,6 @@ topology_template: container: type: container conditions: true + managed: false properties: some_property: {get_input: some_input} diff --git a/tests/conformance/inputs/pruning-not-consumed-anymore-v3/template.yaml b/tests/conformance/inputs/pruning-not-consumed-anymore-v3/template.yaml index 31beeb893a..4c0abc94ea 100644 --- a/tests/conformance/inputs/pruning-not-consumed-anymore-v3/template.yaml +++ b/tests/conformance/inputs/pruning-not-consumed-anymore-v3/template.yaml @@ -1,11 +1,6 @@ tosca_definitions_version: tosca_variability_1_0_rc_3 topology_template: - variability: - options: - technology_required: false - enrich_technologies: false - inputs: some_input: type: string @@ -15,5 +10,6 @@ topology_template: type: container conditions: false persistent: true + managed: false properties: some_property: {get_input: some_input} diff --git a/tests/conformance/outputs/pruning-not-produced-anymore-xopera-v3/template.yaml b/tests/conformance/outputs/pruning-not-produced-anymore-xopera-v3/template.yaml index e9952a2a35..8edcbbb2d8 100644 --- a/tests/conformance/outputs/pruning-not-produced-anymore-xopera-v3/template.yaml +++ b/tests/conformance/outputs/pruning-not-produced-anymore-xopera-v3/template.yaml @@ -5,6 +5,7 @@ topology_template: options: technology_required: false enrich_technologies: false + technology_constraint: false outputs: input: diff --git a/tests/conformance/technologies/optimization/template.yaml b/tests/conformance/technologies/optimization/template.yaml index 6fc85abdab..0457cbe5b8 100644 --- a/tests/conformance/technologies/optimization/template.yaml +++ b/tests/conformance/technologies/optimization/template.yaml @@ -7,6 +7,7 @@ topology_template: optimization_technologies_unique: true technology_pruning: true technology_constraint: true + node_templates: container: type: container diff --git a/tests/conformance/technologies/throw-required/template.yaml b/tests/conformance/technologies/throw-required/template.yaml index 8e1a5d8273..68c17b07cd 100644 --- a/tests/conformance/technologies/throw-required/template.yaml +++ b/tests/conformance/technologies/throw-required/template.yaml @@ -22,6 +22,7 @@ topology_template: optimization_technologies_unique: true technology_pruning: true technology_constraint: true + required_technology_check: true technology_rules: - component: ansible_host