~rlane/nova/ldapimprovements

« back to all changes in this revision

Viewing changes to nova/volume/driver.py

  • Committer: Ryan Lane
  • Date: 2010-11-24 15:46:32 UTC
  • mfrom: (382.48.1 trunk)
  • Revision ID: laner@controller-20101124154632-zh7kwjuyyd02a2lh
MergeĀ fromĀ trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
16
#    License for the specific language governing permissions and limitations
17
17
#    under the License.
 
18
"""
 
19
Drivers for volumes.
18
20
 
19
 
"""
20
 
Drivers for volumes
21
21
"""
22
22
 
23
23
import logging
 
24
import os
24
25
 
25
26
from twisted.internet import defer
26
27
 
27
28
from nova import exception
28
29
from nova import flags
29
30
from nova import process
 
31
from nova import utils
30
32
 
31
33
 
32
34
FLAGS = flags.FLAGS
36
38
                    'Which device to export the volumes on')
37
39
flags.DEFINE_string('num_shell_tries', 3,
38
40
                    'number of times to attempt to run flakey shell commands')
39
 
 
40
 
 
41
 
class AOEDriver(object):
42
 
    """Executes commands relating to AOE volumes"""
43
 
    def __init__(self, execute=process.simple_execute, *args, **kwargs):
 
41
flags.DEFINE_integer('num_shelves',
 
42
                    100,
 
43
                    'Number of vblade shelves')
 
44
flags.DEFINE_integer('blades_per_shelf',
 
45
                    16,
 
46
                    'Number of vblade blades per shelf')
 
47
flags.DEFINE_integer('iscsi_num_targets',
 
48
                    100,
 
49
                    'Number of iscsi target ids per host')
 
50
flags.DEFINE_string('iscsi_target_prefix', 'iqn.2010-10.org.openstack:',
 
51
                    'prefix for iscsi volumes')
 
52
flags.DEFINE_string('iscsi_ip_prefix', '127.0',
 
53
                    'discover volumes on the ip that starts with this prefix')
 
54
 
 
55
 
 
56
class VolumeDriver(object):
 
57
    """Executes commands relating to Volumes."""
 
58
    def __init__(self, execute=process.simple_execute,
 
59
                 sync_exec=utils.execute, *args, **kwargs):
 
60
        # NOTE(vish): db is set by Manager
 
61
        self.db = None
44
62
        self._execute = execute
 
63
        self._sync_exec = sync_exec
45
64
 
46
65
    @defer.inlineCallbacks
47
66
    def _try_execute(self, command):
61
80
                                  "Try number %s", tries)
62
81
                yield self._execute("sleep %s" % tries ** 2)
63
82
 
 
83
    def check_for_setup_error(self):
 
84
        """Returns an error if prerequisites aren't met"""
 
85
        if not os.path.isdir("/dev/%s" % FLAGS.volume_group):
 
86
            raise exception.Error("volume group %s doesn't exist"
 
87
                                  % FLAGS.volume_group)
 
88
 
64
89
    @defer.inlineCallbacks
65
 
    def create_volume(self, volume_name, size):
66
 
        """Creates a logical volume"""
67
 
        # NOTE(vish): makes sure that the volume group exists
68
 
        yield self._execute("vgs %s" % FLAGS.volume_group)
69
 
        if int(size) == 0:
 
90
    def create_volume(self, volume):
 
91
        """Creates a logical volume."""
 
92
        if int(volume['size']) == 0:
70
93
            sizestr = '100M'
71
94
        else:
72
 
            sizestr = '%sG' % size
 
95
            sizestr = '%sG' % volume['size']
73
96
        yield self._try_execute("sudo lvcreate -L %s -n %s %s" %
74
97
                            (sizestr,
75
 
                             volume_name,
 
98
                             volume['name'],
76
99
                             FLAGS.volume_group))
77
100
 
78
101
    @defer.inlineCallbacks
79
 
    def delete_volume(self, volume_name):
80
 
        """Deletes a logical volume"""
 
102
    def delete_volume(self, volume):
 
103
        """Deletes a logical volume."""
81
104
        yield self._try_execute("sudo lvremove -f %s/%s" %
82
105
                                (FLAGS.volume_group,
83
 
                                 volume_name))
