~chad.smith/charms/precise/block-storage-broker/trunk

« back to all changes in this revision

Viewing changes to hooks/test_nova_util.py

Merge bsb-ec2-support [f=1298496] [r=dpb,fcorrea]

Add EC2 support to block-storage-broker to create, attach, label and detach ec2 volumes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import nova_util as util
2
 
import mocker
3
 
import os
4
 
import subprocess
5
 
from testing import TestHookenv
6
 
 
7
 
 
8
 
class TestNovaUtil(mocker.MockerTestCase):
9
 
 
10
 
    def setUp(self):
11
 
        self.maxDiff = None
12
 
        util.hookenv = TestHookenv(
13
 
            {"key": "myusername", "tenant": "myusername_project",
14
 
             "secret": "password", "region": "region1",
15
 
             "endpoint": "https://keystone_url:443/v2.0/",
16
 
             "default_volume_size": 11})
17
 
        util.log = util.hookenv.log
18
 
 
19
 
    def test_load_environment_with_nova_variables(self):
20
 
        """
21
 
        L{load_environment} will setup script environment variables for nova
22
 
        by mapping configuration values provided to openstack OS_* environment
23
 
        variables and then call L{validate_credentials} to assert
24
 
        that environment variables provided give access to the service.
25
 
        """
26
 
        self.addCleanup(setattr, util.os, "environ", util.os.environ)
27
 
        util.os.environ = {}
28
 
        credentials = self.mocker.replace(util.validate_credentials)
29
 
        credentials()
30
 
        self.mocker.replay()
31
 
 
32
 
        util.load_environment()
33
 
        expected = {
34
 
            "OS_AUTH_URL": "https://keystone_url:443/v2.0/",
35
 
            "OS_PASSWORD": "password",
36
 
            "OS_REGION_NAME": "region1",
37
 
            "OS_TENANT_NAME": "myusername_project",
38
 
            "OS_USERNAME": "myusername"
39
 
        }
40
 
        self.assertEqual(util.os.environ, expected)
41
 
 
42
 
    def test_load_environment_error_missing_config_options(self):
43
 
        """
44
 
        L{load_environment} will exit in failure and log a message if any
45
 
        required configuration option is not set.
46
 
        """
47
 
        self.addCleanup(setattr, util.os, "environ", util.os.environ)
48
 
        credentials = self.mocker.replace(util.validate_credentials)
49
 
        credentials()
50
 
        self.mocker.throw(SystemExit)
51
 
        self.mocker.replay()
52
 
 
53
 
        self.assertRaises(SystemExit, util.load_environment)
54
 
 
55
 
    def test_validate_credentials_failure(self):
56
 
        """
57
 
        L{validate_credentials} will attempt a simple nova command to ensure
58
 
        the environment is properly configured to access the nova service.
59
 
        Upon failure to contact the nova service, L{validate_credentials} will
60
 
        exit in error and log a message.
61
 
        """
62
 
        command = "nova list"
63
 
        nova_cmd = self.mocker.replace(subprocess.check_call)
64
 
        nova_cmd(command, shell=True)
65
 
        self.mocker.throw(subprocess.CalledProcessError(1, command))
66
 
        self.mocker.replay()
67
 
 
68
 
        result = self.assertRaises(SystemExit, util.validate_credentials)
69
 
        self.assertEqual(result.code, 1)
70
 
        message = (
71
 
            "ERROR: Charm configured credentials can't access endpoint. "
72
 
            "Command '%s' returned non-zero exit status 1" % command)
73
 
        self.assertIn(
74
 
            message, util.hookenv._log_ERROR, "Not logged- %s" % message)
75
 
 
76
 
    def test_validate_credentials(self):
77
 
        """
78
 
        L{validate_credentials} will succeed when a simple nova command
79
 
        succeeds due to a properly configured environment based on the charm
80
 
        configuration options.
81
 
        """
82
 
        command = "nova list"
83
 
        nova_cmd = self.mocker.replace(subprocess.check_call)
84
 
        nova_cmd(command, shell=True)
