Skip to content

Commit

Permalink
technology plugin system
Browse files Browse the repository at this point in the history
  • Loading branch information
milesstoetzner authored Feb 3, 2024
1 parent 7af984b commit bea3ba4
Show file tree
Hide file tree
Showing 44 changed files with 600 additions and 105 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ node_modules
yarn-error.log
.DS_STORE

# e.g. node modules of plugins
!tests/**/node_modules

build-docs
dist-docs

Expand Down
54 changes: 40 additions & 14 deletions docs/docs/variability4tosca/specification/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ Such a topology template is also called variable topology template.

A variability definition defines variability inputs, variability presets, variability expressions, and variability options.

| Keyname | Mandatory | Type | Description |
|---------------------------------|-----------|---------------------------------------|-------------------------------------------------------------------------|
| inputs | true | Map(String, VariabilityInput) | A required map of input parameters used inside variability expressions. |
| presets | false | Map(String, VariabilityPreset) | An optional map of variability preset definitions. |
| expressions | false | Map(String, VariabilityExpression) | An optional map of variability expressions. |
| options | false | Map(String, Boolean) | An optional map of variability options. |
| type_specific_conditions | false | List(TypeSpecificDefaultCondition) | An optional definition of type-specific default conditions. |
| technology_assignment_rules | false | Map(String, TechnologyAssignmentRule) | An optional definition of technology assignment rules. |
| Keyname | Mandatory | Type | Description |
|-----------------------------|-----------|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| inputs | false | Map(String, VariabilityInput) | An optional map of input parameters used inside variability expressions. |
| presets | false | Map(String, VariabilityPreset) | An optional map of variability preset definitions. |
| expressions | false | Map(String, VariabilityExpression) | An optional map of variability expressions. |
| options | false | Map(String, Boolean) | An optional map of variability options. |
| type_specific_conditions | false | String | List(TypeSpecificDefaultCondition) | An optional definition of type-specific default conditions. If string, then treated as relative file to import (default: "./type-specific-conditions.yaml") |
| technology_assignment_rules | false | String | Map(String, TechnologyAssignmentRule) | An optional definition of technology assignment rules. If string, then treated as relative file to import (default: "./rules.yaml"). |
| plugins | false | PluginDefinition | An optional definition of plugins. |

The following non-normative and incomplete example contains a variability definition which declares the variability
input `mode` and two variability presets `dev` and `prod` are defined which either assigns `mode` the value `dev` or `prod`.
Expand Down Expand Up @@ -334,12 +335,12 @@ _Conditional types conflict with this feature!_
Technology assignment rules can be defined to automatically select a deployment technology for a component.
A technology assignment rule is defined as follows.

| Keyname | Mandatory | Type | Description |
|------------|-----------|--------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| component | true | String | The type of the component to which the technology can be assigned. |
| host | false | String | The type of the host of the component which the technology requires (conflicts with "conditions") |
| conditions | false | VariabilityCondition | List(VariabilityCondition) | The conditions under which a technology can be assigned to a component (conflicts with "host"). |
| weight | false | Number | The weight which is minimized (default is 1). |
| Keyname | Mandatory | Type | Description |
|------------|-----------|--------------------------------------------------------|-------------------------------------------------------------------------|
| component | true | String | The type of the component to which the technology can be assigned. |
| host | false | String | The type of the host of the component which the technology requires. |
| conditions | false | VariabilityCondition | List(VariabilityCondition) | The conditions under which a technology can be assigned to a component. |
| weight | false | Number | The weight which is minimized (default is 1). |

For example, the node type `application` can be deployed using the deployment technology `terraform` if the host is of type `terraform_host`.

Expand Down Expand Up @@ -941,6 +942,31 @@ The following date operators can be used inside a variability expression.
| after_or_same | Tuple(String | Number, String | Number) | Boolean | Returns if first date is after or same as the second date. |
| within | Tuple(String | Number, Tuple(String | Number, String | Number)) | Boolean | Returns if given date is within the given dates. |

## Plugin Definition

The following plugins can be defined.

| Keyname | Mandatory | Type | Description |
|-------------------------------|-----------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| technology | false | List(String) | An optional list of technology rule plugins. Strings are treated as relative imports. Plugins are also loaded from "./plugins/technology". |


