Add FuelEngine
This engine deploys OpenStack with Fuel blueprint fuel-engine Change-Id: Ifff933197b02c7dc7b47aedbe52810bc923d8678
This commit is contained in:
parent
d2aaa24872
commit
59f1d031c1
28
doc/samples/deployments/fuel-ha.json
Normal file
28
doc/samples/deployments/fuel-ha.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"type": "FuelEngine",
|
||||
"deploy_name": "Rally HA 01",
|
||||
"release": "Havana on CentOS 6.4",
|
||||
"api_url": "http://10.20.0.2:8000/api/v1/",
|
||||
"mode": "ha_compact",
|
||||
"nodes": {
|
||||
"controller": {"amount": 3, "filters": ["storage>80G"]},
|
||||
"compute": {"amount": 1, "filters": ["storage>80G"]}
|
||||
},
|
||||
"net_provider": "nova_network",
|
||||
"dns_nameservers": ["172.18.208.44", "8.8.8.8"],
|
||||
"networks": {
|
||||
|
||||
"public": {
|
||||
"cidr": "10.3.5.0/24",
|
||||
"gateway": "10.3.5.1",
|
||||
"ip_ranges": [["10.3.5.5", "10.3.5.254"]],
|
||||
"vlan_start": 15
|
||||
},
|
||||
|
||||
"floating": {
|
||||
"cidr": "10.3.6.0/24",
|
||||
"ip_ranges": [["10.3.6.5", "10.3.6.254"]],
|
||||
"vlan_start": 15
|
||||
}
|
||||
}
|
||||
}
|
28
doc/samples/deployments/fuel-multinode.json
Normal file
28
doc/samples/deployments/fuel-multinode.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"type": "FuelEngine",
|
||||
"deploy_name": "Rally multinode 01",
|
||||
"release": "Havana on CentOS 6.4",
|
||||
"api_url": "http://10.20.0.2:8000/api/v1/",
|
||||
"mode": "multinode",
|
||||
"nodes": {
|
||||
"controller": {"amount": 1, "filters": ["storage>80G"]},
|
||||
"compute": {"amount": 1, "filters": ["storage>80G"]}
|
||||
},
|
||||
"net_provider": "nova_network",
|
||||
"dns_nameservers": ["172.18.208.44", "8.8.8.8"],
|
||||
"networks": {
|
||||
|
||||
"public": {
|
||||
"cidr": "10.3.3.0/24",
|
||||
"gateway": "10.3.3.1",
|
||||
"ip_ranges": [["10.3.3.5", "10.3.3.254"]],
|
||||
"vlan_start": 14
|
||||
},
|
||||
|
||||
"floating": {
|
||||
"cidr": "10.3.4.0/24",
|
||||
"ip_ranges": [["10.3.4.5", "10.3.4.254"]],
|
||||
"vlan_start": 14
|
||||
}
|
||||
}
|
||||
}
|
181
rally/deploy/engines/fuel.py
Normal file
181
rally/deploy/engines/fuel.py
Normal file
@ -0,0 +1,181 @@
|
||||
# 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 import consts
|
||||
from rally.deploy import engine
|
||||
from rally.deploy.fuel import fuelclient
|
||||
from rally import exceptions
|
||||
from rally import objects
|
||||
from rally.openstack.common.gettextutils import _ # noqa
|
||||
|
||||
|
||||
FILTER_SCHEMA = {
|
||||
'type': 'string',
|
||||
'pattern': '^(ram|cpus|storage|mac)(==|<=?|>=?|!=)(.+)$',
|
||||
}
|
||||
|
||||
NODE_SCHEMA = {
|
||||
'type': 'object',
|
||||
'required': ['amount'],
|
||||
'properties': {
|
||||
'amount': {'type': 'integer'},
|
||||
'filters': {
|
||||
'type': 'array',
|
||||
'uniqueItems': True,
|
||||
'items': FILTER_SCHEMA,
|
||||
},
|
||||
},
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
||||
|
||||
IPV4_PATTERN = '(\d+\.){3}\d+'
|
||||
IPV4_ADDRESS_PATTERN = '^%s$' % IPV4_PATTERN
|
||||
IPV4_CIDR_PATTERN = '^%s\/\d+$' % IPV4_PATTERN
|
||||
|
||||
IP_RANGE_SCHEMA = {
|
||||
'type': 'array',
|
||||
'maxItems': 2,
|
||||
'minItems': 2,
|
||||
'items': {
|
||||
'type': 'string',
|
||||
'pattern': IPV4_ADDRESS_PATTERN,
|
||||
}
|
||||
}
|
||||
|
||||
NETWORK_SCHEMA = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'cidr': {'type': 'string', 'pattern': IPV4_CIDR_PATTERN},
|
||||
'gateway': {'type': 'string', 'pattern': IPV4_ADDRESS_PATTERN},
|
||||
'ip_ranges': {'type': 'array', 'items': IP_RANGE_SCHEMA},
|
||||
'vlan_start': {'type': 'integer'},
|
||||
}
|
||||
}
|
||||
|
||||
NETWORKS_SCHEMA = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'public': NETWORK_SCHEMA,
|
||||
'floating': NETWORK_SCHEMA,
|
||||
'management': NETWORK_SCHEMA,
|
||||
'storage': NETWORK_SCHEMA,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class FuelEngine(engine.EngineFactory):
|
||||
"""Deploy with FuelWeb."""
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
'type': 'object',
|
||||
'required': ['deploy_name', 'api_url', 'mode', 'networks',
|
||||
'nodes', 'release', 'net_provider'],
|
||||
'properties': {
|
||||
'release': {'type': 'string'},
|
||||
'deploy_name': {'type': 'string'},
|
||||
'api_url': {'type': 'string'},
|
||||
'mode': {'type': 'string'},
|
||||
'net_provider': {'type': 'string'},
|
||||
'networks': NETWORKS_SCHEMA,
|
||||
'nodes': {
|
||||
'type': 'object',
|
||||
'required': ['controller'],
|
||||
'properties': {
|
||||
'controller': NODE_SCHEMA,
|
||||
'compute': NODE_SCHEMA,
|
||||
'cinder': NODE_SCHEMA,
|
||||
'cinder+compute': NODE_SCHEMA,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
super(FuelEngine, self).validate()
|
||||
if 'compute' not in self.config['nodes']:
|
||||
if 'cinder+compute' not in self.config['nodes']:
|
||||
raise exceptions.ValidationError(
|
||||
message=_('At least one compute is required.'))
|
||||
|
||||
def _get_nodes(self, key):
|
||||
if key not in self.config['nodes']:
|
||||
return []
|
||||
amount = self.config['nodes'][key]['amount']
|
||||
filters = self.config['nodes'][key]['filters']
|
||||
nodes = []
|
||||
for i in range(amount):
|
||||
node = self.nodes.pop(filters)
|
||||
if node is None:
|
||||
raise exceptions.NoNodesFound(filters=filters)
|
||||
nodes.append(node)
|
||||
return nodes
|
||||
|
||||
def _get_release_id(self):
|
||||
releases = self.client.get_releases()
|
||||
for release in releases:
|
||||
if release['name'] == self.config['release']:
|
||||
return release['id']
|
||||
raise exceptions.UnknownRelease(release=self.config['release'])
|
||||
|
||||
def deploy(self):
|
||||
self.client = fuelclient.FuelClient(self.config['api_url'])
|
||||
|
||||
self.nodes = self.client.get_nodes()
|
||||
|
||||
controllers = self._get_nodes('controller')
|
||||
computes = self._get_nodes('compute')
|
||||
cinders = self._get_nodes('cinder')
|
||||
computes_cinders = self._get_nodes('cinder+compute')
|
||||
|
||||
cluster = fuelclient.FuelCluster(
|
||||
self.client,
|
||||
name=self.config['deploy_name'],
|
||||
release=self._get_release_id(),
|
||||
mode=self.config['mode'],
|
||||
net_provider=self.config['net_provider'],
|
||||
net_segment_type=self.config.get('net_segment_type', 'gre'),
|
||||
)
|
||||
|
||||
cluster.set_nodes(controllers, ['controller'])
|
||||
cluster.set_nodes(computes, ['compute'])
|
||||
cluster.set_nodes(cinders, ['cinder'])
|
||||
cluster.set_nodes(computes_cinders, ['compute', 'cinder'])
|
||||
|
||||
cluster.configure_network(self.config['networks'])
|
||||
cluster.deploy()
|
||||
|
||||
self.deployment.add_resource('FuelEngine',
|
||||
type='cloud',
|
||||
info={'id': cluster.cluster['id']})
|
||||
|
||||
ip = cluster.get_endpoint_ip()
|
||||
attrs = cluster.get_attributes()['editable']['access']
|
||||
|
||||
return [objects.Endpoint(
|
||||
'http://%s:5000/v2.0/' % ip,
|
||||
attrs['user']['value'],
|
||||
attrs['password']['value'],
|
||||
attrs['tenant']['value'],
|
||||
consts.EndpointPermission.ADMIN
|
||||
)]
|
||||
|
||||
def cleanup(self):
|
||||
resources = self.deployment.get_resources(provider_name='FuelEngine',
|
||||
type='cloud')
|
||||
self.client = fuelclient.FuelClient(self.config['api_url'])
|
||||
for res in resources:
|
||||
self.client.delete_cluster(res['info']['id'])
|
||||
objects.Deployment.delete_resource(res['id'])
|
@ -225,3 +225,15 @@ class BenchmarkSetupFailure(RallyException):
|
||||
|
||||
class DummyScenarioException(RallyException):
|
||||
msg_fmt = _("Dummy scenario expected exception: '%(message)s'")
|
||||
|
||||
|
||||
class ValidationError(RallyException):
|
||||
msg_fmt = _("Validation error: %(message)s")
|
||||
|
||||
|
||||
class NoNodesFound(RallyException):
|
||||
msg_fmt = _("There is no nodes matching filters: %(filters)r")
|
||||
|
||||
|
||||
class UnknownRelease(RallyException):
|
||||
msg_fmt = _("Unknown release '%(release)s'")
|
||||
|
139
tests/deploy/engines/test_fuel.py
Normal file
139
tests/deploy/engines/test_fuel.py
Normal file
@ -0,0 +1,139 @@
|
||||
# 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 import consts
|
||||
from rally.deploy.engines import fuel
|
||||
from rally import exceptions
|
||||
from tests import fakes
|
||||
from tests import test
|
||||
|
||||
import mock
|
||||
|
||||
|
||||
SAMPLE_CONFIG = {
|
||||
'type': 'FuelEngine',
|
||||
'deploy_name': 'TestDeploy01',
|
||||
'net_provider': 'nova_network',
|
||||
'release': 'Havana on Ubuntu 12.04',
|
||||
'api_url': 'http://example.net:8000/api/v1/',
|
||||
'mode': 'multinode',
|
||||
'networks': {'public': {'cidr': '10.1.1.0/24'}},
|
||||
'nodes': {
|
||||
'controller': {'amount': 1, 'filters': ['cpus==2']},
|
||||
'cinder+compute': {'amount': 4, 'filters': ['cpus==8',
|
||||
'storage>=2T']},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class FuelEngineTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FuelEngineTestCase, self).setUp()
|
||||
self.deployment = fakes.FakeDeployment({'config': SAMPLE_CONFIG})
|
||||
|
||||
def test_construct(self):
|
||||
fuel.FuelEngine(self.deployment)
|
||||
|
||||
def test_validate_no_computes(self):
|
||||
config = SAMPLE_CONFIG.copy()
|
||||
config['nodes'].pop('cinder+compute')
|
||||
deployment = {'config': config}
|
||||
self.assertRaises(exceptions.ValidationError,
|
||||
fuel.FuelEngine, deployment)
|
||||
|
||||
def test__get_nodes(self):
|
||||
engine = fuel.FuelEngine(self.deployment)
|
||||
engine.nodes = mock.MagicMock()
|
||||
engine.nodes.pop.side_effect = [1, 2, 3, 4]
|
||||
nodes = engine._get_nodes('cinder+compute')
|
||||
self.assertEqual([1, 2, 3, 4], nodes)
|
||||
expected_calls = [mock.call(['cpus==8', 'storage>=2T'])] * 4
|
||||
self.assertEqual(expected_calls, engine.nodes.pop.mock_calls)
|
||||
|
||||
def test__get_nodes_no_nodes(self):
|
||||
engine = fuel.FuelEngine(self.deployment)
|
||||
engine.nodes = mock.MagicMock()
|
||||
engine.nodes.pop.return_value = None
|
||||
self.assertRaises(exceptions.NoNodesFound,
|
||||
engine._get_nodes, 'controller')
|
||||
|
||||
def test__get_nodes_empty(self):
|
||||
engine = fuel.FuelEngine(self.deployment)
|
||||
self.assertEqual([], engine._get_nodes('nonexistent'))
|
||||
|
||||
def test__get_release_id(self):
|
||||
engine = fuel.FuelEngine(self.deployment)
|
||||
engine.client = mock.Mock()
|
||||
fake_releases = [{'name': 'fake', 'id': 1},
|
||||
{'name': 'Havana on Ubuntu 12.04', 'id': 42}]
|
||||
engine.client.get_releases = mock.Mock(return_value=fake_releases)
|
||||
self.assertEqual(42, engine._get_release_id())
|
||||
|
||||
@mock.patch('rally.deploy.fuel.fuelclient.FuelClient')
|
||||
@mock.patch('rally.deploy.fuel.fuelclient.FuelCluster')
|
||||
def test_deploy(self, m_cluster, m_client):
|
||||
attributes = {'editable': {'access': {'user': {'value': 'user'},
|
||||
'password': {'value': 'pw'},
|
||||
'tenant': {'value': 'tn'}}}}
|
||||
client = mock.Mock()
|
||||
cluster = mock.Mock()
|
||||
cluster.cluster = {'id': 42}
|
||||
cluster.get_endpoint_ip = mock.Mock(return_value='2.3.4.5')
|
||||
cluster.get_attributes = mock.Mock(return_value=attributes)
|
||||
m_client.return_value = client
|
||||
m_cluster.return_value = cluster
|
||||
self.deployment.add_resource = mock.Mock()
|
||||
|
||||
engine = fuel.FuelEngine(self.deployment)
|
||||
|
||||
engine._get_nodes = mock.Mock(side_effect=[1, 2, 3, 4])
|
||||
engine._get_release_id = mock.Mock()
|
||||
|
||||
endpoint = engine.deploy()
|
||||
self.assertEqual(1, len(endpoint))
|
||||
endpoint = endpoint[0]
|
||||
|
||||
self.assertEqual('user', endpoint.username)
|
||||
self.assertEqual('pw', endpoint.password)
|
||||
self.assertEqual('tn', endpoint.tenant_name)
|
||||
self.assertEqual('http://2.3.4.5:5000/v2.0/', endpoint.auth_url)
|
||||
self.assertEqual(consts.EndpointPermission.ADMIN, endpoint.permission)
|
||||
|
||||
expected_cluster_calls = [
|
||||
mock.call.set_nodes(1, ['controller']),
|
||||
mock.call.set_nodes(2, ['compute']),
|
||||
mock.call.set_nodes(3, ['cinder']),
|
||||
mock.call.set_nodes(4, ['compute', 'cinder']),
|
||||
mock.call.configure_network({'public': {'cidr': '10.1.1.0/24'}}),
|
||||
mock.call.deploy(),
|
||||
mock.call.get_endpoint_ip(),
|
||||
mock.call.get_attributes()
|
||||
]
|
||||
self.assertEqual(expected_cluster_calls, cluster.mock_calls)
|
||||
self.assertEqual([mock.call.get_nodes()], client.mock_calls)
|
||||
|
||||
@mock.patch('rally.deploy.fuel.fuelclient.FuelClient')
|
||||
@mock.patch('rally.deploy.engines.fuel.objects.Deployment')
|
||||
def test_cleanup(self, m_deployment, m_client):
|
||||
fake_resources = [{'id': 41, 'info': {'id': 42}}]
|
||||
self.deployment.get_resources = mock.Mock(return_value=fake_resources)
|
||||
|
||||
engine = fuel.FuelEngine(self.deployment)
|
||||
engine.client = mock.Mock()
|
||||
engine.cleanup()
|
||||
|
||||
engine.client.delete_cluster.assert_called_once_with(42)
|
||||
m_deployment.delete_resource.assert_called_once_with(41)
|
Loading…
x
Reference in New Issue
Block a user