1# Copyright 2010 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# All Rights Reserved.
4#
5#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6#    not use this file except in compliance with the License. You may obtain
7#    a copy of the License at
8#
9#         http://www.apache.org/licenses/LICENSE-2.0
10#
11#    Unless required by applicable law or agreed to in writing, software
12#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14#    License for the specific language governing permissions and limitations
15#    under the License.
16import mock
17from oslo_config import cfg
18
19from cinder import context
20from cinder import db
21from cinder import exception
22from cinder import objects
23from cinder.objects import fields
24from cinder import service
25from cinder.tests.unit.api import fakes
26from cinder.tests.unit import utils as tests_utils
27from cinder.tests.unit import volume as base
28
29
30CONF = cfg.CONF
31
32
33class VolumeCleanupTestCase(base.BaseVolumeTestCase):
34    MOCK_WORKER = False
35
36    def setUp(self):
37        super(VolumeCleanupTestCase, self).setUp()
38        self.service_id = 1
39        self.mock_object(service.Service, 'service_id', self.service_id)
40        self.patch('cinder.volume.utils.clear_volume', autospec=True)
41
42    def _assert_workers_are_removed(self):
43        workers = db.worker_get_all(self.context, read_deleted='yes')
44        self.assertListEqual([], workers)
45
46    def test_init_host_clears_uploads_available_volume(self):
47        """init_host will clean an available volume stuck in uploading."""
48        volume = tests_utils.create_volume(self.context, status='uploading',
49                                           size=0, host=CONF.host)
50
51        db.worker_create(self.context, resource_type='Volume',
52                         resource_id=volume.id, status=volume.status,
53                         service_id=self.service_id)
54
55        self.volume.init_host(service_id=service.Service.service_id)
56        volume.refresh()
57        self.assertEqual("available", volume.status)
58        self._assert_workers_are_removed()
59
60    @mock.patch('cinder.manager.CleanableManager.init_host')
61    def test_init_host_clears_uploads_in_use_volume(self, init_host_mock):
62        """init_host will clean an in-use volume stuck in uploading."""
63        volume = tests_utils.create_volume(self.context, status='uploading',
64                                           size=0, host=CONF.host)
65
66        db.worker_create(self.context, resource_type='Volume',
67                         resource_id=volume.id, status=volume.status,
68                         service_id=self.service_id)
69
70        fake_uuid = fakes.get_fake_uuid()
71        tests_utils.attach_volume(self.context, volume.id, fake_uuid,
72                                  'fake_host', '/dev/vda')
73        self.volume.init_host(service_id=mock.sentinel.service_id)
74        init_host_mock.assert_called_once_with(
75            service_id=mock.sentinel.service_id, added_to_cluster=None)
76        volume.refresh()
77        self.assertEqual("in-use", volume.status)
78        self._assert_workers_are_removed()
79
80    @mock.patch('cinder.image.image_utils.cleanup_temporary_file')
81    def test_init_host_clears_downloads(self, mock_cleanup_tmp_file):
82        """Test that init_host will unwedge a volume stuck in downloading."""
83        volume = tests_utils.create_volume(self.context, status='downloading',
84                                           size=0, host=CONF.host)
85        db.worker_create(self.context, resource_type='Volume',
86                         resource_id=volume.id, status=volume.status,
87                         service_id=self.service_id)
88        mock_clear = self.mock_object(self.volume.driver, 'clear_download')
89
90        self.volume.init_host(service_id=service.Service.service_id)
91        self.assertEqual(1, mock_clear.call_count)
92        self.assertEqual(volume.id, mock_clear.call_args[0][1].id)
93        volume.refresh()
94        self.assertEqual("error", volume['status'])
95        mock_cleanup_tmp_file.assert_called_once_with(CONF.host)
96
97        self.volume.delete_volume(self.context, volume=volume)
98        self._assert_workers_are_removed()
99
100    @mock.patch('cinder.image.image_utils.cleanup_temporary_file')
101    def test_init_host_resumes_deletes(self, mock_cleanup_tmp_file):
102        """init_host will resume deleting volume in deleting status."""
103        volume = tests_utils.create_volume(self.context, status='deleting',
104                                           size=0, host=CONF.host)
105
106        db.worker_create(self.context, resource_type='Volume',
107                         resource_id=volume.id, status=volume.status,
108                         service_id=self.service_id)
109
110        self.volume.init_host(service_id=service.Service.service_id)
111
112        self.assertRaises(exception.VolumeNotFound, db.volume_get,
113                          context.get_admin_context(), volume.id)
114        mock_cleanup_tmp_file.assert_called_once_with(CONF.host)
115        self._assert_workers_are_removed()
116
117    @mock.patch('cinder.image.image_utils.cleanup_temporary_file')
118    def test_create_volume_fails_with_creating_and_downloading_status(
119            self, mock_cleanup_tmp_file):
120        """Test init_host_with_service in case of volume.
121
122        While the status of volume is 'creating' or 'downloading',
123        volume process down.
124        After process restarting this 'creating' status is changed to 'error'.
125        """
126        for status in ('creating', 'downloading'):
127            volume = tests_utils.create_volume(self.context, status=status,
128                                               size=0, host=CONF.host)
129
130            db.worker_create(self.context, resource_type='Volume',
131                             resource_id=volume.id, status=volume.status,
132                             service_id=self.service_id)
133
134            self.volume.init_host(service_id=service.Service.service_id)
135            volume.refresh()
136
137            self.assertEqual('error', volume['status'])
138            self.volume.delete_volume(self.context, volume)
139            self.assertTrue(mock_cleanup_tmp_file.called)
140            self._assert_workers_are_removed()
141
142    def test_create_snapshot_fails_with_creating_status(self):
143        """Test init_host_with_service in case of snapshot.
144
145        While the status of snapshot is 'creating', volume process
146        down. After process restarting this 'creating' status is
147        changed to 'error'.
148        """
149        volume = tests_utils.create_volume(self.context,
150                                           **self.volume_params)
151        snapshot = tests_utils.create_snapshot(
152            self.context,
153            volume.id,
154            status=fields.SnapshotStatus.CREATING)
155        db.worker_create(self.context, resource_type='Snapshot',
156                         resource_id=snapshot.id, status=snapshot.status,
157                         service_id=self.service_id)
158
159        self.volume.init_host(service_id=service.Service.service_id)
160
161        snapshot_obj = objects.Snapshot.get_by_id(self.context, snapshot.id)
162
163        self.assertEqual(fields.SnapshotStatus.ERROR, snapshot_obj.status)
164        self.assertEqual(service.Service.service_id,
165                         self.volume.service_id)
166        self._assert_workers_are_removed()
167
168        self.volume.delete_snapshot(self.context, snapshot_obj)
169        self.volume.delete_volume(self.context, volume)
170
171    def test_init_host_clears_deleting_snapshots(self):
172        """Test that init_host will delete a snapshot stuck in deleting."""
173        volume = tests_utils.create_volume(self.context, status='deleting',
174                                           size=1, host=CONF.host)
175        snapshot = tests_utils.create_snapshot(self.context,
176                                               volume.id, status='deleting')
177
178        db.worker_create(self.context, resource_type='Volume',
179                         resource_id=volume.id, status=volume.status,
180                         service_id=self.service_id)
181
182        self.volume.init_host(service_id=self.service_id)
183        self.assertRaises(exception.VolumeNotFound, volume.refresh)
184        self.assertRaises(exception.SnapshotNotFound, snapshot.refresh)
185