~openstack-charmers-next/charms/vivid/lxd/trunk

« back to all changes in this revision

Viewing changes to tests/basic_deployment.py

  • Committer: Ryan Beisner
  • Date: 2016-02-19 16:47:00 UTC
  • mfrom: (49.1.21 trunk)
  • Revision ID: ryan.beisner@canonical.com-20160219164700-alxf2blxyyzc4d28
[1chb1n,r=rockstar]

Add initial amulet tests; update Makefile; add tests/charmhelpers and sync; add NotImplemented unit_tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Basic functional black-box test
 
2
# See tests/README before modifying or adding new tests.
 
3
 
 
4
import amulet
 
5
import time
 
6
 
 
7
from charmhelpers.contrib.openstack.amulet.deployment import (
 
8
    OpenStackAmuletDeployment
 
9
)
 
10
 
 
11
# NOTE(beisner):
 
12
#   LXDAmuletUtils inherits and extends OpenStackAmuletUtils, with
 
13
#   the intention of ultimately moving the relevant helpers into
 
14
#   OpenStackAmuletUtils.
 
15
#
 
16
# from charmhelpers.contrib.openstack.amulet.utils import (
 
17
#     OpenStackAmuletUtils,
 
18
from lxd_amulet_utils import (
 
19
    LXDAmuletUtils,
 
20
    DEBUG,
 
21
)
 
22
 
 
23
 
 
24
# u = OpenStackAmuletUtils(DEBUG)
 
25
u = LXDAmuletUtils(DEBUG)
 
26
 
 
27
LXD_IMAGE_URL = 'http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-root.tar.xz'  # noqa
 
28
LXD_IMAGE_NAME = 'trusty-server-cloudimg-amd64-root.tar.xz'
 
29
 
 
30
 
 
31
class LXDBasicDeployment(OpenStackAmuletDeployment):
 
32
    """Amulet tests on a basic nova compute deployment."""
 
33
 
 
34
    def __init__(self, series=None, openstack=None, source=None,
 
35
                 stable=False):
 
36
        """Deploy the test environment."""
 
37
        super(LXDBasicDeployment, self).__init__(series, openstack,
 
38
                                                 source, stable)
 
39
        self._add_services()
 
40
        self._add_relations()
 
41
        self._configure_services()
 
42
        self._deploy()
 
43
 
 
44
        u.log.info('Waiting on extended status checks...')
 
45
        exclude_services = ['mysql']
 
46
        self._auto_wait_for_status(exclude_services=exclude_services)
 
47
 
 
48
        self._initialize_tests()
 
49
 
 
50
    def _add_services(self):
 
51
        """Add services
 
52
 
 
53
           Add the services that we're testing, where lxd is local,
 
54
           and the rest of the service are from lp branches that are
 
55
           compatible with the local charm (e.g. stable or next).
 
56
           """
 
57
        this_service = {'name': 'lxd'}
 
58
 
 
59
        other_services = [{'name': 'mysql'},
 
60
                          {'name': 'nova-compute', 'units': 2},
 
61
                          {'name': 'rabbitmq-server'},
 
62
                          {'name': 'nova-cloud-controller'},
 
63
                          {'name': 'keystone'},
 
64
                          {'name': 'glance'}]
 
65
 
 
66
        super(LXDBasicDeployment, self)._add_services(this_service,
 
67
                                                      other_services)
 
68
 
 
69
    def _add_relations(self):
 
70
        """Add all of the relations for the services."""
 
71
        relations = {
 
72
            'lxd:lxd': 'nova-compute:lxd',
 
73
            'nova-compute:image-service': 'glance:image-service',
 
74
            'nova-compute:shared-db': 'mysql:shared-db',
 
75
            'nova-compute:amqp': 'rabbitmq-server:amqp',
 
76
            'nova-cloud-controller:shared-db': 'mysql:shared-db',
 
77
            'nova-cloud-controller:identity-service': 'keystone:'
 
78
                                                      'identity-service',
 
79
            'nova-cloud-controller:amqp': 'rabbitmq-server:amqp',
 
80
            'nova-cloud-controller:cloud-compute': 'nova-compute:'
 
81
                                                   'cloud-compute',
 
82
            'nova-cloud-controller:image-service': 'glance:image-service',
 
83
            'keystone:shared-db': 'mysql:shared-db',
 
84
            'glance:identity-service': 'keystone:identity-service',
 
85
            'glance:shared-db': 'mysql:shared-db',
 
86
            'glance:amqp': 'rabbitmq-server:amqp'
 
87
        }
 