85
 
        self.mocker.replay()
86
 
 
87
 
        util.validate_credentials()
88
 
        message = (
89
 
            "Validated charm configuration credentials have access to "
90
 
            "block storage service"
91
 
        )
92
 
        self.assertIn(
93
 
            message, util.hookenv._log_INFO, "Not logged- %s" % message)
94
 
 
95
 
    def test_get_volume_attachments_present(self):
96
 
        """
97
 
        L{get_volume_attachments} returns a C{list} of available volume
98
 
        attachments for the given C{volume_id}.
99
 
        """
100
 
        volume_id = "123-123-123"
101
 
        command = (
102
 
            "nova volume-show %s | grep attachments | awk -F '|' '{print $3}'"
103
 
            % volume_id)
104
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
105
 
        nova_cmd(command, shell=True)
106
 
        self.mocker.result(
107
 
            "[{u'device': u'/dev/vdc', u'server_id': u'blah', "
108
 
            "u'id': u'i-123123', u'volume_id': u'%s'}]" % volume_id)
109
 
        self.mocker.replay()
110
 
 
111
 
        expected = [{
112
 
            "device": "/dev/vdc", "server_id": "blah", "id": "i-123123",
113
 
            "volume_id": volume_id}]
114
 
 
115
 
        self.assertEqual(util.get_volume_attachments(volume_id), expected)
116
 
 
117
 
    def test_get_volume_attachments_no_attachments_present(self):
118
 
        """
119
 
        L{get_volume_attachments} returns an empty C{list} if no available
120
 
        volume attachments are reported for the given C{volume_id}.
121
 
        """
122
 
        volume_id = "123-123-123"
123
 
        command = (
124
 
            "nova volume-show %s | grep attachments | awk -F '|' '{print $3}'"
125
 
            % volume_id)
126
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
127
 
        nova_cmd(command, shell=True)
128
 
        self.mocker.result("[]")
129
 
        self.mocker.replay()
130
 
 
131
 
        self.assertEqual(util.get_volume_attachments(volume_id), [])
132
 
 
133
 
    def test_get_volume_attachments_no_volume_present(self):
134
 
        """
135
 
        L{get_volume_attachments} returns an empty C{list} if no available
136
 
        volume is discovered for the given C{volume_id}.
137
 
        """
138
 
        volume_id = "123-123-123"
139
 
        command = (
140
 
            "nova volume-show %s | grep attachments | awk -F '|' '{print $3}'"
141
 
            % volume_id)
142
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
143
 
        nova_cmd(command, shell=True)
144
 
        self.mocker.throw(subprocess.CalledProcessError(1, command))
145
 
        self.mocker.replay()
146
 
 
147
 
        self.assertEqual(util.get_volume_attachments(volume_id), [])
148
 
 
149
 
    def test_volume_exists_true(self):
150
 
        """
151
 
        L{volume_exists} returns C{True} when C{volume_id} is seen by the nova
152
 
        client command C{nova volume-show}.
153
 
        """
154
 
        volume_id = "123134124-1241412-1242141"
155
 
        command = "nova volume-show %s" % volume_id
156
 
        nova_cmd = self.mocker.replace(subprocess.call)
157
 
        nova_cmd(command, shell=True)
158
 
        self.mocker.result(0)
159
 
        self.mocker.replay()
160
 
        self.assertTrue(util.volume_exists(volume_id))
161
 
 
162
 
    def test_volume_exists_false(self):
163
 
        """
164
 
        L{volume_exists} returns C{False} when C{volume_id} is not seen by the
165
 
        nova client command C{nova volume-show}.
166
 
        """
167
 
        volume_id = "123134124-1241412-1242141"
168
 
        command = "nova volume-show %s" % volume_id
169
 
        nova_cmd = self.mocker.replace(subprocess.call)
170
 
        nova_cmd(command, shell=True)
171
 
        self.mocker.throw(subprocess.CalledProcessError(1, "Volume not here"))
172
 
        self.mocker.replay()
173
 
 
174
 
        self.assertFalse(util.volume_exists(volume_id))
