diff --git a/requirements.txt b/requirements.txt index a25a0cc88..93d439713 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pbr>=1.6 Babel>=1.3 - +networkx>=1.10 oslo.log>=1.12.0 # Apache-2.0 pecan>=0.8.0 PasteDeploy>=1.5.0 diff --git a/test-requirements.txt b/test-requirements.txt index 5ff5975f2..e5da1f235 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,9 +3,9 @@ # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 - coverage>=3.6 discover +networkx>=1.10 python-subunit>=0.0.18 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 oslo.log>=1.12.0 # Apache-2.0 diff --git a/vitrage/graph/__init__.py b/vitrage/graph/__init__.py new file mode 100644 index 000000000..93dfb1e0a --- /dev/null +++ b/vitrage/graph/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Graph abstraction +""" + +import networkx_graph + + +def graph_factory(name): + """For now only return NXGraph + + :rtype: Graph + """ + return networkx_graph.NXGraph(name) diff --git a/vitrage/graph/driver.py b/vitrage/graph/driver.py new file mode 100644 index 000000000..c7c0b4c9d --- /dev/null +++ b/vitrage/graph/driver.py @@ -0,0 +1,276 @@ +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Defines interface for Graph access and manipulation + +Functions in this module are imported into the vitrage.graph namespace. +Call these functions from vitrage.graph namespace and not the +vitrage.graph.driver namespace. + +""" +import abc +import six + + +class Vertex(object): + """Class Vertex + + A vertex is defined as follows: + * vertex_id is a unique identifier + * properties is a dictionary + + """ + def __init__(self, vertex_id, properties=None): + """Create a Vertex instance + + :type vertex_id: str + :type properties: dict + :rtype: Vertex + """ + if not vertex_id: + raise AttributeError('Attribute vertex_id is missing') + self.vertex_id = vertex_id + self.properties = properties + + +class Edge(object): + """Class Edge represents a directional edge between two vertices + + An edge is defined as follows: + * source_id is the first vertex id + * target_id is the second vertex id + * properties is a dictionary + + +---------------+ edge +---------------+ + | source vertex |-----------> | target vertex | + +---------------+ +---------------+ + + """ + + def __init__(self, source_id, target_id, label, properties=None): + """Create an Edge instance + + :param source_id: source vertex id + :type source_id: str + + :param target_id: target vertex id + :type target_id: str + + :param label: + :type label: str + + :type properties: dict + :rtype: Edge + """ + if not source_id: + raise AttributeError('Attribute source_id is missing') + if not target_id: + raise AttributeError('Attribute target_id is missing') + if not label: + raise AttributeError('Attribute label is missing') + self.source_id = source_id + self.target_id = target_id + self.label = label + self.properties = properties + + +@six.add_metaclass(abc.ABCMeta) +class Graph(object): + def __init__(self, name, graph_type): + """Create a Graph instance + + :type name: str + :type graph_type: str + :rtype: Graph + """ + self.name = name + self.graph_type = graph_type + + @abc.abstractmethod + def copy(self): + """Create a copy of the graph + + :return: A copy of the graph + :rtype: Graph + """ + pass + + @abc.abstractmethod + def add_vertex(self, v): + """Add a vertex to the graph + + A copy of Vertex v will be added to the graph. + + Example: + -------- + graph = Graph() + v = Vertex(vertex_id=1, properties={prop_key:prop_value}) + graph.add_vertex(v) + + :param v: the vertex to add + :type v: Vertex + """ + pass + + @abc.abstractmethod + def add_edge(self, e): + """Add an edge to the graph + + A copy of Edge e will be added to the graph. + + Example: + -------- + graph = Graph() + + v1_prop = {'prop_key':'some value for my first vertex'} + v2_prop = {'prop_key':'another value for my second vertex'} + v1 = Vertex(vertex_id=1, properties=v1_prop) + v2 = Vertex(vertex_id=1, properties=v2_prop) + graph.add_vertex(v1) + graph.add_vertex(v2) + + e_prop = {'edge_prop':'and here is my edge property value'} + e = Edge(source_id=v1.vertex_id, target_id=v2.vertex_id, + label='BELONGS', properties=e_prop) + graph.add_edge(e) + + :param e: the edge to add + :type e: Edge + """ + pass + + @abc.abstractmethod + def get_vertex(self, v_id): + """Fetch a vertex from the graph + + :param v_id: vertex id + :type v_id: str + + :return: the vertex or None if it does not exist + :rtype: Vertex + """ + pass + + # TODO(ihefetz) uncomment: + # @abc.abstractmethod + # def get_vertices(self, properties_filter): + # pass + + @abc.abstractmethod + def get_edge(self, source_id, target_id, label): + """Fetch an edge from the graph, + + Fetch an edge from the graph, according to its two vertices and label + + :param source_id: vertex id of the source vertex + :type source_id: str + + :param target_id: vertex id of the target vertex + :type target_id: str + + :param label: the label property of the edge + :type label: str + + :return: The edge between the two vertices or None + :rtype: Edge + """ + pass + + @abc.abstractmethod + def get_edges(self, source_id, target_id, labels=None, directed=True): + """Fetch multiple edges from the graph, + + Fetch an edge from the graph, according to its two vertices and label + + :param source_id: vertex id of the source vertex + :type source_id: str + + :param target_id: vertex id of the target vertex + :type target_id: str + + :param labels: the label property of the edge + :type labels: str or list of str + + :param directed: consider edge direction + :type directed: bool + + :return: The edge between the two vertices or None + :rtype: Edge + """ + pass + + # TODO(ihefetz) uncomment: + # @abc.abstractmethod + # def get_neighboring_edges(self, v_id, properties_filter): + # pass + + @abc.abstractmethod + def update_vertex(self, v, hard_update=False): + """Update the vertex properties + + Update an existing vertex and create it if non existing. + Hard update: can be used to remove existing fields. + + :param v: the vertex with the new data + :type v: Vertex + :param hard_update: if True, original properties will be removed. + :type hard_update: bool + """ + pass + + @abc.abstractmethod + def update_edge(self, e, hard_update=False): + """Update the edge properties + + Update an existing edge and create it if non existing. + Hard update: can be used to remove existing fields. + + :param e: the edge with the new data + :type e: Edge + :param hard_update: if True, original properties will be removed. + :type hard_update: bool + """ + pass + + @abc.abstractmethod + def remove_vertex(self, v): + """Remove Vertex v and its edges from the graph + + :type v: Vertex + """ + pass + + @abc.abstractmethod + def remove_edge(self, e): + """Remove an edge from the graph + + :type e: Edge + """ + pass + + # @abc.abstractmethod + # def neighbors(self, v, vertex_attr=None, edge_attr=None): + # # TODO(ihefetz) also direction? also return the edges? + # """TODO(ihefetz) + # + # :param v: TODO(ihefetz) + # :param vertex_attr: TODO(ihefetz) + # :param edge_attr: TODO(ihefetz) + # :return: TODO(ihefetz) + # :rtype: list of Vertex + # """ + # pass diff --git a/vitrage/graph/networkx_graph.py b/vitrage/graph/networkx_graph.py new file mode 100644 index 000000000..775036589 --- /dev/null +++ b/vitrage/graph/networkx_graph.py @@ -0,0 +1,135 @@ +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +import networkx as nx + +from driver import Edge +from driver import Graph +from driver import Vertex + + +class NXGraph(Graph): + + GRAPH_TYPE = "networkx" + + def __init__(self, name): + self.g = nx.MultiDiGraph() + super(NXGraph, self).__init__(name=name, graph_type=NXGraph.GRAPH_TYPE) + + def __len__(self): + return len(self.g) + + def copy(self): + self_copy = NXGraph(self.name) + self_copy.g = self.g.copy() + return self_copy + + def add_vertex(self, v): + """Add a vertex to the graph + + :type v: Vertex + """ + properties_copy = copy.copy(v.properties) + self.g.add_node(n=v.vertex_id, attr_dict=properties_copy) + + def add_edge(self, e): + """Add an edge to the graph + + :type e: Edge + """ + properties_copy = copy.copy(e.properties) + self.g.add_edge(u=e.source_id, v=e.target_id, + key=e.label, attr_dict=properties_copy) + + def get_vertex(self, v_id): + """Fetch a vertex from the graph + + :rtype: Vertex + """ + properties = self.g.node.get(v_id, None) + properties_copy = copy.copy(properties) if properties else None + vertex = Vertex(vertex_id=v_id, properties=properties_copy) + return vertex + + def _get_edge_properties(self, source_id, target_id, label): + try: + properties = self.g.adj[source_id][target_id][label] + return properties + except KeyError: + return None + + def get_edge(self, source_id, target_id, label): + """Fetch an edge from the graph, + + :rtype: Edge + """ + properties = self._get_edge_properties(source_id, target_id, label) + if properties: + properties_copy = copy.copy(properties) + item = Edge(source_id=source_id, target_id=target_id, + label=label, properties=properties_copy) + return item + else: + return None + + def get_edges(self, source_id, target_id, labels=None, directed=True): + """Fetch multiple edges from the graph + + :rtype: list of Edge + """ + # TODO(ihefetz) implement this function + pass + + def update_vertex(self, v, hard_update=False): + """Update the vertex properties + + :type v: Vertex + """ + if hard_update: + properties = self.g.node.get(v.vertex_id, None) + if properties: + properties.clear() + self.add_vertex(v) + + def update_edge(self, e, hard_update=False): + """Update the edge properties + + :type e: Edge + """ + if hard_update: + properties = self._get_edge_properties(e.source_id, + e.target_id, + e.label) + if properties: + properties.clear() + self.add_edge(e) + + def remove_vertex(self, v): + """Remove Vertex v and its edges from the graph + + :type v: Vertex + """ + self.g.remove_node(n=v.vertex_id) + + def remove_edge(self, e): + """Remove an edge from the graph + + :type e: Edge + """ + self.g.remove_edge(u=e.source_id, v=e.target_id)