Allow k8s cluster without LBaaS

This patch adds an environment file and a couple of template resources
to allow the LBaaS resources to be conditionally enabled/disabled.

Change-Id: I40ef0839dca84f398efb02022fa7c1de821fb1a3
Partially-Implements: blueprint decouple-lbaas
Partially-Implements: blueprint bay-with-no-floating-ips
This commit is contained in:
Drago Rosson 2016-06-24 12:25:39 -05:00
parent cb99c64614
commit 2ea72d739d
12 changed files with 174 additions and 18 deletions

View File

@ -164,6 +164,12 @@ def validate_os_resources(context, baymodel):
validate_method(baymodel[attr]) validate_method(baymodel[attr])
def validate_master_count(bay, baymodel):
if bay['master_count'] > 1 and not baymodel['master_lb_enabled']:
raise exception.InvalidParameterValue(_(
"master_count must be 1 when master_lb_enabled is False"))
# Dictionary that maintains a list of validation functions # Dictionary that maintains a list of validation functions
validators = {'image_id': validate_image, validators = {'image_id': validate_image,
'flavor_id': validate_flavor, 'flavor_id': validate_flavor,

View File

@ -329,6 +329,7 @@ class BaysController(rest.RestController):
action='bay:create') action='bay:create')
baymodel = objects.BayModel.get_by_uuid(context, bay.baymodel_id) baymodel = objects.BayModel.get_by_uuid(context, bay.baymodel_id)
attr_validator.validate_os_resources(context, baymodel.as_dict()) attr_validator.validate_os_resources(context, baymodel.as_dict())
attr_validator.validate_master_count(bay.as_dict(), baymodel.as_dict())
bay_dict = bay.as_dict() bay_dict = bay.as_dict()
bay_dict['project_id'] = context.project_id bay_dict['project_id'] = context.project_id
bay_dict['user_id'] = context.user_id bay_dict['user_id'] = context.user_id

View File

@ -161,6 +161,12 @@ class K8sTemplateDefinition(template_def.BaseTemplateDefinition):
extra_params=extra_params, extra_params=extra_params,
**kwargs) **kwargs)
def get_env_files(self, baymodel):
if baymodel.master_lb_enabled:
return ['environments/with_master_lb.yaml']
else:
return ['environments/no_master_lb.yaml']
class AtomicK8sTemplateDefinition(K8sTemplateDefinition): class AtomicK8sTemplateDefinition(K8sTemplateDefinition):
"""Kubernetes template for a Fedora Atomic VM.""" """Kubernetes template for a Fedora Atomic VM."""

View File

@ -0,0 +1,12 @@
# Environment file to disable LBaaS in a Kubernetes cluster by mapping
# LBaaS-related resource types to OS::Heat::None
resource_registry:
"Magnum::ApiGatewaySwitcher": ../fragments/api_gateway_switcher_master.yaml
# kubecluster.yaml
"Magnum::Optional::Neutron::Pool": "OS::Heat::None"
"Magnum::Optional::Neutron::Pool::FloatingIP": "OS::Heat::None"
"Magnum::Optional::Neutron::Pool::HealthMonitor": "OS::Heat::None"
# kubemaster.yaml
"Magnum::Optional::Neutron::PoolMember": "OS::Heat::None"

View File

@ -0,0 +1,12 @@
# Environment file to enable LBaaS in a Kubernetes cluster by mapping
# LBaaS-related resource types to the real LBaaS resource types.
resource_registry:
"Magnum::ApiGatewaySwitcher": ../fragments/api_gateway_switcher_pool.yaml
# kubecluster.yaml
"Magnum::Optional::Neutron::Pool": "OS::Neutron::Pool"
"Magnum::Optional::Neutron::Pool::FloatingIP": "OS::Neutron::FloatingIP"
"Magnum::Optional::Neutron::Pool::HealthMonitor": "OS::Neutron::HealthMonitor"
# kubemaster.yaml
"Magnum::Optional::Neutron::PoolMember": "OS::Neutron::PoolMember"

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: >
This is a template resource that accepts public and private IPs from both
a Neutron LBaaS Pool and a master node. It connects the master inputs
to its outputs, essentially acting as one state of a multiplexer.
parameters:
pool_public_ip:
type: string
default: ""
pool_private_ip:
type: string
default: ""
master_public_ip:
type: string
default: ""
master_private_ip:
type: string
default: ""
outputs:
public_ip:
value: {get_param: master_public_ip}
private_ip:
value: {get_param: master_private_ip}

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: >
This is a template resource that accepts public and private IPs from both
a Neutron LBaaS Pool and a master node. It connects the pool inputs
to its outputs, essentially acting as one state of a multiplexer.
parameters:
pool_public_ip:
type: string
default: ""
pool_private_ip:
type: string
default: ""
master_public_ip:
type: string
default: ""
master_private_ip:
type: string
default: ""
outputs:
public_ip:
value: {get_param: pool_public_ip}
private_ip:
value: {get_param: pool_private_ip}

