Skip to content

Commit

Permalink
solver: convert more eval expression syntax to solver queries.
Browse files Browse the repository at this point in the history
  • Loading branch information
aszs committed Dec 10, 2024
1 parent 6a919a1 commit 8e10d36
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 32 deletions.
12 changes: 8 additions & 4 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use topology::{sym, Topology};

/// A partial representations of a TOSCA node template (enough for [solve()])
#[cfg_attr(feature = "python", derive(FromPyObject))]
#[derive(Debug)]
pub struct Node {
/// node template name
pub name: String,
Expand Down Expand Up @@ -141,22 +142,23 @@ fn add_field_to_topology(
sym(n),
));
}
CriteriaTerm::NodeMatch { query } => {
CriteriaTerm::NodeMatch { start_node, query } => {
topology.result.push((
sym(node_name),
sym(&f.name),
0,
sym(node_name),
sym(start_node),
false,
));

for (index, (q, n)) in query.iter().enumerate() {
for (index, (q, n, param)) in query.iter().enumerate() {
topology.query.push((
sym(node_name),
sym(&f.name),
index,
*q,
sym(n),
sym(param),
index + 1 == query.len(),
));
}
Expand Down Expand Up @@ -394,6 +396,7 @@ pub fn solve(
add_node_to_topology(node, &mut prog, &type_parents, true, false)?;
}
run_program(&mut prog, timeout)?;
// println!("term_match: {:#?}", prog.term_match.iter().map(|x| (x.0.clone(), x.1.clone(), x.3.clone(), x.4.clone())).collect::<Vec<_>>());

// return requirement_match
let mut requirements = RequirementMatches::new();
Expand Down Expand Up @@ -458,7 +461,8 @@ mod tests {
n: "Service".into(),
},
CriteriaTerm::NodeMatch {
query: vec![(QueryType::Sources, "connects_to".into())],
start_node: name.into(),
query: vec![(QueryType::Sources, "connects_to".into(), "".into())],
},
],
tosca_type: Some("tosca.relationships.Root".into()),
Expand Down
41 changes: 22 additions & 19 deletions rust/src/topology.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ pub enum CriteriaTerm {
constraints: Vec<Constraint>,
},
NodeMatch {
query: Vec<(QueryType, Symbol)>,
start_node: Symbol,
query: Vec<(QueryType, Symbol, Symbol)>,
},
}

Expand Down Expand Up @@ -510,7 +511,7 @@ ascent! {

term_match(source, req, criteria, ct, target, None) <--
result(source, req, q_id, target, true), requirement(source, req, criteria, restrictions),
req_term_query(node, req, ct, q_id);
req_term_query(source, req, ct, q_id);

filtered(name, req_name, target, cn, criteria, Criteria::singleton(term.clone())) <--
term_match(name, req_name, criteria, term, target, cn);
Expand All @@ -535,39 +536,41 @@ ascent! {
transitive_match(x, r, z) <-- requirement_match(x, r, y, c), transitive_match(y, r, z);

// querying
relation query(NodeName, ReqName, QueryId, QueryType, ReqName, bool);
relation query(NodeName, ReqName, QueryId, QueryType, ReqName, Symbol, bool);
relation result(NodeName, ReqName, QueryId, NodeName, bool);

// rules for generating for each query type:

// include self in result
result(n, r, q_id + 1, s ,last) <--
query(n, r, q_id, qt, ?req, sym("SELF"), last) if *qt != QueryType::PropSource,
result(n, r, q_id, s, false);

result(n, r, q_id + 1, t ,last) <-- transitive_match(s, a, t),
query(n, r, q_id, QueryType::TransitiveRelation, a, last),
result(n, r, q_id, s, false);
query(n, r, q_id, QueryType::TransitiveRelation, a, _, last),
result(n, r, q_id, s, false);

result(n, r, q_id + 1, s, last) <-- required_by(s, a, t),
query(n, r, q_id, QueryType::RequiredBy, a, last),
result(n, r, q_id, t, false);
query(n, r, q_id, QueryType::RequiredBy, a, _, last),
result(n, r, q_id, t, false);

result(n, r2, q_id + 1, s, last) <-- required_by(s, r2, t),
query(n, r, q_id, QueryType::RequiredByType, a, last),
relationship(n, r, a),
result(n, r, q_id, t, false);
query(n, r, q_id, QueryType::RequiredByType, a, _, last),
relationship(n, r, a),
result(n, r, q_id, t, false);

result(n, r2, q_id + 1, t ,last) <-- transitive_match(s, r2, t),
query(n, r, q_id, QueryType::TransitiveRelationType, a, last),
query(n, r, q_id, QueryType::TransitiveRelationType, a, _, last),
relationship(n, r, a),
result(n, r, q_id, s, false);

result(node_name, req_name, q_id + 1, source, last) <-- requirement_match(source, a, target, ?cap),
query(node_name, req_name, q_id, QueryType::Sources, a, last),
result(node_name, req_name, q_id, target, false);
query(node_name, req_name, q_id, QueryType::Sources, a, _, last),
result(node_name, req_name, q_id, target, false);

result(node_name, req_name, q_id + 1, target, last) <-- requirement_match(source, a, target, ?cap),
query(node_name, req_name, q_id, QueryType::Targets, a, last),
result(node_name, req_name, q_id, source, false);

// result(t, q_id, final) <-- property_source(s, None, prop_name, t), query(q_id, QueryType::PropertySource"), prop_name, last), result(s, q_id, false);
// given an expression like configured_by::property, generate:
// [result(source_node, 1, false), query(1, "required_by", "configured_by", false), property_source_query(1, property, true)]
query(node_name, req_name, q_id, QueryType::Targets, a, _, last),
result(node_name, req_name, q_id, source, false);
}

#[cfg(test)]
Expand Down
60 changes: 51 additions & 9 deletions unfurl/solver.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Copyright (c) 2024 Adam Souzis
# SPDX-License-Identifier: MIT
from typing import Any, Dict, List, Optional, Tuple, cast
from typing import Any, Dict, List, Optional, Tuple, Union, cast
import sys

from .support import resolve_function_keyword

# import types from rust extension
from .tosca_solver import ( # type: ignore
solve,
Expand Down Expand Up @@ -276,49 +278,89 @@ def convert(
return entity


def add_match(terms, match) -> None:
def add_match(
node_template: NodeTemplate, terms: list, match: Union[str, dict]
) -> None:
if isinstance(match, dict) and (get_property := match.get("get_property")):
assert isinstance(get_property, list), get_property
match = cast(str, resolve_function_keyword(get_property[0]))
if len(get_property) > 2:
match += "::.capabilities" # XXX or .targets (cf. .names)
match += "::" + "::".join(get_property[1:])
if isinstance(match, dict) and (node_type := match.get("get_nodes_of_type")):
terms.append(CriteriaTerm.NodeType(node_type))
else:
skip = False
query = []
query: List[Tuple[QueryType, str, str]] = []
start_node: str = node_template.name
result = analyze_expr(match)
if result:
expr_list = result.get_keys()
# logger.warning(f"{match} {expr_list=}")
# logger.warning(f"expr_list: {node_template.name} with {match}:\n {expr_list=}")
query_type = None
cap = ""
for key in expr_list:
if key == "$start":
continue
if cap == ".capabilities":
cap = key # assume this key is capability name
continue
if query_type is not None:
query.append((query_type, key))
# Sources or Targets consume next key
query.append((query_type, key, ""))
query_type = None
else:
if key.startswith("::"):
query = []
start_node = key[2:]
if key.startswith("."):
if key == ".configured_by":
if key == ".":
continue
elif key == "..":
query.append((QueryType.Targets, "host", ""))
elif key.startswith(".root"):
query = []
start_node = "root"
elif key == ".instances":
query.append((QueryType.Sources, "host", ""))
elif key == ".configured_by":
query.append(
(
QueryType.RequiredByType,
"unfurl.relationships.Configures",
"",
)
)
elif key == ".parents" or key == ".ancestors":
query.append(
(
QueryType.TransitiveRelation,
"host",
"SELF" if key == ".ancestors" else "",
)
)
elif key == ".hosted_on":
query.append(
(
QueryType.TransitiveRelationType,
"tosca.relationships.HostedOn",
"",
)
)
elif key == ".capabilities":
cap = ".capabilities" # assume next key is capability name
else:
# matches Sources or Targets
query_type = getattr(QueryType, key[1:].title(), None)
if query_type is None:
skip = True
break
else: # key is prop
query.append((QueryType.PropSource, key))
query.append((QueryType.PropSource, key, cap))
cap = ""
# logger.warning(f"{skip} {query=}")
if query and not skip:
terms.append(CriteriaTerm.NodeMatch(query))
terms.append(CriteriaTerm.NodeMatch(start_node, query))


def get_req_terms(
Expand Down Expand Up @@ -371,7 +413,7 @@ def get_req_terms(
if node_filter:
match = node_filter.get("match")
if match:
add_match(terms, match)
add_match(node_template, terms, match)
if not filter2term(terms, node_filter, None):
return None, False # has an unsupported constraint, bail
for cap_filters in node_filter.get("capabilities", []):
Expand Down

0 comments on commit 8e10d36

Please sign in to comment.