175
 
 
176
 
    def test_get_volume_id_by_volume_name(self):
177
 
        """
178
 
        L{get_volume_id} provided with a existing C{volume_name} returns the
179
 
        corresponding nova volume id.
180
 
        """
181
 
        volume_name = "my-volume"
182
 
        volume_id = "12312412-412312\n"
183
 
        command = (
184
 
            "nova volume-show '%s' | grep ' id ' | awk '{ print $4 }'" %
185
 
            volume_name)
186
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
187
 
        nova_cmd(command, shell=True)
188
 
        self.mocker.result(volume_id)
189
 
        self.mocker.replay()
190
 
        self.assertEqual(util.get_volume_id(volume_name), volume_id.strip())
191
 
 
192
 
    def test_get_volume_id_command_error(self):
193
 
        """
194
 
        L{get_volume_id} handles any nova command error by reporting the error
195
 
        and exiting the hook.
196
 
        """
197
 
        volume_name = "my-volume"
198
 
        command = (
199
 
            "nova volume-show '%s' | grep ' id ' | awk '{ print $4 }'" %
200
 
            volume_name)
201
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
202
 
        nova_cmd(command, shell=True)
203
 
        self.mocker.throw(subprocess.CalledProcessError(1, command))
204
 
        self.mocker.replay()
205
 
 
206
 
        result = self.assertRaises(SystemExit, util.get_volume_id, volume_name)
207
 
        self.assertEqual(result.code, 1)
208
 
        message = (
209
 
            "ERROR: Couldn't find nova volume id for %s. Command '%s' "
210
 
            "returned non-zero exit status 1" % (volume_name, command))
211
 
        self.assertIn(
212
 
            message, util.hookenv._log_ERROR, "Not logged- %s" % message)
213
 
 
214
 
    def test_get_volume_id_without_volume_name(self):
215
 
        """
216
 
        L{get_volume_id} without a provided C{volume_name} will discover the
217
 
        nova volume id by searching nova volume-list for volumes labelled with
218
 
        the os.environ[JUJU_REMOTE_UNIT].
219
 
        """
220
 
        unit_name = "postgresql/0"
221
 
        self.addCleanup(
222
 
            setattr, os, "environ", os.environ)
223
 
        os.environ = {"JUJU_REMOTE_UNIT": unit_name}
224
 
        volume_id = "123134124-1241412-1242141\n"
225
 
        command = (
226
 
            "nova volume-list | grep %s | awk '{print $2}'" % unit_name)
227
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
228
 
        nova_cmd(command, shell=True)
229
 
        self.mocker.result(volume_id)
230
 
        self.mocker.replay()
231
 
 
232
 
        self.assertEqual(util.get_volume_id(), volume_id.strip())
233
 
 
234
 
    def test_get_volume_id_without_volume_name_no_matching_volume(self):
235
 
        """
236
 
        L{get_volume_id} without a provided C{volume_name} will return C{None}
237
 
        when it cannot find a matching volume label from nova volume-list for
238
 
        the os.environ[JUJU_REMOTE_UNIT].
239
 
        """
240
 
        unit_name = "postgresql/0"
241
 
        self.addCleanup(
242
 
            setattr, os, "environ", os.environ)
243
 
        os.environ = {"JUJU_REMOTE_UNIT": unit_name}
244
 
        command = (
245
 
            "nova volume-list | grep %s | awk '{print $2}'" % unit_name)
246
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
247
 
        nova_cmd(command, shell=True)
248
 
        self.mocker.result("\n")   # Empty result string from awk
249
 
        self.mocker.replay()
250
 
 
251
 
        self.assertIsNone(util.get_volume_id())
252
 
 
253
 
    def test_get_volume_id_without_volume_name_multiple_matching_volumes(self):
254
 
        """
255
 
        L{get_volume_id} does not support multiple volumes associated with the
256
 
        the instance represented by os.environ[JUJU_REMOTE_UNIT]. When
257
 
        C{volume_name} is not specified and nova volume-list returns multiple
258
 
        results the function exits with an error.
259
 
        """