View File

@ -350,7 +350,7 @@ resources:
# #
api_monitor: api_monitor:
type: OS::Neutron::HealthMonitor type: Magnum::Optional::Neutron::Pool::HealthMonitor
properties: properties:
type: TCP type: TCP
delay: 5 delay: 5
@ -358,7 +358,7 @@ resources:
timeout: 5 timeout: 5
api_pool: api_pool:
type: OS::Neutron::Pool type: Magnum::Optional::Neutron::Pool
properties: properties:
protocol: {get_param: loadbalancing_protocol} protocol: {get_param: loadbalancing_protocol}
monitors: [{get_resource: api_monitor}] monitors: [{get_resource: api_monitor}]
@ -368,7 +368,7 @@ resources:
protocol_port: {get_param: kubernetes_port} protocol_port: {get_param: kubernetes_port}
api_pool_floating: api_pool_floating:
type: OS::Neutron::FloatingIP type: Magnum::Optional::Neutron::Pool::FloatingIP
depends_on: depends_on:
- extrouter_inside - extrouter_inside
properties: properties:
@ -376,7 +376,7 @@ resources:
port_id: {get_attr: [api_pool, vip, port_id]} port_id: {get_attr: [api_pool, vip, port_id]}
etcd_monitor: etcd_monitor:
type: OS::Neutron::HealthMonitor type: Magnum::Optional::Neutron::Pool::HealthMonitor
properties: properties:
type: TCP type: TCP
delay: 5 delay: 5
@ -384,7 +384,7 @@ resources:
timeout: 5 timeout: 5
etcd_pool: etcd_pool:
type: OS::Neutron::Pool type: Magnum::Optional::Neutron::Pool
properties: properties:
protocol: HTTP protocol: HTTP
monitors: [{get_resource: etcd_monitor}] monitors: [{get_resource: etcd_monitor}]
@ -393,6 +393,26 @@ resources:
vip: vip:
protocol_port: 2379 protocol_port: 2379
######################################################################
#
# resources that expose the IPs of either the kube master or a given
# LBaaS pool depending on whether LBaaS is enabled for the bay.
#
api_address_switch:
type: Magnum::ApiGatewaySwitcher
properties:
pool_public_ip: {get_attr: [api_pool_floating, floating_ip_address]}
pool_private_ip: {get_attr: [api_pool, vip, address]}
master_public_ip: {get_attr: [kube_masters, resource.0.kube_master_external_ip]}
master_private_ip: {get_attr: [kube_masters, resource.0.kube_master_ip]}
etcd_address_switch:
type: Magnum::ApiGatewaySwitcher
properties:
pool_private_ip: {get_attr: [etcd_pool, vip, address]}
master_private_ip: {get_attr: [kube_masters, resource.0.kube_master_ip]}
###################################################################### ######################################################################
# #
# kubernetes masters. This is a resource group that will create # kubernetes masters. This is a resource group that will create
@ -470,8 +490,8 @@ resources:
fixed_subnet: {get_resource: fixed_subnet} fixed_subnet: {get_resource: fixed_subnet}
network_driver: {get_param: network_driver} network_driver: {get_param: network_driver}
flannel_network_cidr: {get_param: flannel_network_cidr} flannel_network_cidr: {get_param: flannel_network_cidr}
kube_master_ip: {get_attr: [api_pool, vip, address]} kube_master_ip: {get_attr: [api_address_switch, private_ip]}
etcd_server_ip: {get_attr: [etcd_pool, vip, address]} etcd_server_ip: {get_attr: [etcd_address_switch, private_ip]}
external_network: {get_param: external_network} external_network: {get_param: external_network}
kube_allow_priv: {get_param: kube_allow_priv} kube_allow_priv: {get_param: kube_allow_priv}
docker_volume_size: {get_param: docker_volume_size} docker_volume_size: {get_param: docker_volume_size}
@ -513,7 +533,7 @@ outputs:
str_replace: str_replace:
template: api_ip_address template: api_ip_address
params: params:
api_ip_address: {get_attr: [api_pool_floating, floating_ip_address]} api_ip_address: {get_attr: [api_address_switch, public_ip]}
description: > description: >
This is the API endpoint of the Kubernetes server. Use this to access This is the API endpoint of the Kubernetes server. Use this to access
the Kubernetes API from outside the cluster. the Kubernetes API from outside the cluster.