88
        super(LXDBasicDeployment, self)._add_relations(relations)
 
89
 
 
90
    def _configure_services(self):
 
91
        """Configure all of the services."""
 
92
        nova_cc_config = {
 
93
            'ram-allocation-ratio': '5.0'
 
94
        }
 
95
 
 
96
        lxd_config = {
 
97
            'block-device': '/dev/vdb',
 
98
            'ephemeral-unmount': '/mnt',
 
99
            'storage-type': 'lvm'
 
100
        }
 
101
 
 
102
        nova_config = {
 
103
            'config-flags': 'auto_assign_floating_ip=False',
 
104
            'enable-live-migration': True,
 
105
            'enable-resize': True,
 
106
            'migration-auth-type': 'ssh',
 
107
            'virt-type': 'lxd'
 
108
        }
 
109
 
 
110
        keystone_config = {
 
111
            'admin-password': 'openstack',
 
112
            'admin-token': 'ubuntutesting'
 
113
        }
 
114
 
 
115
        configs = {
 
116
            'nova-compute': nova_config,
 
117
            'lxd': lxd_config,
 
118
            'keystone': keystone_config,
 
119
            'nova-cloud-controller': nova_cc_config
 
120
        }
 
121
 
 
122
        super(LXDBasicDeployment, self)._configure_services(configs)
 
123
 
 
124
    def _initialize_tests(self):
 
125
        """Perform final initialization before tests get run."""
 
126
 
 
127
        # Access the sentries for inspecting service units
 
128
        self.lxd0_sentry = self.d.sentry['lxd'][0]
 
129
        self.lxd1_sentry = self.d.sentry['lxd'][1]
 
130
        self.compute0_sentry = self.d.sentry['nova-compute'][0]
 
131
        self.compute1_sentry = self.d.sentry['nova-compute'][1]
 
132
 
 
133
        self.mysql_sentry = self.d.sentry['mysql'][0]
 
134
        self.keystone_sentry = self.d.sentry['keystone'][0]
 
135
        self.rabbitmq_sentry = self.d.sentry['rabbitmq-server'][0]
 
136
        self.nova_cc_sentry = self.d.sentry['nova-cloud-controller'][0]
 
137
        self.glance_sentry = self.d.sentry['glance'][0]
 
138
 
 
139
        u.log.debug('openstack release val: {}'.format(
 
140
            self._get_openstack_release()))
 
141
        u.log.debug('openstack release str: {}'.format(
 
142
            self._get_openstack_release_string()))
 
143
 
 
144
        # Authenticate admin with keystone
 
145
        self.keystone = u.authenticate_keystone_admin(self.keystone_sentry,
 
146
                                                      user='admin',
 
147
                                                      password='openstack',
 
148
                                                      tenant='admin')
 
149
 
 
150
        # Authenticate admin with glance endpoint
 
151
        self.glance = u.authenticate_glance_admin(self.keystone)
 
152
 
 
153
        # Create a demo tenant/role/user
 
154
        self.demo_tenant = 'demoTenant'
 
155
        self.demo_role = 'demoRole'
 
156
        self.demo_user = 'demoUser'
 
157
        if not u.tenant_exists(self.keystone, self.demo_tenant):
 
158
            tenant = self.keystone.tenants.create(tenant_name=self.demo_tenant,
 
159
                                                  description='demo tenant',
 
160
                                                  enabled=True)
 
161
            self.keystone.roles.create(name=self.demo_role)
 
162
            self.keystone.users.create(name=self.demo_user,
 
163
                                       password='password',
 
164
                                       tenant_id=tenant.id,
 
165
                                       email='demo@demo.com')
 
166
 
 
167
        # Authenticate demo user with keystone
 
168
        self.keystone_demo = \
 
169
            u.authenticate_keystone_user(self.keystone, user=self.demo_user,
 
170
                                         password='password',
 
171
                                         tenant=self.demo_tenant)
 
172
 
 
173
        # Authenticate demo user with nova-api
 
174
        self.nova_demo = u.authenticate_nova_user(self.keystone,
 
175
                                                  user=self.demo_user,
 
176
                                                  password='password',
 
177
                                                  tenant=self.demo_tenant)
 
