topology tempest

Change-Id: I17002ef155422b1c2f64db540a6875a0079b6215
This commit is contained in:
Alexey Weyl 2016-04-07 16:20:47 +03:00
parent 5cb445c624
commit 0ca78719f6
6 changed files with 324 additions and 51 deletions

View File

@ -19,6 +19,7 @@ from oslo_log import log
import pecan as pecan import pecan as pecan
from pecan import abort from pecan import abort
from pecan import rest from pecan import rest
from vitrage.common.constants import EntityCategory
from vitrage.datasources import OPENSTACK_NODE from vitrage.datasources import OPENSTACK_NODE
@ -28,7 +29,9 @@ LOG = log.getLogger(__name__)
class RootRestController(rest.RestController): class RootRestController(rest.RestController):
@staticmethod @staticmethod
def as_tree(graph, root=OPENSTACK_NODE, reverse=False): def as_tree(graph,
root='%s:%s' % (EntityCategory.RESOURCE, OPENSTACK_NODE),
reverse=False):
linked_graph = json_graph.node_link_graph(graph) linked_graph = json_graph.node_link_graph(graph)
if reverse: if reverse:
linked_graph = linked_graph.reverse() linked_graph = linked_graph.reverse()

View File

@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy import copy
import json import json
import networkx as nx import networkx as nx

View File

@ -12,8 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import unittest
from oslo_config import cfg from oslo_config import cfg
from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import DatasourceProperties as DSProps
@ -49,8 +47,6 @@ class TestProcessor(TestEntityGraphUnitBase):
cls.conf.register_opts(cls.DATASOURCES_OPTS, group='datasources') cls.conf.register_opts(cls.DATASOURCES_OPTS, group='datasources')
cls.load_datasources(cls.conf) cls.load_datasources(cls.conf)
# TODO(Alexey): un skip this test when instance transformer update is ready
@unittest.skip('Not ready yet')
def test_process_event(self): def test_process_event(self):
# check create instance event # check create instance event
processor = proc.Processor(self.conf, InitializationStatus()) processor = proc.Processor(self.conf, InitializationStatus())
@ -61,11 +57,12 @@ class TestProcessor(TestEntityGraphUnitBase):
self.NUM_EDGES_AFTER_CREATION) self.NUM_EDGES_AFTER_CREATION)
# check update instance even # check update instance even
# TODO(Alexey): Create an event in update event structure
# (update snapshot fields won't work)
event[DSProps.SYNC_MODE] = SyncMode.UPDATE event[DSProps.SYNC_MODE] = SyncMode.UPDATE
event[DSProps.EVENT_TYPE] = 'compute.instance.volume.attach' event[DSProps.EVENT_TYPE] = 'compute.instance.volume.attach'
event['hostname'] = 'new_host' event['hostname'] = 'new_host'
event['instance_id'] = event['id']
event['state'] = event['status']
event['host'] = event['OS-EXT-SRV-ATTR:host']
processor.process_event(event) processor.process_event(event)
self._check_graph(processor, self.NUM_VERTICES_AFTER_CREATION, self._check_graph(processor, self.NUM_VERTICES_AFTER_CREATION,
self.NUM_EDGES_AFTER_CREATION) self.NUM_EDGES_AFTER_CREATION)

View File