260
 
        unit_name = "postgresql/0"
261
 
        self.addCleanup(setattr, os, "environ", os.environ)
262
 
        os.environ = {"JUJU_REMOTE_UNIT": unit_name}
263
 
        command = (
264
 
            "nova volume-list | grep %s | awk '{print $2}'" % unit_name)
265
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
266
 
        nova_cmd(command, shell=True)
267
 
        self.mocker.result("123-123-123\n456-456-456\n")   # Two results
268
 
        self.mocker.replay()
269
 
 
270
 
        result = self.assertRaises(SystemExit, util.get_volume_id)
271
 
        self.assertEqual(result.code, 1)
272
 
        message = (
273
 
            "Error: Multiple nova volumes labeled as associated with "
274
 
            "%s. Cannot get_volume_id." % unit_name)
275
 
        self.assertIn(
276
 
            message, util.hookenv._log_ERROR, "Not logged- %s" % message)
277
 
 
278
 
    def test_get_volume_status_by_known_volume_id(self):
279
 
        """
280
 
        L{get_volume_status} returns the status of a volume matching
281
 
        C{volume_id} by using the nova client commands.
282
 
        """
283
 
        volume_id = "123134124-1241412-1242141"
284
 
        command = (
285
 
            "nova volume-show '%s' | grep ' status ' | awk '{ print $4 }'" %
286
 
            volume_id)
287
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
288
 
        nova_cmd(command, shell=True)
289
 
        self.mocker.result("available\n")
290
 
        self.mocker.replay()
291
 
        self.assertEqual(util.get_volume_status(volume_id), "available")
292
 
 
293
 
    def test_get_volume_status_by_invalid_volume_id(self):
294
 
        """
295
 
        L{get_volume_status} returns the status of a volume matching
296
 
        C{volume_id} by using the nova client commands.
297
 
        """
298
 
        volume_id = "123134124-1241412-1242141"
299
 
        command = (
300
 
            "nova volume-show '%s' | grep ' status ' | awk '{ print $4 }'" %
301
 
            volume_id)
302
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
303
 
        nova_cmd(command, shell=True)
304
 
        self.mocker.throw(subprocess.CalledProcessError(1, command))
305
 
        self.mocker.replay()
306
 
        self.assertIsNone(util.get_volume_status(volume_id))
307
 
        message = (
308
 
            "Error: nova couldn't get status of volume %s. "
309
 
            "Command '%s' returned non-zero exit status 1" %
310
 
            (volume_id, command))
311
 
        self.assertIn(
312
 
            message, util.hookenv._log_ERROR, "Not logged- %s" % message)
313
 
 
314
 
    def test_get_volume_status_when_get_volume_id_none(self):
315
 
        """
316
 
        L{get_volume_status} logs a warning and returns C{None} when
317
 
        C{volume_id} is not specified and L{get_volume_id} returns C{None}.
318
 
        """
319
 
        get_vol_id = self.mocker.replace(util.get_volume_id)
320
 
        get_vol_id()
321
 
        self.mocker.result(None)
322
 
        self.mocker.replay()
323
 
 
324
 
        self.assertIsNone(util.get_volume_status())
325
 
        message = "WARNING: Can't find volume_id to get status."
326
 
        self.assertIn(
327
 
            message, util.hookenv._log_WARNING, "Not logged- %s" % message)
328
 
 
329
 
    def test_get_volume_status_when_get_volume_id_discovers(self):
330
 
        """
331
 
        When C{volume_id} is not specified, L{get_volume_status} obtains the
332
 
        volume id from L{get_volume_id} gets the status using nova commands.
333
 
        """
334
 
        volume_id = "123-123-123"
335
 
        get_vol_id = self.mocker.replace(util.get_volume_id)
336
 
        get_vol_id()
337
 
        self.mocker.result(volume_id)
338
 
        command = (
339
 
            "nova volume-show '%s' | grep ' status ' | awk '{ print $4 }'" %
340
 
            volume_id)