178
 
 
179
    def test_100_services(self):
 
180
        """Verify the expected services are running on the corresponding
 
181
           service units."""
 
182
        u.log.debug('Checking system services on units...')
 
183
 
 
184
        services = {
 
185
            self.lxd0_sentry: ['lxd'],
 
186
            self.lxd1_sentry: ['lxd'],
 
187
            self.compute0_sentry: ['nova-compute',
 
188
                                   'nova-network',
 
189
                                   'nova-api'],
 
190
            self.compute1_sentry: ['nova-compute',
 
191
                                   'nova-network',
 
192
                                   'nova-api'],
 
193
            self.mysql_sentry: ['mysql'],
 
194
            self.rabbitmq_sentry: ['rabbitmq-server'],
 
195
            self.nova_cc_sentry: ['nova-api-os-compute',
 
196
                                  'nova-conductor',
 
197
                                  'nova-cert',
 
198
                                  'nova-scheduler'],
 
199
            self.keystone_sentry: ['keystone'],
 
200
            self.glance_sentry: ['glance-registry',
 
201
                                 'glance-api']
 
202
        }
 
203
 
 
204
        ret = u.validate_services_by_name(services)
 
205
        if ret:
 
206
            amulet.raise_status(amulet.FAIL, msg=ret)
 
207
 
 
208
        u.log.debug('Ok')
 
209
 
 
210
    def test_102_service_catalog(self):
 
211
        """Verify that the service catalog endpoint data is valid."""
 
212
        u.log.debug('Checking keystone service catalog...')
 
213
 
 
214
        endpoint_vol = {'adminURL': u.valid_url,
 
215
                        'region': 'RegionOne',
 
216
                        'publicURL': u.valid_url,
 
217
                        'internalURL': u.valid_url}
 
218
 
 
219
        endpoint_id = {'adminURL': u.valid_url,
 
220
                       'region': 'RegionOne',
 
221
                       'publicURL': u.valid_url,
 
222
                       'internalURL': u.valid_url}
 
223
 
 
224
        if self._get_openstack_release() >= self.precise_folsom:
 
225
            endpoint_vol['id'] = u.not_null
 
226
            endpoint_id['id'] = u.not_null
 
227
 
 
228
        expected = {
 
229
            'compute': [endpoint_vol],
 
230
            'identity': [endpoint_id]
 
231
        }
 
232
 
 
233
        if self._get_openstack_release() < self.trusty_kilo:
 
234
            expected.update({
 
235
                's3': [endpoint_vol],
 
236
                'ec2': [endpoint_vol]
 
237
            })
 
238
 
 
239
        actual = self.keystone_demo.service_catalog.get_endpoints()
 
240
 
 
241
        ret = u.validate_svc_catalog_endpoint_data(expected, actual)
 
242
        if ret:
 
243
            amulet.raise_status(amulet.FAIL, msg=ret)
 
244
 
 
245
        u.log.debug('Ok')
 
246
 
 
247
    def test_104_openstack_compute_api_endpoint(self):
 
248
        """Verify the openstack compute api (osapi) endpoint data."""
 
249
        u.log.debug('Checking compute endpoint data...')
 
250
 
 
251
        endpoints = self.keystone.endpoints.list()
 
252
        admin_port = internal_port = public_port = '8774'
 
253
        expected = {
 
254
            'id': u.not_null,
 
255
            'region': 'RegionOne',
 
256
            'adminurl': u.valid_url,
 
257
            'internalurl': u.valid_url,
 
258
            'publicurl': u.valid_url,
 
259
            'service_id': u.not_null
 
260
        }
 
261
 
 
262
        ret = u.validate_endpoint_data(endpoints, admin_port, internal_port,
 
263
                                       public_port, expected)
 
264
        if ret:
 
265
            message = 'osapi endpoint: {}'.format(ret)
 
266
            amulet.raise_status(amulet.FAIL, msg=message)
 
267
 
 
268
        u.log.debug('Ok')
 
269
 
 
270
    # TODO:  Add bi-directional lxd service relation introspection
 
271
 
 
272
    def test_200_nova_compute_shared_db_relation(self):
 
273
        """Verify the nova-compute to mysql shared-db relation data"""
 
274
        u.log.debug('Checking n-c:mysql db relation data...')
 
