Add Neutron benchmarks scenario for creating and listing routers

In this patch:
    * scenario NeutronNetworks.create_and_list_routers is added,
      which does the following:
        - creates networks, subnets, routers according to scenario file
        - adds routers interfaces to created subnets
        - lists routers
    * refactoring in NeutronNetworks class for PEP257
      and methods arguments
    * a piece of rafactoring in ResourceCleaner tests
    * classmethod NeutronScenario._generate_subnet_cidr is re-implemented
      for using netaddr.IPNetwork class
    * base implementation for class fakes.FakeNeutronClient
    * some improvements and refactoring for fakes

blueprint benchmark-scenarios-for-neutron

Change-Id: I6dbbe9e476cdf0f1fd4c7f1387e830b5a46e2aa6
This commit is contained in:
Alexander Maretskiy 2014-05-12 18:21:58 +03:00
parent e1c555c7cf
commit 4c77a8748d
15 changed files with 717 additions and 172 deletions

View File

@ -2,7 +2,7 @@
"NeutronNetworks.create_and_list_networks": [ "NeutronNetworks.create_and_list_networks": [
{ {
"args": { "args": {
"network_data": {} "network_create_args": {}
}, },
"runner": { "runner": {
"type": "constant", "type": "constant",
@ -13,6 +13,11 @@
"users": { "users": {
"tenants": 1, "tenants": 1,
"users_per_tenant": 1 "users_per_tenant": 1
},
"quotas": {
"neutron": {
"network": -1
}
} }
} }
} }

View File

@ -2,7 +2,7 @@
NeutronNetworks.create_and_list_networks: NeutronNetworks.create_and_list_networks:
- -
args: args:
network_data: network_create_args:
runner: runner:
type: "constant" type: "constant"
times: 100 times: 100
@ -11,3 +11,6 @@
users: users:
tenants: 1 tenants: 1
users_per_tenant: 1 users_per_tenant: 1
quotas:
neutron:
network: -1

View File

@ -0,0 +1,31 @@
{
"NeutronNetworks.create_and_list_routers": [
{
"args": {
"network_create_args": {},
"subnet_create_args": {},
"subnet_cidr_start": "1.1.0.0/30",
"subnets_per_network": 2,
"router_create_args": {}
},
"runner": {
"type": "constant",
"times": 100,
"concurrency": 10
},
"context": {
"users": {
"tenants": 1,
"users_per_tenant": 1
},
"quotas": {
"neutron": {
"network": -1,
"subnet": -1,
"router": -1
}
}
}
}
]
}

View File

@ -0,0 +1,22 @@
---
NeutronNetworks.create_and_list_routers:
-
args:
network_create_args:
subnet_create_args:
subnet_cidr_start: "1.1.0.0/30"
subnets_per_network: 2
router_create_args:
runner:
type: "constant"
times: 100
concurrency: 10
context:
users:
tenants: 1
users_per_tenant: 1
quotas:
neutron:
network: -1
subnet: -1
router: -1

View File

@ -2,8 +2,9 @@
"NeutronNetworks.create_and_list_subnets": [ "NeutronNetworks.create_and_list_subnets": [
{ {
"args": { "args": {
"network_data": {}, "network_create_args": {},
"subnet_data": {}, "subnet_create_args": {},
"subnet_cidr_start": "1.1.0.0/30",
"subnets_per_network": 2 "subnets_per_network": 2
}, },
"runner": { "runner": {
@ -15,6 +16,12 @@
"users": { "users": {
"tenants": 5, "tenants": 5,
"users_per_tenant": 5 "users_per_tenant": 5
},
"quotas": {
"neutron": {
"network": -1,
"subnet": -1
}
} }
} }
} }

View File

@ -2,14 +2,19 @@
NeutronNetworks.create_and_list_subnets: NeutronNetworks.create_and_list_subnets:
- -
args: args:
network_data: network_create_args:
subnet_data: subnet_create_args:
subnet_cidr_start: "1.1.0.0/30"
subnets_per_network: 2 subnets_per_network: 2
runner: runner:
type: "constant" type: "constant"
times: 10 times: 100
concurrency: 5 concurrency: 10
context: context:
users: users:
tenants: 5 tenants: 1
users_per_tenant: 5 users_per_tenant: 1
quotas:
neutron:
network: -1
subnet: -1

View File

@ -37,7 +37,7 @@
NeutronNetworks.create_and_list_networks: NeutronNetworks.create_and_list_networks:
- -
args: args:
network_data: network_create_args:
runner: runner:
type: "constant" type: "constant"
times: 100 times: 100
@ -53,18 +53,41 @@
NeutronNetworks.create_and_list_subnets: NeutronNetworks.create_and_list_subnets:
- -
args: args:
network_data: network_create_args:
subnet_data: subnet_create_args:
subnet_cidr_start: "1.1.0.0/30"
subnets_per_network: 2 subnets_per_network: 2
runner: runner:
type: "constant" type: "constant"
times: 100 times: 100
concurrency: 5 concurrency: 10
context: context:
users: users:
tenants: 1 tenants: 1
users_per_tenant: 1 users_per_tenant: 1
quotas: quotas:
neutron: neutron:
subnet: -1
network: -1 network: -1
subnet: -1
NeutronNetworks.create_and_list_routers:
-
args:
network_create_args:
subnet_create_args:
subnet_cidr_start: "1.1.0.0/30"
subnets_per_network: 2
router_create_args:
runner:
type: "constant"
times: 100
concurrency: 10
context:
users:
tenants: 1
users_per_tenant: 1
quotas:
neutron:
network: -1
subnet: -1
router: -1

View File

@ -15,6 +15,8 @@
import logging import logging
from neutronclient.common import exceptions as neutron_exceptions
from rally.benchmark.scenarios.keystone import utils as kutils from rally.benchmark.scenarios.keystone import utils as kutils
from rally.benchmark import utils as bench_utils from rally.benchmark import utils as bench_utils
@ -116,21 +118,36 @@ def delete_keypairs(nova):
_wait_for_empty_list(nova.keypairs) _wait_for_empty_list(nova.keypairs)
def delete_neutron_networks(neutron, project_uuid):
for network in neutron.list_networks()['networks']:
if network['tenant_id'] == project_uuid:
neutron.delete_network(network['id'])
def delete_neutron_subnets(neutron, project_uuid):
for subnet in neutron.list_subnets()['subnets']:
if subnet['tenant_id'] == project_uuid:
neutron.delete_subnet(subnet['id'])
def delete_neutron_resources(neutron, project_uuid): def delete_neutron_resources(neutron, project_uuid):
delete_neutron_subnets(neutron, project_uuid) # Ports
delete_neutron_networks(neutron, project_uuid) for port in neutron.list_ports()["ports"]:
if port["tenant_id"] == project_uuid:
# Detach routers
for fip in port["fixed_ips"]:
neutron.remove_interface_router(
port["device_id"], {
"subnet_id": fip["subnet_id"]
})
try:
neutron.delete_port(port["id"])
except neutron_exceptions.PortNotFoundClient:
# Port can be already auto-deleted, skip silently
pass
# Routers
for router in neutron.list_routers()["routers"]:
if router["tenant_id"] == project_uuid:
neutron.delete_router(router["id"])
# Subnets
for subnet in neutron.list_subnets()["subnets"]:
if subnet["tenant_id"] == project_uuid:
neutron.delete_subnet(subnet["id"])
# Networks
for network in neutron.list_networks()["networks"]:
if network["tenant_id"] == project_uuid:
neutron.delete_network(network["id"])
def delete_ceilometer_resources(ceilometer, project_uuid): def delete_ceilometer_resources(ceilometer, project_uuid):

View File

@ -15,13 +15,14 @@
from rally.benchmark.scenarios import base from rally.benchmark.scenarios import base
from rally.benchmark.scenarios.neutron import utils from rally.benchmark.scenarios.neutron import utils
from rally.benchmark import validation
class NeutronNetworks(utils.NeutronScenario): class NeutronNetworks(utils.NeutronScenario):
@base.scenario(context={"cleanup": ["neutron"]}) @base.scenario(context={"cleanup": ["neutron"]})
def create_and_list_networks(self, network_data=None): def create_and_list_networks(self, network_create_args=None):
"""Tests creating a network and then listing all networks. """Create a network and then listing all networks.
This scenario is a very useful tool to measure This scenario is a very useful tool to measure
the "neutron net-list" command performance. the "neutron net-list" command performance.
@ -32,26 +33,59 @@ class NeutronNetworks(utils.NeutronScenario):
performance of the "neutron net-list" command depending on performance of the "neutron net-list" command depending on
the number of networks owned by users. the number of networks owned by users.
:param network_data: dict, network options :param network_create_args: dict, POST /v2.0/networks request options
""" """
self._create_network(network_data or {}) self._create_network(network_create_args or {})
self._list_networks() self._list_networks()
@base.scenario(context={"cleanup": ["neutron"]}) @base.scenario(context={"cleanup": ["neutron"]})
@validation.add(validation.required_parameters(['subnets_per_network']))
def create_and_list_subnets(self, def create_and_list_subnets(self,
network_data=None, network_create_args=None,
subnet_data=None, subnet_create_args=None,
subnets_per_network=1): subnet_cidr_start=None,
"""Tests creating a network, a given number of subnets subnets_per_network=None):
and then list subnets. """Create a network, a given number of subnets
and then list all subnets.
:param network_data: dict, network options :param network_create_args: dict, POST /v2.0/networks request options
:param subnet_data: dict, subnet options :param subnet_create_args: dict, POST /v2.0/subnets request options
:param subnet_cidr_start: str, start value for subnets CIDR
:param subnets_per_network: int, number of subnets for one network :param subnets_per_network: int, number of subnets for one network
""" """
if subnet_cidr_start:
network = self._create_network(network_data or {}) NeutronNetworks.SUBNET_CIDR_START = subnet_cidr_start
network = self._create_network(network_create_args or {})
for i in range(subnets_per_network): for i in range(subnets_per_network):
self._create_subnet(network, subnet_data or {}) self._create_subnet(network, subnet_create_args or {})
self._list_subnets() self._list_subnets()
@base.scenario(context={"cleanup": ["neutron"]})
@validation.add(validation.required_parameters(['subnets_per_network']))
def create_and_list_routers(self,
network_create_args=None,
subnet_create_args=None,
subnet_cidr_start=None,
subnets_per_network=None,
router_create_args=None):
"""Create a network, a given number of subnets and routers
and then list all routers.
:param network_create_args: dict, POST /v2.0/networks request options
:param subnet_create_args: dict, POST /v2.0/subnets request options
:param subnet_cidr_start: str, start value for subnets CIDR
:param subnets_per_network: int, number of subnets for one network
:param router_create_args: dict, POST /v2.0/routers request options
"""
if subnet_cidr_start:
NeutronNetworks.SUBNET_CIDR_START = subnet_cidr_start
network = self._create_network(network_create_args or {})
for i in range(subnets_per_network):
subnet = self._create_subnet(network, subnet_create_args or {})
router = self._create_router(router_create_args or {})
self.clients("neutron").add_interface_router(
router["router"]["id"],
{"subnet_id": subnet["subnet"]["id"]})
self._list_routers()

View File

@ -13,6 +13,9 @@
# 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 multiprocessing
import netaddr
from rally.benchmark.scenarios import base from rally.benchmark.scenarios import base
from rally.benchmark.scenarios import utils as scenario_utils from rally.benchmark.scenarios import utils as scenario_utils
@ -24,62 +27,81 @@ class NeutronScenario(base.Scenario):
RESOURCE_NAME_PREFIX = "rally_net_" RESOURCE_NAME_PREFIX = "rally_net_"
SUBNET_IP_VERSION = 4 SUBNET_IP_VERSION = 4
SUBNET_CIDR_PATTERN = "192.168.%d.0/24" SUBNET_CIDR_START = "1.1.0.0/30"
_subnet_cidrs = {} _subnet_cidrs = {}
@classmethod @classmethod
def _generate_subnet_cidr(cls, network_id): def _generate_subnet_cidr(cls, network_id):
"""Generates next subnet CIDR for given network, """Generate next subnet CIDR for given network,
without IP overlapping. without IP overlapping.
"""
if network_id in cls._subnet_cidrs:
cidr_no = cls._subnet_cidrs[network_id]
if cidr_no > 255:
# NOTE(amaretskiy): consider whether max number of
# 255 subnets per network is enough.
raise ValueError(
"can not generate more than 255 subnets CIDRs "
"per one network due to IP pattern limitation")
else:
cidr_no = 0
cls._subnet_cidrs[network_id] = cidr_no + 1 :param network_id: str, network UUID for subnet
return cls.SUBNET_CIDR_PATTERN % cidr_no :returns: str, next available subnet CIDR
"""
with multiprocessing.Lock():
if network_id in cls._subnet_cidrs:
crnt_cidr = cls._subnet_cidrs[network_id]
cidr = str(netaddr.IPNetwork(crnt_cidr).next())
else:
cidr = str(netaddr.IPNetwork(cls.SUBNET_CIDR_START))
cls._subnet_cidrs[network_id] = cidr
return cidr
@scenario_utils.atomic_action_timer('neutron.create_network') @scenario_utils.atomic_action_timer('neutron.create_network')
def _create_network(self, network_data): def _create_network(self, network_create_args):
"""Creates neutron network. """Create neutron network.
:param network_data: options for API v2.0 networks POST request :param network_create_args: dict, POST /v2.0/networks request options
:returns: neutron network dict :returns: neutron network dict
""" """
network_data.setdefault("name", self._generate_random_name()) network_create_args.setdefault("name", self._generate_random_name())
return self.clients("neutron").create_network({ return self.clients("neutron"
"network": network_data}) ).create_network({"network": network_create_args})
@scenario_utils.atomic_action_timer('neutron.list_networks') @scenario_utils.atomic_action_timer('neutron.list_networks')
def _list_networks(self): def _list_networks(self):
"""Returns user networks list.""" """Return user networks list."""
return self.clients("neutron").list_networks()['networks'] return self.clients("neutron").list_networks()['networks']
@scenario_utils.atomic_action_timer('neutron.create_subnet') @scenario_utils.atomic_action_timer('neutron.create_subnet')
def _create_subnet(self, network, subnet_data): def _create_subnet(self, network, subnet_create_args):
"""Creates neutron subnet. """Create neutron subnet.
:param network: neutron network dict :param network: neutron network dict
:param subnet_data: options for API v2.0 subnets POST request :param subnet_create_args: POST /v2.0/subnets request options
:returns: neutron subnet dict :returns: neutron subnet dict
""" """
network_id = network["network"]["id"] network_id = network["network"]["id"]
subnet_data["network_id"] = network_id subnet_create_args["network_id"] = network_id
subnet_data.setdefault("cidr", subnet_create_args.setdefault(
self._generate_subnet_cidr(network_id)) "name", self._generate_random_name("rally_subnet_"))
subnet_data.setdefault("ip_version", self.SUBNET_IP_VERSION) subnet_create_args.setdefault(
"cidr", self._generate_subnet_cidr(network_id))
subnet_create_args.setdefault(
"ip_version", self.SUBNET_IP_VERSION)
return self.clients("neutron").create_subnet({"subnet": subnet_data}) return self.clients("neutron"
).create_subnet({"subnet": subnet_create_args})
@scenario_utils.atomic_action_timer('neutron.list_subnets') @scenario_utils.atomic_action_timer('neutron.list_subnets')
def _list_subnets(self): def _list_subnets(self):
"""Returns user subnetworks list.""" """Returns user subnetworks list."""
return self.clients("neutron").list_subnets()["subnets"] return self.clients("neutron").list_subnets()["subnets"]
@scenario_utils.atomic_action_timer('neutron.create_router')
def _create_router(self, router_create_args):
"""Create neutron router.
:param router_create_args: POST /v2.0/routers request options
:returns: neutron router dict
"""
router_create_args.setdefault(
"name", self._generate_random_name("rally_router_"))
return self.clients("neutron"
).create_router({"router": router_create_args})
@scenario_utils.atomic_action_timer('neutron.list_routers')
def _list_routers(self):
"""Returns user routers list."""
return self.clients("neutron").list_routers()["routers"]

View File

@ -75,14 +75,18 @@ class ResourceCleanerTestCase(test.TestCase):
@mock.patch("%s.utils.delete_nova_resources" % BASE) @mock.patch("%s.utils.delete_nova_resources" % BASE)
@mock.patch("%s.utils.delete_glance_resources" % BASE) @mock.patch("%s.utils.delete_glance_resources" % BASE)
@mock.patch("%s.utils.delete_cinder_resources" % BASE) @mock.patch("%s.utils.delete_cinder_resources" % BASE)
def test_cleaner_users_all_services(self, mock_del_cinder, @mock.patch("%s.utils.delete_neutron_resources" % BASE)
mock_del_glance, mock_del_nova, def test_cleaner_users_resources(self,
mock_del_neutron,
mock_del_cinder,
mock_del_glance,
mock_del_nova,
mock_clients): mock_clients):
context = { context = {
"task": mock.MagicMock(), "task": mock.MagicMock(),
"users": [{"endpoint": mock.MagicMock()}, "users": [{"endpoint": mock.MagicMock()},
{"endpoint": mock.MagicMock()}], {"endpoint": mock.MagicMock()}],
"config": {"cleanup": ["cinder", "nova", "glance"]}, "config": {"cleanup": ["cinder", "nova", "glance", "neutron"]},
"tenants": [mock.MagicMock()] "tenants": [mock.MagicMock()]
} }
res_cleaner = cleanup_ctx.ResourceCleaner(context) res_cleaner = cleanup_ctx.ResourceCleaner(context)
@ -97,6 +101,7 @@ class ResourceCleanerTestCase(test.TestCase):
self.assertEqual(mock_del_nova.call_count, 2) self.assertEqual(mock_del_nova.call_count, 2)
self.assertEqual(mock_del_glance.call_count, 2) self.assertEqual(mock_del_glance.call_count, 2)
self.assertEqual(mock_del_cinder.call_count, 2) self.assertEqual(mock_del_cinder.call_count, 2)
self.assertEqual(mock_del_neutron.call_count, 2)
@mock.patch("%s.ResourceCleaner._cleanup_users_resources" % BASE) @mock.patch("%s.ResourceCleaner._cleanup_users_resources" % BASE)
def test_cleaner_users_default_behavior(self, mock_cleanup): def test_cleaner_users_default_behavior(self, mock_cleanup):
@ -111,29 +116,3 @@ class ResourceCleanerTestCase(test.TestCase):
res_cleaner.setup() res_cleaner.setup()
self.assertEqual(mock_cleanup.call_count, 0) self.assertEqual(mock_cleanup.call_count, 0)
@mock.patch("%s.osclients.Clients" % BASE)
@mock.patch("%s.utils.delete_nova_resources" % BASE)
@mock.patch("%s.utils.delete_glance_resources" % BASE)
@mock.patch("%s.utils.delete_cinder_resources" % BASE)
def test_cleaner_users_by_service(self, mock_del_cinder, mock_del_glance,
mock_del_nova, mock_clients):
context = {
"task": mock.MagicMock(),
"users": [{"endpoint": mock.MagicMock()},
{"endpoint": mock.MagicMock()}],
"config": {"cleanup": ["cinder", "nova"]},
"tenants": [mock.MagicMock()]
}
res_cleaner = cleanup_ctx.ResourceCleaner(context)
with res_cleaner:
res_cleaner.setup()
expected = [mock.call(context["users"][0]["endpoint"]),
mock.call(context["users"][1]["endpoint"])]
mock_clients.assert_has_calls(expected, any_order=True)
self.assertEqual(mock_del_nova.call_count, 2)
self.assertEqual(mock_del_glance.call_count, 0)
self.assertEqual(mock_del_cinder.call_count, 2)

View File

@ -0,0 +1,48 @@
# Copyright 2014: Mirantis Inc.
# 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.
from rally.benchmark.context.cleanup import utils
from rally.benchmark import scenarios
from tests import fakes
from tests import test
class CleanupUtilsTestCase(test.TestCase):
def test_delete_neutron_resources(self):
neutron = fakes.FakeClients().neutron()
scenario = scenarios.neutron.utils.NeutronScenario()
scenario.clients = lambda ins: neutron
network1 = scenario._create_network({})
subnet1 = scenario._create_subnet(network1, {})
router1 = scenario._create_router({})
neutron.add_interface_router(router1["router"]["id"],
{"subnet_id": subnet1["subnet"]["id"]})
network2 = scenario._create_network({})
scenario._create_subnet(network2, {})
scenario._create_router({})
total = lambda neutron: (len(neutron.list_networks()["networks"])
+ len(neutron.list_subnets()["subnets"])
+ len(neutron.list_routers()["routers"]))
self.assertEqual(total(neutron), 6)
utils.delete_neutron_resources(neutron,
network1["network"]["tenant_id"])
self.assertEqual(total(neutron), 0)

View File

@ -29,52 +29,139 @@ class NeutronNetworksTestCase(test.TestCase):
def test_create_and_list_networks(self, mock_create, mock_list): def test_create_and_list_networks(self, mock_create, mock_list):
neutron_scenario = network.NeutronNetworks() neutron_scenario = network.NeutronNetworks()
# Empty options are specified # Default options
network_data = {} network_create_args = {}
neutron_scenario.create_and_list_networks(network_data=network_data) neutron_scenario.create_and_list_networks(
mock_create.assert_called_once_with(network_data) network_create_args=network_create_args)
mock_create.assert_called_once_with(network_create_args)
mock_list.assert_called_once_with() mock_list.assert_called_once_with()
mock_create.reset_mock() mock_create.reset_mock()
mock_list.reset_mock() mock_list.reset_mock()
# Explicit network name is specified # Explicit network name is specified
network_data = {"name": "given-name"} network_create_args = {"name": "given-name"}
neutron_scenario.create_and_list_networks(network_data=network_data) neutron_scenario.create_and_list_networks(
mock_create.assert_called_once_with(network_data) network_create_args=network_create_args)
mock_create.assert_called_once_with(network_create_args)
mock_list.assert_called_once_with() mock_list.assert_called_once_with()
@mock.patch(NEUTRON_NETWORKS + "._generate_random_name")
@mock.patch(NEUTRON_NETWORKS + "._list_subnets") @mock.patch(NEUTRON_NETWORKS + "._list_subnets")
@mock.patch(NEUTRON_NETWORKS + "._create_subnet") @mock.patch(NEUTRON_NETWORKS + "._create_subnet")
@mock.patch(NEUTRON_NETWORKS + "._create_network") @mock.patch(NEUTRON_NETWORKS + "._create_network")
@mock.patch(NEUTRON_NETWORKS + ".SUBNET_CIDR_START",
new_callable=mock.PropertyMock(return_value="default_cidr"))
def test_create_and_list_subnets(self, def test_create_and_list_subnets(self,
mock_cidr_start,
mock_create_network, mock_create_network,
mock_create_subnet, mock_create_subnet,
mock_list, mock_list):
mock_random_name):
scenario = network.NeutronNetworks() scenario = network.NeutronNetworks()
mock_random_name.return_value = "random-name"
mock_create_network.return_value = {"network": {"id": "fake-id"}} mock_create_network.return_value = {"network": {"id": "fake-id"}}
subnets_per_network = 4
# Empty options self.assertRaises(TypeError, scenario.create_and_list_subnets)
scenario.create_and_list_subnets()
mock_create_network.assert_called_once_with({})
mock_create_subnet.assert_called_once_with(
{"network": {"id": "fake-id"}}, {})
mock_list.assert_called_once_with()
mock_create_network.reset_mock() mock_create_network.reset_mock()
mock_create_subnet.reset_mock() mock_create_subnet.reset_mock()
mock_list.reset_mock() mock_list.reset_mock()
# Extra options are specified # Default options
subnets_num = 4 scenario.create_and_list_subnets(
scenario.create_and_list_subnets(network_data={"name": "given-name"}, subnets_per_network=subnets_per_network)
subnet_data={"allocation_pools": []}, mock_create_network.assert_called_once_with({})
subnets_per_network=subnets_num)
mock_create_network.assert_called_once_with({"name": "given-name"})
self.assertEqual(mock_create_subnet.mock_calls, self.assertEqual(mock_create_subnet.mock_calls,
[mock.call({"network": {"id": "fake-id"}}, [mock.call({"network": {"id": "fake-id"}},
{"allocation_pools": []})] * subnets_num) {})] * subnets_per_network)
mock_list.assert_called_once_with()
self.assertEqual(scenario.SUBNET_CIDR_START, "default_cidr")
mock_create_network.reset_mock()
mock_create_subnet.reset_mock()
mock_list.reset_mock()
# Custom options
scenario.create_and_list_subnets(
subnet_create_args={"allocation_pools": []},
subnet_cidr_start="custom_cidr",
subnets_per_network=subnets_per_network)
self.assertEqual(scenario.SUBNET_CIDR_START, "custom_cidr")
mock_create_network.assert_called_once_with({})
self.assertEqual(
mock_create_subnet.mock_calls,
[mock.call({"network": {"id": "fake-id"}},
{"allocation_pools": []})] * subnets_per_network)
mock_list.assert_called_once_with()
@mock.patch(NEUTRON_NETWORKS + "._list_routers")
@mock.patch(NEUTRON_NETWORKS + "._create_router")
@mock.patch(NEUTRON_NETWORKS + "._create_subnet")
@mock.patch(NEUTRON_NETWORKS + "._create_network")
@mock.patch(NEUTRON_NETWORKS + ".clients")
def test_create_and_list_routers(self,
mock_clients,
mock_create_network,
mock_create_subnet,
mock_create_router,
mock_list):
scenario = network.NeutronNetworks()
subnets_per_network = 4
mock_clients("neutron").add_interface_router = mock.Mock()
net = {"network": {"id": "network-id"}}
mock_create_network.return_value = net
subnet = {"subnet": {"name": "subnet-name", "id": "subnet-id"}}
mock_create_subnet.return_value = subnet
router = {"router": {"name": "router-name", "id": "router-id"}}
mock_create_router.return_value = router
# Default options
scenario.create_and_list_routers(
subnets_per_network=subnets_per_network)
mock_create_network.assert_called_once_with({})
self.assertEqual(
mock_create_subnet.mock_calls,
[mock.call(net, {})] * subnets_per_network)
self.assertEqual(
mock_create_router.mock_calls,
[mock.call({})] * subnets_per_network)
self.assertEqual(
mock_clients("neutron").add_interface_router.mock_calls,
[mock.call(router["router"]["id"],
{"subnet_id": subnet["subnet"]["id"]})
] * subnets_per_network)
mock_create_network.reset_mock()
mock_create_subnet.reset_mock()
mock_create_router.reset_mock()
mock_clients("neutron").add_interface_router.reset_mock()
mock_list.reset_mock()
# Custom options
subnet_create_args = {"allocation_pools": []}
router_create_args = {"admin_state_up": False}
scenario.create_and_list_routers(
subnet_create_args=subnet_create_args,
subnet_cidr_start="custom_cidr",
subnets_per_network=subnets_per_network,
router_create_args=router_create_args)
self.assertEqual(scenario.SUBNET_CIDR_START, "custom_cidr")
mock_create_network.assert_called_once_with({})
self.assertEqual(
mock_create_subnet.mock_calls, [
mock.call({"network": {"id": "network-id"}},
subnet_create_args)
] * subnets_per_network)
self.assertEqual(
mock_create_router.mock_calls, [
mock.call(router_create_args)
] * subnets_per_network)
self.assertEqual(
mock_clients("neutron").add_interface_router.mock_calls, [
mock.call(router["router"]["id"],
{"subnet_id": subnet["subnet"]["id"]})
] * subnets_per_network)
mock_list.assert_called_once_with() mock_list.assert_called_once_with()

View File

@ -67,33 +67,36 @@ class NeutronScenarioTestCase(test.TestCase):
@mock.patch(NEUTRON_UTILS + 'NeutronScenario.clients') @mock.patch(NEUTRON_UTILS + 'NeutronScenario.clients')
def test_list_networks(self, mock_clients): def test_list_networks(self, mock_clients):
scenario = utils.NeutronScenario()
networks_list = [] networks_list = []
networks_dict = {"networks": networks_list} networks_dict = {"networks": networks_list}
mock_clients("neutron").list_networks.return_value = networks_dict mock_clients("neutron").list_networks.return_value = networks_dict
neutron_scenario = utils.NeutronScenario() return_networks_list = scenario._list_networks()
return_networks_list = neutron_scenario._list_networks()
self.assertEqual(networks_list, return_networks_list) self.assertEqual(networks_list, return_networks_list)
self._test_atomic_action_timer(neutron_scenario.atomic_actions(), self._test_atomic_action_timer(scenario.atomic_actions(),
'neutron.list_networks') 'neutron.list_networks')
@mock.patch(NEUTRON_UTILS + 'NeutronScenario._generate_random_name')
@mock.patch(NEUTRON_UTILS + "NeutronScenario._generate_subnet_cidr") @mock.patch(NEUTRON_UTILS + "NeutronScenario._generate_subnet_cidr")
@mock.patch(NEUTRON_UTILS + "NeutronScenario.clients") @mock.patch(NEUTRON_UTILS + "NeutronScenario.clients")
def test_create_subnet(self, mock_clients, mock_cidr): def test_create_subnet(self, mock_clients, mock_cidr, mock_random_name):
scenario = utils.NeutronScenario()
network_id = "fake-id" network_id = "fake-id"
subnet_cidr = "192.168.0.0/24" subnet_cidr = "192.168.0.0/24"
mock_cidr.return_value = subnet_cidr mock_cidr.return_value = subnet_cidr
scenario = utils.NeutronScenario() mock_random_name.return_value = "fake-name"
network = {"network": {"id": network_id}} network = {"network": {"id": network_id}}
expected_subnet_data = { expected_subnet_data = {
"subnet": { "subnet": {
"network_id": network_id, "network_id": network_id,
"cidr": subnet_cidr, "cidr": subnet_cidr,
"ip_version": scenario.SUBNET_IP_VERSION "ip_version": scenario.SUBNET_IP_VERSION,
"name": "fake-name"
} }
} }
# No extra options # Default options
subnet_data = {"network_id": network_id} subnet_data = {"network_id": network_id}
scenario._create_subnet(network, subnet_data) scenario._create_subnet(network, subnet_data)
mock_clients("neutron")\ mock_clients("neutron")\
@ -103,7 +106,7 @@ class NeutronScenarioTestCase(test.TestCase):
mock_clients("neutron").create_subnet.reset_mock() mock_clients("neutron").create_subnet.reset_mock()
# Extra options are specified # Custom options
extras = {"cidr": "192.168.16.0/24", "allocation_pools": []} extras = {"cidr": "192.168.16.0/24", "allocation_pools": []}
subnet_data.update(extras) subnet_data.update(extras)
expected_subnet_data["subnet"].update(extras) expected_subnet_data["subnet"].update(extras)
@ -122,32 +125,61 @@ class NeutronScenarioTestCase(test.TestCase):
self._test_atomic_action_timer(scenario.atomic_actions(), self._test_atomic_action_timer(scenario.atomic_actions(),
"neutron.list_subnets") "neutron.list_subnets")
@mock.patch(NEUTRON_UTILS + 'NeutronScenario._generate_random_name')
@mock.patch(NEUTRON_UTILS + 'NeutronScenario.clients')
def test_create_router(self, mock_clients, mock_random_name):
scenario = utils.NeutronScenario()
router = mock.Mock()
explicit_name = "explicit_name"
random_name = "random_name"
mock_random_name.return_value = random_name
mock_clients("neutron").create_router.return_value = router
# Default options
result_router = scenario._create_router({})
mock_clients("neutron").create_router.assert_called_once_with(
{"router": {"name": random_name}})
self.assertEqual(result_router, router)
self._test_atomic_action_timer(scenario.atomic_actions(),
'neutron.create_router')
mock_clients("neutron").create_router.reset_mock()
# Custom options
router_data = {"name": explicit_name, "admin_state_up": True}
result_router = scenario._create_router(router_data)
mock_clients("neutron").create_router.assert_called_once_with(
{"router": router_data})
@mock.patch(NEUTRON_UTILS + 'NeutronScenario.clients')
def test_list_routers(self, mock_clients):
scenario = utils.NeutronScenario()
routers = [mock.Mock()]
mock_clients("neutron").list_routers.return_value = {
"routers": routers}
self.assertEqual(routers, scenario._list_routers())
self._test_atomic_action_timer(scenario.atomic_actions(),
'neutron.list_routers')
def test_SUBNET_IP_VERSION(self): def test_SUBNET_IP_VERSION(self):
"Curent NeutronScenario implementation supports only IPv4" """Curent NeutronScenario implementation supports only IPv4."""
self.assertEqual(utils.NeutronScenario.SUBNET_IP_VERSION, 4) self.assertEqual(utils.NeutronScenario.SUBNET_IP_VERSION, 4)
def test_SUBNET_CIDR_PATTERN(self): def test_SUBNET_CIDR_START(self):
netaddr.IPNetwork(utils.NeutronScenario.SUBNET_CIDR_PATTERN % 0) """Valid default CIDR to generate first subnet."""
self.assertRaises(netaddr.core.AddrFormatError, netaddr.IPNetwork(utils.NeutronScenario.SUBNET_CIDR_START)
netaddr.IPNetwork,
utils.NeutronScenario.SUBNET_CIDR_PATTERN % 256)
def test_generate_subnet_cidr(self): def test_generate_subnet_cidr(self):
scenario = utils.NeutronScenario() scenario = utils.NeutronScenario()
network1_id = "fake1" network1_id = "fake1"
network2_id = "fake2" network2_id = "fake2"
subnets_num = 300
cidrs1 = map(scenario._generate_subnet_cidr, [network1_id] * 256) cidrs1 = map(scenario._generate_subnet_cidr,
[network1_id] * subnets_num)
self.assertRaises(ValueError, cidrs2 = map(scenario._generate_subnet_cidr,
scenario._generate_subnet_cidr, [network2_id] * subnets_num)
network1_id)
cidrs2 = map(scenario._generate_subnet_cidr, [network2_id] * 256)
self.assertRaises(ValueError,
scenario._generate_subnet_cidr,
network2_id)
# All CIDRs must differ # All CIDRs must differ
self.assertEqual(len(cidrs1), len(set(cidrs1))) self.assertEqual(len(cidrs1), len(set(cidrs1)))

View File

@ -13,13 +13,18 @@
# 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 mock
import multiprocessing import multiprocessing
import random
import re
import string
import uuid import uuid
import mock
from ceilometerclient import exc as ceilometer_exc from ceilometerclient import exc as ceilometer_exc
from glanceclient import exc from glanceclient import exc
from novaclient import exceptions from neutronclient.common import exceptions as neutron_exceptions
from novaclient import exceptions as nova_exceptions
from rally.benchmark.context import base as base_ctx from rally.benchmark.context import base as base_ctx
from rally.benchmark.scenarios import base from rally.benchmark.scenarios import base
from rally.objects import endpoint from rally.objects import endpoint
@ -30,6 +35,49 @@ def generate_uuid():
return str(uuid.uuid4()) return str(uuid.uuid4())
def generate_name(prefix="", length=12, choices=string.lowercase):
"""Generate pseudo-random name.
:param prefix: str, custom prefix for genertated name
:param length: int, length of autogenerated part of result name
:param choices: str, chars that accurs in generated name
:returns: str, pseudo-random name
"""
return prefix + ''.join(random.choice(choices) for i in range(length))
def generate_mac():
"""Generate pseudo-random MAC address.
:returns: str, MAC address
"""
rand_str = generate_name(choices="0123456789abcdef", length=12)
return ":".join(re.findall("..", rand_str))
def setup_dict(data, required=None, defaults=None):
"""Setup and validate dict based on mandatory keys and default data.
This function reduces code that constructs dict objects
with specific schema (e.g. for API data).
:param data: dict, input data
:param required: list, mandatory keys to check
:param defaults: dict, default data
:returns: dict, with all keys set
:raises: IndexError, ValueError
"""
required = required or []
for i in set(required) - set(data.keys()):
raise IndexError("Missed: %s" % i)
defaults = defaults or {}
for i in set(data.keys()) - set(required + defaults.keys()):
raise ValueError("Unexpected: %s" % i)
defaults.update(data)
return defaults
class FakeResource(object): class FakeResource(object):
def __init__(self, manager=None, name=None, status="ACTIVE", items=None, def __init__(self, manager=None, name=None, status="ACTIVE", items=None,
@ -227,7 +275,7 @@ class FakeServerManager(FakeManager):
server = self.cache.get(resource_uuid, None) server = self.cache.get(resource_uuid, None)
if server is not None: if server is not None:
return server return server
raise exceptions.NotFound("Server %s not found" % (resource_uuid)) raise nova_exceptions.NotFound("Server %s not found" % (resource_uuid))
def _create(self, server_class=FakeServer, name=None): def _create(self, server_class=FakeServer, name=None):
server = self._cache(server_class(self)) server = self._cache(server_class(self))
@ -351,7 +399,7 @@ class FakeSecurityGroupManager(FakeManager):
break break
if match: if match:
return resource return resource
raise exceptions.NotFound('Security Group not found') raise nova_exceptions.NotFound('Security Group not found')
class FakeSecurityGroupRuleManager(FakeManager): class FakeSecurityGroupRuleManager(FakeManager):
@ -559,9 +607,185 @@ class FakeCeilometerClient(object):
class FakeNeutronClient(object): class FakeNeutronClient(object):
def __init__(self): def __init__(self, **kwargs):
#TODO(bsemp): Fake Manager subclasses to manage networks. self.__networks = {}
pass self.__subnets = {}
self.__routers = {}
self.__ports = {}
self.__tenant_id = kwargs.get("tenant_id", generate_uuid())
self.format = "json"
self.version = "2.0"
def add_interface_router(self, router_id, data):
subnet_id = data["subnet_id"]
if router_id not in self.__routers\
or subnet_id not in self.__subnets:
raise neutron_exceptions.NeutronClientException
subnet = self.__subnets[subnet_id]
port = self.create_port(
{"port": {"network_id": subnet["network_id"]}})["port"]
port["device_id"] = router_id
port["fixed_ips"].append({"subnet_id": subnet_id,
"ip_address": subnet["gateway_ip"]})
return {"subnet_id": subnet_id,
"tenant_id": port["tenant_id"],
"port_id": port["id"],
"id": router_id}
def create_network(self, data):
network = setup_dict(data["network"],
defaults={"name": generate_name("net_"),
"admin_state_up": True})
network_id = generate_uuid()
network.update({"id": network_id,
"status": "ACTIVE",
"subnets": [],
"provider:physical_network": None,
"tenant_id": self.__tenant_id,
"provider:network_type": "local",
"router:external": True,
"shared": False,
"provider:segmentation_id": None})
self.__networks[network_id] = network
return {"network": network}
def create_port(self, data):
port = setup_dict(data["port"],
required=["network_id"],
defaults={"name": generate_name("port_"),
"admin_state_up": True})
if port["network_id"] not in self.__networks:
raise neutron_exceptions.NeutronClientException
port_id = generate_uuid()
port.update({"id": port_id,
"status": "ACTIVE",
"binding:host_id": "fakehost",
"extra_dhcp_opts": [],
"binding:vnic_type": "normal",
"binding:vif_type": "ovs",
"device_owner": "",
"mac_address": generate_mac(),
"binding:profile": {},
"binding:vif_details": {u'port_filter': True},
"security_groups": [],
"fixed_ips": [],
"device_id": "",
"tenant_id": self.__tenant_id,
"allowed_address_pairs": []})
self.__ports[port_id] = port
return {"port": port}
def create_router(self, data):
router = setup_dict(data["router"],
defaults={"name": generate_name("router_"),
"admin_state_up": True})
router_id = generate_uuid()
router.update({"id": router_id,
"status": "ACTIVE",
"external_gateway_info": None,
"tenant_id": self.__tenant_id})
self.__routers[router_id] = router
return {"router": router}
def create_subnet(self, data):
subnet = setup_dict(data["subnet"],
required=["network_id", "cidr", "ip_version"],
defaults={"name": generate_name("subnet_")})
if subnet["network_id"] not in self.__networks:
raise neutron_exceptions.NeutronClientException
subnet_id = generate_uuid()
subnet.update({"id": subnet_id,
"enable_dhcp": True,
"tenant_id": self.__tenant_id,
"dns_nameservers": [],
"ipv6_ra_mode": None,
"allocation_pools": [],
"gateway_ip": re.sub('./.*$', '1', subnet["cidr"]),
"ipv6_address_mode": None,
"ip_version": 4,
"host_routes": []})
self.__subnets[subnet_id] = subnet
return {"subnet": subnet}
def delete_network(self, network_id):
if network_id not in self.__networks:
raise neutron_exceptions.NeutronClientException
for port in self.__ports.values():
if port["network_id"] == network_id:
# Network is in use by port
raise neutron_exceptions.NeutronClientException
del self.__networks[network_id]
return ""
def delete_port(self, port_id):
if port_id not in self.__ports:
raise neutron_exceptions.PortNotFoundClient
if self.__ports[port_id]["device_owner"]:
# Port is owned by some device
raise neutron_exceptions.NeutronClientException
del self.__ports[port_id]
return ""
def delete_router(self, router_id):
if router_id not in self.__routers:
raise neutron_exceptions.NeutronClientException
for port in self.__ports.values():
if port["device_id"] == router_id:
# Router has active port
raise neutron_exceptions.NeutronClientException
del self.__routers[router_id]
return ""
def delete_subnet(self, subnet_id):
if subnet_id not in self.__subnets:
raise neutron_exceptions.NeutronClientException
for port in self.__ports.values():
for fip in port["fixed_ips"]:
if fip["subnet_id"] == subnet_id:
# Subnet has IP allocation from some port
raise neutron_exceptions.NeutronClientException
del self.__subnets[subnet_id]
return ""
def list_networks(self):
return {"networks": self.__networks.values()}
def list_ports(self):
return {"ports": self.__ports.values()}
def list_routers(self):
return {"routers": self.__routers.values()}
def list_subnets(self):
return {"subnets": self.__subnets.values()}
def remove_interface_router(self, router_id, data):
subnet_id = data["subnet_id"]
if router_id not in self.__routers\
or subnet_id not in self.__subnets:
raise neutron_exceptions.NeutronClientException
subnet = self.__subnets[subnet_id]
for port_id, port in self.__ports.items():
if port["device_id"] == router_id:
for fip in port["fixed_ips"]:
if fip["subnet_id"] == subnet_id:
del self.__ports[port_id]
return {"subnet_id": subnet_id,
"tenant_id": subnet["tenant_id"],
"port_id": port_id,
"id": router_id}
raise neutron_exceptions.NeutronClientException
class FakeIronicClient(object): class FakeIronicClient(object):
@ -573,16 +797,20 @@ class FakeIronicClient(object):
class FakeClients(object): class FakeClients(object):
def __init__(self): def __init__(self, endpoint_=None):
self._nova = None self._nova = None
self._glance = None self._glance = None
self._keystone = None self._keystone = None
self._cinder = None self._cinder = None
self._endpoint = None self._neutron = None
self._endpoint = endpoint_ or endpoint.Endpoint(
"http://fake.example.org:5000/v2.0/",
"fake_username",
"fake_password",
"fake_tenant_name")
def keystone(self): def keystone(self):
if self._keystone is not None: if not self._keystone:
return self._keystone
self._keystone = FakeKeystoneClient() self._keystone = FakeKeystoneClient()
return self._keystone return self._keystone
@ -590,23 +818,25 @@ class FakeClients(object):
return self.keystone() return self.keystone()
def nova(self): def nova(self):
if self._nova is not None: if not self._nova:
return self._nova
self._nova = FakeNovaClient() self._nova = FakeNovaClient()
return self._nova return self._nova
def glance(self): def glance(self):
if self._glance is not None: if not self._glance:
return self._glance
self._glance = FakeGlanceClient() self._glance = FakeGlanceClient()
return self._glance return self._glance
def cinder(self): def cinder(self):
if self._cinder is not None: if not self._cinder:
return self._cinder
self._cinder = FakeCinderClient() self._cinder = FakeCinderClient()
return self._cinder return self._cinder
def neutron(self):
if not self._neutron:
self._neutron = FakeNeutronClient()
return self._neutron
class FakeRunner(object): class FakeRunner(object):