import wrappers.modelverse as mv import commons class Edge(object): """ Small helper class for association validation. Represents an edge as a connection between two nodes (n1, n2). Does not assume any direction. As such, the eq and hash implementations do not care about the ordering and an Edge("a", "b") object is equal to Edge("b", "a"). """ def __init__(self, n1, n2): self.n1 = n1 self.n2 = n2 def __eq__(self, other): # type: (Edge) -> bool if other.n1 == self.n1 or other.n1 == self.n2 and other.n2 == self.n1 or other.n2 == self.n2: return True return False def __hash__(self): return hash(self.n1) ^ hash(self.n2) def __repr__(self): return "{}-{}".format(self.n1, self.n2) class Verifier(object): def __init__(self, instance_model): self._instance_model = instance_model self._example_models = None self._available_types = None def set_instance_model(self, model): self._instance_model = model def get_instance_model(self): return self._instance_model def init(self): """ Inits the verifier. Loads required data from the modelverse to avoid reloading it for every step. """ self._example_models = commons.all_example_models() self._available_types = commons.get_available_types() def verify_node_typing(self): """ Checks if every node in instance model is typed by a node in any example model. Should actually not be neccessary since instance modeling only allows types from example models. """ # Check for every node in instance model if it has a valid type all_nodes = mv.all_instances(self._instance_model, "Node") for node in all_nodes: node_typ = commons.get_node_type(self._instance_model, node) if node_typ not in self._available_types: return {"OK": False, "error": "Type {} from instance model not in example models".format(node_typ)} return {"OK":True, "error":None, "affected":[]} def verify_node_multiplicity(self): """ A node is mandatory in the instance model if it occurs in every example model. More specifically, if a node type appears occ = [n0, n1, ... n_i] times in example model i, it needs to occur min(occ) in the instance model. """ total_occurences = {k: [] for k in self._available_types} for node, occ_list in total_occurences.iteritems(): for exm in self._example_models: num_occ = commons.count_occurences(node, exm) occ_list.append(num_occ) elem_cardinality = {k: min(v) for k, v in total_occurences.iteritems()} for el, min_occ in elem_cardinality.iteritems(): if not commons.count_occurences(el, self._instance_model) >= min_occ: return {"OK": False, "error": "Node with type {} needs to occur at least {} times".format(el, min_occ)} return {"OK": True, "error": None, "affected":[]} def _get_attributes_of_all_types(self): """ Helper for attribute check that returns a dictionary of the form {type:[attr_key_1, attr_key_2, ...], ...}, listing all attributes of every type from example models """ attrs_of_types = {node_type:[] for node_type in self._available_types} for exm in self._example_models: for node_type, attrs in attrs_of_types.iteritems(): attrs_of_type_in_exm = [x.key for x in commons.get_all_attributes_of_type(exm, node_type)] for attr in attrs_of_type_in_exm: if not attr in attrs: attrs.append(attr) return attrs_of_types def _is_attribute_mandatory_in(self, model, node_type, attr_key): """ Helper for attribute check that returns True if the attribute attr_key of type node_type is mandatory (i.e occurs in every node of the type) in model. """ nodes_of_type = commons.all_nodes_with_type(model, node_type) attrs_of_type = commons.get_all_attributes_of_type(model, node_type) ctr = 0 for attr in attrs_of_type: if attr.key == attr_key: ctr += 1 return len(nodes_of_type) == ctr def _is_attribute_mandatory(self, node_type, attr_key): """ Helper for attribute check that returns True if the attribute attr_key of type node_type is mandatory by looking at all example models. """ mand_list = [] for exm in self._example_models: is_mandatory = self._is_attribute_mandatory_in(exm, node_type, attr_key) mand_list.append(is_mandatory) return all(mand_list) def verify_attributes(self): """ 1. For every attribute key of a typed node in the instance model, there must be a corresponding attribute key in some example model. 2. An attribute for a type is mandatory if it occurs in every example model. """ # Check attribute keys for every node in instance model all_nodes = mv.all_instances(self._instance_model, "Node") for node in all_nodes: attrs = commons.get_attributes_of_node(self._instance_model, node) if not attrs: continue node_typ = commons.get_node_type(self._instance_model, node) for attr in attrs: # check every example model if such an attribute key is present for the specific type for exm in self._example_models: exm_attrs = commons.get_all_attributes_of_type(exm, node_typ) if attr.key in [x.key for x in exm_attrs]: break else: # loop completed without break, so we did not find any key in example models return {"OK": False, "error": "No key {} for type {} in example models".format(attr.key, node_typ), "affected":[node]} # Check if mandatory attributes are present in instance model attr_mandatory = {node_type:{} for node_type in self._available_types} all_attrs = self._get_attributes_of_all_types() for typ_i, attr_list in all_attrs.iteritems(): for typ_j, dic in attr_mandatory.iteritems(): if typ_j == typ_i: for attr in attr_list: dic[attr] = False # have dict like {"PC": {"owner":False}, "Router": {"IP":False}, ...} # now need to check which one is mandatory and set the boolean accordingly for node_type, attr_dict in attr_mandatory.iteritems(): for attr, _ in attr_dict.iteritems(): if self._is_attribute_mandatory(node_type, attr): attr_dict[attr] = True # for every node in instance model, check if it has the mandatory attributes for node in mv.all_instances(self._instance_model, "Node"): node_type = commons.get_node_type(self._instance_model, node) node_attrs = commons.get_attributes_of_node(self._instance_model, node) attr_mand = attr_mandatory[node_type] for attr, mand in attr_mand.iteritems(): if mand: if not attr in [x.key for x in node_attrs]: return {"OK": False, "error":"Attribute {} for type {} mandatory".format(attr, node_type), "affected":[node]} return {"OK": True, "error": None, "affected":[]} def verify_associations(self): """ 1. If an association between two types is present in all example models, it is mandatory and must therefore be present in the instance model (if it contains the same two types). 2. For every association in the instance model, the types of the source and target must correspond to the types of an association in some example model. This check should not be necessary since this is already enforced while instance modeling (see im_scene.py, draw_edge() which checks this when trying to connect two nodes). """ # get a set of mandatory edges (= edges that occur in every example model) all_edges = {model: set() for model in self._example_models} for exm in self._example_models: all_links = mv.all_instances(exm, "Edge") for link in all_links: src_id = mv.read_association_source(exm, link)[0] dest_id = mv.read_association_destination(exm, link)[0] src_type = commons.get_node_type(exm, src_id) dest_type = commons.get_node_type(exm, dest_id) all_edges[exm].add(Edge(src_type, dest_type)) mandatory_edges = set.intersection(*all_edges.values()) # check if instance model contains the types and the edge for edge in mandatory_edges: if not commons.model_contains_type(self._instance_model, edge.n1): continue if not commons.model_contains_type(self._instance_model, edge.n2): continue # instance model contains both types for node in commons.all_nodes_with_type(self._instance_model, edge.n1): if not commons.has_edge_to_type(self._instance_model, node, edge.n2): return {"OK":False, "error": "Edge between {} and {} mandatory".format(edge.n1, edge.n2), "affected":[]} # lastly, check if all edges in the instance model are actually supported all_edges = mv.all_instances(self._instance_model, "Edge") for edge in all_edges: src_id = mv.read_association_source(self._instance_model, edge)[0] dest_id = mv.read_association_destination(self._instance_model, edge)[0] src_type = commons.get_node_type(self._instance_model, src_id) dest_type = commons.get_node_type(self._instance_model, dest_id) if not commons.is_edge_supported(src_type, dest_type): return {"OK":False, "error": "Edge between {} and {} not supported".format(edge.n1, edge.n2), "affected":[src_id, dest_id]} return {"OK": True, "error": None, "affected":[]}