~vishvananda/nova/lp615857

« back to all changes in this revision

Viewing changes to nova/volume/service.py

  • Committer: Tarmac
  • Author(s): Vishvananda Ishaya
  • Date: 2010-08-10 15:37:07 UTC
  • mfrom: (202.1.5 fix-exceptions)
  • Revision ID: hudson@openstack.org-20100810153707-bnr9d7lin07szi23
More changes to volume to fix concurrency issues.  Also testing updates.

volumes now store a set of available shelf/blades in the datastore, instead of depending on a filesystem glob.  This avoids a race condition that occurs where two different volumes are attempting to export to the same location.

The general idea of pooled resources needs to be abstracted out into the datamodel.  It is used for vpn ports and volumes now, and should be uses for ip addresses as well.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
Currently uses Ata-over-Ethernet.
23
23
"""
24
24
 
25
 
import glob
26
25
import logging
27
26
import os
28
 
import shutil
29
 
import socket
30
 
import tempfile
31
27
 
32
28
from twisted.internet import defer
33
29
 
47
43
                    'Name for the VG that will contain exported volumes')
48
44
flags.DEFINE_string('aoe_eth_dev', 'eth0',
49
45
                    'Which device to export the volumes on')
50
 
flags.DEFINE_string('storage_name',
51
 
                    socket.gethostname(),
52
 
                    'name of this service')
53
46
flags.DEFINE_integer('first_shelf_id',
54
47
                    utils.last_octet(utils.get_my_ip()) * 10,
55
48
                    'AoE starting shelf_id for this service')
59
52
flags.DEFINE_string('aoe_export_dir',
60
53
                    '/var/lib/vblade-persist/vblades',
61
54
                    'AoE directory where exports are created')
62
 
flags.DEFINE_integer('slots_per_shelf',
 
55
flags.DEFINE_integer('blades_per_shelf',
63
56
                    16,
64
 
                    'Number of AoE slots per shelf')
 
57
                    'Number of AoE blades per shelf')
65
58
flags.DEFINE_string('storage_availability_zone',
66
59
                    'nova',
67
60
                    'availability zone of this service')
69
62
                     'Should we make real storage volumes to attach?')
70
63
 
71
64
 
72
 
class NoMoreVolumes(exception.Error):
 
65
class NoMoreBlades(exception.Error):
73
66
    pass
74
67
 
75
68
def get_volume(volume_id):
77
70
    volume_class = Volume
78
71
    if FLAGS.fake_storage:
79
72
        volume_class = FakeVolume
80
 
    if datastore.Redis.instance().sismember('volumes', volume_id):
81
 
        return volume_class(volume_id=volume_id)
 
73
    vol = volume_class.lookup(volume_id)
 
74
    if vol:
 
75
        return vol
82
76
    raise exception.Error("Volume does not exist")
83
77
 
84
78
class VolumeService(service.Service):
91
85
        super(VolumeService, self).__init__()
92
86
        self.volume_class = Volume
93
87
        if FLAGS.fake_storage:
94
 
            FLAGS.aoe_export_dir = tempfile.mkdtemp()
95
88
            self.volume_class = FakeVolume
96
89
        self._init_volume_group()
97
90
 
98
 
    def __del__(self):
99
 
        # TODO(josh): Get rid of this destructor, volumes destroy themselves
100
 
        if FLAGS.fake_storage:
101
 
            try:
102
 
                shutil.rmtree(FLAGS.aoe_export_dir)
103
 
            except Exception, err:
104
 
                pass
105
 
 
106
91
    @defer.inlineCallbacks
107
92
    @validate.rangetest(size=(0, 1000))
108
93
    def create_volume(self, size, user_id, project_id):
113
98
        """
114
99
        logging.debug("Creating volume of size: %s" % (size))
115
100
        vol = yield self.volume_class.create(size, user_id, project_id)
116
 
        datastore.Redis.instance().sadd('volumes', vol['volume_id'])
117
 
        datastore.Redis.instance().sadd('volumes:%s' % (FLAGS.storage_name), vol['volume_id'])
118
101
        logging.debug("restarting exports")
119
102
        yield self._restart_exports()
