~ubuntu-branches/ubuntu/raring/nova/raring-proposed

« back to all changes in this revision

Viewing changes to nova/virt/baremetal/volume_driver.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2013-01-11 13:06:56 UTC
  • mto: This revision was merged to the branch mainline in revision 96.
  • Revision ID: package-import@ubuntu.com-20130111130656-z9mceux6qpkqomma
Tags: upstream-2013.1~g2
ImportĀ upstreamĀ versionĀ 2013.1~g2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
# coding=utf-8
 
3
 
 
4
# Copyright (c) 2012 NTT DOCOMO, INC.
 
5
# All Rights Reserved.
 
6
#
 
7
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
8
#    not use this file except in compliance with the License. You may obtain
 
9
#    a copy of the License at
 
10
#
 
11
#         http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
13
#    Unless required by applicable law or agreed to in writing, software
 
14
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
15
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
16
#    License for the specific language governing permissions and limitations
 
17
#    under the License.
 
18
 
 
19
import re
 
20
 
 
21
from nova import context as nova_context
 
22
from nova import exception
 
23
from nova.openstack.common import cfg
 
24
from nova.openstack.common import importutils
 
25
from nova.openstack.common import log as logging
 
26
from nova import utils
 
27
from nova.virt.baremetal import db as bmdb
 
28
from nova.virt.libvirt import utils as libvirt_utils
 
29
 
 
30
opts = [
 
31
    cfg.BoolOpt('use_unsafe_iscsi',
 
32
                 default=False,
 
33
                 help='Do not set this out of dev/test environments. '
 
34
                      'If a node does not have an fixed PXE IP address, '
 
35
                      'volumes are exported with globally opened ACL'),
 
36
    cfg.StrOpt('iscsi_iqn_prefix',
 
37
               default='iqn.2010-10.org.openstack.baremetal',
 
38
               help='iSCSI IQN prefix used in baremetal volume connections.'),
 
39
    ]
 
40
 
 
41
baremetal_group = cfg.OptGroup(name='baremetal',
 
42
                               title='Baremetal Options')
 
43
 
 
44
CONF = cfg.CONF
 
45
CONF.register_group(baremetal_group)
 
46
CONF.register_opts(opts, baremetal_group)
 
47
 
 
48
CONF.import_opt('libvirt_volume_drivers', 'nova.virt.libvirt.driver')
 
49
 
 
50
LOG = logging.getLogger(__name__)
 
51
 
 
52
 
 
53
def _get_baremetal_node_by_instance_uuid(instance_uuid):
 
54
    context = nova_context.get_admin_context()
 
55
    return bmdb.bm_node_get_by_instance_uuid(context, instance_uuid)
 
56
 
 
57
 
 
58
def _create_iscsi_export_tgtadm(path, tid, iqn):
 
59
    utils.execute('tgtadm', '--lld', 'iscsi',
 
60
                  '--mode', 'target',
 
61
                  '--op', 'new',
 
62
                  '--tid', tid,
 
63
                  '--targetname', iqn,
 
64
                  run_as_root=True)
 
65
    utils.execute('tgtadm', '--lld', 'iscsi',
 
66
                  '--mode', 'logicalunit',
 
67
                  '--op', 'new',
 
68
                  '--tid', tid,
 
69
                  '--lun', '1',
 
70
                  '--backing-store', path,
 
71
                  run_as_root=True)
 
72
 
 
73
 
 
74
def _allow_iscsi_tgtadm(tid, address):
 
75
    utils.execute('tgtadm', '--lld', 'iscsi',
 
76
                  '--mode', 'target',
 
77
                  '--op', 'bind',
 
78
                  '--tid', tid,
 
79
                  '--initiator-address', address,
 
80
                  run_as_root=True)
 
81
 
 
82
 
 
83
def _delete_iscsi_export_tgtadm(tid):
 
84
    try:
 
85
        utils.execute('tgtadm', '--lld', 'iscsi',
 
86
                      '--mode', 'logicalunit',
 
87
                      '--op', 'delete',
 
88
                      '--tid', tid,
 
89
                      '--lun', '1',
 
90
                      run_as_root=True)
 