84
 
 
85
 
    @defer.inlineCallbacks
86
 
    def create_export(self, volume_name, shelf_id, blade_id):
87
 
        """Creates an export for a logical volume"""
 
106
                                 volume['name']))
 
107
 
 
108
    @defer.inlineCallbacks
 
109
    def local_path(self, volume):
 
110
        yield  # NOTE(vish): stops deprecation warning
 
111
        escaped_group = FLAGS.volume_group.replace('-', '--')
 
112
        escaped_name = volume['name'].replace('-', '--')
 
113
        defer.returnValue("/dev/mapper/%s-%s" % (escaped_group,
 
114
                                                 escaped_name))
 
115
 
 
116
    def ensure_export(self, context, volume):
 
117
        """Synchronously recreates an export for a logical volume."""
 
118
        raise NotImplementedError()
 
119
 
 
120
    @defer.inlineCallbacks
 
121
    def create_export(self, context, volume):
 
122
        """Exports the volume."""
 
123
        raise NotImplementedError()
 
124
 
 
125
    @defer.inlineCallbacks
 
126
    def remove_export(self, context, volume):
 
127
        """Removes an export for a logical volume."""
 
128
        raise NotImplementedError()
 
129
 
 
130
    @defer.inlineCallbacks
 
131
    def discover_volume(self, volume):
 
132
        """Discover volume on a remote host."""
 
133
        raise NotImplementedError()
 
134
 
 
135
    @defer.inlineCallbacks
 
136
    def undiscover_volume(self, volume):
 
137
        """Undiscover volume on a remote host."""
 
138
        raise NotImplementedError()
 
139
 
 
140
 
 
141
class AOEDriver(VolumeDriver):
 
142
    """Implements AOE specific volume commands."""
 
143
 
 
144
    def ensure_export(self, context, volume):
 
145
        # NOTE(vish): we depend on vblade-persist for recreating exports
 
146
        pass
 
147
 
 
148
    def _ensure_blades(self, context):
 
149
        """Ensure that blades have been created in datastore."""
 
150
        total_blades = FLAGS.num_shelves * FLAGS.blades_per_shelf
 
151
        if self.db.export_device_count(context) >= total_blades:
 
152
            return
 
153
        for shelf_id in xrange(FLAGS.num_shelves):
 
154
            for blade_id in xrange(FLAGS.blades_per_shelf):
 
155
                dev = {'shelf_id': shelf_id, 'blade_id': blade_id}
 
156
                self.db.export_device_create_safe(context, dev)
 
157
 
 
158
    @defer.inlineCallbacks
 
159
    def create_export(self, context, volume):
 
160
        """Creates an export for a logical volume."""
 
161
        self._ensure_blades(context)
 
162
        (shelf_id,
 
163
         blade_id) = self.db.volume_allocate_shelf_and_blade(context,
 
164
                                                             volume['id'])
88
165
        yield self._try_execute(
89
166
                "sudo vblade-persist setup %s %s %s /dev/%s/%s" %
90
167
                (shelf_id,
91
168
                 blade_id,
92
169
                 FLAGS.aoe_eth_dev,
93
170
                 FLAGS.volume_group,
94
 
                 volume_name))
95
 
 
96
 
    @defer.inlineCallbacks
97
 
    def discover_volume(self, _volume_name):
98
 
        """Discover volume on a remote host"""
99
 
        yield self._execute("sudo aoe-discover")
100
 
        yield self._execute("sudo aoe-stat")
101
 
 
102
 
    @defer.inlineCallbacks
103
 
    def remove_export(self, _volume_name, shelf_id, blade_id):
104
 
        """Removes an export for a logical volume"""
105
 
        yield self._try_execute("sudo vblade-persist stop %s %s" %
106
 
                                (shelf_id, blade_id))
107
 
        yield self._try_execute("sudo vblade-persist destroy %s %s" %
108
 
                                (shelf_id, blade_id))
109
 
 
110
 
    @defer.inlineCallbacks
111
 
    def ensure_exports(self):
112
 
        """Runs all existing exports"""
 
171
                 volume['name']))