120
103
        defer.returnValue(vol['volume_id'])
134
117
    def delete_volume(self, volume_id):
135
118
        logging.debug("Deleting volume with id of: %s" % (volume_id))
136
119
        vol = get_volume(volume_id)
137
 
        if vol['status'] == "attached":
 
120
        if vol['attach_status'] == "attached":
138
121
            raise exception.Error("Volume is still attached")
139
 
        if vol['node_name'] != FLAGS.storage_name:
 
122
        if vol['node_name'] != FLAGS.node_name:
140
123
            raise exception.Error("Volume is not local to this node")
141
124
        yield vol.destroy()
142
 
        datastore.Redis.instance().srem('volumes', vol['volume_id'])
143
 
        datastore.Redis.instance().srem('volumes:%s' % (FLAGS.storage_name), vol['volume_id'])
144
125
        defer.returnValue(True)
145
126
 
146
127
    @defer.inlineCallbacks
147
128
    def _restart_exports(self):
148
129
        if FLAGS.fake_storage:
149
130
            return
150
 
        yield process.simple_execute("sudo vblade-persist auto all")
151
 
        # NOTE(vish): this command sometimes sends output to stderr for warnings
 
131
        # NOTE(vish): these commands sometimes sends output to stderr for warnings
 
132
        yield process.simple_execute("sudo vblade-persist auto all", error_ok=1)
152
133
        yield process.simple_execute("sudo vblade-persist start all", error_ok=1)
153
134
 
154
135
    @defer.inlineCallbacks
172
153
        return self.volume_id
173
154
 
174
155
    def default_state(self):
175
 
        return {"volume_id": self.volume_id}
 
156
        return {"volume_id": self.volume_id,
 
157
                "node_name": "unassigned"}
176
158
 
177
159
    @classmethod
178
160
    @defer.inlineCallbacks
179
161
    def create(cls, size, user_id, project_id):
180
162
        volume_id = utils.generate_uid('vol')
181
163
        vol = cls(volume_id)
182
 
        vol['node_name'] = FLAGS.storage_name
 
164
        vol['node_name'] = FLAGS.node_name
183
165
        vol['size'] = size
184
166
        vol['user_id'] = user_id
185
167
        vol['project_id'] = project_id
225
207
        self['attach_status'] = "detached"
226
208
        self.save()
227
209
 
 
210
    def save(self):
 
211
        is_new = self.is_new_record()
 
212
        super(Volume, self).save()
 
213
        if is_new:
 
214
            redis = datastore.Redis.instance()
 
215
            key = self.__devices_key
 
216
            # TODO(vish): these should be added by admin commands
 
217
            more = redis.scard(self._redis_association_name("node",
 
218
                                                            self['node_name']))
 
219
            if (not redis.exists(key) and not more):
 
220
                for shelf_id in range(FLAGS.first_shelf_id,
 
221
                                      FLAGS.last_shelf_id + 1):
 
222
                    for blade_id in range(FLAGS.blades_per_shelf):
 
223
                        redis.sadd(key, "%s.%s" % (shelf_id, blade_id))
 
224
            self.associate_with("node", self['node_name'])
 
225
 
228
226
    @defer.inlineCallbacks
229
227
    def destroy(self):
230
 
        try:
231
 
            yield self._remove_export()
232
 
        except Exception as ex:
233
 
            logging.debug("Ingnoring failure to remove export %s" % ex)
234
 
            pass
 
228
        yield self._remove_export()
235
229
        yield self._delete_lv()
 
230
        self.unassociate_with("node", self['node_name'])
 
231
        if self.get('shelf_id', None) and self.get('blade_id', None):
 
232
            redis = datastore.Redis.instance()
 
233
            key = self.__devices_key
 
234
            redis.sadd(key, "%s.%s" % (self['shelf_id'], self['blade_id']))
236
235
        super(Volume, self).destroy()
237
236
 
238
237
    @defer.inlineCallbacks
244
243
        yield process.simple_execute(
245
244
                "sudo lvcreate -L %s -n %s %s" % (sizestr,
246
245
                                                  self['volume_id'],
247
 
                                                  FLAGS.volume_group))
 