275
 
 
276
        unit = self.compute0_sentry
 
277
        relation = ['shared-db', 'mysql:shared-db']
 
278
        expected = {
 
279
            'private-address': u.valid_ip,
 
280
            'nova_database': 'nova',
 
281
            'nova_username': 'nova',
 
282
            'nova_hostname': u.valid_ip
 
283
        }
 
284
 
 
285
        ret = u.validate_relation_data(unit, relation, expected)
 
286
        if ret:
 
287
            message = u.relation_error('nova-compute shared-db', ret)
 
288
            amulet.raise_status(amulet.FAIL, msg=message)
 
289
 
 
290
        u.log.debug('Ok')
 
291
 
 
292
    def test_202_mysql_nova_compute_shared_db_relation(self):
 
293
        """Verify the mysql to nova-compute shared-db relation data"""
 
294
        u.log.debug('Checking mysql:n-c db relation data...')
 
295
        unit = self.mysql_sentry
 
296
        relation = ['shared-db', 'nova-compute:shared-db']
 
297
        expected = {
 
298
            'private-address': u.valid_ip,
 
299
            'nova_password': u.not_null,
 
300
            'db_host': u.valid_ip
 
301
        }
 
302
 
 
303
        ret = u.validate_relation_data(unit, relation, expected)
 
304
        if ret:
 
305
            message = u.relation_error('mysql shared-db', ret)
 
306
            amulet.raise_status(amulet.FAIL, msg=message)
 
307
 
 
308
        u.log.debug('Ok')
 
309
 
 
310
    def test_204_nova_compute_amqp_relation(self):
 
311
        """Verify the nova-compute to rabbitmq-server amqp relation data"""
 
312
        u.log.debug('Checking n-c:rmq amqp relation data...')
 
313
        unit = self.compute0_sentry
 
314
        relation = ['amqp', 'rabbitmq-server:amqp']
 
315
        expected = {
 
316
            'username': 'nova',
 
317
            'private-address': u.valid_ip,
 
318
            'vhost': 'openstack'
 
319
        }
 
320
 
 
321
        ret = u.validate_relation_data(unit, relation, expected)
 
322
        if ret:
 
323
            message = u.relation_error('nova-compute amqp', ret)
 
324
            amulet.raise_status(amulet.FAIL, msg=message)
 
325
 
 
326
        u.log.debug('Ok')
 
327
 
 
328
    def test_206_rabbitmq_nova_compute_amqp_relation(self):
 
329
        """Verify the rabbitmq-server to nova-compute amqp relation data"""
 
330
        u.log.debug('Checking rmq:n-c amqp relation data...')
 
331
        unit = self.rabbitmq_sentry
 
332
        relation = ['amqp', 'nova-compute:amqp']
 
333
        expected = {
 
334
            'private-address': u.valid_ip,
 
335
            'password': u.not_null,
 
336
            'hostname': u.valid_ip
 
337
        }
 
338
 
 
339
        ret = u.validate_relation_data(unit, relation, expected)
 
340
        if ret:
 
341
            message = u.relation_error('rabbitmq amqp', ret)
 
342
            amulet.raise_status(amulet.FAIL, msg=message)
 
343
 
 
344
        u.log.debug('Ok')
 
345
 
 
346
    def test_208_nova_compute_cloud_compute_relation(self):
 
347
        """Verify the nova-compute to nova-cc cloud-compute relation data"""
 
348
        u.log.debug('Checking n-c:n-c-c cloud-compute relation data...')
 
349
        unit = self.compute0_sentry
 
350
        relation = ['cloud-compute', 'nova-cloud-controller:cloud-compute']
 
351
        expected = {
 
352
            'private-address': u.valid_ip,
 
353
        }
 
354
 
 
355
        ret = u.validate_relation_data(unit, relation, expected)
 
356
        if ret:
 
357
            message = u.relation_error('nova-compute cloud-compute', ret)
 
358
            amulet.raise_status(amulet.FAIL, msg=message)
 
359
 
 
360
        u.log.debug('Ok')
 
361
 
 
362
    def test_300_nova_compute_config(self):
 
363
        """Verify the data in the nova-compute config file."""
 
364
        u.log.debug('Checking nova-compute config file data...')
 
365
        units = [self.compute0_sentry, self.compute1_sentry]
 