91
    except exception.ProcessExecutionError:
 
92
        pass
 
93
    try:
 
94
        utils.execute('tgtadm', '--lld', 'iscsi',
 
95
                      '--mode', 'target',
 
96
                      '--op', 'delete',
 
97
                      '--tid', tid,
 
98
                      run_as_root=True)
 
99
    except exception.ProcessExecutionError:
 
100
        pass
 
101
    # Check if the tid is deleted, that is, check the tid no longer exists.
 
102
    # If the tid dose not exist, tgtadm returns with exit_code 22.
 
103
    # utils.execute() can check the exit_code if check_exit_code parameter is
 
104
    # passed. But, regardless of whether check_exit_code contains 0 or not,
 
105
    # if the exit_code is 0, the function dose not report errors. So we have to
 
106
    # catch a ProcessExecutionError and test its exit_code is 22.
 
107
    try:
 
108
        utils.execute('tgtadm', '--lld', 'iscsi',
 
109
                      '--mode', 'target',
 
110
                      '--op', 'show',
 
111
                      '--tid', tid,
 
112
                      run_as_root=True)
 
113
    except exception.ProcessExecutionError as e:
 
114
        if e.exit_code == 22:
 
115
            # OK, the tid is deleted
 
116
            return
 
117
        raise
 
118
    raise exception.NovaException(_(
 
119
            'baremetal driver was unable to delete tid %s') % tid)
 
120
 
 
121
 
 
122
def _show_tgtadm():
 
123
    out, _ = utils.execute('tgtadm', '--lld', 'iscsi',
 
124
                           '--mode', 'target',
 
125
                           '--op', 'show',
 
126
                           run_as_root=True)
 
127
    return out
 
128
 
 
129
 
 
130
def _list_backingstore_path():
 
131
    out = _show_tgtadm()
 
132
    l = []
 
133
    for line in out.split('\n'):
 
134
        m = re.search(r'Backing store path: (.*)$', line)
 
135
        if m:
 
136
            if '/' in m.group(1):
 
137
                l.append(m.group(1))
 
138
    return l
 
139
 
 
140
 
 
141
def _get_next_tid():
 
142
    out = _show_tgtadm()
 
143
    last_tid = 0
 
144
    for line in out.split('\n'):
 
145
        m = re.search(r'^Target (\d+):', line)
 
146
        if m:
 
147
            tid = int(m.group(1))
 
148
            if last_tid < tid:
 
149
                last_tid = tid
 
150
    return last_tid + 1
 
151
 
 
152
 
 
153
def _find_tid(iqn):
 
154
    out = _show_tgtadm()
 
155
    pattern = r'^Target (\d+): *' + re.escape(iqn)
 
156
    for line in out.split('\n'):
 
157
        m = re.search(pattern, line)
 
158
        if m:
 
159
            return int(m.group(1))
 
160
    return None
 
161
 
 
162
 
 
163
def _get_iqn(instance_name, mountpoint):
 
164
    mp = mountpoint.replace('/', '-').strip('-')
 
165
    iqn = '%s:%s-%s' % (CONF.baremetal.iscsi_iqn_prefix,
 
166
                        instance_name,
 
167
                        mp)
 
168
    return iqn
 
169
 
 
170
 
 
171
class VolumeDriver(object):
 
172
 
 
173
    def __init__(self, virtapi):
 
174
        super(VolumeDriver, self).__init__()
 
175
        self.virtapi = virtapi
 
176
        self._initiator = None
 
177
 
 
178
    def get_volume_connector(self, instance):
 
179
        if not self._initiator:
 
180
            self._initiator = libvirt_utils.get_iscsi_initiator()
 
181
            if not self._initiator:
 
182
                LOG.warn(_('Could not determine iscsi initiator name '
 
183
                           'for instance %s') % instance)
 
