Add support for Octavia resources in Heat

Octavia is already an official LBaaS solution for Openstack
(https://governance.openstack.org/tc/reference/projects/octavia.html) and
will deprecate the neutron-lbaas extension starting from Queens release.

For deployment support Octavia service for load balancing functionlity,
Octavia related resources instead of LBaaS should be used in Heat template.

Tested in my DevStack environment.

Change-Id: Icc45e0a126c648fbcba4ebcd1bb258d60957f2d6
Closes-Bug: #1748577
This commit is contained in:
Lingxian Kong 2018-02-12 11:35:14 +13:00
parent 21c87f35a0
commit 3c8edd4d88
6 changed files with 220 additions and 3 deletions

View File

@ -367,6 +367,10 @@ class RegionsListFailed(MagnumException):
message = _("Failed to list regions.")
class ServicesListFailed(MagnumException):
message = _("Failed to list services.")
class TrusteeOrTrustToClusterFailed(MagnumException):
message = _("Failed to create trustee or trust for Cluster: "
"%(cluster_uuid)s")

View File

@ -291,3 +291,32 @@ class KeystoneClientV3(object):
'region_name_list': '/'.join(
region_list + ['unspecified'])})
return region_name
def is_octavia_enabled():
"""Check if Octavia service is deployed in the cloud.
Octavia is already an official LBaaS solution for Openstack
(https://governance.openstack.org/tc/reference/projects/octavia.html) and
will deprecate the neutron-lbaas extension starting from Queens release.
We use Octavia instead of Neutron LBaaS API for load balancing
functionality for k8s cluster if Octavia service is deployed and enabled
in the cloud.
"""
# Put the import here to avoid circular importing.
from magnum.common import context
admin_context = context.make_admin_context()
keystone = KeystoneClientV3(admin_context)
try:
octavia_svc = keystone.client.services.list(type='load-balancer')
except Exception:
LOG.exception('Failed to list services')
raise exception.ServicesListFailed()
# Always assume there is only one load balancing service configured.
if octavia_svc and octavia_svc[0].enabled:
return True
return False

View File

@ -0,0 +1,14 @@
# Environment file to enable LBaaS in a cluster by mapping
# LBaaS-related resource types to the real Octavia resource types.
resource_registry:
"Magnum::ApiGatewaySwitcher": ../fragments/api_gateway_switcher_pool.yaml
# Cluster template
"Magnum::Optional::Neutron::LBaaS::LoadBalancer": "OS::Octavia::LoadBalancer"
"Magnum::Optional::Neutron::LBaaS::Listener": "OS::Octavia::Listener"
"Magnum::Optional::Neutron::LBaaS::Pool": "OS::Octavia::Pool"
"Magnum::Optional::Neutron::LBaaS::HealthMonitor": "OS::Octavia::HealthMonitor"
"Magnum::Optional::Neutron::LBaaS::FloatingIP": "OS::Neutron::FloatingIP"
# Master node template
"Magnum::Optional::Neutron::LBaaS::PoolMember": "OS::Octavia::PoolMember"

View File

@ -20,6 +20,7 @@ import six
from magnum.common import clients
from magnum.common import exception
from magnum.common import keystone
from magnum.common import utils
import magnum.conf
@ -328,7 +329,10 @@ class BaseTemplateDefinition(TemplateDefinition):
def add_lb_env_file(env_files, cluster_template):
if cluster_template.master_lb_enabled:
env_files.append(COMMON_ENV_PATH + 'with_master_lb.yaml')
if keystone.is_octavia_enabled():
env_files.append(COMMON_ENV_PATH + 'with_master_lb_octavia.yaml')
else:
env_files.append(COMMON_ENV_PATH + 'with_master_lb.yaml')
else:
env_files.append(COMMON_ENV_PATH + 'no_master_lb.yaml')

View File