366
        conf = '/etc/nova/nova-compute.conf'
 
367
 
 
368
        expected = {
 
369
            'DEFAULT': {
 
370
                'compute_driver': 'nclxd.nova.virt.lxd.LXDDriver'
 
371
            }
 
372
        }
 
373
 
 
374
        for unit in units:
 
375
            for section, pairs in expected.iteritems():
 
376
                ret = u.validate_config_data(unit, conf, section, pairs)
 
377
                if ret:
 
378
                    message = "nova config error: {}".format(ret)
 
379
                    amulet.raise_status(amulet.FAIL, msg=message)
 
380
 
 
381
        u.log.debug('Ok')
 
382
 
 
383
    def test_302_nova_compute_nova_config(self):
 
384
        """Verify the data in the nova config file."""
 
385
        u.log.debug('Checking nova config file data...')
 
386
        units = [self.compute0_sentry, self.compute1_sentry]
 
387
        conf = '/etc/nova/nova.conf'
 
388
        rmq_nc_rel = self.rabbitmq_sentry.relation('amqp',
 
389
                                                   'nova-compute:amqp')
 
390
        gl_nc_rel = self.glance_sentry.relation('image-service',
 
391
                                                'nova-compute:image-service')
 
392
        db_nc_rel = self.mysql_sentry.relation('shared-db',
 
393
                                               'nova-compute:shared-db')
 
394
        db_uri = "mysql://{}:{}@{}/{}".format('nova',
 
395
                                              db_nc_rel['nova_password'],
 
396
                                              db_nc_rel['db_host'],
 
397
                                              'nova')
 
398
        expected = {
 
399
            'DEFAULT': {
 
400
                'dhcpbridge_flagfile': '/etc/nova/nova.conf',
 
401
                'dhcpbridge': '/usr/bin/nova-dhcpbridge',
 
402
                'logdir': '/var/log/nova',
 
403
                'state_path': '/var/lib/nova',
 
404
                'force_dhcp_release': 'True',
 
405
                'verbose': 'False',
 
406
                'use_syslog': 'False',
 
407
                'ec2_private_dns_show_ip': 'True',
 
408
                'api_paste_config': '/etc/nova/api-paste.ini',
 
409
                'enabled_apis': 'ec2,osapi_compute,metadata',
 
410
                'auth_strategy': 'keystone',
 
411
                'flat_interface': 'eth1',
 
412
                'network_manager': 'nova.network.manager.FlatDHCPManager',
 
413
                'volume_api_class': 'nova.volume.cinder.API',
 
414
            },
 
415
            'oslo_concurrency': {
 
416
                'lock_path': '/var/lock/nova'
 
417
            },
 
418
            'database': {
 
419
                'connection': db_uri
 
420
            },
 
421
            'oslo_messaging_rabbit': {
 
422
                'rabbit_userid': 'nova',
 
423
                'rabbit_virtual_host': 'openstack',
 
424
                'rabbit_password': rmq_nc_rel['password'],
 
425
                'rabbit_host': rmq_nc_rel['hostname'],
 
426
            },
 
427
            'glance': {
 
428
                'api_servers': gl_nc_rel['glance-api-server']
 
429
            }
 
430
        }
 
431
 
 
432
        for unit in units:
 
433
            for section, pairs in expected.iteritems():
 
434
                ret = u.validate_config_data(unit, conf, section, pairs)
 
435
                if ret:
 
436
                    message = "nova config error: {}".format(ret)
 
437
                    amulet.raise_status(amulet.FAIL, msg=message)
 
438
 
 
439
        u.log.debug('Ok')
 
440
 
 
441
    def test_400_check_logical_volume_groups(self):
 
442
        """Inspect and validate vgs on all lxd units."""
 
443
        u.log.debug('Checking logical volume groups on lxd units...')
 
444
 
 
445
        u.log.warn('Skipping test due to http://pastebin.ubuntu.com/15111453/')
 
446
        # Disabled, pending resolution of lxd lvm issue, where LXDPool
 
447
        # and logical volumes don't always get created on all units.
 
448
        #   http://pastebin.ubuntu.com/15111453/
 
449
        return
 
450
 
 
451
        cmd = 'sudo vgs'
 
452
        expected = ['lxd_vg']
 
453
 
 
454
        invalid = []
 
455
        for sentry_unit in self.d.sentry['lxd']:
 
