diff --git a/src/enricher/constraints.ts b/src/enricher/constraints.ts index f8f3b0960b..9df13ac70a 100644 --- a/src/enricher/constraints.ts +++ b/src/enricher/constraints.ts @@ -40,8 +40,10 @@ export class ConstraintEnricher { } assert.isDefined(left, 'Left not defined') + // TODO: issue a manaul id + // Sanity check - if (!(element.isRelation() || element.isArtifact())) + if (!(element.isRelation() || element.isArtifact() || element.isProperty())) throw new Error(`${element.Display} is not issued a manual id`) this.graph.addConstraint({ @@ -107,7 +109,11 @@ export class ConstraintEnricher { ...this.graph.artifacts, ]) { for (const properties of element.propertiesMap.values()) { - this.graph.addConstraint({amo: properties.map(it => it.id)}) + // TODO: CRITICAL: REVERT THIS + if (properties.length === 0) continue + this.graph.addConstraint({ + implies: [properties[0].container.id, {exo: properties.map(it => it.id)}], + }) } } } diff --git a/src/graph/graph.ts b/src/graph/graph.ts index bd2eec88cf..4eedd4f858 100644 --- a/src/graph/graph.ts +++ b/src/graph/graph.ts @@ -95,7 +95,7 @@ export default class Graph { plugins: {technology: TechnologyPlugin[]} = {technology: []} - constructor(serviceTemplate: ServiceTemplate) { + constructor(serviceTemplate: ServiceTemplate, options: {nope: boolean} = {nope: false}) { this.serviceTemplate = serviceTemplate /** @@ -125,7 +125,7 @@ export default class Graph { /** * Populator */ - new Populator(this).run() + new Populator(this, options).run() } getNode(name: string | 'SELF' | 'CONTAINER' | 'SOURCE' | 'TARGET', context: Context = {}): Node { diff --git a/src/graph/populator.ts b/src/graph/populator.ts index d50204a67e..3a1e4011ec 100644 --- a/src/graph/populator.ts +++ b/src/graph/populator.ts @@ -19,9 +19,11 @@ import * as utils from '#utils' export class Populator { graph: Graph + options: {nope: boolean} - constructor(graph: Graph) { + constructor(graph: Graph, options: {nope: boolean} = {nope: false}) { this.graph = graph + this.options = options } run() { @@ -392,6 +394,38 @@ export class Populator { this.graph.properties.push(property) } + // TODO: move this into normalizer? + // TODO: dont extend after transpiling bratans + // TODO: NEXT: add undefined default alternative + if (!this.options.nope) { + // Ensure that there is only one default property per property name + element.propertiesMap.forEach(properties => { + const alternative = properties.find(it => it.defaultAlternative) + if (check.isDefined(alternative)) return + + const some = properties[0] + assert.isDefined(some) + + console.log(`not defined for ${some.display}`) + + const property = new Property({ + name: some.name, + container: some.container, + index: properties.length, + raw: { + value: 'VINTNER_UNDEFINED', + default_alternative: true, + implied: true, + }, + }) + + property.graph = this.graph + properties.push(property) + element.properties.push(property) + this.graph.properties.push(property) + }) + } + // Ensure that there is only one default property per property name element.propertiesMap.forEach(properties => { const candidates = properties.filter(it => it.defaultAlternative) @@ -481,27 +515,37 @@ export class Populator { /** * We only support simple consumers, i.e., directly accessed by properties */ + // TODO: unfurl syntax support? private populateConsumers() { - for (const input of this.graph.inputs) { - for (const property of this.graph.properties) { - const value = property.value - if (check.isObject(value) && !check.isArray(value) && check.isDefined(value.get_input)) { - // Referenced by name - if (check.isString(value.get_input)) { - if (value.get_input === input.name) input.consumers.push(property) - continue - } - - // Referenced by index - if (check.isNumber(value.get_input)) { - if (value.get_input === input.index) input.consumers.push(property) - continue - } + for (const property of this.graph.properties) { + const value = property.value + if (!check.isObject(value) || check.isArray(value)) continue - throw new Error( - `${property.Display} has neither number nor string in get_input but "${value.get_input}"` - ) - } + if (check.isDefined(value.get_input)) { + assert.isStringOrNumber(value.get_input) + const input = this.graph.getInput(value.get_input) + + input.consumers.push(property) + property.consuming = input + } + + if (check.isDefined(value.get_property)) { + assert.isArray(value.get_property) + assert.isString(value.get_property[0]) + assert.isString(value.get_property[1]) + // TODO: find all properties? only component? + // TODO: only for nodes? + property.consuming = this.graph.getNodeProperty([value.get_property[0], value.get_property[1]], { + element: property.container, + }) + console.log(`${property.Display} is consuming ${property.consuming.display}`) + } + + if (check.isDefined(value.get_attribute)) { + assert.isArray(value.get_attribute) + assert.isString(value.get_attribute[0]) + // TODO: this is only correct for get_attribute SELF + property.consuming = property.container } } } diff --git a/src/graph/property.ts b/src/graph/property.ts index a7024ac626..ba8710850c 100644 --- a/src/graph/property.ts +++ b/src/graph/property.ts @@ -1,5 +1,6 @@ import * as check from '#check' -import {bratify} from '#graph/utils' +import Input from '#graph/input' +import {andify, bratify} from '#graph/utils' import {ArtifactDefinition} from '#spec/artifact-definitions' import {GroupTemplate} from '#spec/group-template' import {NodeTemplate} from '#spec/node-template' @@ -33,6 +34,8 @@ export default class Property extends Element { value?: PropertyAssignmentValue readonly expression?: ValueExpression + consuming?: Property | Input | Node | Relation | Group | Policy | Artifact + constructor(data: { name: string raw: ConditionalPropertyAssignmentValue @@ -91,7 +94,17 @@ export default class Property extends Element { // TODO: getTypeSpecificCondition, however, get type from type definition being part of the container type ... getElementGenericCondition() { - return [{conditions: this.container.presenceCondition, consistency: true, semantic: false}] + const conditions: LogicExpression[] = [] + + // Container pruning + conditions.push(this.container.presenceCondition) + + // Input value pruning + if (check.isDefined(this.consuming)) { + conditions.push(this.consuming.presenceCondition) + } + + return [{conditions: andify(conditions), consistency: true, semantic: false}] } constructPresenceCondition() { @@ -103,6 +116,14 @@ export default class Property extends Element { return bratify(this.container.propertiesMap.get(this.name)!.filter(it => it !== this)) } + /** + * A property is implied by default. + * This is also okay for default alternatives since bratan condition is a manual condition. + */ + get implied() { + return this.raw.implied ?? true + } + isProperty() { return true } diff --git a/src/resolver/solver.ts b/src/resolver/solver.ts index 581697b0b0..6e2b509a70 100644 --- a/src/resolver/solver.ts +++ b/src/resolver/solver.ts @@ -254,28 +254,29 @@ export default class Solver { // Add manual conditions of a relation separately as own variable into the sat solver. // Manual conditions are referenced by has_incoming_relation and has_artifact as well as by implied relations. - if (element.isRelation() || element.isArtifact()) { - // Optimization if manual conditions are empty, thus, "true" is fallback - if (utils.isEmpty(conditions)) { - this.minisat.require(element.manualId) - } else { - this.minisat.require( - MiniSat.equiv( - element.manualId, - this.transformLogicExpression( - andify( - conditions.filter(it => { - // This also includes _bratan - if (check.isObject(it)) return !it._generated - return true - }) - ), - {element} - ) + // Also, they are used to prevent pruning input-consuming properties + //if (element.isRelation() || element.isArtifact() || element.isProperty()) { + // Optimization if manual conditions are empty, thus, "true" is fallback + if (utils.isEmpty(conditions)) { + this.minisat.require(element.manualId) + } else { + this.minisat.require( + MiniSat.equiv( + element.manualId, + this.transformLogicExpression( + andify( + conditions.filter(it => { + // This also includes _bratan + if (check.isObject(it)) return !it._generated + return true + }) + ), + {element} ) ) - } + ) } + //} // If there are no conditions assigned, then the element is present if (utils.isEmpty(conditions)) return this.minisat.require(element.id) diff --git a/src/specification/property-assignments.ts b/src/specification/property-assignments.ts index ea7b0e95ef..6b230f9c89 100644 --- a/src/specification/property-assignments.ts +++ b/src/specification/property-assignments.ts @@ -14,6 +14,7 @@ export type PropertyAssignmentValue = | boolean | PropertyAssignmentValue[] | {[key: string]: PropertyAssignmentValue} + | {get_property: [string, string]; get_input: string; get_attribute: [string, string]} export type PropertyAssignmentList = PropertyAssignmentListEntry[] export type PropertyAssignmentListEntry = { diff --git a/tests/minisat/minisat.play.ts b/tests/minisat/minisat.play.ts index 4c6157b228..5c5387d304 100644 --- a/tests/minisat/minisat.play.ts +++ b/tests/minisat/minisat.play.ts @@ -23,7 +23,8 @@ async function play(data: string) { hotfixPersistentCheck(template) - const solver = new Solver(new Graph(template), {}) + const solver = new Solver(new Graph(template, {nope: true}), {}) + const results = solver.runAll().map(it => utils.sort(it)) std.log(`Results: ${results.length}`) std.log(results) diff --git a/tests/minisat/plays/enrich.sh b/tests/minisat/plays/enrich.sh old mode 100644 new mode 100755 index 915095bcf3..657aca6bce --- a/tests/minisat/plays/enrich.sh +++ b/tests/minisat/plays/enrich.sh @@ -1,4 +1,4 @@ #!/bin/bash set -e -task cli -- template enrich --template ${PWD}/old.minimal.yaml --output ${PWD}/enriched.old.minimal.yaml +./task template enrich --template ${PWD}/old.minimal.yaml --output ${PWD}/enriched.old.minimal.yaml diff --git a/tests/minisat/plays/play.yaml b/tests/minisat/plays/play.yaml index 6b620e44d4..d7e1a581da 100644 --- a/tests/minisat/plays/play.yaml +++ b/tests/minisat/plays/play.yaml @@ -1,12 +1,35 @@ -tosca_definitions_version: tosca_variability_1_0 +tosca_definitions_version: tosca_variability_1_0_rc_3 + +node_types: + container: + type: tosca.nodes.Root topology_template: variability: options: - type_default_condition: true - input_default_condition: true - property_default_condition: true + enrich_implementations_check: false + required_technology_check: false inputs: some_input: type: string + + node_templates: + container: + type: container + conditions: true + properties: + - some_property: + value: {get_input: some_input} + conditions: false + implied: true + + - some_property: + value: something else + conditions: false + implied: true + + # - some_property: + # value: NOT_DEFINED + # default_alternative: true + # implied: true