View File

@ -89,10 +89,12 @@ parameters:
api_public_address: api_public_address:
type: string type: string
description: Public IP address of the Kubernetes master server. description: Public IP address of the Kubernetes master server.
default: ""
api_private_address: api_private_address:
type: string type: string
description: Private IP address of the Kubernetes master server. description: Private IP address of the Kubernetes master server.
default: ""
fixed_network: fixed_network:
type: string type: string
@ -193,6 +195,20 @@ resources:
handle: {get_resource: master_wait_handle} handle: {get_resource: master_wait_handle}
timeout: {get_param: wait_condition_timeout} timeout: {get_param: wait_condition_timeout}
######################################################################
#
# resource that exposes the IPs of either the kube master or the API
# LBaaS pool depending on whether LBaaS is enabled for the bay.
#
api_address_switch:
type: Magnum::ApiGatewaySwitcher
properties:
pool_public_ip: {get_param: api_public_address}
pool_private_ip: {get_param: api_private_address}
master_public_ip: {get_attr: [kube_master_floating, floating_ip_address]}
master_private_ip: {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]}
###################################################################### ######################################################################
# #
# software configs. these are components that are combined into # software configs. these are components that are combined into
@ -207,8 +223,8 @@ resources:
str_replace: str_replace:
template: {get_file: fragments/write-heat-params-master.yaml} template: {get_file: fragments/write-heat-params-master.yaml}
params: params:
"$KUBE_API_PUBLIC_ADDRESS": {get_param: api_public_address} "$KUBE_API_PUBLIC_ADDRESS": {get_attr: [api_address_switch, public_ip]}
"$KUBE_API_PRIVATE_ADDRESS": {get_param: api_private_address} "$KUBE_API_PRIVATE_ADDRESS": {get_attr: [api_address_switch, private_ip]}
"$KUBE_API_PORT": {get_param: kubernetes_port} "$KUBE_API_PORT": {get_param: kubernetes_port}
"$KUBE_NODE_IP": {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]} "$KUBE_NODE_IP": {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]}
"$KUBE_ALLOW_PRIV": {get_param: kube_allow_priv} "$KUBE_ALLOW_PRIV": {get_param: kube_allow_priv}
@ -410,14 +426,14 @@ resources:
port_id: {get_resource: kube_master_eth0} port_id: {get_resource: kube_master_eth0}
api_pool_member: api_pool_member:
type: OS::Neutron::PoolMember type: Magnum::Optional::Neutron::PoolMember
properties: properties:
pool_id: {get_param: api_pool_id} pool_id: {get_param: api_pool_id}
address: {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]} address: {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]}
protocol_port: {get_param: kubernetes_port} protocol_port: {get_param: kubernetes_port}
etcd_pool_member: etcd_pool_member:
type: OS::Neutron::PoolMember type: Magnum::Optional::Neutron::PoolMember
properties: properties:
pool_id: {get_param: etcd_pool_id} pool_id: {get_param: etcd_pool_id}
address: {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]} address: {get_attr: [kube_master_eth0, fixed_ips, 0, ip_address]}

View File

