diff --git a/Makefile b/Makefile index 7965721..be5004c 100644 --- a/Makefile +++ b/Makefile @@ -21,4 +21,8 @@ sync: bin/charm_helpers_sync.py unit_test: @$(PYTHON) /usr/bin/nosetests --nologcapture --with-coverage unit_tests +test: + @echo "Running amulet tests: " + @for f in tests/*; do $$f; done + all: unit_test lint diff --git a/config.yaml b/config.yaml index 1e39182..5347505 100644 --- a/config.yaml +++ b/config.yaml @@ -22,6 +22,13 @@ options: default: heat type: string description: Database name + instance-user: + default: + type: string + description: | + The default user for new instances. This option is deprecated as of Juno. + If left empty, Heat will use the default user set up with your cloud + image (for OS::Nova::Server) or 'ec2-user' (for AWS::EC2::Instance). region: default: RegionOne type: string @@ -62,4 +69,4 @@ options: default: description: | SSL CA to use with the certificate and key provided - this is only - required if you are providing a privately signed ssl_cert and ssl_key. \ No newline at end of file + required if you are providing a privately signed ssl_cert and ssl_key. diff --git a/hooks/heat_context.py b/hooks/heat_context.py index 922c4bc..20ab49b 100644 --- a/hooks/heat_context.py +++ b/hooks/heat_context.py @@ -96,3 +96,15 @@ class HeatApacheSSLContext(context.ApacheSSLContext): external_ports = API_PORTS.values() service_namespace = 'heat' + + +class InstanceUserContext(context.OSContextGenerator): + + def __call__(self): + ctxt = {} + + instance_user = '' + if config('instance-user'): + instance_user = config('instance-user') + ctxt['instance_user'] = instance_user + return ctxt diff --git a/hooks/heat_utils.py b/hooks/heat_utils.py index 651d936..6dce806 100644 --- a/hooks/heat_utils.py +++ b/hooks/heat_utils.py @@ -29,6 +29,7 @@ from heat_context import ( API_PORTS, HeatIdentityServiceContext, EncryptionContext, + InstanceUserContext, HeatApacheSSLContext, HeatHAProxyContext, ) @@ -68,6 +69,7 @@ CONFIG_FILES = OrderedDict([ HeatIdentityServiceContext(service=SVC, service_user=SVC), HeatHAProxyContext(), EncryptionContext(), + InstanceUserContext(), context.SyslogContext()] }), (HEAT_API_PASTE, { diff --git a/templates/heat.conf b/templates/heat.conf index 9e35ffb..2fa58c0 100644 --- a/templates/heat.conf +++ b/templates/heat.conf @@ -3,7 +3,9 @@ use_syslog = {{ use_syslog }} debug = False verbose = False log_dir = /var/log/heat -instance_user=ec2-user +# Icehouse expects 'instance_user=' to allow the image's default user +# Not including instance_user at all results in 'ec2-user' being used +instance_user={{ instance_user }} instance_driver=heat.engine.nova plugin_dirs=/usr/lib64/heat,/usr/lib/heat environment_dir=/etc/heat/environment.d diff --git a/tests/00-setup b/tests/00-setup new file mode 100755 index 0000000..cd5b3a2 --- /dev/null +++ b/tests/00-setup @@ -0,0 +1,5 @@ +#!/bin/bash + +sudo add-apt-repository ppa:juju/stable -y +sudo apt-get update +sudo apt-get install amulet python3-requests -y diff --git a/tests/50-basic-deploy b/tests/50-basic-deploy new file mode 100755 index 0000000..0a5ed2a --- /dev/null +++ b/tests/50-basic-deploy @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +import amulet +import unittest + + +class TestDeployment(unittest.TestCase): + + @classmethod + def setUpClass(cls): + try: + cls.d = amulet.Deployment(series='trusty') + cls.d.add('heat') + cls.d.configure('heat', {'instance-user': 'ubuntu'}) + cls.d.setup(timeout=1800) + cls.d.sentry.wait() + + cls.u = cls.d.sentry.unit['heat/0'] + except amulet.helpers.TimeoutError: + msg = "Environment wasn't stood up in time" + amulet.raise_status(amulet.SKIP, msg=msg) + except: + raise + + # amulet.raise_status(): + # - amulet.PASS + # - amulet.FAIL + # - amulet.SKIP + # Each unit has the following methods: + # - .info - An array of the information of that unit from Juju + # - .file(PATH) - Get the details of a file on that unit + # - .file_contents(PATH) - Get plain text output of PATH file from that unit + # - .directory(PATH) - Get details of directory + # - .directory_contents(PATH) - List files and folders in PATH on that unit + # - .relation(relation, service:rel) - Get relation data from return service + # add tests here to confirm service is up and working properly + # - .run(something) + # For example, to confirm that it has a functioning HTTP server: + # page = requests.get('http://{}'.format(self.unit.info['public-address'])) + # page.raise_for_status() + # More information on writing Amulet tests can be found at: + # https://juju.ubuntu.com/docs/tools-amulet.html + + def check_file_content(self, name, find): + """Check that the named file exists and contains the find text.""" + stat = TestDeployment.u.file(name) + if stat is None: + msg = "Could not retrieve status of %s" % name + amulet.raise_status(amulet.FAIL, msg=msg) + content = TestDeployment.u.file_contents(name) + if content.find(find) < 0: + msg = "%s does not contain the required text (%s)" % (name, find) + amulet.raise_status(amulet.FAIL, msg=msg) + return False + else: + print("%s contains the required text (%s)" % (name, find)) + return True + + def test_instance_user(self): + """Check that /etc/heat/heat.conf has instance_user item""" + # finding only 'instance_user=' will catch comments too + self.check_file_content(name='/etc/heat/heat.conf', find='instance_user=ubuntu') + + +if __name__ == '__main__': + unittest.main() diff --git a/unit_tests/test_heat_context.py b/unit_tests/test_heat_context.py index 4241668..9692d4d 100644 --- a/unit_tests/test_heat_context.py +++ b/unit_tests/test_heat_context.py @@ -4,7 +4,8 @@ from test_utils import CharmTestCase TO_PATCH = [ 'get_encryption_key', - 'generate_ec2_tokens' + 'generate_ec2_tokens', + 'config' ] @@ -19,6 +20,12 @@ class TestHeatContext(CharmTestCase): heat_context.EncryptionContext()(), {'encryption_key': 'key'}) + def test_instance_user_empty_configuration(self): + self.config.return_value = None + self.assertEquals( + heat_context.InstanceUserContext()(), + {'instance_user': ''}) + @patch('charmhelpers.contrib.openstack.' 'context.IdentityServiceContext.__call__') def test_identity_configuration(self, __call__):