113
172
        # NOTE(vish): The standard _try_execute does not work here
114
173
        #             because these methods throw errors if other
115
174
        #             volumes on this host are in the process of
123
182
        yield self._execute("sudo vblade-persist start all",
124
183
                            check_exit_code=False)
125
184
 
 
185
    @defer.inlineCallbacks
 
186
    def remove_export(self, context, volume):
 
187
        """Removes an export for a logical volume."""
 
188
        (shelf_id,
 
189
         blade_id) = self.db.volume_get_shelf_and_blade(context,
 
190
                                                        volume['id'])
 
191
        yield self._try_execute("sudo vblade-persist stop %s %s" %
 
192
                                (shelf_id, blade_id))
 
193
        yield self._try_execute("sudo vblade-persist destroy %s %s" %
 
194
                                (shelf_id, blade_id))
 
195
 
 
196
    @defer.inlineCallbacks
 
197
    def discover_volume(self, _volume):
 
198
        """Discover volume on a remote host."""
 
199
        yield self._execute("sudo aoe-discover")
 
200
        yield self._execute("sudo aoe-stat", check_exit_code=False)
 
201
 
 
202
    @defer.inlineCallbacks
 
203
    def undiscover_volume(self, _volume):
 
204
        """Undiscover volume on a remote host."""
 
205
        yield
 
206
 
126
207
 
127
208
class FakeAOEDriver(AOEDriver):
128
 
    """Logs calls instead of executing"""
 
209
    """Logs calls instead of executing."""
 
210
 
129
211
    def __init__(self, *args, **kwargs):
130
 
        super(FakeAOEDriver, self).__init__(self.fake_execute)
 
212
        super(FakeAOEDriver, self).__init__(execute=self.fake_execute,
 
213
                                            sync_exec=self.fake_execute,
 
214
                                            *args, **kwargs)
 
215
 
 
216
    def check_for_setup_error(self):
 
217
        """No setup necessary in fake mode."""
 
218
        pass
131
219
 
132
220
    @staticmethod
133
221
    def fake_execute(cmd, *_args, **_kwargs):
134
 
        """Execute that simply logs the command"""
 
222
        """Execute that simply logs the command."""
135
223
        logging.debug("FAKE AOE: %s", cmd)
 
224
        return (None, None)
 
225
 
 
226
 
 
227
class ISCSIDriver(VolumeDriver):
 
228
    """Executes commands relating to ISCSI volumes."""
 
229
 
 
230
    def ensure_export(self, context, volume):
 
231
        """Synchronously recreates an export for a logical volume."""
 
232
        iscsi_target = self.db.volume_get_iscsi_target_num(context,
 
233
                                                           volume['id'])
 
234
        iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name'])
 
235
        volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name'])
 
236
        self._sync_exec("sudo ietadm --op new "
 
237
                        "--tid=%s --params Name=%s" %
 
238
                        (iscsi_target, iscsi_name),
 
239
                        check_exit_code=False)
 
240
        self._sync_exec("sudo ietadm --op new --tid=%s "
 
241
                        "--lun=0 --params Path=%s,Type=fileio" %
 
242
                        (iscsi_target, volume_path),
 
243
                        check_exit_code=False)
 
244
 
 
245
    def _ensure_iscsi_targets(self, context, host):
 
246
        """Ensure that target ids have been created in datastore."""
 
247
        host_iscsi_targets = self.db.iscsi_target_count_by_host(context, host)
 
248
        if host_iscsi_targets >= FLAGS.iscsi_num_targets:
 
249
            return
 
250
        # NOTE(vish): Target ids start at 1, not 0.
 
251
        for target_num in xrange(1, FLAGS.iscsi_num_targets + 1):
 
252
            target = {'host': host, 'target_num': target_num}
 
253
            self.db.iscsi_target_create_safe(context, target)
 
254
 
 
255
    @defer.inlineCallbacks
 
256
    def create_export(self, context, volume):
 
257
        """Creates an export for a logical volume."""
 
258
        self._ensure_iscsi_targets(context, volume['host'])
 
259
        iscsi_target = self.db.volume_allocate_iscsi_target(context,
 
260
                                                      volume['id'],
 
261
                                                      volume['host'])
 
