Getting Started¶
Get familiar with Amulet quickly by following this short series of increasingly feature-rich examples.
Remember that Amulet tests are executable files stored in the /tests directory of the charm or bundle they are testing.
Deploying a Service¶
The “Hello, world” of Amulet:
#!/usr/bin/env python
import amulet
# Create a new deployment. Use trusty series charms by default.
d = amulet.Deployment(series="trusty")
# Add a mysql service to the deployment.
d.add('mysql')
# Set the mysql service to be exposed (accessible to the outside world).
d.expose('mysql')
try:
# Execute the deployment with a timeout of 600 seconds.
d.setup(timeout=600)
# After services are deployed, related, and configured, allow up to
# 600 seconds for hooks to finish running. This call will block until
# all hooks complete (the deployment is "settled") or the timeout is
# hit.
d.sentry.wait(timeout=300)
except amulet.TimeoutError:
# If we end up here, our setup() or wait() call timed out.
raise
# Deployment completed within the timeout, now we can test it!
This is a valid (albeit marginally useful) Amulet test. It doesn’t test much, but it does test that the mysql service deploys successfully (without any hook failures) within the timeout.
Relating and Configuring Services¶
In the previous example we simply dumped all of our Amulet code into the
module scope of our test file. For the remaining examples we’ll adopt a better
style, one that will be familiar to those who have used Python’s unittest
module.
In this example we deploy two services, relate them, and change some configuration on one of the services.
import unittest
import amulet
class TestDeployment(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.d = amulet.Deployment(series='trusty')
cls.d.add('mysql')
cls.d.add('mediawiki')
cls.d.add('haproxy')
# set up service relations
cls.d.relate('mysql:db', 'mediawiki:db')
cls.d.relate('mediawiki:website', 'haproxy:reverseproxy')
# change some configuration on mediawiki
cls.d.configure('mediawiki', {
'title': 'My Wiki',
'skin': 'Nostolgia',
})
cls.d.expose('haproxy')
cls.d.setup()
cls.d.sentry.wait()
if __name__ == '__main__':
unittest.main()
Testing the Deployment¶
Now that we know how to set up, configure, and deploy our workload, let’s see how to run tests against it.
The Deployment.sentry
object supplies us with a
UnitSentry
for each unit in our deployment, which we
can use to interact with the unit in ways that are useful for testing.
This example will deploy the same workload as the previous example, but now we’ll add some test methods to exercise and inspect the deployment.
import unittest
import requests
import amulet
class TestDeployment(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Set up our deployment.
This happens once, after which all 'test_' methods are run.
"""
cls.d = amulet.Deployment(series='trusty')
cls.d.add('mysql')
cls.d.add('mediawiki')
cls.d.add('haproxy')
cls.d.relate('mysql:db', 'mediawiki:db')
cls.d.relate('mediawiki:website', 'haproxy:reverseproxy')
cls.d.expose('haproxy')
cls.d.setup()
cls.d.sentry.wait()
def test_scale_up(self):
"""Test that haproxy config is updated when a new web unit is added.
"""
# add another mediawiki unit and wait for hooks to complete
self.d.add_unit('mediawiki')
self.d.sentry.wait()
# get the UnitSentry for the last mediawiki unit (the one we just
# added)
mediawiki = self.d.sentry['mediawiki'][-1]
# get UnitSentry for the haproxy unit
haproxy = self.d.sentry['haproky'][0]
# get contents of the haproxy config on the runnning unit
haproxy_config = haproxy.file_contents('/etc/haproxy/haproxy.cfg')
# get the mediawiki private address from it's relation with haproxy
mediawiki_address = mediawiki.relation(
'website', 'haproxy:reverseproxy')['private-address']
# test that the haproxy config contains the address of the
# new mediawiki unit that we added
self.assertTrue(mediawiki_address in haproxy_config)
# here's an alternate way to do the same thing
output, exit_code = haproxy.run(
'grep %s /etc/haproxy/haproxy.cfg' % mediawiki_address)
self.assertTrue(exit_code == 0)
def test_reconfigure(self):
"""Test that website is updated when mediawiki config is changed.
"""
# change mediawiki config, setting a new title, and wait for hooks
# to complete
new_title = 'My New Title'
cls.d.configure('mediawiki', {
'title': new_title,
})
self.d.sentry.wait()
# get url to the mediawiki website (fronted by haproxy)
haproxy = self.d.sentry['haproxy'][0]
haproxy_url = 'http://{public-address}'.format(**haproxy.info)
# fetch website homepage
homepage = requests.get(haproxy_url)
# test that homepage contains our new title
self.assertTrue(new_title in homepage)
if __name__ == '__main__':
unittest.main()
Environment Variables¶
- AMULET_SETUP_TIMEOUT - overrides the timeout value passed to
setup()
- AMULET_WAIT_TIMEOUT - overrides the timeout value passed to
wait()
andwait_for_status()
Next Steps¶
These examples have shown a few basic ways to manipulate your deployment and
interact with deployed units using Amulet. For more details on the full Amulet
API, please consult the documentation for
Deployment
and UnitSentry
.