summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--samples/ping-heat-context.yaml45
-rw-r--r--tests/unit/benchmark/contexts/__init__.py (renamed from tests/unit/benchmark/context/__init__.py)0
-rw-r--r--tests/unit/benchmark/contexts/test_heat.py114
-rw-r--r--tests/unit/benchmark/contexts/test_model.py (renamed from tests/unit/benchmark/context/test_model.py)135
-rw-r--r--yardstick/benchmark/__init__.py1
-rw-r--r--yardstick/benchmark/context/model.py469
-rw-r--r--yardstick/benchmark/contexts/__init__.py (renamed from yardstick/benchmark/context/__init__.py)0
-rw-r--r--yardstick/benchmark/contexts/base.py70
-rw-r--r--yardstick/benchmark/contexts/heat.py223
-rw-r--r--yardstick/benchmark/contexts/model.py238
-rwxr-xr-xyardstick/cmd/commands/task.py11
11 files changed, 703 insertions, 603 deletions
diff --git a/samples/ping-heat-context.yaml b/samples/ping-heat-context.yaml
new file mode 100644
index 000000000..5a8d09fbe
--- /dev/null
+++ b/samples/ping-heat-context.yaml
@@ -0,0 +1,45 @@
+---
+# Sample benchmark task config file
+# measure network latency using ping
+
+schema: "yardstick:task:0.1"
+
+scenarios:
+-
+ type: Ping
+ options:
+ packetsize: 200
+ host: athena.demo
+ target: ares.demo
+
+ runner:
+ type: Duration
+ duration: 60
+ interval: 1
+
+ sla:
+ max_rtt: 10
+ action: monitor
+
+context:
+ type: Heat
+ name: demo
+ image: cirros-0.3.3
+ flavor: m1.tiny
+ user: cirros
+
+ placement_groups:
+ pgrp1:
+ policy: "availability"
+
+ servers:
+ athena:
+ floating_ip: true
+ placement: "pgrp1"
+ ares:
+ placement: "pgrp1"
+
+ networks:
+ test:
+ cidr: '10.0.1.0/24'
+
diff --git a/tests/unit/benchmark/context/__init__.py b/tests/unit/benchmark/contexts/__init__.py
index e69de29bb..e69de29bb 100644
--- a/tests/unit/benchmark/context/__init__.py
+++ b/tests/unit/benchmark/contexts/__init__.py
diff --git a/tests/unit/benchmark/contexts/test_heat.py b/tests/unit/benchmark/contexts/test_heat.py
new file mode 100644
index 000000000..bf1174e27
--- /dev/null
+++ b/tests/unit/benchmark/contexts/test_heat.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+
+##############################################################################
+# Copyright (c) 2015 Ericsson AB and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+# Unittest for yardstick.benchmark.contexts.heat
+
+import mock
+import unittest
+
+from yardstick.benchmark.contexts import model
+from yardstick.benchmark.contexts import heat
+
+
+class HeatContextTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.test_context = heat.HeatContext()
+ self.mock_context = mock.Mock(spec=heat.HeatContext())
+
+ def test_construct(self):
+
+ self.assertIsNone(self.test_context.name)
+ self.assertIsNone(self.test_context.stack)
+ self.assertEqual(self.test_context.networks, [])
+ self.assertEqual(self.test_context.servers, [])
+ self.assertEqual(self.test_context.placement_groups, [])
+ self.assertIsNone(self.test_context.keypair_name)
+ self.assertIsNone(self.test_context.secgroup_name)
+ self.assertEqual(self.test_context._server_map, {})
+ self.assertIsNone(self.test_context._image)
+ self.assertIsNone(self.test_context._flavor)
+ self.assertIsNone(self.test_context._user)
+ self.assertIsNone(self.test_context.template_file)
+ self.assertIsNone(self.test_context.heat_parameters)
+
+ @mock.patch('yardstick.benchmark.contexts.heat.PlacementGroup')
+ @mock.patch('yardstick.benchmark.contexts.heat.Network')
+ @mock.patch('yardstick.benchmark.contexts.heat.Server')
+ def test_init(self, mock_server, mock_network, mock_pg):
+
+ pgs = {'pgrp1': {'policy': 'availability'}}
+ networks = {'bar': {'cidr': '10.0.1.0/24'}}
+ servers = {'baz': {'floating_ip': True, 'placement': 'pgrp1'}}
+ attrs = {'name': 'foo',
+ 'placement_groups': pgs,
+ 'networks': networks,
+ 'servers': servers}
+
+ self.test_context.init(attrs)
+
+ self.assertEqual(self.test_context.keypair_name, "foo-key")
+ self.assertEqual(self.test_context.secgroup_name, "foo-secgroup")
+
+ mock_pg.assert_called_with('pgrp1', self.test_context,
+ pgs['pgrp1']['policy'])
+ self.assertTrue(len(self.test_context.placement_groups) == 1)
+
+ mock_network.assert_called_with(
+ 'bar', self.test_context, networks['bar'])
+ self.assertTrue(len(self.test_context.networks) == 1)
+
+ mock_server.assert_called_with('baz', self.test_context, servers['baz'])
+ self.assertTrue(len(self.test_context.servers) == 1)
+
+ @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
+ def test__add_resources_to_template_no_servers(self, mock_template):
+
+ self.test_context.keypair_name = "foo-key"
+ self.test_context.secgroup_name = "foo-secgroup"
+
+ self.test_context._add_resources_to_template(mock_template)
+ mock_template.add_keypair.assert_called_with("foo-key")
+ mock_template.add_security_group.assert_called_with("foo-secgroup")
+
+ @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
+ def test_deploy(self, mock_template):
+
+ self.test_context.name = 'foo'
+ self.test_context.template_file = '/bar/baz/some-heat-file'
+ self.test_context.heat_parameters = {'image': 'cirros'}
+ self.test_context.deploy()
+
+ mock_template.assert_called_with(self.test_context.name,
+ self.test_context.template_file,
+ self.test_context.heat_parameters)
+ self.assertIsNotNone(self.test_context.stack)
+
+ @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
+ def test_undeploy(self, mock_template):
+
+ self.test_context.stack = mock_template
+ self.test_context.undeploy()
+
+ self.assertTrue(mock_template.delete.called)
+
+ def test__get_server(self):
+
+ self.mock_context.name = 'bar'
+ self.mock_context.stack.outputs = {'public_ip': '127.0.0.1',
+ 'private_ip': '10.0.0.1'}
+ attr_name = {'name': 'foo.bar',
+ 'public_ip_attr': 'public_ip',
+ 'private_ip_attr': 'private_ip'}
+ result = heat.HeatContext._get_server(self.mock_context, attr_name)
+
+ self.assertEqual(result.public_ip, '127.0.0.1')
+ self.assertEqual(result.private_ip, '10.0.0.1')
diff --git a/tests/unit/benchmark/context/test_model.py b/tests/unit/benchmark/contexts/test_model.py
index cf0a605f4..48584cf33 100644
--- a/tests/unit/benchmark/context/test_model.py
+++ b/tests/unit/benchmark/contexts/test_model.py
@@ -9,12 +9,12 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-# Unittest for yardstick.benchmark.context.model
+# Unittest for yardstick.benchmark.contexts.model
import mock
import unittest
-from yardstick.benchmark.context import model
+from yardstick.benchmark.contexts import model
class ObjectTestCase(unittest.TestCase):
@@ -141,7 +141,7 @@ class NetworkTestCase(unittest.TestCase):
self.assertFalse(test_network.has_route_to('ext_net'))
- @mock.patch('yardstick.benchmark.context.model.Network.has_route_to')
+ @mock.patch('yardstick.benchmark.contexts.model.Network.has_route_to')
def test_find_by_route_to(self, mock_has_route_to):
mock_network = mock.Mock()
@@ -183,7 +183,7 @@ class ServerTestCase(unittest.TestCase):
self.assertIsNone(test_server._flavor)
self.assertIn(test_server, model.Server.list)
- @mock.patch('yardstick.benchmark.context.model.PlacementGroup')
+ @mock.patch('yardstick.benchmark.contexts.model.PlacementGroup')
def test_construct_get_wrong_placement_group(self, mock_pg):
attrs = {'placement': 'baz'}
@@ -192,7 +192,7 @@ class ServerTestCase(unittest.TestCase):
self.assertRaises(ValueError, model.Server, 'foo',
self.mock_context, attrs)
- @mock.patch('yardstick.benchmark.context.model.HeatTemplate')
+ @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate')
def test__add_instance(self, mock_template):
attrs = {'image': 'some-image', 'flavor': 'some-flavor'}
@@ -218,128 +218,3 @@ class ServerTestCase(unittest.TestCase):
key_name=self.mock_context.keypair_name,
scheduler_hints='hints')
-
-class ContextTestCase(unittest.TestCase):
-
- def setUp(self):
- self.test_context = model.Context()
- self.mock_context = mock.Mock()
-
- def tearDown(self):
- model.Context.list = []
-
- def test_construct(self):
-
- self.assertIsNone(self.test_context.name)
- self.assertIsNone(self.test_context.stack)
- self.assertEqual(self.test_context.networks, [])
- self.assertEqual(self.test_context.servers, [])
- self.assertEqual(self.test_context.placement_groups, [])
- self.assertIsNone(self.test_context.keypair_name)
- self.assertIsNone(self.test_context.secgroup_name)
- self.assertEqual(self.test_context._server_map, {})
- self.assertIsNone(self.test_context._image)
- self.assertIsNone(self.test_context._flavor)
- self.assertIsNone(self.test_context._user)
- self.assertIsNone(self.test_context.template_file)
- self.assertIsNone(self.test_context.heat_parameters)
- self.assertIn(self.test_context, model.Context.list)
-
- @mock.patch('yardstick.benchmark.context.model.PlacementGroup')
- @mock.patch('yardstick.benchmark.context.model.Network')
- @mock.patch('yardstick.benchmark.context.model.Server')
- def test_init(self, mock_server, mock_network, mock_pg):
-
- pgs = {'pgrp1': {'policy': 'availability'}}
- networks = {'bar': {'cidr': '10.0.1.0/24'}}
- servers = {'baz': {'floating_ip': True, 'placement': 'pgrp1'}}
- attrs = {'name': 'foo',
- 'placement_groups': pgs,
- 'networks': networks,
- 'servers': servers}
-
- self.test_context.init(attrs)
-
- self.assertEqual(self.test_context.keypair_name, "foo-key")
- self.assertEqual(self.test_context.secgroup_name, "foo-secgroup")
-
- mock_pg.assert_called_with('pgrp1', self.test_context,
- pgs['pgrp1']['policy'])
- self.assertTrue(len(self.test_context.placement_groups) == 1)
-
- mock_network.assert_called_with(
- 'bar', self.test_context, networks['bar'])
- self.assertTrue(len(self.test_context.networks) == 1)
-
- mock_server.assert_called_with('baz', self.test_context, servers['baz'])
- self.assertTrue(len(self.test_context.servers) == 1)
-
- @mock.patch('yardstick.benchmark.context.model.HeatTemplate')
- def test__add_resources_to_template_no_servers(self, mock_template):
-
- self.test_context.keypair_name = "foo-key"
- self.test_context.secgroup_name = "foo-secgroup"
-
- self.test_context._add_resources_to_template(mock_template)
- mock_template.add_keypair.assert_called_with("foo-key")
- mock_template.add_security_group.assert_called_with("foo-secgroup")
-
- @mock.patch('yardstick.benchmark.context.model.HeatTemplate')
- def test_deploy(self, mock_template):
-
- self.test_context.name = 'foo'
- self.test_context.template_file = '/bar/baz/some-heat-file'
- self.test_context.heat_parameters = {'image': 'cirros'}
- self.test_context.deploy()
-
- mock_template.assert_called_with(self.test_context.name,
- self.test_context.template_file,
- self.test_context.heat_parameters)
- self.assertIsNotNone(self.test_context.stack)
-
- @mock.patch('yardstick.benchmark.context.model.HeatTemplate')
- def test_undeploy(self, mock_template):
-
- self.test_context.stack = mock_template
- self.test_context.undeploy()
-
- self.assertTrue(mock_template.delete.called)
-
- def test_get_server_by_name(self):
-
- self.mock_context._server_map = {'foo.bar': True}
- model.Context.list = [self.mock_context]
-
- self.assertTrue(model.Context.get_server_by_name('foo.bar'))
-
- def test_get_server_by_wrong_name(self):
-
- self.assertRaises(ValueError, model.Context.get_server_by_name, 'foo')
-
- def test_get_context_by_name(self):
-
- self.mock_context.name = 'foo'
- model.Context.list = [self.mock_context]
-
- self.assertIs(model.Context.get_context_by_name('foo'),
- self.mock_context)
-
- def test_get_unknown_context_by_name(self):
-
- model.Context.list = []
- self.assertIsNone(model.Context.get_context_by_name('foo'))
-
- @mock.patch('yardstick.benchmark.context.model.Server')
- def test_get_server(self, mock_server):
-
- self.mock_context.name = 'bar'
- self.mock_context.stack.outputs = {'public_ip': '127.0.0.1',
- 'private_ip': '10.0.0.1'}
- model.Context.list = [self.mock_context]
- attr_name = {'name': 'foo.bar',
- 'public_ip_attr': 'public_ip',
- 'private_ip_attr': 'private_ip'}
- result = model.Context.get_server(attr_name)
-
- self.assertEqual(result.public_ip, '127.0.0.1')
- self.assertEqual(result.private_ip, '10.0.0.1')
diff --git a/yardstick/benchmark/__init__.py b/yardstick/benchmark/__init__.py
index 94357a53a..8b292ac30 100644
--- a/yardstick/benchmark/__init__.py
+++ b/yardstick/benchmark/__init__.py
@@ -9,5 +9,6 @@
import yardstick.common.utils as utils
+utils.import_modules_from_package("yardstick.benchmark.contexts")
utils.import_modules_from_package("yardstick.benchmark.runners")
utils.import_modules_from_package("yardstick.benchmark.scenarios")
diff --git a/yardstick/benchmark/context/model.py b/yardstick/benchmark/context/model.py
deleted file mode 100644
index 6e754d421..000000000
--- a/yardstick/benchmark/context/model.py
+++ /dev/null
@@ -1,469 +0,0 @@
-##############################################################################
-# Copyright (c) 2015 Ericsson AB and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-""" Logical model
-
-"""
-
-import sys
-
-from yardstick.orchestrator.heat import HeatTemplate
-
-
-class Object(object):
- '''Base class for classes in the logical model
- Contains common attributes and methods
- '''
- def __init__(self, name, context):
- # model identities and reference
- self.name = name
- self._context = context
-
- # stack identities
- self.stack_name = None
- self.stack_id = None
-
- @property
- def dn(self):
- '''returns distinguished name for object'''
- return self.name + "." + self._context.name
-
-
-class PlacementGroup(Object):
- '''Class that represents a placement group in the logical model
- Concept comes from the OVF specification. Policy should be one of
- "availability" or "affinity (there are more but they are not supported)"
- '''
- map = {}
-
- def __init__(self, name, context, policy):
- if policy not in ["affinity", "availability"]:
- raise ValueError("placement group '%s', policy '%s' is not valid" %
- (name, policy))
- self.name = name
- self.members = set()
- self.stack_name = context.name + "-" + name
- self.policy = policy
- PlacementGroup.map[name] = self
-
- def add_member(self, name):
- self.members.add(name)
-
- @staticmethod
- def get(name):
- if name in PlacementGroup.map:
- return PlacementGroup.map[name]
- else:
- return None
-
-
-class Router(Object):
- '''Class that represents a router in the logical model'''
- def __init__(self, name, network_name, context, external_gateway_info):
- super(Router, self).__init__(name, context)
-
- self.stack_name = context.name + "-" + network_name + "-" + self.name
- self.stack_if_name = self.stack_name + "-if0"
- self.external_gateway_info = external_gateway_info
-
-
-class Network(Object):
- '''Class that represents a network in the logical model'''
- list = []
-
- def __init__(self, name, context, attrs):
- super(Network, self).__init__(name, context)
- self.stack_name = context.name + "-" + self.name
- self.subnet_stack_name = self.stack_name + "-subnet"
- self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
- self.router = None
-
- if "external_network" in attrs:
- self.router = Router("router", self.name,
- context, attrs["external_network"])
-
- Network.list.append(self)
-
- def has_route_to(self, network_name):
- '''determines if this network has a route to the named network'''
- if self.router and self.router.external_gateway_info == network_name:
- return True
- return False
-
- @staticmethod
- def find_by_route_to(external_network):
- '''finds a network that has a route to the specified network'''
- for network in Network.list:
- if network.has_route_to(external_network):
- return network
-
- @staticmethod
- def find_external_network():
- '''return the name of an external network some network in this
- context has a route to'''
- for network in Network.list:
- if network.router:
- return network.router.external_gateway_info
- return None
-
-
-class Server(Object):
- '''Class that represents a server in the logical model'''
- list = []
-
- def __init__(self, name, context, attrs):
- super(Server, self).__init__(name, context)
- self.stack_name = self.name + "." + context.name
- self.keypair_name = context.keypair_name
- self.secgroup_name = context.secgroup_name
- self.context = context
- self.public_ip = None
- self.private_ip = None
-
- if attrs is None:
- attrs = {}
-
- self.placement_groups = []
- placement = attrs.get("placement", [])
- placement = placement if type(placement) is list else [placement]
- for p in placement:
- pg = PlacementGroup.get(p)
- if not pg:
- raise ValueError("server '%s', placement '%s' is invalid" %
- (name, p))
- self.placement_groups.append(pg)
- pg.add_member(self.stack_name)
-
- self.instances = 1
- if "instances" in attrs:
- self.instances = attrs["instances"]
-
- # dict with key network name, each item is a dict with port name and ip
- self.ports = {}
-
- self.floating_ip = None
- if "floating_ip" in attrs:
- self.floating_ip = {}
-
- if self.floating_ip is not None:
- ext_net = Network.find_external_network()
- assert ext_net is not None
- self.floating_ip["external_network"] = ext_net
-
- self._image = None
- if "image" in attrs:
- self._image = attrs["image"]
-
- self._flavor = None
- if "flavor" in attrs:
- self._flavor = attrs["flavor"]
-
- Server.list.append(self)
-
- @property
- def image(self):
- '''returns a server's image name'''
- if self._image:
- return self._image
- else:
- return self._context.image
-
- @property
- def flavor(self):
- '''returns a server's flavor name'''
- if self._flavor:
- return self._flavor
- else:
- return self._context.flavor
-
- def _add_instance(self, template, server_name, networks, scheduler_hints):
- '''adds to the template one server and corresponding resources'''
- port_name_list = []
- for network in networks:
- port_name = server_name + "-" + network.name + "-port"
- self.ports[network.name] = {"stack_name": port_name}
- template.add_port(port_name, network.stack_name,
- network.subnet_stack_name,
- sec_group_id=self.secgroup_name)
- port_name_list.append(port_name)
-
- if self.floating_ip:
- external_network = self.floating_ip["external_network"]
- if network.has_route_to(external_network):
- self.floating_ip["stack_name"] = server_name + "-fip"
- template.add_floating_ip(self.floating_ip["stack_name"],
- external_network,
- port_name,
- network.router.stack_if_name,
- self.secgroup_name)
-
- template.add_server(server_name, self.image, self.flavor,
- ports=port_name_list,
- key_name=self.keypair_name,
- scheduler_hints=scheduler_hints)
-
- def add_to_template(self, template, networks, scheduler_hints=None):
- '''adds to the template one or more servers (instances)'''
- if self.instances == 1:
- server_name = self.stack_name
- self._add_instance(template, server_name, networks,
- scheduler_hints=scheduler_hints)
- else:
- # TODO(hafe) fix or remove, no test/sample for this
- for i in range(self.instances):
- server_name = "%s-%d" % (self.stack_name, i)
- self._add_instance(template, server_name, networks,
- scheduler_hints=scheduler_hints)
-
-
-def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
- ''' update scheduler hints from server's placement configuration
- TODO: this code is openstack specific and should move somewhere else
- '''
- if placement_group.policy == "affinity":
- if "same_host" in scheduler_hints:
- host_list = scheduler_hints["same_host"]
- else:
- host_list = scheduler_hints["same_host"] = []
- else:
- if "different_host" in scheduler_hints:
- host_list = scheduler_hints["different_host"]
- else:
- host_list = scheduler_hints["different_host"] = []
-
- for name in added_servers:
- if name in placement_group.members:
- host_list.append({'get_resource': name})
-
-
-class Context(object):
- '''Class that represents a context in the logical model'''
- list = []
-
- def __init__(self):
- self.name = None
- self.stack = None
- self.networks = []
- self.servers = []
- self.placement_groups = []
- self.keypair_name = None
- self.secgroup_name = None
- self._server_map = {}
- self._image = None
- self._flavor = None
- self._user = None
- self.template_file = None
- self.heat_parameters = None
- Context.list.append(self)
-
- def init(self, attrs):
- '''initializes itself from the supplied arguments'''
- self.name = attrs["name"]
-
- if "user" in attrs:
- self._user = attrs["user"]
-
- if "heat_template" in attrs:
- self.template_file = attrs["heat_template"]
- self.heat_parameters = attrs.get("heat_parameters", None)
- return
-
- self.keypair_name = self.name + "-key"
- self.secgroup_name = self.name + "-secgroup"
-
- if "image" in attrs:
- self._image = attrs["image"]
-
- if "flavor" in attrs:
- self._flavor = attrs["flavor"]
-
- if "placement_groups" in attrs:
- for name, pgattrs in attrs["placement_groups"].items():
- pg = PlacementGroup(name, self, pgattrs["policy"])
- self.placement_groups.append(pg)
-
- for name, netattrs in attrs["networks"].items():
- network = Network(name, self, netattrs)
- self.networks.append(network)
-
- for name, serverattrs in attrs["servers"].items():
- server = Server(name, self, serverattrs)
- self.servers.append(server)
- self._server_map[server.dn] = server
-
- @property
- def image(self):
- '''returns application's default image name'''
- return self._image
-
- @property
- def flavor(self):
- '''returns application's default flavor name'''
- return self._flavor
-
- @property
- def user(self):
- '''return login user name corresponding to image'''
- return self._user
-
- def _add_resources_to_template(self, template):
- '''add to the template the resources represented by this context'''
- template.add_keypair(self.keypair_name)
- template.add_security_group(self.secgroup_name)
-
- for network in self.networks:
- template.add_network(network.stack_name)
- template.add_subnet(network.subnet_stack_name, network.stack_name,
- network.subnet_cidr)
-
- if network.router:
- template.add_router(network.router.stack_name,
- network.router.external_gateway_info,
- network.subnet_stack_name)
- template.add_router_interface(network.router.stack_if_name,
- network.router.stack_name,
- network.subnet_stack_name)
-
- # create a list of servers sorted by increasing no of placement groups
- list_of_servers = sorted(self.servers,
- key=lambda s: len(s.placement_groups))
-
- #
- # add servers with scheduler hints derived from placement groups
- #
-
- # create list of servers with availability policy
- availability_servers = []
- for server in list_of_servers:
- for pg in server.placement_groups:
- if pg.policy == "availability":
- availability_servers.append(server)
- break
-
- # add servers with availability policy
- added_servers = []
- for server in availability_servers:
- scheduler_hints = {}
- for pg in server.placement_groups:
- update_scheduler_hints(scheduler_hints, added_servers, pg)
- server.add_to_template(template, self.networks, scheduler_hints)
- added_servers.append(server.stack_name)
-
- # create list of servers with affinity policy
- affinity_servers = []
- for server in list_of_servers:
- for pg in server.placement_groups:
- if pg.policy == "affinity":
- affinity_servers.append(server)
- break
-
- # add servers with affinity policy
- for server in affinity_servers:
- if server.stack_name in added_servers:
- continue
- scheduler_hints = {}
- for pg in server.placement_groups:
- update_scheduler_hints(scheduler_hints, added_servers, pg)
- server.add_to_template(template, self.networks, scheduler_hints)
- added_servers.append(server.stack_name)
-
- # add remaining servers with no placement group configured
- for server in list_of_servers:
- if len(server.placement_groups) == 0:
- server.add_to_template(template, self.networks, {})
-
- def deploy(self):
- '''deploys template into a stack using cloud'''
- print "Deploying context '%s'" % self.name
-
- heat_template = HeatTemplate(self.name, self.template_file,
- self.heat_parameters)
-
- if self.template_file is None:
- self._add_resources_to_template(heat_template)
-
- try:
- self.stack = heat_template.create()
- except KeyboardInterrupt:
- sys.exit("\nStack create interrupted")
- except RuntimeError as err:
- sys.exit("error: failed to deploy stack: '%s'" % err.args)
- except Exception as err:
- sys.exit("error: failed to deploy stack: '%s'" % err)
-
- # copy some vital stack output into server objects
- for server in self.servers:
- if len(server.ports) > 0:
- # TODO(hafe) can only handle one internal network for now
- port = server.ports.values()[0]
- server.private_ip = self.stack.outputs[port["stack_name"]]
-
- if server.floating_ip:
- server.public_ip = \
- self.stack.outputs[server.floating_ip["stack_name"]]
-
- print "Context '%s' deployed" % self.name
-
- def undeploy(self):
- '''undeploys stack from cloud'''
- if self.stack:
- print "Undeploying context '%s'" % self.name
- self.stack.delete()
- self.stack = None
- print "Context '%s' undeployed" % self.name
-
- @staticmethod
- def get_server_by_name(dn):
- '''lookup server object by DN
-
- dn is a distinguished name including the context name'''
- if "." not in dn:
- raise ValueError("dn '%s' is malformed" % dn)
-
- for context in Context.list:
- if dn in context._server_map:
- return context._server_map[dn]
-
- return None
-
- @staticmethod
- def get_context_by_name(name):
- for context in Context.list:
- if name == context.name:
- return context
- return None
-
- @staticmethod
- def get_server(attr_name):
- '''lookup server object by name from context
- attr_name: either a name for a server created by yardstick or a dict
- with attribute name mapping when using external heat templates
- '''
- if type(attr_name) is dict:
- cname = attr_name["name"].split(".")[1]
- context = Context.get_context_by_name(cname)
- if context is None:
- raise ValueError("context not found for server '%s'" %
- attr_name["name"])
-
- public_ip = None
- private_ip = None
- if "public_ip_attr" in attr_name:
- public_ip = context.stack.outputs[attr_name["public_ip_attr"]]
- if "private_ip_attr" in attr_name:
- private_ip = context.stack.outputs[
- attr_name["private_ip_attr"]]
-
- # Create a dummy server instance for holding the *_ip attributes
- server = Server(attr_name["name"].split(".")[0], context, {})
- server.public_ip = public_ip
- server.private_ip = private_ip
- return server
- else:
- return Context.get_server_by_name(attr_name)
diff --git a/yardstick/benchmark/context/__init__.py b/yardstick/benchmark/contexts/__init__.py
index e69de29bb..e69de29bb 100644
--- a/yardstick/benchmark/context/__init__.py
+++ b/yardstick/benchmark/contexts/__init__.py
diff --git a/yardstick/benchmark/contexts/base.py b/yardstick/benchmark/contexts/base.py
new file mode 100644
index 000000000..ae860accd
--- /dev/null
+++ b/yardstick/benchmark/contexts/base.py
@@ -0,0 +1,70 @@
+##############################################################################
+# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import abc
+import six
+
+import yardstick.common.utils as utils
+
+
+@six.add_metaclass(abc.ABCMeta)
+class Context(object):
+ '''Class that represents a context in the logical model'''
+ list = []
+
+ def __init__(self):
+ Context.list.append(self)
+
+ @abc.abstractmethod
+ def init(self, attrs):
+ "Initiate context."
+
+ @staticmethod
+ def get_cls(context_type):
+ '''Return class of specified type.'''
+ for context in utils.itersubclasses(Context):
+ if context_type == context.__context_type__:
+ return context
+ raise RuntimeError("No such context_type %s" % context_type)
+
+ @staticmethod
+ def get(context_type):
+ """Returns instance of a context for context type.
+ """
+ return Context.get_cls(context_type)()
+
+ @abc.abstractmethod
+ def deploy(self):
+ '''Deploy context.'''
+
+ @abc.abstractmethod
+ def undeploy(self):
+ '''Undeploy context.'''
+
+ @abc.abstractmethod
+ def _get_server(self, attr_name):
+ '''get server object by name from context
+ '''
+
+ @staticmethod
+ def get_server(attr_name):
+ '''lookup server object by name from context
+ attr_name: either a name for a server created by yardstick or a dict
+ with attribute name mapping when using external heat templates
+ '''
+ server = None
+ for context in Context.list:
+ server = context._get_server(attr_name)
+ if server is not None:
+ break
+
+ if server is None:
+ raise ValueError("context not found for server '%s'" %
+ attr_name["name"])
+
+ return server
diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py
new file mode 100644
index 000000000..9cf29981b
--- /dev/null
+++ b/yardstick/benchmark/contexts/heat.py
@@ -0,0 +1,223 @@
+##############################################################################
+# Copyright (c) 2015 Ericsson AB and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+import sys
+
+from yardstick.benchmark.contexts.base import Context
+from yardstick.benchmark.contexts.model import Server
+from yardstick.benchmark.contexts.model import PlacementGroup
+from yardstick.benchmark.contexts.model import Network
+from yardstick.benchmark.contexts.model import update_scheduler_hints
+from yardstick.orchestrator.heat import HeatTemplate
+
+
+class HeatContext(Context):
+ '''Class that represents a context in the logical model'''
+
+ __context_type__ = "Heat"
+
+ def __init__(self):
+ self.name = None
+ self.stack = None
+ self.networks = []
+ self.servers = []
+ self.placement_groups = []
+ self.keypair_name = None
+ self.secgroup_name = None
+ self._server_map = {}
+ self._image = None
+ self._flavor = None
+ self._user = None
+ self.template_file = None
+ self.heat_parameters = None
+ super(self.__class__, self).__init__()
+
+ def init(self, attrs):
+ '''initializes itself from the supplied arguments'''
+ self.name = attrs["name"]
+
+ if "user" in attrs:
+ self._user = attrs["user"]
+
+ if "heat_template" in attrs:
+ self.template_file = attrs["heat_template"]
+ self.heat_parameters = attrs.get("heat_parameters", None)
+ return
+
+ self.keypair_name = self.name + "-key"
+ self.secgroup_name = self.name + "-secgroup"
+
+ if "image" in attrs:
+ self._image = attrs["image"]
+
+ if "flavor" in attrs:
+ self._flavor = attrs["flavor"]
+
+ if "placement_groups" in attrs:
+ for name, pgattrs in attrs["placement_groups"].items():
+ pg = PlacementGroup(name, self, pgattrs["policy"])
+ self.placement_groups.append(pg)
+
+ for name, netattrs in attrs["networks"].items():
+ network = Network(name, self, netattrs)
+ self.networks.append(network)
+
+ for name, serverattrs in attrs["servers"].items():
+ server = Server(name, self, serverattrs)
+ self.servers.append(server)
+ self._server_map[server.dn] = server
+
+ @property
+ def image(self):
+ '''returns application's default image name'''
+ return self._image
+
+ @property
+ def flavor(self):
+ '''returns application's default flavor name'''
+ return self._flavor
+
+ @property
+ def user(self):
+ '''return login user name corresponding to image'''
+ return self._user
+
+ def _add_resources_to_template(self, template):
+ '''add to the template the resources represented by this context'''
+ template.add_keypair(self.keypair_name)
+ template.add_security_group(self.secgroup_name)
+
+ for network in self.networks:
+ template.add_network(network.stack_name)
+ template.add_subnet(network.subnet_stack_name, network.stack_name,
+ network.subnet_cidr)
+
+ if network.router:
+ template.add_router(network.router.stack_name,
+ network.router.external_gateway_info,
+ network.subnet_stack_name)
+ template.add_router_interface(network.router.stack_if_name,
+ network.router.stack_name,
+ network.subnet_stack_name)
+
+ # create a list of servers sorted by increasing no of placement groups
+ list_of_servers = sorted(self.servers,
+ key=lambda s: len(s.placement_groups))
+
+ #
+ # add servers with scheduler hints derived from placement groups
+ #
+
+ # create list of servers with availability policy
+ availability_servers = []
+ for server in list_of_servers:
+ for pg in server.placement_groups:
+ if pg.policy == "availability":
+ availability_servers.append(server)
+ break
+
+ # add servers with availability policy
+ added_servers = []
+ for server in availability_servers:
+ scheduler_hints = {}
+ for pg in server.placement_groups:
+ update_scheduler_hints(scheduler_hints, added_servers, pg)
+ server.add_to_template(template, self.networks, scheduler_hints)
+ added_servers.append(server.stack_name)
+
+ # create list of servers with affinity policy
+ affinity_servers = []
+ for server in list_of_servers:
+ for pg in server.placement_groups:
+ if pg.policy == "affinity":
+ affinity_servers.append(server)
+ break
+
+ # add servers with affinity policy
+ for server in affinity_servers:
+ if server.stack_name in added_servers:
+ continue
+ scheduler_hints = {}
+ for pg in server.placement_groups:
+ update_scheduler_hints(scheduler_hints, added_servers, pg)
+ server.add_to_template(template, self.networks, scheduler_hints)
+ added_servers.append(server.stack_name)
+
+ # add remaining servers with no placement group configured
+ for server in list_of_servers:
+ if len(server.placement_groups) == 0:
+ server.add_to_template(template, self.networks, {})
+
+ def deploy(self):
+ '''deploys template into a stack using cloud'''
+ print "Deploying context '%s'" % self.name
+
+ heat_template = HeatTemplate(self.name, self.template_file,
+ self.heat_parameters)
+
+ if self.template_file is None:
+ self._add_resources_to_template(heat_template)
+
+ try:
+ self.stack = heat_template.create()
+ except KeyboardInterrupt:
+ sys.exit("\nStack create interrupted")
+ except RuntimeError as err:
+ sys.exit("error: failed to deploy stack: '%s'" % err.args)
+ except Exception as err:
+ sys.exit("error: failed to deploy stack: '%s'" % err)
+
+ # copy some vital stack output into server objects
+ for server in self.servers:
+ if len(server.ports) > 0:
+ # TODO(hafe) can only handle one internal network for now
+ port = server.ports.values()[0]
+ server.private_ip = self.stack.outputs[port["stack_name"]]
+
+ if server.floating_ip:
+ server.public_ip = \
+ self.stack.outputs[server.floating_ip["stack_name"]]
+
+ print "Context '%s' deployed" % self.name
+
+ def undeploy(self):
+ '''undeploys stack from cloud'''
+ if self.stack:
+ print "Undeploying context '%s'" % self.name
+ self.stack.delete()
+ self.stack = None
+ print "Context '%s' undeployed" % self.name
+
+ def _get_server(self, attr_name):
+ '''lookup server object by name from context
+ attr_name: either a name for a server created by yardstick or a dict
+ with attribute name mapping when using external heat templates
+ '''
+ if type(attr_name) is dict:
+ cname = attr_name["name"].split(".")[1]
+ if cname != self.name:
+ return None
+
+ public_ip = None
+ private_ip = None
+ if "public_ip_attr" in attr_name:
+ public_ip = self.stack.outputs[attr_name["public_ip_attr"]]
+ if "private_ip_attr" in attr_name:
+ private_ip = self.stack.outputs[
+ attr_name["private_ip_attr"]]
+
+ # Create a dummy server instance for holding the *_ip attributes
+ server = Server(attr_name["name"].split(".")[0], self, {})
+ server.public_ip = public_ip
+ server.private_ip = private_ip
+ return server
+ else:
+ if attr_name not in self._server_map:
+ return None
+ return self._server_map[attr_name]
diff --git a/yardstick/benchmark/contexts/model.py b/yardstick/benchmark/contexts/model.py
new file mode 100644
index 000000000..91020b92f
--- /dev/null
+++ b/yardstick/benchmark/contexts/model.py
@@ -0,0 +1,238 @@
+##############################################################################
+# Copyright (c) 2015 Ericsson AB and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+""" Logical model
+
+"""
+
+
+class Object(object):
+ '''Base class for classes in the logical model
+ Contains common attributes and methods
+ '''
+ def __init__(self, name, context):
+ # model identities and reference
+ self.name = name
+ self._context = context
+
+ # stack identities
+ self.stack_name = None
+ self.stack_id = None
+
+ @property
+ def dn(self):
+ '''returns distinguished name for object'''
+ return self.name + "." + self._context.name
+
+
+class PlacementGroup(Object):
+ '''Class that represents a placement group in the logical model
+ Concept comes from the OVF specification. Policy should be one of
+ "availability" or "affinity (there are more but they are not supported)"
+ '''
+ map = {}
+
+ def __init__(self, name, context, policy):
+ if policy not in ["affinity", "availability"]:
+ raise ValueError("placement group '%s', policy '%s' is not valid" %
+ (name, policy))
+ self.name = name
+ self.members = set()
+ self.stack_name = context.name + "-" + name
+ self.policy = policy
+ PlacementGroup.map[name] = self
+
+ def add_member(self, name):
+ self.members.add(name)
+
+ @staticmethod
+ def get(name):
+ if name in PlacementGroup.map:
+ return PlacementGroup.map[name]
+ else:
+ return None
+
+
+class Router(Object):
+ '''Class that represents a router in the logical model'''
+ def __init__(self, name, network_name, context, external_gateway_info):
+ super(self.__class__, self).__init__(name, context)
+
+ self.stack_name = context.name + "-" + network_name + "-" + self.name
+ self.stack_if_name = self.stack_name + "-if0"
+ self.external_gateway_info = external_gateway_info
+
+
+class Network(Object):
+ '''Class that represents a network in the logical model'''
+ list = []
+
+ def __init__(self, name, context, attrs):
+ super(self.__class__, self).__init__(name, context)
+ self.stack_name = context.name + "-" + self.name
+ self.subnet_stack_name = self.stack_name + "-subnet"
+ self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24')
+ self.router = None
+
+ if "external_network" in attrs:
+ self.router = Router("router", self.name,
+ context, attrs["external_network"])
+
+ Network.list.append(self)
+
+ def has_route_to(self, network_name):
+ '''determines if this network has a route to the named network'''
+ if self.router and self.router.external_gateway_info == network_name:
+ return True
+ return False
+
+ @staticmethod
+ def find_by_route_to(external_network):
+ '''finds a network that has a route to the specified network'''
+ for network in Network.list:
+ if network.has_route_to(external_network):
+ return network
+
+ @staticmethod
+ def find_external_network():
+ '''return the name of an external network some network in this
+ context has a route to'''
+ for network in Network.list:
+ if network.router:
+ return network.router.external_gateway_info
+ return None
+
+
+class Server(Object):
+ '''Class that represents a server in the logical model'''
+ list = []
+
+ def __init__(self, name, context, attrs):
+ super(self.__class__, self).__init__(name, context)
+ self.stack_name = self.name + "." + context.name
+ self.keypair_name = context.keypair_name
+ self.secgroup_name = context.secgroup_name
+ self.context = context
+ self.public_ip = None
+ self.private_ip = None
+
+ if attrs is None:
+ attrs = {}
+
+ self.placement_groups = []
+ placement = attrs.get("placement", [])
+ placement = placement if type(placement) is list else [placement]
+ for p in placement:
+ pg = PlacementGroup.get(p)
+ if not pg:
+ raise ValueError("server '%s', placement '%s' is invalid" %
+ (name, p))
+ self.placement_groups.append(pg)
+ pg.add_member(self.stack_name)
+
+ self.instances = 1
+ if "instances" in attrs:
+ self.instances = attrs["instances"]
+
+ # dict with key network name, each item is a dict with port name and ip
+ self.ports = {}
+
+ self.floating_ip = None
+ if "floating_ip" in attrs:
+ self.floating_ip = {}
+
+ if self.floating_ip is not None:
+ ext_net = Network.find_external_network()
+ assert ext_net is not None
+ self.floating_ip["external_network"] = ext_net
+
+ self._image = None
+ if "image" in attrs:
+ self._image = attrs["image"]
+
+ self._flavor = None
+ if "flavor" in attrs:
+ self._flavor = attrs["flavor"]
+
+ Server.list.append(self)
+
+ @property
+ def image(self):
+ '''returns a server's image name'''
+ if self._image:
+ return self._image
+ else:
+ return self._context.image
+
+ @property
+ def flavor(self):
+ '''returns a server's flavor name'''
+ if self._flavor:
+ return self._flavor
+ else:
+ return self._context.flavor
+
+ def _add_instance(self, template, server_name, networks, scheduler_hints):
+ '''adds to the template one server and corresponding resources'''
+ port_name_list = []
+ for network in networks:
+ port_name = server_name + "-" + network.name + "-port"
+ self.ports[network.name] = {"stack_name": port_name}
+ template.add_port(port_name, network.stack_name,
+ network.subnet_stack_name,
+ sec_group_id=self.secgroup_name)
+ port_name_list.append(port_name)
+
+ if self.floating_ip:
+ external_network = self.floating_ip["external_network"]
+ if network.has_route_to(external_network):
+ self.floating_ip["stack_name"] = server_name + "-fip"
+ template.add_floating_ip(self.floating_ip["stack_name"],
+ external_network,
+ port_name,
+ network.router.stack_if_name,
+ self.secgroup_name)
+
+ template.add_server(server_name, self.image, self.flavor,
+ ports=port_name_list,
+ key_name=self.keypair_name,
+ scheduler_hints=scheduler_hints)
+
+ def add_to_template(self, template, networks, scheduler_hints=None):
+ '''adds to the template one or more servers (instances)'''
+ if self.instances == 1:
+ server_name = self.stack_name
+ self._add_instance(template, server_name, networks,
+ scheduler_hints=scheduler_hints)
+ else:
+ # TODO(hafe) fix or remove, no test/sample for this
+ for i in range(self.instances):
+ server_name = "%s-%d" % (self.stack_name, i)
+ self._add_instance(template, server_name, networks,
+ scheduler_hints=scheduler_hints)
+
+
+def update_scheduler_hints(scheduler_hints, added_servers, placement_group):
+ ''' update scheduler hints from server's placement configuration
+ TODO: this code is openstack specific and should move somewhere else
+ '''
+ if placement_group.policy == "affinity":
+ if "same_host" in scheduler_hints:
+ host_list = scheduler_hints["same_host"]
+ else:
+ host_list = scheduler_hints["same_host"] = []
+ else:
+ if "different_host" in scheduler_hints:
+ host_list = scheduler_hints["different_host"]
+ else:
+ host_list = scheduler_hints["different_host"] = []
+
+ for name in added_servers:
+ if name in placement_group.members:
+ host_list.append({'get_resource': name})
diff --git a/yardstick/cmd/commands/task.py b/yardstick/cmd/commands/task.py
index 5c25c576a..5eb38989a 100755
--- a/yardstick/cmd/commands/task.py
+++ b/yardstick/cmd/commands/task.py
@@ -16,7 +16,7 @@ import atexit
import pkg_resources
import ipaddress
-from yardstick.benchmark.context.model import Context
+from yardstick.benchmark.contexts.base import Context
from yardstick.benchmark.runners import base as base_runner
from yardstick.common.task_template import TaskTemplate
from yardstick.common.utils import cliargs
@@ -194,18 +194,20 @@ class TaskParser(object):
self._check_schema(cfg["schema"], "task")
# TODO: support one or many contexts? Many would simpler and precise
+ # TODO: support hybrid context type
if "context" in cfg:
context_cfgs = [cfg["context"]]
else:
context_cfgs = cfg["contexts"]
for cfg_attrs in context_cfgs:
- # config external_network based on env var
- if "networks" in cfg_attrs:
+ context_type = cfg_attrs.get("type", "Heat")
+ if "Heat" == context_type and "networks" in cfg_attrs:
+ # config external_network based on env var
for _, attrs in cfg_attrs["networks"].items():
attrs["external_network"] = os.environ.get(
'EXTERNAL_NETWORK', 'net04_ext')
- context = Context()
+ context = Context.get(context_type)
context.init(cfg_attrs)
run_in_parallel = cfg.get("run_in_parallel", False)
@@ -245,6 +247,7 @@ def run_one_scenario(scenario_cfg, output_file):
key_filename = pkg_resources.resource_filename(
'yardstick.resources', 'files/yardstick_key')
+ # TODO support get multi hosts/vms info
host = Context.get_server(scenario_cfg["host"])
runner_cfg = scenario_cfg["runner"]