341
 
        nova_cmd = self.mocker.replace(subprocess.check_output)
342
 
        nova_cmd(command, shell=True)
343
 
        self.mocker.result("in-use\n")
344
 
        self.mocker.replay()
345
 
 
346
 
        self.assertEqual(util.get_volume_status(), "in-use")
347
 
 
348
 
    def test_attach_nova_volume_failure_when_volume_id_does_not_exist(self):
349
 
        """
350
 
        When L{attach_nova_volume} is provided a C{volume_id} that doesn't
351
 
        exist it logs and error and exits.
352
 
        """
353
 
        unit_name = "postgresql/0"
354
 
        instance_id = "i-123123"
355
 
        volume_id = "123-123-123"
356
 
        self.addCleanup(setattr, os, "environ", os.environ)
357
 
        os.environ = {"JUJU_REMOTE_UNIT": unit_name}
358
 
 
359
 
        load_environment = self.mocker.replace(util.load_environment)
360
 
        load_environment()
361
 
        volume_exists = self.mocker.replace(util.volume_exists)
362
 
        volume_exists(volume_id)
363
 
        self.mocker.result(False)
364
 
        self.mocker.replay()
365
 
 
366
 
        result = self.assertRaises(
367
 
            SystemExit, util.attach_nova_volume, instance_id, volume_id)
368
 
        self.assertEqual(result.code, 1)
369
 
        message = (
370
 
            "Requested volume-id (%s) does not exist. "
371
 
            "Unable to associate storage with %s" % (volume_id, unit_name))
372
 
        self.assertIn(
373
 
            message, util.hookenv._log_ERROR, "Not logged- %s" % message)
374
 
 
375
 
    def test_attach_nova_volume_when_volume_id_already_attached(self):
376
 
        """
377
 
        When L{attach_nova_volume} is provided a C{volume_id} that already
378
 
        has the state C{in-use} it logs that the volume is already attached
379
 
        and returns.
380
 
        """
381
 
        unit_name = "postgresql/0"
382
 
        instance_id = "i-123123"
383
 
        volume_id = "123-123-123"
384
 
        self.addCleanup(setattr, os, "environ", os.environ)
385
 
        os.environ = {"JUJU_REMOTE_UNIT": unit_name}
386
 
 
387
 
        load_environment = self.mocker.replace(util.load_environment)
388
 
        load_environment()
389
 
        volume_exists = self.mocker.replace(util.volume_exists)
390
 
        volume_exists(volume_id)
391
 
        self.mocker.result(True)
392
 
        get_vol_status = self.mocker.replace(util.get_volume_status)
393
 
        get_vol_status(volume_id)
394
 
        self.mocker.result("in-use")
395
 
        get_attachments = self.mocker.replace(util.get_volume_attachments)
396
 
        get_attachments(volume_id)
397
 
        self.mocker.result([{"device": "/dev/vdc"}])
398
 
        self.mocker.replay()
399
 
 
400
 
        self.assertEqual(
401
 
            util.attach_nova_volume(instance_id, volume_id), "/dev/vdc")
402
 
 
403
 
        message = "Volume %s already attached. Done" % volume_id
404
 
        self.assertIn(
405
 
            message, util.hookenv._log_INFO, "Not logged- %s" % message)
406
 
 
407
 
    def test_attach_nova_volume_failure_when_volume_unsupported_status(self):
408
 
        """
409
 
        When L{attach_nova_volume} is provided a C{volume_id} that has an
410
 
        unsupported status. It logs the error and exits.
411
 
        """
412
 
        unit_name = "postgresql/0"
413
 
        instance_id = "i-123123"
414
 
        volume_id = "123-123-123"
415
 
        self.addCleanup(setattr, os, "environ", os.environ)
416
 
        os.environ = {"JUJU_REMOTE_UNIT": unit_name}
417
 
 
418
 
        load_environment = self.mocker.replace(util.load_environment)
419
 
        load_environment()
420
 
        volume_exists = self.mocker.replace(util.volume_exists)