246
                                                  FLAGS.volume_group),
 
247
                error_ok=1)
248
248
 
249
249
    @defer.inlineCallbacks
250
250
    def _delete_lv(self):
251
251
        yield process.simple_execute(
252
252
                "sudo lvremove -f %s/%s" % (FLAGS.volume_group,
253
 
                                            self['volume_id']))
 
253
                                            self['volume_id']), error_ok=1)
 
254
 
 
255
    @property
 
256
    def __devices_key(self):
 
257
        return 'volume_devices:%s' % FLAGS.node_name
254
258
 
255
259
    @defer.inlineCallbacks
256
260
    def _setup_export(self):
257
 
        (shelf_id, blade_id) = get_next_aoe_numbers()
 
261
        redis = datastore.Redis.instance()
 
262
        key = self.__devices_key
 
263
        device = redis.spop(key)
 
264
        if not device:
 
265
            raise NoMoreBlades()
 
266
        (shelf_id, blade_id) = device.split('.')
258
267
        self['aoe_device'] = "e%s.%s" % (shelf_id, blade_id)
259
268
        self['shelf_id'] = shelf_id
260
269
        self['blade_id'] = blade_id
261
270
        self.save()
262
 
        yield self._exec_export()
 
271
        yield self._exec_setup_export()
263
272
 
264
273
    @defer.inlineCallbacks
265
 
    def _exec_export(self):
 
274
    def _exec_setup_export(self):
266
275
        yield process.simple_execute(
267
276
                "sudo vblade-persist setup %s %s %s /dev/%s/%s" %
268
277
                (self['shelf_id'],
269
278
                 self['blade_id'],
270
279
                 FLAGS.aoe_eth_dev,
271
280
                 FLAGS.volume_group,
272
 
                 self['volume_id']))
 
281
                 self['volume_id']), error_ok=1)
273
282
 
274
283
    @defer.inlineCallbacks
275
284
    def _remove_export(self):
 
285
        if not self.get('shelf_id', None) or not self.get('blade_id', None):
 
286
            defer.returnValue(False)
 
287
        yield self._exec_remove_export()
 
288
        defer.returnValue(True)
 
289
 
 
290
    @defer.inlineCallbacks
 
291
    def _exec_remove_export(self):
276
292
        yield process.simple_execute(
277
293
                "sudo vblade-persist stop %s %s" % (self['shelf_id'],
278
 
                                                    self['blade_id']))
 
294
                                                    self['blade_id']), error_ok=1)
279
295
        yield process.simple_execute(
280
296
                "sudo vblade-persist destroy %s %s" % (self['shelf_id'],
281
 
                                                       self['blade_id']))
 
297
                                                       self['blade_id']), error_ok=1)
 
298
 
282
299
 
283
300
 
284
301
class FakeVolume(Volume):
285
302
    def _create_lv(self):
286
303
        pass
287
304
 
288
 
    def _exec_export(self):
 
305
    def _exec_setup_export(self):
289
306
        fname = os.path.join(FLAGS.aoe_export_dir, self['aoe_device'])
290
307
        f = file(fname, "w")
291
308
        f.close()
292
309
 
293
 
    def _remove_export(self):
294
 
        pass
 
310
    def _exec_remove_export(self):
 
311
        os.unlink(os.path.join(FLAGS.aoe_export_dir, self['aoe_device']))
295
312
 
296
313
    def _delete_lv(self):
297
314
        pass
298
 
 
299
 
def get_next_aoe_numbers():
300
 
    for shelf_id in xrange(FLAGS.first_shelf_id, FLAGS.last_shelf_id + 1):
301
 
        aoes = glob.glob("%s/e%s.*" % (FLAGS.aoe_export_dir, shelf_id))
302
 
        if not aoes:
303
 
            blade_id = 0
304
 
        else:
305
 
            blade_id = int(max([int(a.rpartition('.')[2]) for a in aoes])) + 1
306
 
        if blade_id < FLAGS.slots_per_shelf:
307
 
            logging.debug("Next shelf.blade is %s.%s", shelf_id, blade_id)
308
 
            return (shelf_id, blade_id)
309
 
    raise NoMoreVolumes()