~ubuntu-cloud-archive/ubuntu/precise/cinder/trunk

« back to all changes in this revision

Viewing changes to cinder/volume/drivers/san/san.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-11-23 08:39:28 UTC
  • mfrom: (1.1.9)
  • Revision ID: package-import@ubuntu.com-20121123083928-xvzet603cjfj9p1t
Tags: 2013.1~g1-0ubuntu1
* New upstream release.
* debian/patches/avoid_setuptools_git_dependency.patch:
  Avoid git installation. (LP: #1075948) 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2011 Justin Santa Barbara
 
4
# All Rights Reserved.
 
5
#
 
6
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
7
#    not use this file except in compliance with the License. You may obtain
 
8
#    a copy of the License at
 
9
#
 
10
#         http://www.apache.org/licenses/LICENSE-2.0
 
11
#
 
12
#    Unless required by applicable law or agreed to in writing, software
 
13
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
14
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
15
#    License for the specific language governing permissions and limitations
 
16
#    under the License.
 
17
"""
 
18
Default Driver for san-stored volumes.
 
19
 
 
20
The unique thing about a SAN is that we don't expect that we can run the volume
 
21
controller on the SAN hardware.  We expect to access it over SSH or some API.
 
22
"""
 
23
 
 
24
import paramiko
 
25
import random
 
26
 
 
27
from eventlet import greenthread
 
28
 
 
29
from cinder import exception
 
30
from cinder import flags
 
31
from cinder.openstack.common import cfg
 
32
from cinder.openstack.common import log as logging
 
33
from cinder import utils
 
34
from cinder.volume.driver import ISCSIDriver
 
35
 
 
36
 
 
37
LOG = logging.getLogger(__name__)
 
38
 
 
39
san_opts = [
 
40
    cfg.BoolOpt('san_thin_provision',
 
41
                default=True,
 
42
                help='Use thin provisioning for SAN volumes?'),
 
43
    cfg.StrOpt('san_ip',
 
44
               default='',
 
45
               help='IP address of SAN controller'),
 
46
    cfg.StrOpt('san_login',
 
47
               default='admin',
 
48
               help='Username for SAN controller'),
 
49
    cfg.StrOpt('san_password',
 
50
               default='',
 
51
               help='Password for SAN controller'),
 
52
    cfg.StrOpt('san_private_key',
 
53
               default='',
 
54
               help='Filename of private key to use for SSH authentication'),
 
55
    cfg.StrOpt('san_clustername',
 
56
               default='',
 
57
               help='Cluster name to use for creating volumes'),
 
58
    cfg.IntOpt('san_ssh_port',
 
59
               default=22,
 
60
               help='SSH port to use with SAN'),
 
61
    cfg.BoolOpt('san_is_local',
 
62
                default=False,
 
63
                help='Execute commands locally instead of over SSH; '
 
64
                     'use if the volume service is running on the SAN device'),
 
65
    cfg.IntOpt('ssh_conn_timeout',
 
66
               default=30,
 
67
               help="SSH connection timeout in seconds"),
 
68
    cfg.IntOpt('ssh_min_pool_conn',
 
69
               default=1,
 
70
               help='Minimum ssh connections in the pool'),
 
71
    cfg.IntOpt('ssh_max_pool_conn',
 
72
               default=5,
 
73
               help='Maximum ssh connections in the pool'),
 
74
]
 
75
 
 
76
FLAGS = flags.FLAGS
 
77
FLAGS.register_opts(san_opts)
 
78
 
 
79
 
 
80
class SanISCSIDriver(ISCSIDriver):
 
81
    """Base class for SAN-style storage volumes
 
82
 
 
83
    A SAN-style storage value is 'different' because the volume controller
 
84
    probably won't run on it, so we need to access is over SSH or another
 
85
    remote protocol.
 
86
    """
 
87
 
 
88
    def __init__(self, *args, **kwargs):
 
89
        super(SanISCSIDriver, self).__init__(*args, **kwargs)
 
90
        self.run_local = FLAGS.san_is_local
 
91
        self.sshpool = None
 
92
 
 
93
    def _build_iscsi_target_name(self, volume):
 
94
        return "%s%s" % (FLAGS.iscsi_target_prefix, volume['name'])
 
95
 
 
96
    def _execute(self, *cmd, **kwargs):
 
97
        if self.run_local:
 
98
            return utils.execute(*cmd, **kwargs)
 
99
        else:
 
100
            check_exit_code = kwargs.pop('check_exit_code', None)
 
101
            command = ' '.join(cmd)
 
102
            return self._run_ssh(command, check_exit_code)
 
103
 
 
104
    def _run_ssh(self, command, check_exit_code=True, attempts=1):
 
105
        if not self.sshpool:
 
106
            self.sshpool = utils.SSHPool(FLAGS.san_ip,
 
107
                                         FLAGS.san_ssh_port,
 
108
                                         FLAGS.ssh_conn_timeout,
 
109
                                         FLAGS.san_login,
 
110
                                         password=FLAGS.san_password,
 
111
                                         privatekey=FLAGS.san_private_key,
 
112
                                         min_size=FLAGS.ssh_min_pool_conn,
 
113
                                         max_size=FLAGS.ssh_max_pool_conn)
 
114
        try:
 
115
            total_attempts = attempts
 
116
            with self.sshpool.item() as ssh:
 
117
                while attempts > 0:
 
118
                    attempts -= 1
 
119
                    try:
 
120
                        return utils.ssh_execute(ssh, command,
 
121
                                               check_exit_code=check_exit_code)
 
122
                    except Exception as e:
 
123
                        LOG.error(e)
 
124
                        greenthread.sleep(random.randint(20, 500) / 100.0)
 
125
                raise paramiko.SSHException(_("SSH Command failed after "
 
126
                                              "'%(total_attempts)r' attempts"
 
127
                                              ": '%(command)s'"), locals())
 
128
        except Exception as e:
 
129
            LOG.error(_("Error running ssh command: %s") % command)
 
130
            raise e
 
131
 
 
132
    def ensure_export(self, context, volume):
 
133
        """Synchronously recreates an export for a logical volume."""
 
134
        pass
 
135
 
 
136
    def create_export(self, context, volume):
 
137
        """Exports the volume."""
 
138
        pass
 
139
 
 
140
    def remove_export(self, context, volume):
 
141
        """Removes an export for a logical volume."""
 
142
        pass
 
143
 
 
144
    def check_for_setup_error(self):
 
145
        """Returns an error if prerequisites aren't met."""
 
146
        if not self.run_local:
 
147
            if not (FLAGS.san_password or FLAGS.san_private_key):
 
148
                raise exception.InvalidInput(
 
149
                    reason=_('Specify san_password or san_private_key'))
 
150
 
 
151
        # The san_ip must always be set, because we use it for the target
 
152
        if not FLAGS.san_ip:
 
153
            raise exception.InvalidInput(reason=_("san_ip must be set"))