421
 
        volume_exists(volume_id)
422
 
        self.mocker.result(True)
423
 
        get_vol_status = self.mocker.replace(util.get_volume_status)
424
 
        get_vol_status(volume_id)
425
 
        self.mocker.result("deleting")
426
 
        self.mocker.replay()
427
 
 
428
 
        result = self.assertRaises(
429
 
            SystemExit, util.attach_nova_volume, instance_id, volume_id)
430
 
        self.assertEqual(result.code, 1)
431
 
        message = ("Cannot attach nova volume. "
432
 
                   "Volume has unsupported status: deleting")
433
 
        self.assertIn(
434
 
            message, util.hookenv._log_INFO, "Not logged- %s" % message)
435
 
 
436
 
    def test_attach_nova_volume_creates_with_config_size(self):
437
 
        """
438
 
        When C{volume_id} is C{None}, L{attach_nova_volume} will create a new
439
 
        nova volume with the configured C{default_volume_size} when the volume
440
 
        doesn't exist and C{size} is not provided.
441
 
        """
442
 
        unit_name = "postgresql/0"
443
 
        instance_id = "i-123123"
444
 
        volume_id = "123-123-123"
445
 
        volume_label = "%s unit volume" % unit_name
446
 
        default_volume_size = util.hookenv.config("default_volume_size")
447
 
        self.addCleanup(setattr, os, "environ", os.environ)
448
 
        os.environ = {"JUJU_REMOTE_UNIT": unit_name}
449
 
 
450
 
        load_environment = self.mocker.replace(util.load_environment)
451
 
        load_environment()
452
 
        get_vol_id = self.mocker.replace(util.get_volume_id)
453
 
        get_vol_id(volume_label)
454
 
        self.mocker.result(None)
455
 
        command = (
456
 
            "nova volume-create --display-name '%s' %s" %
457
 
            (volume_label, default_volume_size))
458
 
        nova_cmd = self.mocker.replace(subprocess.check_call)
459
 
        nova_cmd(command, shell=True)
460
 
        get_vol_id(volume_label)
461
 
        self.mocker.result(volume_id)   # Found the volume now
462
 
        get_vol_status = self.mocker.replace(util.get_volume_status)
463
 
        get_vol_status(volume_id)
464
 
        self.mocker.result("available")
465
 
        command = (
466
 
            "nova volume-attach %s %s auto | egrep -o \"/dev/vd[b-z]\"" %
467
 
            (instance_id, volume_id))
468
 
        attach_cmd = self.mocker.replace(subprocess.check_output)
469
 
        attach_cmd(command, shell=True)
470
 
        self.mocker.result("/dev/vdc\n")
471
 
        self.mocker.replay()
472
 
 
473
 
        self.assertEqual(util.attach_nova_volume(instance_id), "/dev/vdc")
474
 
        messages = [
475
 
            "Creating a %sGig volume named (%s) for instance %s" %
476
 
            (default_volume_size, volume_label, instance_id),
477
 
            "Attaching %s (%s)" % (volume_label, volume_id)]
478
 
        for message in messages:
479
 
            self.assertIn(
480
 
                message, util.hookenv._log_INFO, "Not logged- %s" % message)
481
 
 
482
 
    def test_detach_nova_volume_no_volume_found(self):
483
 
        """
484
 
        When L{get_volume_id} is unable to find an attached volume and returns
485
 
        C{None}, L{detach_volume} will log a message and perform no work.
486
 
        """
487
 
        instance_id = "i-123123"
488
 
        load_environment = self.mocker.replace(util.load_environment)
489
 
        load_environment()
490
 
        get_vol_id = self.mocker.replace(util.get_volume_id)
491
 
        get_vol_id(instance_id=instance_id)
492
 
        self.mocker.result(None)
493
 
        self.mocker.replay()
494
 
 
495
 
        util.detach_nova_volume(instance_id)
496
 
        message = "Cannot find volume name to detach, done"
497
 
        self.assertIn(
498
 
            message, util.hookenv._log_INFO, "Not logged- %s" % message)