@ -12,11 +12,26 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import time
from oslo_log import log as logging from oslo_log import log as logging
from vitrage import clients
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources import CINDER_VOLUME_DATASOURCE
from vitrage.datasources import NOVA_HOST_DATASOURCE
from vitrage.datasources import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources import NOVA_ZONE_DATASOURCE
from vitrage.datasources import OPENSTACK_NODE
from vitrage.graph import Edge
from vitrage.graph import NXGraph
from vitrage.graph import Vertex
from vitrage import keystone_client
from vitrage_tempest_tests.tests.api.base import BaseVitrageTest from vitrage_tempest_tests.tests.api.base import BaseVitrageTest
from vitrage_tempest_tests.tests.api.topology.utils \ import vitrage_tempest_tests.tests.utils as utils
import TopologyHelper from vitrageclient import client as v_client
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -24,39 +39,301 @@ LOG = logging.getLogger(__name__)
class BaseTopologyTest(BaseVitrageTest): class BaseTopologyTest(BaseVitrageTest):
"""Topology test class for Vitrage API tests.""" """Topology test class for Vitrage API tests."""
NUM_ENTITIES_PER_TYPE = 'num_vertices'
NUM_EDGES_PER_TYPE = 'num_edges_per_type'
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(BaseTopologyTest, cls).setUpClass() super(BaseTopologyTest, cls).setUpClass()
cls.topology_client = TopologyHelper() cls.conf = utils.get_conf()
cls.vitrage_client = \
v_client.Client('1', session=keystone_client.get_session(cls.conf))
cls.nova_client = clients.nova_client(cls.conf)
cls.cinder_client = clients.cinder_client(cls.conf)
def test_compare_graphs(self): def test_compare_api_and_cli(self):
"""Wrapper that returns a test graph.""" """Wrapper that returns a test graph."""
api_graph = self.topology_client.get_api_topology('graph')
cli_graph = self.topology_client.show_cli_topology()
if self.topology_client.compare_graphs(api_graph, cli_graph) is False: api_graph = self.vitrage_client.topology.get()
LOG.error('The graph tempest_graph is not correct') cli_graph = utils.run_vitrage_command('vitrage topology show')
else: self.assertEqual(True, self._compare_graphs(api_graph, cli_graph))
LOG.info('The graph tempest_graph is correct')
def test_get_tree_with_vms(self): def test_default_graph(self):
"""Wrapper that returns a test tree with created vm's""" try:
resources = self.topology_client.create_machines(4) # create entities
cli_graph = self.topology_client.show_cli_topology() self._create_entities(num_instances=3, num_volumes=1)
api_graph = self.vitrage_client.topology.get()
graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data(
host_entities=1, host_edges=4,
instance_entities=3, instance_edges=4,
volume_entities=1, volume_edges=1)
self._validate_graph_correctness(graph, 7, 6, entities)
finally:
self._rollback_to_default()
if self.topology_client.validate_graph_correctness( def test_graph_with_query(self):
cli_graph, resources) is False: try:
LOG.error('The graph ' + self.name + ' is not correct') # create entities
else: self._create_entities(num_instances=3, num_volumes=1)
LOG.info('The graph ' + self.name + ' is correct') api_graph = self.vitrage_client.topology.get(
query=self._graph_query())
graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data(
host_entities=1, host_edges=4,
instance_entities=3, instance_edges=3)
self._validate_graph_correctness(graph, 6, 5, entities)
finally:
self._rollback_to_default()
def test_get_graph_with_volume(self): def test_default_tree(self):
"""Wrapper that returns a test graph.""" try:
resources = self.topology_client.create_volume() # create entities
cli_graph = self.topology_client.show_cli_topology() self._create_entities(num_instances=3, num_volumes=1)
api_graph = self.vitrage_client.topology.get(graph_type='tree')
graph = self._create_graph_from_tree_dictionary(api_graph)
entities = self._entities_validation_data(
host_entities=1, host_edges=4,
instance_entities=3, instance_edges=3)
self._validate_graph_correctness(graph, 6, 5, entities)
finally:
self._rollback_to_default()
if self.topology_client.validate_graph_correctness( def test_tree_with_query(self):
cli_graph, resources) is False: try:
LOG.error('The graph ' + self.name + ' is not correct') # create entities
else: self._create_entities(num_instances=3, end_sleep=10)
LOG.info('The graph ' + self.name + ' is correct') api_graph = self.vitrage_client.topology.get(
graph_type='tree', query=self._tree_query())
graph = self._create_graph_from_tree_dictionary(api_graph)
entities = self._entities_validation_data(
host_entities=1, host_edges=1)
self._validate_graph_correctness(graph, 3, 2, entities)
finally:
self._rollback_to_default()
def _rollback_to_default(self):
self._delete_entities(instance=True, volume=True)
api_graph = self.vitrage_client.topology.get()
graph = self._create_graph_from_graph_dictionary(api_graph)
entities = self._entities_validation_data()
self._validate_graph_correctness(graph, 3, 2, entities)
def _create_entities(self, num_instances=0, num_volumes=0, end_sleep=3):
if num_instances > 0:
resources = self._create_instances(num_instances)
self._wait_for_status(20,
self._check_num_instances,
num_instances=num_instances)
time.sleep(1)
if num_volumes > 0:
self._create_volume_and_attach('volume-1', 1,
resources[0].__dict__['id'],
'/tmp/vda')
self._wait_for_status(20,
self._check_num_volumes,
num_volumes=1)
# waiting until all the entities creation were processed by the
# entity graph processor
time.sleep(end_sleep)
def _delete_entities(self, instance=False, volume=False):
if volume:
self._delete_volumes()
self._wait_for_status(30,
self._check_num_volumes,
num_volumes=0)
if instance:
self._delete_instances()
self._wait_for_status(20,
self._check_num_instances,
num_instances=0)
# waiting until all the entities deletion were processed by the
# entity graph processor
time.sleep(5)
def _entities_validation_data(self, node_entities=1, node_edges=1,
zone_entities=1, zone_edges=2,
host_entities=1, host_edges=1,
instance_entities=0, instance_edges=0,
volume_entities=0, volume_edges=0):
return [
{VProps.TYPE: OPENSTACK_NODE,
self.NUM_ENTITIES_PER_TYPE: node_entities,
self.NUM_EDGES_PER_TYPE: node_edges},
{VProps.TYPE: NOVA_ZONE_DATASOURCE,
self.NUM_ENTITIES_PER_TYPE: zone_entities,
self.NUM_EDGES_PER_TYPE: zone_edges},
{VProps.TYPE: NOVA_HOST_DATASOURCE,
self.NUM_ENTITIES_PER_TYPE: host_entities,
self.NUM_EDGES_PER_TYPE: host_edges},
{VProps.TYPE: NOVA_INSTANCE_DATASOURCE,
self.NUM_ENTITIES_PER_TYPE: instance_entities,
self.NUM_EDGES_PER_TYPE: instance_edges},
{VProps.TYPE: CINDER_VOLUME_DATASOURCE,
self.NUM_ENTITIES_PER_TYPE: volume_entities,
self.NUM_EDGES_PER_TYPE: volume_edges}
]
def _validate_graph_correctness(self,
graph,
num_entities,
num_edges,
entities):
self.assertIsNot(None, graph)
self.assertIsNot(None, entities)
self.assertEqual(num_entities, graph.num_vertices())
self.assertEqual(num_edges, graph.num_edges())
for entity in entities:
query = {
VProps.CATEGORY: EntityCategory.RESOURCE,
VProps.TYPE: entity[VProps.TYPE],
VProps.IS_DELETED: False,
VProps.IS_PLACEHOLDER: False
}
vertices = graph.get_vertices(vertex_attr_filter=query)
self.assertEqual(entity[self.NUM_ENTITIES_PER_TYPE], len(vertices))
num_edges = sum([len(graph.get_edges(vertex.vertex_id))
for vertex in vertices])
self.assertEqual(entity[self.NUM_EDGES_PER_TYPE], num_edges)
def _check_num_instances(self, num_instances=0):
return len(self.nova_client.servers.list()) == num_instances
def _check_num_volumes(self, num_volumes=0):
return len(self.cinder_client.volumes.list()) == num_volumes
def _create_volume_and_attach(self, name, size, instance_id, mount_point):
volume = self.cinder_client.volumes.create(display_name=name,
size=size)
time.sleep(3)
self.cinder_client.volumes.attach(volume=volume,
instance_uuid=instance_id,
mountpoint=mount_point)
return volume
def _create_instances(self, num_machines):
flavors_list = self.nova_client.flavors.list()
images_list = self.nova_client.images.list()
resources = [self.nova_client.servers.create(
name='%s-%s' % ('vm', index),
flavor=flavors_list[0],
image=images_list[0]) for index in range(num_machines)]
return resources
def _delete_instances(self):
instances = self.nova_client.servers.list()
for instance in instances:
try:
self.nova_client.servers.delete(instance)
except Exception:
pass
def _delete_volumes(self):
volumes = self.cinder_client.volumes.list()
for volume in volumes:
try:
self.cinder_client.volumes.detach(volume)
self.cinder_client.volumes.force_delete(volume)
except Exception:
self.cinder_client.volumes.force_delete(volume)
@staticmethod
def _wait_for_status(max_waiting, func, **kwargs):
count = 0
while count < max_waiting:
if func(**kwargs):
return True
count += 1
time.sleep(2)
LOG.info("wait_for_status - False ")
return False
@staticmethod
def _compare_graphs(api_graph, cli_graph):
"""Compare Graph object to graph form terminal """
if not api_graph:
LOG.error("The topology graph taken from rest api is empty")
return False
if not cli_graph:
LOG.error("The topology graph taken from terminal is empty")
return False
parsed_topology = json.loads(cli_graph)
sorted_cli_graph = sorted(parsed_topology.items())
sorted_api_graph = sorted(api_graph.items())
for item in sorted_cli_graph[4][1]:
item.pop(VProps.UPDATE_TIMESTAMP, None)
for item in sorted_api_graph[4][1]:
item.pop(VProps.UPDATE_TIMESTAMP, None)
return sorted_cli_graph == sorted_api_graph
@staticmethod
def _create_graph_from_graph_dictionary(api_graph):
graph = NXGraph()
nodes = api_graph['nodes']
for i in xrange(len(nodes)):
graph.add_vertex(Vertex(str(i), nodes[i]))
edges = api_graph['links']
for i in xrange(len(edges)):
graph.add_edge(Edge(str(edges[i]['source']),
str(edges[i]['target']),
edges[i]['relationship_type']))
return graph
def _create_graph_from_tree_dictionary(self,
api_graph,
graph=None,
ancestor=None):
children = []
graph = NXGraph() if not graph else graph
if 'children' in api_graph:
children = api_graph.copy()['children']
del api_graph['children']
vertex = Vertex(api_graph[VProps.VITRAGE_ID], api_graph)
graph.add_vertex(vertex)
if ancestor:
graph.add_edge(Edge(ancestor[VProps.VITRAGE_ID],
vertex[VProps.VITRAGE_ID],
'label'))
for entity in children:
self._create_graph_from_tree_dictionary(entity, graph, vertex)
return graph
@staticmethod
def _graph_query():
return '{"and": [{"==": {"category": "RESOURCE"}},' \
'{"==": {"is_deleted": false}},' \
'{"==": {"is_placeholder": false}},' \
'{"or": [{"==": {"type": "openstack.node"}},' \
'{"==": {"type": "nova.instance"}},' \
'{"==": {"type": "nova.host"}},' \
'{"==": {"type": "nova.zone"}}]}]}'
@staticmethod
def _tree_query():
return '{"and": [{"==": {"category": "RESOURCE"}},' \
'{"==": {"is_deleted": false}},' \
'{"==": {"is_placeholder": false}},' \
'{"or": [{"==": {"type": "openstack.node"}},' \
'{"==": {"type": "nova.host"}},' \
'{"==": {"type": "nova.zone"}}]}]}'