184
        return {
 
185
            'ip': CONF.my_ip,
 
186
            'initiator': self._initiator,
 
187
            'host': CONF.host,
 
188
        }
 
189
 
 
190
    def attach_volume(self, connection_info, instance, mountpoint):
 
191
        raise NotImplementedError()
 
192
 
 
193
    def detach_volume(self, connection_info, instance, mountpoint):
 
194
        raise NotImplementedError()
 
195
 
 
196
 
 
197
class LibvirtVolumeDriver(VolumeDriver):
 
198
    """The VolumeDriver deligates to nova.virt.libvirt.volume."""
 
199
 
 
200
    def __init__(self, virtapi):
 
201
        super(LibvirtVolumeDriver, self).__init__(virtapi)
 
202
        self.volume_drivers = {}
 
203
        for driver_str in CONF.libvirt_volume_drivers:
 
204
            driver_type, _sep, driver = driver_str.partition('=')
 
205
            driver_class = importutils.import_class(driver)
 
206
            self.volume_drivers[driver_type] = driver_class(self)
 
207
 
 
208
    def _volume_driver_method(self, method_name, connection_info,
 
209
                             *args, **kwargs):
 
210
        driver_type = connection_info.get('driver_volume_type')
 
211
        if not driver_type in self.volume_drivers:
 
212
            raise exception.VolumeDriverNotFound(driver_type=driver_type)
 
213
        driver = self.volume_drivers[driver_type]
 
214
        method = getattr(driver, method_name)
 
215
        return method(connection_info, *args, **kwargs)
 
216
 
 
217
    def attach_volume(self, connection_info, instance, mountpoint):
 
218
        node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
 
219
        ctx = nova_context.get_admin_context()
 
220
        pxe_ip = bmdb.bm_pxe_ip_get_by_bm_node_id(ctx, node['id'])
 
221
        if not pxe_ip:
 
222
            if not CONF.baremetal.use_unsafe_iscsi:
 
223
                raise exception.NovaException(_(
 
224
                    'No fixed PXE IP is associated to %s') % instance['uuid'])
 
225
 
 
226
        mount_device = mountpoint.rpartition("/")[2]
 
227
        self._volume_driver_method('connect_volume',
 
228
                                   connection_info,
 
229
                                   mount_device)
 
230
        device_path = connection_info['data']['device_path']
 
231
        iqn = _get_iqn(instance['name'], mountpoint)
 
232
        tid = _get_next_tid()
 
233
        _create_iscsi_export_tgtadm(device_path, tid, iqn)
 
234
 
 
235
        if pxe_ip:
 
236
            _allow_iscsi_tgtadm(tid, pxe_ip['address'])
 
237
        else:
 
238
            # NOTE(NTTdocomo): Since nova-compute does not know the
 
239
            # instance's initiator ip, it allows any initiators
 
240
            # to connect to the volume. This means other bare-metal
 
241
            # instances that are not attached the volume can connect
 
242
            # to the volume. Do not set CONF.baremetal.use_unsafe_iscsi
 
243
            # out of dev/test environments.
 
244
            # TODO(NTTdocomo): support CHAP
 
245
            _allow_iscsi_tgtadm(tid, 'ALL')
 
246
 
 
247
    @exception.wrap_exception()
 
248
    def detach_volume(self, connection_info, instance, mountpoint):
 
249
        mount_device = mountpoint.rpartition("/")[2]
 
250
        try:
 
251
            iqn = _get_iqn(instance['name'], mountpoint)
 
252
            tid = _find_tid(iqn)
 
253
            if tid is not None:
 
254
                _delete_iscsi_export_tgtadm(tid)
 
255
            else:
 
256
                LOG.warn(_('detach volume could not find tid for %s') % iqn)
 
257
        finally:
 
258
            self._volume_driver_method('disconnect_volume',
 
259
                                       connection_info,
 
260
                                       mount_device)
 
261
 
 
262
    def get_all_block_devices(self):
 
263
        """
 
264
        Return all block devices in use on this node.
 
265
        """
 
266
        return _list_backingstore_path()