Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quality keep until submission #379

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/contributing/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ cd opentosca-vintner
git lfs install
git lfs pull
./task install
./task tasks:build
./task examples:pull:link
```

Expand Down
17 changes: 17 additions & 0 deletions src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,23 @@ template
})
)

template
.command('quality')
.description('get quality of template (experimental)')
.requiredOption('--experimental', 'enable experimental feature')
.requiredOption('--template <string>', 'path to service template')
.option('--dir [string]', 'override dir')
.option('--presets [string...]', 'names of variability presets(env: OPENTOSCA_VINTNER_VARIABILITY_PRESETS)', [])
.option(
'--inputs [string]',
'path to the variability inputs (supported: [YAML, FeatureIDE ExtendedXML], env: OPENTOSCA_VINTNER_VARIABILITY_INPUT_${KEY})'
)
.action(
hae.exit(async options => {
std.out(await Controller.template.quality(options))
})
)

const puml = template.command('puml').description('generate puml')

const pumlTopology = puml
Expand Down
2 changes: 2 additions & 0 deletions src/controller/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _normalize from './normalize'
import _package from './package'
import _pull from './pull'
import PUMLController from './puml'
import _quality from './quality'
import _query from './query'
import _resolve from './resolve'
import _sign from './sign'
Expand All @@ -29,5 +30,6 @@ export default {
sign: _sign,
verify: _verify,
pull: _pull,
quality: _quality,
unpull: _unpull,
}
92 changes: 92 additions & 0 deletions src/controller/template/quality.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import Graph from '#graph/graph'
import Loader from '#graph/loader'
import * as Resolver from '#resolver'
import * as assert from '#utils/assert'
import * as check from '#utils/check'
import path from 'path'

export type TemplateQualityOptions = {
template: string
dir?: string
presets?: string[]
inputs?: string
experimental: boolean
}

export type TemplateQualityResult = TemplateQuality[]

export type TemplateQuality = {
quality: number
count: number
normalized: number
}

// TODO: this is so dirty
export default async function (options: TemplateQualityOptions): Promise<TemplateQualityResult> {
assert.isDefined(options.template, 'Template not defined')
assert.isTrue(options.experimental)

if (check.isDefined(options.presets) || check.isDefined(options.inputs)) {
// TODO: this makes only randomized or non unique results sense?
const results = await Resolver.optimize({
template: options.template,
inputs: options.inputs,
presets: options.presets,
})

/**
* TODO: checker does not run on these results!
* should resolve it twice: once for best and once for worst
*
* but due to topology optimization and uniqueness we can assume that nodes at fixed and therefore their technologies?
*
*/

if (results.length === 0) return []

if (results.length === 1) return [results[0].quality]

return [results[0].quality, results[results.length - 1].quality]
}

// TODO: should the graph detect technology in types? and then utilize Resolver.optimize

const template = await new Loader(options.template, options.dir ? path.resolve(options.dir) : undefined).load()
const graph = new Graph(template)

const map = graph.serviceTemplate.topology_template?.variability?.technology_assignment_rules
if (check.isUndefined(map)) return [{quality: 0, count: 0, normalized: 0}]
assert.isObject(map, 'Rules not loaded')

let weight = 0
let count = 0

for (const node of graph.nodes) {
const type = node.getType()

for (const [technology, rules] of Object.entries(map)) {
for (const rule of rules) {
// TODO: fix this
assert.isDefined(rule.assign)

// TODO: why can this be undefined?!
assert.isDefined(rule.weight)

// TODO: check if there are collisions?

if (rule.assign === type.name) {
console.log({
node: node.name,
type: type.name,
weight: rule.weight,
})
weight += rule.weight
count++
}
}
}
}

if (count === 0) return [{quality: weight, count, normalized: 0}]
return [{quality: weight, count, normalized: weight / count}]
}
4 changes: 2 additions & 2 deletions src/graph/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export default class Loader {
private serviceTemplate?: ServiceTemplate
private graph?: Graph

constructor(file: string) {
constructor(file: string, dir?: string) {
this.file = file
this.dir = files.getDirectory(file)
this.dir = dir ?? files.getDirectory(file)
}

raw() {
Expand Down
40 changes: 31 additions & 9 deletions src/resolver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,36 @@ export type ResolveResult = {
}

export async function run(options: ResolveOptions): Promise<ResolveResult> {
/**
* Graph
*/
const {graph, inputs} = await load(options)

/**
* Resolver
*/
new Resolver(graph, inputs).run()

return {
inputs: inputs,
template: graph.serviceTemplate,
}
}

// TODO: rename this
export async function optimize(options: ResolveOptions) {
/**
* Graph
*/
const {graph, inputs} = await load(options)

/**
* Resolver
*/
return new Resolver(graph, inputs).optimize()
}

async function load(options: ResolveOptions) {
if (check.isUndefined(options.presets)) options.presets = []
if (!check.isArray(options.presets)) throw new Error(`Presets must be a list`)

Expand Down Expand Up @@ -84,13 +114,5 @@ export async function run(options: ResolveOptions): Promise<ResolveResult> {
*/
const graph = new Graph(options.template)

/**
* Resolver
*/
new Resolver(graph, inputs.inputs).run()

return {
inputs: inputs.inputs,
template: options.template,
}
return {graph, inputs: inputs.inputs}
}
1 change: 0 additions & 1 deletion src/resolver/optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export default class Optimizer {

return this.results
}

private optimizeTopology() {
/**
* Minimize
Expand Down
4 changes: 4 additions & 0 deletions src/resolver/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ export default class Resolver {
*/
new Transformer(this.graph).run()
}

optimize() {
return new Solver(this.graph, this.inputs).optimize()
}
}
15 changes: 14 additions & 1 deletion src/resolver/result.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as check from '#check'
import {TemplateQuality} from '#controller/template/quality'
import Element from '#graph/element'
import Graph from '#graph/graph'
import * as utils from '#utils'
Expand All @@ -12,13 +13,15 @@ export class Result {
private readonly result: MiniSat.Solution
readonly topology: {count: number; weight: number}
readonly technologies: {count: number; weight: number}
readonly quality: TemplateQuality

constructor(graph: Graph, result: MiniSat.Solution) {
this.graph = graph
this.result = result

this.topology = this.weightTopology()
this.technologies = this.weightTechnologies()
this.quality = this.assessQuality()
}

private _map?: ResultMap
Expand Down Expand Up @@ -55,8 +58,18 @@ export class Result {
}
}

private assessQuality(): TemplateQuality {
const values = this.weightTechnologies()
const count = this.graph.technologies.filter(it => this.isPresent(it)).length
return {
quality: values.weight,
count,
normalized: values.weight / count,
}
}

/**
* Can not use element.present yet since we are currently selecting the result!
* Note, we can not use element.present yet since we are currently selecting the result!
*/
isPresent(element: Element) {
return check.isTrue(this.map[element.id])
Expand Down
18 changes: 18 additions & 0 deletions src/resolver/solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,24 @@ export default class Solver {
return result.map
}

optimize() {
if (this.solved) throw new Error('Has been already solved')
this.solved = true

this.transform()

/**
* Get all results
*/
const results = this.solveAll()
if (utils.isEmpty(results)) throw new Error('Could not solve')

/**
* Optimizer
*/
return new Optimizer(this.graph, results).optimize()
}

runAll(): ResultMap[] {
if (this.solved) throw new Error(`Has been already solved`)
this.solved = true
Expand Down
Loading