View File

@ -27,8 +27,8 @@ LOG = logging.getLogger(__name__)
class TopologyHelper(BaseVitrageTest): class TopologyHelper(BaseVitrageTest):
"""Topology test class for Vitrage API tests.""" """Topology test class for Vitrage API tests."""
def setUp(self): def __init__(self):
super(TopologyHelper, self).setUp() super(TopologyHelper, self).__init__()
self.client = utils.get_client() self.client = utils.get_client()
self.depth = '' self.depth = ''
self.query = '' self.query = ''
@ -55,18 +55,6 @@ class TopologyHelper(BaseVitrageTest):
return utils.run_vitrage_command_with_user( return utils.run_vitrage_command_with_user(
"vitrage topology show", self.conf.service_credentials.user) "vitrage topology show", self.conf.service_credentials.user)
def create_machines(self, machine_number):
flavor_id = self.get_flavor_id_from_list()
image_id = self.get_image_id_from_list()
resources = []
for r in range(start=0, stop=machine_number, step=1):
self.create_vm_with_exist_image("vm_for_test_" + str(r),
flavor_id, image_id)
resources.append("vm_for_test_" + str(r))
return resources
def create_volume(self): def create_volume(self):
flavor_id = self.get_flavor_id_from_list() flavor_id = self.get_flavor_id_from_list()
image_id = self.get_image_id_from_list() image_id = self.get_image_id_from_list()

View File

@ -55,7 +55,14 @@ def get_from_terminal(command):
def run_vitrage_command(command): def run_vitrage_command(command):
p = subprocess.Popen(command, auth_url = '--os-auth-url http://10.41.251.201:5000/v2.0'
user = '--os-user-name admin'
password = '--os-password password'
project_name = '--os-project-name admin'
full_command = '%s %s %s %s %s' % \
(command, user, password, project_name, auth_url)
p = subprocess.Popen(full_command,
shell=True, shell=True,
executable="/bin/bash", executable="/bin/bash",
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -71,7 +78,7 @@ def run_vitrage_command(command):
def run_vitrage_command_with_user(command, user): def run_vitrage_command_with_user(command, user):
run_vitrage_command( run_vitrage_command(
"cd /home/stack/devstack; . openrc " + "cd /openstack/devstack; . openrc " +
user + " " + user + user + " " + user +
"; " + command) "; " + command)