@ -640,6 +640,24 @@ class TestPost(api_base.FunctionalTest):
self.assertTrue(self.mock_valid_os_res.called) self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(400, response.status_int) self.assertEqual(400, response.status_int)
def test_create_bay_with_no_lb_one_node(self):
baymodel = obj_utils.create_test_baymodel(
self.context, name='foo', uuid='foo', master_lb_enabled=False)
bdict = apiutils.bay_post_data(baymodel_id=baymodel.name,
master_count=1)
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
def test_create_bay_with_no_lb_multi_node(self):
baymodel = obj_utils.create_test_baymodel(
self.context, name='foo', uuid='foo', master_lb_enabled=False)
bdict = apiutils.bay_post_data(baymodel_id=baymodel.name,
master_count=3)
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
class TestDelete(api_base.FunctionalTest): class TestDelete(api_base.FunctionalTest):

View File

@ -48,6 +48,7 @@ class TestBayConductorWithK8s(base.TestCase):
'server_type': 'vm', 'server_type': 'vm',
'registry_enabled': False, 'registry_enabled': False,
'insecure_registry': '10.0.0.1:5000', 'insecure_registry': '10.0.0.1:5000',
'master_lb_enabled': False,
} }
self.bay_dict = { self.bay_dict = {
'uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52', 'uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
@ -176,7 +177,7 @@ class TestBayConductorWithK8s(base.TestCase):
expected.pop(mapping[missing_attr], None) expected.pop(mapping[missing_attr], None)
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
self.assertEqual([], env_files) self.assertEqual(['environments/no_master_lb.yaml'], env_files)
@patch('requests.get') @patch('requests.get')
@patch('magnum.objects.BayModel.get_by_uuid') @patch('magnum.objects.BayModel.get_by_uuid')
@ -242,7 +243,7 @@ class TestBayConductorWithK8s(base.TestCase):
} }
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
self.assertEqual([], env_files) self.assertEqual(['environments/no_master_lb.yaml'], env_files)
@patch('requests.get') @patch('requests.get')
@patch('magnum.objects.BayModel.get_by_uuid') @patch('magnum.objects.BayModel.get_by_uuid')
@ -296,7 +297,7 @@ class TestBayConductorWithK8s(base.TestCase):
'insecure_registry_url': '10.0.0.1:5000', 'insecure_registry_url': '10.0.0.1:5000',
} }
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
self.assertEqual([], env_files) self.assertEqual(['environments/no_master_lb.yaml'], env_files)
@patch('requests.get') @patch('requests.get')
@patch('magnum.objects.BayModel.get_by_uuid') @patch('magnum.objects.BayModel.get_by_uuid')
@ -348,7 +349,7 @@ class TestBayConductorWithK8s(base.TestCase):
'insecure_registry_url': '10.0.0.1:5000', 'insecure_registry_url': '10.0.0.1:5000',
} }
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
self.assertEqual([], env_files) self.assertEqual(['environments/no_master_lb.yaml'], env_files)
@patch('requests.get') @patch('requests.get')
@patch('magnum.objects.BayModel.get_by_uuid') @patch('magnum.objects.BayModel.get_by_uuid')
@ -508,7 +509,7 @@ class TestBayConductorWithK8s(base.TestCase):
'insecure_registry_url': '10.0.0.1:5000', 'insecure_registry_url': '10.0.0.1:5000',
} }
self.assertEqual(expected, definition) self.assertEqual(expected, definition)
self.assertEqual([], env_files) self.assertEqual(['environments/no_master_lb.yaml'], env_files)
reqget.assert_called_once_with('http://etcd/test?size=1') reqget.assert_called_once_with('http://etcd/test?size=1')
@patch('magnum.common.short_id.generate_id') @patch('magnum.common.short_id.generate_id')

View File

@ -53,7 +53,7 @@ def get_test_baymodel(**kw):
'public': kw.get('public', False), 'public': kw.get('public', False),
'server_type': kw.get('server_type', 'vm'), 'server_type': kw.get('server_type', 'vm'),
'insecure_registry': kw.get('insecure_registry', '10.0.0.1:5000'), 'insecure_registry': kw.get('insecure_registry', '10.0.0.1:5000'),
'master_lb_enabled': kw.get('master_lb_enabled', False), 'master_lb_enabled': kw.get('master_lb_enabled', True),
} }