499
 
 
500
 
    def test_detach_nova_volume_volume_already_detached(self):
501
 
        """
502
 
        When L{get_volume_id} finds a volume that is already C{available} it
503
 
        logs that the volume is already detached and does no work.
504
 
        """
505
 
        instance_id = "i-123123"
506
 
        volume_id = "123-123-123"
507
 
        load_environment = self.mocker.replace(util.load_environment)
508
 
        load_environment()
509
 
        get_vol_id = self.mocker.replace(util.get_volume_id)
510
 
        get_vol_id(instance_id=instance_id)
511
 
        self.mocker.result(volume_id)
512
 
        get_vol_status = self.mocker.replace(util.get_volume_status)
513
 
        get_vol_status(volume_id)
514
 
        self.mocker.result("available")
515
 
        self.mocker.replay()
516
 
 
517
 
        util.detach_nova_volume(instance_id)  # pass in our instance_id
518
 
        message = "Volume (%s) already detached. Done" % volume_id
519
 
        self.assertIn(
520
 
            message, util.hookenv._log_INFO, "Not logged- %s" % message)
521
 
 
522
 
    def test_detach_nova_volume_command_error(self):
523
 
        """
524
 
        When the nova volume-detach command fails, L{detach_nova_volume} will
525
 
        log a message and exit in error.
526
 
        """
527
 
        volume_id = "123-123-123"
528
 
        instance_id = "i-123123"
529
 
        load_environment = self.mocker.replace(util.load_environment)
530
 
        load_environment()
531
 
        get_vol_id = self.mocker.replace(util.get_volume_id)
532
 
        get_vol_id(instance_id=instance_id)
533
 
        self.mocker.result(volume_id)
534
 
        get_vol_status = self.mocker.replace(util.get_volume_status)
535
 
        get_vol_status(volume_id)
536
 
        self.mocker.result("in-use")
537
 
        command = "nova volume-detach %s %s" % (instance_id, volume_id)
538
 
        nova_cmd = self.mocker.replace(subprocess.check_call)
539
 
        nova_cmd(command, shell=True)
540
 
        self.mocker.throw(subprocess.CalledProcessError(1, command))
541
 
        self.mocker.replay()
542
 
 
543
 
        result = self.assertRaises(
544
 
            SystemExit, util.detach_nova_volume, instance_id)
545
 
        self.assertEqual(result.code, 1)
546
 
        message = (
547
 
            "ERROR: Couldn't detach nova volume %s. Command '%s' "
548
 
            "returned non-zero exit status 1" % (volume_id, command))
549
 
        self.assertIn(
550
 
            message, util.hookenv._log_ERROR, "Not logged- %s" % message)
551
 
 
552
 
    def test_detach_nova_volume(self):
553
 
        """
554
 
        When L{get_volume_id} finds a volume associated with this instance
555
 
        which has a volume state not equal to C{available}, it detaches that
556
 
        volume using nova commands.
557
 
        """
558
 
        volume_id = "123-123-123"
559
 
        instance_id = "i-123123"
560
 
        load_environment = self.mocker.replace(util.load_environment)
561
 
        load_environment()
562
 
        get_vol_id = self.mocker.replace(util.get_volume_id)
563
 
        get_vol_id(instance_id=instance_id)
564
 
        self.mocker.result(volume_id)
565
 
        get_vol_status = self.mocker.replace(util.get_volume_status)
566
 
        get_vol_status(volume_id)
567
 
        self.mocker.result("in-use")
568
 
        command = "nova volume-detach %s %s" % (instance_id, volume_id)
569
 
        nova_cmd = self.mocker.replace(subprocess.check_call)
570
 
        nova_cmd(command, shell=True)
571
 
        self.mocker.replay()
572
 
 
573
 
        util.detach_nova_volume(instance_id)
574
 
        message = (
575
 
            "Detaching volume (%s) from instance %s" %
576
 
            (volume_id, instance_id))
577
 
        self.assertIn(
578
 
            message, util.hookenv._log_INFO, "Not logged- %s" % message)