@ -207,8 +207,10 @@ class TestClusterConductorWithMesos(base.TestCase):
@patch('magnum.objects.ClusterTemplate.get_by_uuid')
@patch('magnum.drivers.common.driver.Driver.get_driver')
def test_extract_template_definition_with_lb(
@patch('magnum.common.keystone.KeystoneClientV3')
def test_extract_template_definition_with_lb_neutron(
self,
mock_kc,
mock_driver,
mock_objects_cluster_template_get_by_uuid):
self.cluster_template_dict['master_lb_enabled'] = True
@ -219,6 +221,8 @@ class TestClusterConductorWithMesos(base.TestCase):
cluster = objects.Cluster(self.context, **self.cluster_dict)
mock_driver.return_value = mesos_dr.Driver()
mock_kc.return_value.client.services.list.return_value = []
(template_path,
definition,
env_files) = mock_driver()._extract_template_definition(self.context,
@ -266,8 +270,78 @@ class TestClusterConductorWithMesos(base.TestCase):
@patch('magnum.objects.ClusterTemplate.get_by_uuid')
@patch('magnum.drivers.common.driver.Driver.get_driver')
@patch('magnum.common.keystone.KeystoneClientV3')
def test_extract_template_definition_with_lb_octavia(
self,
mock_kc,
mock_driver,
mock_objects_cluster_template_get_by_uuid):
self.cluster_template_dict['master_lb_enabled'] = True
cluster_template = objects.ClusterTemplate(
self.context, **self.cluster_template_dict)
mock_objects_cluster_template_get_by_uuid.return_value = \
cluster_template
cluster = objects.Cluster(self.context, **self.cluster_dict)
mock_driver.return_value = mesos_dr.Driver()
class Service(object):
def __init__(self):
self.enabled = True
mock_kc.return_value.client.services.list.return_value = [Service()]
(template_path,
definition,
env_files) = mock_driver()._extract_template_definition(self.context,
cluster)
expected = {
'ssh_key_name': 'keypair_id',
'external_network': 'external_network_id',
'fixed_network': 'fixed_network',
'fixed_subnet': 'fixed_subnet',
'dns_nameserver': 'dns_nameserver',
'server_image': 'image_id',
'master_flavor': 'master_flavor_id',
'slave_flavor': 'flavor_id',
'number_of_slaves': 1,
'number_of_masters': 1,
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
'cluster_name': 'cluster1',
'trustee_domain_id': self.mock_keystone.trustee_domain_id,
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': '',
'volume_driver': 'volume_driver',
'auth_url': 'http://192.168.10.10:5000/v3',
'region_name': self.mock_osc.cinder_region_name.return_value,
'username': 'mesos_user',
'tenant_name': 'admin',
'domain_name': 'domainname',
'rexray_preempt': 'False',
'mesos_slave_executor_env_variables': '{}',
'mesos_slave_isolation': 'docker/runtime,filesystem/linux',
'mesos_slave_work_dir': '/tmp/mesos/slave',
'mesos_slave_image_providers': 'docker',
'verify_ca': True,
'openstack_ca': '',
}
self.assertEqual(expected, definition)
self.assertEqual(
['../../common/templates/environments/no_private_network.yaml',
'../../common/templates/environments/with_master_lb_octavia.yaml'
],
env_files)
@patch('magnum.objects.ClusterTemplate.get_by_uuid')
@patch('magnum.drivers.common.driver.Driver.get_driver')
@patch('magnum.common.keystone.KeystoneClientV3')
def test_extract_template_definition_multi_master(
self,
mock_kc,
mock_driver,
mock_objects_cluster_template_get_by_uuid):
self.cluster_template_dict['master_lb_enabled'] = True
@ -279,6 +353,8 @@ class TestClusterConductorWithMesos(base.TestCase):
cluster = objects.Cluster(self.context, **self.cluster_dict)
mock_driver.return_value = mesos_dr.Driver()
mock_kc.return_value.client.services.list.return_value = []
(template_path,
definition,
env_files) = mock_driver()._extract_template_definition(self.context,

View File

@ -330,8 +330,10 @@ class TestClusterConductorWithSwarm(base.TestCase):
@patch('requests.get')
@patch('magnum.objects.ClusterTemplate.get_by_uuid')
@patch('magnum.drivers.common.driver.Driver.get_driver')
def test_extract_template_definition_with_lb(
@patch('magnum.common.keystone.KeystoneClientV3')
def test_extract_template_definition_with_lb_neutron(
self,
mock_kc,
mock_driver,
mock_objects_cluster_template_get_by_uuid,
mock_get):
@ -348,6 +350,8 @@ class TestClusterConductorWithSwarm(base.TestCase):
mock_driver.return_value = swarm_dr.Driver()
cluster = objects.Cluster(self.context, **self.cluster_dict)
mock_kc.return_value.client.services.list.return_value = []
(template_path,
definition,
env_files) = mock_driver()._extract_template_definition(self.context,
@ -403,8 +407,92 @@ class TestClusterConductorWithSwarm(base.TestCase):
@patch('requests.get')
@patch('magnum.objects.ClusterTemplate.get_by_uuid')
@patch('magnum.drivers.common.driver.Driver.get_driver')
@patch('magnum.common.keystone.KeystoneClientV3')
def test_extract_template_definition_with_lb_octavia(
self,
mock_kc,
mock_driver,
mock_objects_cluster_template_get_by_uuid,
mock_get):
self.cluster_template_dict['master_lb_enabled'] = True
cluster_template = objects.ClusterTemplate(
self.context, **self.cluster_template_dict)
mock_objects_cluster_template_get_by_uuid.return_value = \
cluster_template
expected_result = str('{"action":"get","node":{"key":"test","value":'
'"1","modifiedIndex":10,"createdIndex":10}}')
mock_resp = mock.MagicMock()
mock_resp.text = expected_result
mock_get.return_value = mock_resp
mock_driver.return_value = swarm_dr.Driver()
cluster = objects.Cluster(self.context, **self.cluster_dict)
class Service(object):
def __init__(self):
self.enabled = True
mock_kc.return_value.client.services.list.return_value = [Service()]
(template_path,
definition,
env_files) = mock_driver()._extract_template_definition(self.context,
cluster)
expected = {
'ssh_key_name': 'keypair_id',
'external_network': 'external_network_id',
'fixed_network': 'fixed_network',
'fixed_subnet': 'fixed_subnet',
'dns_nameserver': 'dns_nameserver',
'server_image': 'image_id',
'master_flavor': 'master_flavor_id',
'node_flavor': 'flavor_id',
'number_of_masters': 1,
'number_of_nodes': 1,
'docker_volume_size': 20,
'docker_storage_driver': 'devicemapper',
'discovery_url': 'https://discovery.test.io/123456789',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
'magnum_url': self.mock_osc.magnum_url.return_value,
'tls_disabled': False,
'registry_enabled': False,
'network_driver': 'network_driver',
'flannel_network_cidr': '10.101.0.0/16',
'flannel_network_subnetlen': '26',
'flannel_backend': 'vxlan',
'trustee_domain_id': self.mock_keystone.trustee_domain_id,
'trustee_username': 'fake_trustee',
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'auth_url': 'http://192.168.10.10:5000/v3',
'swarm_version': 'fake-version',
'swarm_strategy': u'spread',
'volume_driver': 'rexray',
'rexray_preempt': 'False',
'docker_volume_type': 'lvmdriver-1',
'verify_ca': True,
'openstack_ca': '',
'nodes_affinity_policy': 'soft-anti-affinity'
}
self.assertEqual(expected, definition)
self.assertEqual(
['../../common/templates/environments/no_private_network.yaml',
'../../common/templates/environments/with_volume.yaml',
'../../common/templates/environments/with_master_lb_octavia.yaml'
],
env_files)
@patch('requests.get')
@patch('magnum.objects.ClusterTemplate.get_by_uuid')
@patch('magnum.drivers.common.driver.Driver.get_driver')
@patch('magnum.common.keystone.KeystoneClientV3')
def test_extract_template_definition_multi_master(
self,
mock_kc,
mock_driver,
mock_objects_cluster_template_get_by_uuid,
mock_get):
@ -422,6 +510,8 @@ class TestClusterConductorWithSwarm(base.TestCase):
mock_driver.return_value = swarm_dr.Driver()
cluster = objects.Cluster(self.context, **self.cluster_dict)
mock_kc.return_value.client.services.list.return_value = []
(template_path,
definition,
env_files) = mock_driver()._extract_template_definition(self.context,