262
        iscsi_name = "%s%s" % (FLAGS.iscsi_target_prefix, volume['name'])
 
263
        volume_path = "/dev/%s/%s" % (FLAGS.volume_group, volume['name'])
 
264
        yield self._execute("sudo ietadm --op new "
 
265
                            "--tid=%s --params Name=%s" %
 
266
                            (iscsi_target, iscsi_name))
 
267
        yield self._execute("sudo ietadm --op new --tid=%s "
 
268
                            "--lun=0 --params Path=%s,Type=fileio" %
 
269
                            (iscsi_target, volume_path))
 
270
 
 
271
    @defer.inlineCallbacks
 
272
    def remove_export(self, context, volume):
 
273
        """Removes an export for a logical volume."""
 
274
        iscsi_target = self.db.volume_get_iscsi_target_num(context,
 
275
                                                           volume['id'])
 
276
        yield self._execute("sudo ietadm --op delete --tid=%s "
 
277
                            "--lun=0" % iscsi_target)
 
278
        yield self._execute("sudo ietadm --op delete --tid=%s" %
 
279
                            iscsi_target)
 
280
 
 
281
    @defer.inlineCallbacks
 
282
    def _get_name_and_portal(self, volume_name, host):
 
283
        """Gets iscsi name and portal from volume name and host."""
 
284
        (out, _err) = yield self._execute("sudo iscsiadm -m discovery -t "
 
285
                                         "sendtargets -p %s" % host)
 
286
        for target in out.splitlines():
 
287
            if FLAGS.iscsi_ip_prefix in target and volume_name in target:
 
288
                (location, _sep, iscsi_name) = target.partition(" ")
 
289
                break
 
290
        iscsi_portal = location.split(",")[0]
 
291
        defer.returnValue((iscsi_name, iscsi_portal))
 
292
 
 
293
    @defer.inlineCallbacks
 
294
    def discover_volume(self, volume):
 
295
        """Discover volume on a remote host."""
 
296
        (iscsi_name,
 
297
         iscsi_portal) = yield self._get_name_and_portal(volume['name'],
 
298
                                                         volume['host'])
 
299
        yield self._execute("sudo iscsiadm -m node -T %s -p %s --login" %
 
300
                            (iscsi_name, iscsi_portal))
 
301
        yield self._execute("sudo iscsiadm -m node -T %s -p %s --op update "
 
302
                            "-n node.startup -v automatic" %
 
303
                            (iscsi_name, iscsi_portal))
 
304
        defer.returnValue("/dev/iscsi/%s" % volume['name'])
 
305
 
 
306
    @defer.inlineCallbacks
 
307
    def undiscover_volume(self, volume):
 
308
        """Undiscover volume on a remote host."""
 
309
        (iscsi_name,
 
310
         iscsi_portal) = yield self._get_name_and_portal(volume['name'],
 
311
                                                         volume['host'])
 
312
        yield self._execute("sudo iscsiadm -m node -T %s -p %s --op update "
 
313
                            "-n node.startup -v manual" %
 
314
                            (iscsi_name, iscsi_portal))
 
315
        yield self._execute("sudo iscsiadm -m node -T %s -p %s --logout " %
 
316
                            (iscsi_name, iscsi_portal))
 
317
        yield self._execute("sudo iscsiadm -m node --op delete "
 
318
                            "--targetname %s" % iscsi_name)
 
319
 
 
320
 
 
321
class FakeISCSIDriver(ISCSIDriver):
 
322
    """Logs calls instead of executing."""
 
323
    def __init__(self, *args, **kwargs):
 
324
        super(FakeISCSIDriver, self).__init__(execute=self.fake_execute,
 
325
                                              sync_exec=self.fake_execute,
 
326
                                              *args, **kwargs)
 
327
 
 
328
    def check_for_setup_error(self):
 
329
        """No setup necessary in fake mode."""
 
330
        pass
 
331
 
 
332
    @staticmethod
 
333
    def fake_execute(cmd, *_args, **_kwargs):
 
334
        """Execute that simply logs the command."""
 
335
        logging.debug("FAKE ISCSI: %s", cmd)
 
336
        return (None, None)