456
            host = sentry_unit.info['public-address']
 
457
            unit_name = sentry_unit.info['unit_name']
 
458
 
 
459
            output, _ = u.run_cmd_unit(sentry_unit, cmd)
 
460
            for expected_content in expected:
 
461
                if expected_content not in output:
 
462
                    invalid.append('{} {} vgs does not contain '
 
463
                                   '{}'.format(unit_name, host,
 
464
                                               expected_content))
 
465
 
 
466
            if invalid:
 
467
                u.log.error('Logical volume group check failed.')
 
468
                amulet.raise_status(amulet.FAIL, msg='; '.join(invalid))
 
469
 
 
470
        u.log.debug('Ok')
 
471
 
 
472
    def test_401_check_logical_volumes(self):
 
473
        """Inspect and validate lvs on all lxd units."""
 
474
        u.log.debug('Checking logical volumes on lxd units...')
 
475
 
 
476
        u.log.warn('Skipping test due to http://pastebin.ubuntu.com/15111453/')
 
477
        # Disabled, pending resolution of lxd lvm issue, where LXDPool
 
478
        # and logical volumes don't always get created on all units.
 
479
        #   http://pastebin.ubuntu.com/15111453/
 
480
        return
 
481
 
 
482
        cmd = 'sudo lvs'
 
483
        expected = ['LXDPool']
 
484
 
 
485
        invalid = []
 
486
        for sentry_unit in self.d.sentry['lxd']:
 
487
            host = sentry_unit.info['public-address']
 
488
            unit_name = sentry_unit.info['unit_name']
 
489
 
 
490
            output, _ = u.run_cmd_unit(sentry_unit, cmd)
 
491
            for expected_content in expected:
 
492
                if expected_content not in output:
 
493
                    invalid.append('{} {} lvs does not contain '
 
494
                                   '{}'.format(unit_name, host,
 
495
                                               expected_content))
 
496
 
 
497
            if invalid:
 
498
                u.log.error('Logical volume check failed.')
 
499
                amulet.raise_status(amulet.FAIL, msg='; '.join(invalid))
 
500
 
 
501
        u.log.debug('Ok')
 
502
 
 
503
    def test_402_lxc_config_validate(self):
 
504
        """Inspect and validate lxc running config on all lxd units."""
 
505
        u.log.debug('Checking lxc config on lxd units...')
 
506
 
 
507
        u.log.warn('Skipping test due to http://pastebin.ubuntu.com/15111453/')
 
508
        # Disabled, pending resolution of lxd lvm issue, where LXDPool
 
509
        # and logical volumes don't always get created on all units.
 
510
        #   http://pastebin.ubuntu.com/15111453/
 
511
        return
 
512
 
 
513
        cmd = 'sudo lxc config show'
 
514
        expected = [
 
515
            'core.https_address: \'[::]\'',
 
516
            'core.trust_password: true',
 
517
            'images.remote_cache_expiry: "10"',
 
518
            'storage.lvm_thinpool_name: LXDPool',
 
519
            'storage.lvm_vg_name: lxd_vg',
 
520
        ]
 
521
 
 
522
        invalid = []
 
523
        for sentry_unit in self.d.sentry['lxd']:
 
524
            host = sentry_unit.info['public-address']
 
525
            unit_name = sentry_unit.info['unit_name']
 
526
 
 
527
            output, _ = u.run_cmd_unit(sentry_unit, cmd)
 
528
            for expected_content in expected:
 
529
                if expected_content not in output:
 
530
                    invalid.append('{} {} lxc config does not contain '
 
531
                                   '{}'.format(unit_name, host,
 
532
                                               expected_content))
 
533
 
 
534
            if invalid:
 
535
                u.log.error('lxc config check failed')
 
536
                amulet.raise_status(amulet.FAIL, msg='; '.join(invalid))
 
537
 
 
538
        u.log.debug('Ok')
 
539
 
 
540
    def test_410_image_instance_create(self):
 
541
        """Create an image/instance, verify they exist, and delete them."""
 
542
        u.log.debug('Create glance image, nova LXD instance...')
 
543
 
 
544
        # Add nova key pair
 
545
        # TODO:  Nova keypair create
 
546
 
 
547
        # Add glance image
 