## Technology Plugin

A technology plugin must export the following interface.

```typescript linenums="1"
export type TechnologyPluginBuilder = {
build(graph: Graph): TechnologyPlugin
}
```

```typescript linenums="1"
export type TechnologyPlugin = {
assign: (node: Node) => ConditionalTechnologyAssignment[]
}
```

## Processing

We describe on a high-level the steps to derive a variability-resolved service template from a variable service template.
Expand Down
4 changes: 2 additions & 2 deletions src/controller/template/enrich.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as assert from '#assert'
import Enricher from '#enricher'
import * as files from '#files'
import {ServiceTemplate} from '#spec/service-template'
import Loader from '#graph/loader'

export type TemplateEnrichOptions = {
template: string
Expand All @@ -13,7 +13,7 @@ export type TemplateEnrichOptions = {
export default async function (options: TemplateEnrichOptions) {
assert.isDefined(options.template, 'Template not defined')
assert.isDefined(options.output, 'Output not defined')
const template = files.loadYAML<ServiceTemplate>(options.template)
const template = await new Loader(options.template).load()
await new Enricher(template).run()
files.storeYAML(options.output, template)
}
56 changes: 7 additions & 49 deletions src/enricher/elements.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import * as assert from '#assert'
import * as check from '#check'
import Graph from '#graph/graph'
import Node from '#graph/node'
import {andify, generatify} from '#graph/utils'
import {LogicExpression} from '#spec/variability'
import * as utils from '#utils'

export class ElementEnricher {
private readonly graph: Graph
Expand All @@ -20,53 +16,15 @@ export class ElementEnricher {
}

private enrichTechnologies() {
const map = this.graph.serviceTemplate.topology_template?.variability?.technology_assignment_rules
if (check.isUndefined(map)) return
for (const node of this.graph.nodes) {
if (!utils.isEmpty(node.technologies)) continue

for (const technology of Object.keys(map)) {
const rules = map[technology]
assert.isArray(rules)

for (const rule of rules) {
assert.isString(rule.component)

const nodes = this.graph.nodes.filter(it => it.getType().name === rule.component)
for (const node of nodes) {
if (check.isString(rule.host)) {
const hosts = node.hosts.filter(it => it.getType().name === rule.host)
for (const host of hosts) {
this.addTechnology({
node,
technology,
conditions: {node_presence: host.name},
weight: rule.weight,
})
}
} else {
this.addTechnology({node, technology, conditions: rule.conditions, weight: rule.weight})
}
for (const plugin of this.graph.plugins.technology) {
const assignments = plugin.assign(node)
for (const assignment of assignments) {
this.graph.addTechnology(node, assignment)
}
}
}
}

private addTechnology({
node,
technology,
conditions,
weight,
}: {
node: Node
technology: string
conditions?: LogicExpression | LogicExpression[]
weight?: number
}) {
if (check.isUndefined(node.raw.technology)) node.raw.technology = []
assert.isArray(node.raw.technology, `Technology of ${node.display} not normalized`)

conditions = check.isArray(conditions) ? andify(conditions) : conditions
conditions = check.isDefined(conditions) ? generatify(conditions) : undefined

node.raw.technology.push({[technology]: {conditions, weight}})
}
}
15 changes: 12 additions & 3 deletions src/enricher/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,20 @@ export default class Transformer {
private cleanVariabilityDefinition() {
if (check.isUndefined(this.topology.variability)) return

// Delete technology assignment rules
delete this.topology.variability.technology_assignment_rules

// Delete type-specific conditions variability definition
delete this.topology.variability.type_specific_conditions

// Delete empty inputs
if (check.isDefined(this.topology.variability.inputs) && utils.isEmpty(this.topology.variability.inputs))
delete this.topology.variability.inputs

// Delete plugins
delete this.topology.variability.plugins

// Delete enricher options
if (check.isDefined(this.topology.variability.options)) {
// Delete pruning mode
delete this.topology.variability.options.mode
Expand Down Expand Up @@ -174,9 +186,6 @@ export default class Transformer {
// Remove empty options
if (utils.isEmpty(this.topology.variability.options)) delete this.topology.variability.options

// Delete type-specific conditions variability definition
delete this.topology.variability.type_specific_conditions

// Remove empty variability
if (utils.isEmpty(this.topology.variability)) delete this.topology.variability
}
Expand Down
4 changes: 1 addition & 3 deletions src/graph/artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ export default class Artifact extends Element {
}

getTypeSpecificConditionWrapper() {
return this.graph.serviceTemplate.topology_template?.variability?.type_specific_conditions?.artifact_types?.[
this.raw.type
]
return this.graph.getTypeSpecificConditions()?.artifact_types?.[this.raw.type]
}

getElementGenericCondition() {
Expand Down
41 changes: 39 additions & 2 deletions src/graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import * as assert from '#assert'
import * as check from '#check'
import Import from '#graph/import'
import {Options} from '#graph/options'
import {ConditionalTechnologyAssignment, TechnologyPlugin} from '#graph/plugin'
import {Populator} from '#graph/populator'
import Technology from '#graph/technology'
import {andify, generatify, simplify} from '#graph/utils'
import Normalizer from '#normalizer'
import {ServiceTemplate, TOSCA_DEFINITIONS_VERSION} from '#spec/service-template'
import {
Expand Down Expand Up @@ -83,9 +85,14 @@ export default class Graph {

constraints: LogicExpression[] = []

plugins: {technology: TechnologyPlugin[]} = {technology: []}

constructor(serviceTemplate: ServiceTemplate) {
this.serviceTemplate = serviceTemplate

/**
* Ensure supported TOSCA version
*/
if (
![
TOSCA_DEFINITIONS_VERSION.TOSCA_SIMPLE_YAML_1_3,
Expand All @@ -94,10 +101,17 @@ export default class Graph {
)
throw new Error('Unsupported TOSCA definitions version')

new Normalizer(serviceTemplate).run()
/**
* Normalizer
*/
new Normalizer(this.serviceTemplate).run()

/**
* Populator
*/
new Populator(this).run()
}

getNode(name: string | 'SELF' | 'CONTAINER', context: Context = {}) {
assert.isString(name)

Expand Down Expand Up @@ -508,18 +522,41 @@ export default class Graph {
return technology
}

getTypeSpecificConditions() {
const conditions = this.serviceTemplate.topology_template?.variability?.type_specific_conditions
if (check.isString(conditions)) throw new Error(`Type-specific definitions not loaded`)
return conditions
}

addConstraint(constraint: LogicExpression) {
assert.isDefined(this.serviceTemplate.topology_template, 'Service template has no topology template')

if (check.isUndefined(this.serviceTemplate.topology_template.variability))
this.serviceTemplate.topology_template.variability = {inputs: {}}
this.serviceTemplate.topology_template.variability = {}

if (check.isUndefined(this.serviceTemplate.topology_template.variability.constraints))
this.serviceTemplate.topology_template.variability.constraints = []

this.serviceTemplate.topology_template.variability.constraints.push(constraint)
}

addTechnology(node: Node, assignment: ConditionalTechnologyAssignment) {
if (check.isUndefined(node.raw.technology)) node.raw.technology = []
assert.isArray(node.raw.technology, `Technology of ${node.display} not normalized`)

// Normalize
assignment.conditions = check.isArray(assignment.conditions)
? simplify(andify(assignment.conditions))
: assignment.conditions

// Generatify
assignment.conditions = check.isDefined(assignment.conditions) ? generatify(assignment.conditions) : undefined

node.raw.technology.push({
[assignment.technology]: {conditions: assignment.conditions, weight: assignment.weight},
})
}

regenerate() {
return new Graph(this.serviceTemplate)
}
Expand Down
4 changes: 1 addition & 3 deletions src/graph/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ export default class Group extends Element {
// Not supported when conditional types are used
if (this.types.length > 1) return
const type = this.types[0]
return this.graph.serviceTemplate.topology_template?.variability?.type_specific_conditions?.group_types?.[
type.name
]
return this.graph.getTypeSpecificConditions()?.group_types?.[type.name]
}

getElementGenericCondition() {
Expand Down
Loading

0 comments on commit bea3ba4

Please sign in to comment.