Allow user-data on amphora creation
Currently, Amphora configuration data is being sent as personality files as considered by Nova and some providers have limitations and must use cloud-init user-data. This patch introduces a new config option to enable user-data. If enabled the files that were built, such as the amphora config and certificates for the agent, will be templated into a cloud-init user-data script that loads the files as expected. After this we need to restart the agent as cloud-init happens at a higher level than service scripts. This does increase the boot time. This is configurable so there is no impact if it's not needed. Change-Id: I60fa87722302eee9d3d1fd6ff1b5b5b697a2406e Closes-Bug: #1541231
This commit is contained in:
parent
ca8c263a3d
commit
025ec0024b
|
@ -162,6 +162,7 @@
|
|||
#
|
||||
# Load balancer topology options are SINGLE, ACTIVE_STANDBY
|
||||
# loadbalancer_topology = SINGLE
|
||||
# user_data_config_drive = False
|
||||
|
||||
[task_flow]
|
||||
# engine = serial
|
||||
|
|
|
@ -240,7 +240,12 @@ controller_worker_opts = [
|
|||
choices=constants.SUPPORTED_LB_TOPOLOGIES,
|
||||
help=_('Load balancer topology configuration. '
|
||||
'SINGLE - One amphora per load balancer. '
|
||||
'ACTIVE_STANDBY - Two amphora per load balancer.'))
|
||||
'ACTIVE_STANDBY - Two amphora per load balancer.')),
|
||||
cfg.BoolOpt('user_data_config_drive', default=False,
|
||||
help=_('If True, build cloud-init user-data that is passed '
|
||||
'to the config drive on Amphora boot instead of '
|
||||
'personality files. If False, utilize personality '
|
||||
'files.'))
|
||||
]
|
||||
|
||||
task_flow_opts = [
|
||||
|
|
|
@ -210,8 +210,11 @@ VRRP_PROTOCOL_NUM = 112
|
|||
AUTH_HEADER_PROTOCOL_NUMBER = 51
|
||||
|
||||
|
||||
TEMPLATES = '/templates'
|
||||
AGENT_API_TEMPLATES = '/templates'
|
||||
|
||||
AGENT_CONF_TEMPLATE = 'amphora_agent_conf.template'
|
||||
USER_DATA_CONFIG_DRIVE_TEMPLATE = 'user_data_config_drive.template'
|
||||
|
||||
OPEN = 'OPEN'
|
||||
FULL = 'FULL'
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{# Copyright 2016 Rackspace
|
||||
#
|
||||
# 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.
|
||||
-#}
|
||||
#cloud-config
|
||||
# vim: syntax=yaml
|
||||
#
|
||||
# This configuration with take user-data dict and build a cloud-init
|
||||
# script utilizing the write_files module. The user-data dict should be a
|
||||
# Key Value pair where the Key is the path to store the file and the Value
|
||||
# is the data to store at that location
|
||||
#
|
||||
# Example:
|
||||
# {'/root/path/to/file.cfg': 'I'm a file, write things in me'}
|
||||
write_files:
|
||||
{%- for key, value in user_data.items() %}
|
||||
- path: {{ key }}
|
||||
content: |
|
||||
{{ value|indent(8) }}
|
||||
{%- endfor -%}
|
||||
|
||||
{# restart agent now that configurations are in place #}
|
||||
runcmd:
|
||||
- service amphora-agent restart
|
|
@ -0,0 +1,41 @@
|
|||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
|
||||
import jinja2
|
||||
|
||||
from octavia.common.config import cfg
|
||||
from octavia.common import constants
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_group('amphora_agent', 'octavia.common.config')
|
||||
CONF.import_group('haproxy_amphora', 'octavia.common.config')
|
||||
CONF.import_group('health_manager', 'octavia.common.config')
|
||||
|
||||
TEMPLATES_DIR = (os.path.dirname(os.path.realpath(__file__)) +
|
||||
constants.TEMPLATES + '/')
|
||||
|
||||
|
||||
class UserDataJinjaCfg(object):
|
||||
|
||||
def __init__(self):
|
||||
template_loader = jinja2.FileSystemLoader(searchpath=os.path.dirname(
|
||||
TEMPLATES_DIR))
|
||||
jinja_env = jinja2.Environment(loader=template_loader)
|
||||
self.agent_template = jinja_env.get_template(
|
||||
constants.USER_DATA_CONFIG_DRIVE_TEMPLATE)
|
||||
|
||||
def build_user_data_config(self, user_data):
|
||||
return self.agent_template.render(user_data=user_data)
|
|
@ -25,6 +25,7 @@ from taskflow.types import failure
|
|||
from octavia.amphorae.backends.agent import agent_jinja_cfg
|
||||
from octavia.common import constants
|
||||
from octavia.common import exceptions
|
||||
from octavia.common.jinja import user_data_jinja_cfg
|
||||
from octavia.i18n import _LE, _LW
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
@ -54,8 +55,10 @@ class ComputeCreate(BaseComputeTask):
|
|||
"""
|
||||
ports = ports or []
|
||||
config_drive_files = config_drive_files or {}
|
||||
user_data = None
|
||||
LOG.debug("Compute create execute for amphora with id %s", amphora_id)
|
||||
|
||||
user_data_config_drive = CONF.controller_worker.user_data_config_drive
|
||||
ssh_access = CONF.controller_worker.amp_ssh_access_allowed
|
||||
ssh_key = CONF.controller_worker.amp_ssh_key_name
|
||||
key_name = None if not ssh_access else ssh_key
|
||||
|
@ -64,6 +67,12 @@ class ComputeCreate(BaseComputeTask):
|
|||
agent_cfg = agent_jinja_cfg.AgentJinjaTemplater()
|
||||
config_drive_files['/etc/octavia/amphora-agent.conf'] = (
|
||||
agent_cfg.build_agent_config(amphora_id))
|
||||
if user_data_config_drive:
|
||||
udtemplater = user_data_jinja_cfg.UserDataJinjaCfg()
|
||||
user_data = udtemplater.build_user_data_config(
|
||||
config_drive_files)
|
||||
config_drive_files = None
|
||||
|
||||
compute_id = self.compute.build(
|
||||
name="amphora-" + amphora_id,
|
||||
amphora_flavor=CONF.controller_worker.amp_flavor_id,
|
||||
|
@ -72,7 +81,8 @@ class ComputeCreate(BaseComputeTask):
|
|||
sec_groups=CONF.controller_worker.amp_secgroup_list,
|
||||
network_ids=[CONF.controller_worker.amp_network],
|
||||
port_ids=[port.id for port in ports],
|
||||
config_drive_files=config_drive_files)
|
||||
config_drive_files=config_drive_files,
|
||||
user_data=user_data)
|
||||
|
||||
LOG.debug("Server created with id: %s for amphora id: %s",
|
||||
compute_id, amphora_id)
|
||||
|
@ -109,12 +119,13 @@ class CertComputeCreate(ComputeCreate):
|
|||
|
||||
# load client certificate
|
||||
with open(CONF.controller_worker.client_ca, 'r') as client_ca:
|
||||
config_drive_files = {
|
||||
# '/etc/octavia/octavia.conf'
|
||||
'/etc/octavia/certs/server.pem': server_pem,
|
||||
'/etc/octavia/certs/client_ca.pem': client_ca}
|
||||
return super(CertComputeCreate, self).execute(
|
||||
amphora_id, ports=ports, config_drive_files=config_drive_files)
|
||||
ca = client_ca.read()
|
||||
config_drive_files = {
|
||||
# '/etc/octavia/octavia.conf'
|
||||
'/etc/octavia/certs/server.pem': server_pem,
|
||||
'/etc/octavia/certs/client_ca.pem': ca}
|
||||
return super(CertComputeCreate, self).execute(
|
||||
amphora_id, ports=ports, config_drive_files=config_drive_files)
|
||||
|
||||
|
||||
class DeleteAmphoraeOnLoadBalancer(BaseComputeTask):
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
# Copyright 2016 Rackspace
|
||||
#
|
||||
# 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 octavia.common.jinja import user_data_jinja_cfg
|
||||
import octavia.tests.unit.base as base
|
||||
|
||||
TEST_CONFIG = ('[DEFAULT]\n'
|
||||
'debug = False\n'
|
||||
'[haproxy_amphora]\n'
|
||||
'base_cert_dir = /var/lib/octavia/certs\n')
|
||||
EXPECTED_TEST_CONFIG = (' [DEFAULT]\n'
|
||||
' debug = False\n'
|
||||
' [haproxy_amphora]\n'
|
||||
' base_cert_dir = /var/lib/octavia/certs\n')
|
||||
BASE_CFG = ('#cloud-config\n'
|
||||
'# vim: syntax=yaml\n'
|
||||
'#\n'
|
||||
'# This configuration with take user-data dict and '
|
||||
'build a cloud-init\n'
|
||||
'# script utilizing the write_files module. '
|
||||
'The user-data dict should be a\n'
|
||||
'# Key Value pair where the Key is the path to store the '
|
||||
'file and the Value\n'
|
||||
'# is the data to store at that location\n'
|
||||
'#\n'
|
||||
'# Example:\n'
|
||||
'# {\'/root/path/to/file.cfg\': \'I\'m a file, '
|
||||
'write things in me\'}\n'
|
||||
'write_files:\n')
|
||||
RUN_CMD = ('runcmd:\n'
|
||||
'- service amphora-agent restart')
|
||||
|
||||
|
||||
class TestUserDataJinjaCfg(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestUserDataJinjaCfg, self).setUp()
|
||||
|
||||
def test_build_user_data_config(self):
|
||||
udc = user_data_jinja_cfg.UserDataJinjaCfg()
|
||||
expected_config = (BASE_CFG +
|
||||
'- path: /test/config/path\n'
|
||||
' content: |\n' + EXPECTED_TEST_CONFIG + RUN_CMD)
|
||||
ud_cfg = udc.build_user_data_config({'/test/config/path': TEST_CONFIG})
|
||||
self.assertEqual(expected_config, ud_cfg)
|
|
@ -97,7 +97,8 @@ class TestComputeTasks(base.TestCase):
|
|||
network_ids=[AMP_NET],
|
||||
port_ids=[PORT_ID],
|
||||
config_drive_files={'/etc/octavia/'
|
||||
'amphora-agent.conf': 'test_conf'})
|
||||
'amphora-agent.conf': 'test_conf'},
|
||||
user_data=None)
|
||||
|
||||
# Make sure it returns the expected compute_id
|
||||
assert(compute_id == COMPUTE_ID)
|
||||
|
@ -124,6 +125,65 @@ class TestComputeTasks(base.TestCase):
|
|||
|
||||
createcompute.revert(COMPUTE_ID, _amphora_mock.id)
|
||||
|
||||
@mock.patch('jinja2.Environment.get_template')
|
||||
@mock.patch('octavia.amphorae.backends.agent.'
|
||||
'agent_jinja_cfg.AgentJinjaTemplater.'
|
||||
'build_agent_config', return_value='test_conf')
|
||||
@mock.patch('octavia.common.jinja.'
|
||||
'user_data_jinja_cfg.UserDataJinjaCfg.'
|
||||
'build_user_data_config', return_value='test_conf')
|
||||
@mock.patch('stevedore.driver.DriverManager.driver')
|
||||
def test_compute_create_user_data(self, mock_driver,
|
||||
mock_ud_conf, mock_conf, mock_jinja):
|
||||
|
||||
conf = oslo_fixture.Config(cfg.CONF)
|
||||
conf.config(group="controller_worker", user_data_config_drive=True)
|
||||
mock_ud_conf.return_value = 'test_ud_conf'
|
||||
createcompute = compute_tasks.ComputeCreate()
|
||||
|
||||
mock_driver.build.return_value = COMPUTE_ID
|
||||
# Test execute()
|
||||
compute_id = createcompute.execute(_amphora_mock.id, ports=[_port])
|
||||
|
||||
# Validate that the build method was called properly
|
||||
mock_driver.build.assert_called_once_with(
|
||||
name="amphora-" + _amphora_mock.id,
|
||||
amphora_flavor=AMP_FLAVOR_ID,
|
||||
image_id=AMP_IMAGE_ID,
|
||||
key_name=AMP_SSH_KEY_NAME,
|
||||
sec_groups=AMP_SEC_GROUPS,
|
||||
network_ids=[AMP_NET],
|
||||
port_ids=[PORT_ID],
|
||||
config_drive_files=None,
|
||||
user_data='test_ud_conf')
|
||||
|
||||
# Make sure it returns the expected compute_id
|
||||
assert(compute_id == COMPUTE_ID)
|
||||
|
||||
# Test that a build exception is raised
|
||||
createcompute = compute_tasks.ComputeCreate()
|
||||
|
||||
self.assertRaises(TypeError,
|
||||
createcompute.execute,
|
||||
_amphora_mock, config_drive_files='test_cert')
|
||||
|
||||
# Test revert()
|
||||
|
||||
_amphora_mock.compute_id = COMPUTE_ID
|
||||
|
||||
createcompute = compute_tasks.ComputeCreate()
|
||||
createcompute.revert(compute_id, _amphora_mock.id)
|
||||
|
||||
# Validate that the delete method was called properly
|
||||
mock_driver.delete.assert_called_once_with(
|
||||
COMPUTE_ID)
|
||||
|
||||
# Test that a delete exception is not raised
|
||||
|
||||
createcompute.revert(COMPUTE_ID, _amphora_mock.id)
|
||||
conf = oslo_fixture.Config(cfg.CONF)
|
||||
conf.config(group="controller_worker", user_data_config_drive=False)
|
||||
|
||||
@mock.patch('jinja2.Environment.get_template')
|
||||
@mock.patch('octavia.amphorae.backends.agent.'
|
||||
'agent_jinja_cfg.AgentJinjaTemplater.'
|
||||
|
@ -151,7 +211,8 @@ class TestComputeTasks(base.TestCase):
|
|||
network_ids=[AMP_NET],
|
||||
port_ids=[PORT_ID],
|
||||
config_drive_files={'/etc/octavia/'
|
||||
'amphora-agent.conf': 'test_conf'})
|
||||
'amphora-agent.conf': 'test_conf'},
|
||||
user_data=None)
|
||||
|
||||
# Make sure it returns the expected compute_id
|
||||
self.assertEqual(COMPUTE_ID, compute_id)
|
||||
|
@ -203,9 +264,10 @@ class TestComputeTasks(base.TestCase):
|
|||
sec_groups=AMP_SEC_GROUPS,
|
||||
network_ids=[AMP_NET],
|
||||
port_ids=[],
|
||||
user_data=None,
|
||||
config_drive_files={
|
||||
'/etc/octavia/certs/server.pem': 'test_cert',
|
||||
'/etc/octavia/certs/client_ca.pem': m.return_value,
|
||||
'/etc/octavia/certs/client_ca.pem': 'test',
|
||||
'/etc/octavia/amphora-agent.conf': 'test_conf'})
|
||||
|
||||
# Make sure it returns the expected compute_id
|
||||
|
|
Loading…
Reference in New Issue