548
        image = u.glance_create_image(self.glance,
 
549
                                      LXD_IMAGE_NAME,
 
550
                                      LXD_IMAGE_URL,
 
551
                                      disk_format='root-tar',
 
552
                                      hypervisor_type='lxc')
 
553
        if not image:
 
554
            amulet.raise_status(amulet.FAIL, msg='Image create failed')
 
555
 
 
556
        # Create nova instance
 
557
        instance_name = 'lxd-instance-{}'.format(time.time())
 
558
        instance = u.create_instance(self.nova_demo, LXD_IMAGE_NAME,
 
559
                                     instance_name, 'm1.tiny')
 
560
        if not instance:
 
561
            amulet.raise_status(amulet.FAIL, msg='Nova instance create failed')
 
562
 
 
563
        found = False
 
564
        for instance in self.nova_demo.servers.list():
 
565
            if instance.name == instance_name:
 
566
                found = True
 
567
                # TODO:  Get instance IP address
 
568
                if instance.status != 'ACTIVE':
 
569
                    msg = 'Nova instance is not active'
 
570
                    amulet.raise_status(amulet.FAIL, msg=msg)
 
571
 
 
572
        if not found:
 
573
            message = 'Nova instance does not exist'
 
574
            amulet.raise_status(amulet.FAIL, msg=message)
 
575
 
 
576
        # TODO:  Confirm nova instance:  TCP port knock
 
577
        # NOTE(beisner):
 
578
        #    This will require additional environment configuration
 
579
        #    and post-deployment operation such as network creation
 
580
        #    before it can be tested.  The instance has no IP address.
 
581
        #
 
582
        # host = '1.2.3.4'
 
583
        # port = 22
 
584
        # timeout = 30
 
585
        # connected = u.port_knock_tcp(host, port, timeout)
 
586
        # if connected:
 
587
        #     u.log.debug('Socket connect OK: {}:{}'.format(host, port))
 
588
        # else:
 
589
        #     msg = 'Socket connect failed: {}:{}'.format(host, port)
 
590
        #     amulet.raise_status(amulet.FAIL, msg)
 
591
 
 
592
        # TODO:  ICMP instance ping
 
593
        # TODO:  SSH instance login
 
594
 
 
595
        # Cleanup
 
596
        u.delete_resource(self.glance.images, image.id,
 
597
                          msg='glance image')
 
598
 
 
599
        u.delete_resource(self.nova_demo.servers, instance.id,
 
600
                          msg='nova instance')
 
601
        # TODO:  Delete nova keypair
 
602
 
 
603
        u.log.debug('Ok')
 
604
 
 
605
    def test_900_compute_restart_on_config_change(self):
 
606
        """Verify that the specified services are restarted when the config
 
607
           is changed."""
 
608
        u.log.debug('Checking service restart on charm config '
 
609
                    'option change...')
 
610
 
 
611
        sentry = self.compute0_sentry
 
612
        juju_service = 'nova-compute'
 
613
 
 
614
        # Expected default and alternate values
 
615
        set_default = {'verbose': 'False'}
 
616
        set_alternate = {'verbose': 'True'}
 
617
 
 
618
        # Services which are expected to restart upon config change,
 
619
        # and corresponding config files affected by the change
 
620
        conf_file = '/etc/nova/nova.conf'
 
621
        services = {
 
622
            'nova-compute': conf_file,
 
623
            'nova-api': conf_file,
 
624
            'nova-network': conf_file
 
625
        }
 
626
 
 
627
        # Make config change, check for service restarts
 
628
        u.log.debug('Making config change on {}...'.format(juju_service))
 
629
        mtime = u.get_sentry_time(sentry)
 
630
        self.d.configure(juju_service, set_alternate)
 
631
 
 
632
        sleep_time = 30
 
633
        for s, conf_file in services.iteritems():
 
634
            u.log.debug("Checking that service restarted: {}".format(s))
 
635
            if not u.validate_service_config_changed(sentry, mtime, s,
 
636
                                                     conf_file,
 
637
                                                     sleep_time=sleep_time):
 
638
 
 
639
                self.d.configure(juju_service, set_default)
 
640
                msg = "service {} didn't restart after config change".format(s)
 
641
                amulet.raise_status(amulet.FAIL, msg=msg)
 
642
            sleep_time = 0
 
643
 
 
644
        self.d.configure(juju_service, set_default)
 
645
 
 
646
        u.log.debug('Ok')