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')
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',
43
'Number of vblade shelves')
44
flags.DEFINE_integer('blades_per_shelf',
46
'Number of vblade blades per shelf')
47
flags.DEFINE_integer('iscsi_num_targets',
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')
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
44
62
self._execute = execute
63
self._sync_exec = sync_exec
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)
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"
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)
90
def create_volume(self, volume):
91
"""Creates a logical volume."""
92
if int(volume['size']) == 0:
72
sizestr = '%sG' % size
95
sizestr = '%sG' % volume['size']
73
96
yield self._try_execute("sudo lvcreate -L %s -n %s %s" %
76
99
FLAGS.volume_group))
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,
85
@defer.inlineCallbacks
86
def create_export(self, volume_name, shelf_id, blade_id):
87
"""Creates an export for a logical volume"""
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,
116
def ensure_export(self, context, volume):
117
"""Synchronously recreates an export for a logical volume."""
118
raise NotImplementedError()
120
@defer.inlineCallbacks
121
def create_export(self, context, volume):
122
"""Exports the volume."""
123
raise NotImplementedError()
125
@defer.inlineCallbacks
126
def remove_export(self, context, volume):
127
"""Removes an export for a logical volume."""
128
raise NotImplementedError()
130
@defer.inlineCallbacks
131
def discover_volume(self, volume):
132
"""Discover volume on a remote host."""
133
raise NotImplementedError()
135
@defer.inlineCallbacks
136
def undiscover_volume(self, volume):
137
"""Undiscover volume on a remote host."""
138
raise NotImplementedError()
141
class AOEDriver(VolumeDriver):
142
"""Implements AOE specific volume commands."""
144
def ensure_export(self, context, volume):
145
# NOTE(vish): we depend on vblade-persist for recreating exports
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:
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)
158
@defer.inlineCallbacks
159
def create_export(self, context, volume):
160
"""Creates an export for a logical volume."""
161
self._ensure_blades(context)
163
blade_id) = self.db.volume_allocate_shelf_and_blade(context,
88
165
yield self._try_execute(
89
166
"sudo vblade-persist setup %s %s %s /dev/%s/%s" %
92
169
FLAGS.aoe_eth_dev,
93
170
FLAGS.volume_group,
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")
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))
110
@defer.inlineCallbacks
111
def ensure_exports(self):
112
"""Runs all existing exports"""
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)
185
@defer.inlineCallbacks
186
def remove_export(self, context, volume):
187
"""Removes an export for a logical volume."""
189
blade_id) = self.db.volume_get_shelf_and_blade(context,
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))
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)
202
@defer.inlineCallbacks
203
def undiscover_volume(self, _volume):
204
"""Undiscover volume on a remote host."""
127
208
class FakeAOEDriver(AOEDriver):
128
"""Logs calls instead of executing"""
209
"""Logs calls instead of executing."""
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,
216
def check_for_setup_error(self):
217
"""No setup necessary in fake mode."""
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)
227
class ISCSIDriver(VolumeDriver):
228
"""Executes commands relating to ISCSI volumes."""
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,
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)
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:
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)
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,
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))
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,
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" %
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(" ")
290
iscsi_portal = location.split(",")[0]
291
defer.returnValue((iscsi_name, iscsi_portal))
293
@defer.inlineCallbacks
294
def discover_volume(self, volume):
295
"""Discover volume on a remote host."""
297
iscsi_portal) = yield self._get_name_and_portal(volume['name'],
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'])
306
@defer.inlineCallbacks
307
def undiscover_volume(self, volume):
308
"""Undiscover volume on a remote host."""
310
iscsi_portal) = yield self._get_name_and_portal(volume['name'],
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)
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,
328
def check_for_setup_error(self):
329
"""No setup necessary in fake mode."""
333
def fake_execute(cmd, *_args, **_kwargs):
334
"""Execute that simply logs the command."""
335
logging.debug("FAKE ISCSI: %s", cmd)