Statistics
| Branch: | Revision:

root / svc / storwize_svc.py @ bc598589

History | View | Annotate | Download (31.7 kB)

1 2eba2338 Stratos Psomadakis
# vim: tabstop=4 shiftwidth=4 softtabstop=4
2 2eba2338 Stratos Psomadakis
3 2eba2338 Stratos Psomadakis
# Copyright 2013 GRNET S.A.
4 2eba2338 Stratos Psomadakis
# Copyright 2013 IBM Corp.
5 2eba2338 Stratos Psomadakis
# Copyright 2012 OpenStack LLC.
6 2eba2338 Stratos Psomadakis
# All Rights Reserved.
7 2eba2338 Stratos Psomadakis
#
8 2eba2338 Stratos Psomadakis
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
9 2eba2338 Stratos Psomadakis
#    not use this file except in compliance with the License. You may obtain
10 2eba2338 Stratos Psomadakis
#    a copy of the License at
11 2eba2338 Stratos Psomadakis
#
12 2eba2338 Stratos Psomadakis
#         http://www.apache.org/licenses/LICENSE-2.0
13 2eba2338 Stratos Psomadakis
#
14 2eba2338 Stratos Psomadakis
#    Unless required by applicable law or agreed to in writing, software
15 2eba2338 Stratos Psomadakis
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 2eba2338 Stratos Psomadakis
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 2eba2338 Stratos Psomadakis
#    License for the specific language governing permissions and limitations
18 2eba2338 Stratos Psomadakis
#    under the License.
19 2eba2338 Stratos Psomadakis
#
20 2eba2338 Stratos Psomadakis
# Authors:
21 2eba2338 Stratos Psomadakis
#   Stratos Psomadakis <psomas@grnet.gr>
22 2eba2338 Stratos Psomadakis
#   Ronen Kat <ronenkat@il.ibm.com>
23 2eba2338 Stratos Psomadakis
#   Avishay Traeger <avishay@il.ibm.com>
24 2eba2338 Stratos Psomadakis
25 2eba2338 Stratos Psomadakis
""" Ganeti extstorage provider for IBM Storwize family and SVC storage systems
26 2eba2338 Stratos Psomadakis
(ported from the Cinder Storwize / SVC volume driver).
27 2eba2338 Stratos Psomadakis

28 2eba2338 Stratos Psomadakis
The script takes its input from environment variables. Specifically the
29 2eba2338 Stratos Psomadakis
following variables should be present:
30 2eba2338 Stratos Psomadakis

31 2eba2338 Stratos Psomadakis
 - VOL_NAME: The name of the new Image file
32 2eba2338 Stratos Psomadakis
 - EXTP_SVC_SAN: The provider's configuration file section / identifier for the
33 2eba2338 Stratos Psomadakis
                 SVC SAN
34 2eba2338 Stratos Psomadakis

35 2eba2338 Stratos Psomadakis
The following environmental variables are optional and should be present only
36 2eba2338 Stratos Psomadakis
for specific commands:
37 2eba2338 Stratos Psomadakis

38 2eba2338 Stratos Psomadakis
 - VOL_SIZE: The size of the Image file
39 2eba2338 Stratos Psomadakis

40 2eba2338 Stratos Psomadakis
Based on the EXTP_SVC_SAN variable, the script will also read the SVC
41 2eba2338 Stratos Psomadakis
configration parameters / options from the corresponding section of the INI SVC
42 2eba2338 Stratos Psomadakis
configuration file. Currently, the config file path is hardcoded in
43 2eba2338 Stratos Psomadakis
SVC_CONFIG_PATH (/etc/ganeti/extstorage/svc.conf). Each SAN / SVC section of
44 2eba2338 Stratos Psomadakis
the INI file, which should set the following options:
45 2eba2338 Stratos Psomadakis
 - host: The management hostname / IP for the SVC
46 2eba2338 Stratos Psomadakis
 - port: The SSH daemon port for the SVC
47 2eba2338 Stratos Psomadakis
 - user: The user to use to connect (SSH) to the SVC
48 2eba2338 Stratos Psomadakis
 - password: The SSH password for the user
49 2eba2338 Stratos Psomadakis
 - pool: The SVC volume pool to use to create vdisks / volumes
50 2eba2338 Stratos Psomadakis
 - host_id: The id of the host in the SVC hosts table
51 2eba2338 Stratos Psomadakis

52 2eba2338 Stratos Psomadakis
Example SAN SVC INI config file:
53 2eba2338 Stratos Psomadakis
    [san1]
54 2eba2338 Stratos Psomadakis
    host = mysvchost1
55 2eba2338 Stratos Psomadakis
    port = mysvcport1
56 2eba2338 Stratos Psomadakis
    user = mysvcuser1
57 2eba2338 Stratos Psomadakis
    password = mysvcpass1
58 2eba2338 Stratos Psomadakis
    pool = mypool1
59 2eba2338 Stratos Psomadakis
    host_id = mysvchostid1
60 2eba2338 Stratos Psomadakis
    [san2]
61 2eba2338 Stratos Psomadakis
    host = mysvchost2
62 2eba2338 Stratos Psomadakis
    port = mysvcport2
63 2eba2338 Stratos Psomadakis
    user = mysvcuser2
64 2eba2338 Stratos Psomadakis
    password = mysvcpass2
65 2eba2338 Stratos Psomadakis
    pool = mypool2
66 2eba2338 Stratos Psomadakis
    host_id = mysvchostid2
67 2eba2338 Stratos Psomadakis

68 2eba2338 Stratos Psomadakis
The code branches to the correct function, depending on the name (sys.argv[0])
69 2eba2338 Stratos Psomadakis
of the executed script (attach, create, etc.).
70 2eba2338 Stratos Psomadakis

71 2eba2338 Stratos Psomadakis
Returns O after successful completion, 1 on failure.
72 2eba2338 Stratos Psomadakis

73 2eba2338 Stratos Psomadakis
Limitations:
74 2eba2338 Stratos Psomadakis
1. The provider expects CLI output in English, error messages may be in a
75 2eba2338 Stratos Psomadakis
   localized format.
76 2eba2338 Stratos Psomadakis
2. iSCSI support, public key authentication and detailed configuration for the
77 2eba2338 Stratos Psomadakis
   SVC pool, volumes etc. not implemented yet.
78 2eba2338 Stratos Psomadakis
3. The current version of the provider makes lots of assumptions about the
79 2eba2338 Stratos Psomadakis
   running environment. It needs to be run on a Debian-like distro with various
80 2eba2338 Stratos Psomadakis
   SCSI tools installed and multipath support as well. Multipath udev triggers
81 2eba2338 Stratos Psomadakis
   should be removed, to ensure reliable operation of the provider.
82 2eba2338 Stratos Psomadakis

83 2eba2338 Stratos Psomadakis
"""
84 2eba2338 Stratos Psomadakis
85 2eba2338 Stratos Psomadakis
import ConfigParser
86 2eba2338 Stratos Psomadakis
import glob
87 2eba2338 Stratos Psomadakis
import os
88 2eba2338 Stratos Psomadakis
import re
89 2eba2338 Stratos Psomadakis
import subprocess
90 2eba2338 Stratos Psomadakis
import sys
91 2eba2338 Stratos Psomadakis
import traceback
92 2eba2338 Stratos Psomadakis
93 2eba2338 Stratos Psomadakis
import paramiko
94 2eba2338 Stratos Psomadakis
95 2eba2338 Stratos Psomadakis
96 2eba2338 Stratos Psomadakis
# Hardcoded path to the SVC (Ganeti) config file
97 2eba2338 Stratos Psomadakis
_SVC_CONFIG_PATH = "/etc/ganeti/extstorage/svc.conf"
98 2eba2338 Stratos Psomadakis
99 2eba2338 Stratos Psomadakis
# Hardcoded pool name, used for assertions
100 2eba2338 Stratos Psomadakis
_SVC_POOL_NAME = "VM_POOL_SYNNEFO"
101 2eba2338 Stratos Psomadakis
102 2eba2338 Stratos Psomadakis
# Hardcoded host id range for HDIKA Synnefo nodes, used for assertions
103 2eba2338 Stratos Psomadakis
_SVC_VALID_RANGE = range(7, 12) + range(19, 23)
104 2eba2338 Stratos Psomadakis
105 2eba2338 Stratos Psomadakis
# Hardcoded timeout for SSH (paramiko), in seconds
106 2eba2338 Stratos Psomadakis
SVC_SSH_TIMEOUT = 30
107 2eba2338 Stratos Psomadakis
108 2eba2338 Stratos Psomadakis
# sysfs FC-related paths and tools, needed for adding (rescanning) and deleting
109 2eba2338 Stratos Psomadakis
# FC disks / volumes.
110 2eba2338 Stratos Psomadakis
_RESCAN_SCSI_CMD = 'rescan-scsi-bus'
111 2eba2338 Stratos Psomadakis
_MULTIPATH_CMD = 'multipath'
112 2eba2338 Stratos Psomadakis
_DMSETUP_CMD = 'dmsetup'
113 2eba2338 Stratos Psomadakis
# Might be needing these below, in the future
114 2eba2338 Stratos Psomadakis
#_SYSTOOL_CMD = 'systool'
115 2eba2338 Stratos Psomadakis
#_SYSFS_FCHOSTS_PATH = '/sys/class/fc_host/'
116 2eba2338 Stratos Psomadakis
#_SYSFS_FCSCAN_PATH = '/sys/class/scsi_host/%(fchost)s/scan'
117 2eba2338 Stratos Psomadakis
#_SYSFS_FCSCAN_COMMAND = '- - -'
118 2eba2338 Stratos Psomadakis
_SYSFS_BLOCK_PATH = '/sys/block/%(blodkdev)s/'
119 2eba2338 Stratos Psomadakis
_SYSFS_FCDELETE_PATH = os.path.join(_SYSFS_BLOCK_PATH, '/device/delete')
120 2eba2338 Stratos Psomadakis
_SYSFS_SLAVES_PATH = os.path.join(_SYSFS_BLOCK_PATH, '/slaves/')
121 2eba2338 Stratos Psomadakis
_DEV_BYPATH = '/dev/disk/by-path/'
122 2eba2338 Stratos Psomadakis
123 2eba2338 Stratos Psomadakis
_SCSI_DEV_PREFIX = '3'
124 2eba2338 Stratos Psomadakis
125 2eba2338 Stratos Psomadakis
126 2eba2338 Stratos Psomadakis
class SVCError(Exception):
127 2eba2338 Stratos Psomadakis
    """ Base exception for SVC extstorage provider.
128 2eba2338 Stratos Psomadakis

129 2eba2338 Stratos Psomadakis
    """
130 2eba2338 Stratos Psomadakis
    pass
131 2eba2338 Stratos Psomadakis
132 2eba2338 Stratos Psomadakis
133 2eba2338 Stratos Psomadakis
class SVCConnection(object):
134 2eba2338 Stratos Psomadakis
    """ SSH connection (paramiko) abstraction class.
135 2eba2338 Stratos Psomadakis

136 2eba2338 Stratos Psomadakis
    """
137 2eba2338 Stratos Psomadakis
    def __init__(self, host, port, user, password):
138 2eba2338 Stratos Psomadakis
        """ Initialize the SVCConnection object
139 2eba2338 Stratos Psomadakis

140 2eba2338 Stratos Psomadakis
        @type host: string
141 2eba2338 Stratos Psomadakis
        @param host: host to connect to
142 2eba2338 Stratos Psomadakis
        @type port: integer
143 2eba2338 Stratos Psomadakis
        @param port: SSH port to connect to
144 2eba2338 Stratos Psomadakis
        @type user: string
145 2eba2338 Stratos Psomadakis
        @param user: SSH user to use
146 2eba2338 Stratos Psomadakis
        @type password: string
147 2eba2338 Stratos Psomadakis
        @param password: SSH password to use
148 2eba2338 Stratos Psomadakis

149 2eba2338 Stratos Psomadakis
        """
150 2eba2338 Stratos Psomadakis
        super(SVCConnection, self).__init__()
151 2eba2338 Stratos Psomadakis
152 2eba2338 Stratos Psomadakis
        self._host = host
153 2eba2338 Stratos Psomadakis
        self._port = port
154 2eba2338 Stratos Psomadakis
        self._user = user
155 2eba2338 Stratos Psomadakis
        self._password = password
156 2eba2338 Stratos Psomadakis
157 2eba2338 Stratos Psomadakis
        self._conn = None
158 2eba2338 Stratos Psomadakis
159 2eba2338 Stratos Psomadakis
    @staticmethod
160 2eba2338 Stratos Psomadakis
    def _check_ssh_injection(cmd_list):
161 2eba2338 Stratos Psomadakis
        """ Check for ssh injection patterns in the cmd list.
162 2eba2338 Stratos Psomadakis

163 2eba2338 Stratos Psomadakis
        @type cmd_list: list of strings
164 2eba2338 Stratos Psomadakis
        @param cmd_list: a list containing the command to be executed plus any
165 2eba2338 Stratos Psomadakis
                         arguments to the command
166 2eba2338 Stratos Psomadakis
        """
167 2eba2338 Stratos Psomadakis
        ssh_injection_pattern = ['`', '$', '|', '||', ';', '&', '&&', '>',
168 2eba2338 Stratos Psomadakis
                                 '>>', '<']
169 2eba2338 Stratos Psomadakis
170 2eba2338 Stratos Psomadakis
        # Check whether injection attacks exist
171 2eba2338 Stratos Psomadakis
        for arg in cmd_list:
172 2eba2338 Stratos Psomadakis
            arg = arg.strip()
173 2eba2338 Stratos Psomadakis
            # First, check no space in the middle of arg
174 2eba2338 Stratos Psomadakis
            arg_len = len(arg.split())
175 2eba2338 Stratos Psomadakis
            if arg_len > 1:
176 2eba2338 Stratos Psomadakis
                raise SVCError("SSH injection detected: %s" % cmd_list)
177 2eba2338 Stratos Psomadakis
178 2eba2338 Stratos Psomadakis
            # Second, check whether danger character in command. So the shell
179 2eba2338 Stratos Psomadakis
            # special operator must be a single argument.
180 2eba2338 Stratos Psomadakis
            for char in ssh_injection_pattern:
181 2eba2338 Stratos Psomadakis
                if arg == char:
182 2eba2338 Stratos Psomadakis
                    continue
183 2eba2338 Stratos Psomadakis
184 2eba2338 Stratos Psomadakis
                result = arg.find(char)
185 2eba2338 Stratos Psomadakis
                if not result == -1:
186 2eba2338 Stratos Psomadakis
                    if result == 0 or not arg[result - 1] == '\\':
187 2eba2338 Stratos Psomadakis
                        raise SVCError("SSH injection detected: %s" % cmd_list)
188 2eba2338 Stratos Psomadakis
189 2eba2338 Stratos Psomadakis
    def _create_connection(self):
190 2eba2338 Stratos Psomadakis
        """ Create a paramiko SSH connection to the SVC.
191 2eba2338 Stratos Psomadakis

192 2eba2338 Stratos Psomadakis
        """
193 2eba2338 Stratos Psomadakis
        self._conn = paramiko.SSHClient()
194 2eba2338 Stratos Psomadakis
        self._conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
195 2eba2338 Stratos Psomadakis
        self._conn.connect(self._host, port=self._port,
196 2eba2338 Stratos Psomadakis
                           username=self._user,
197 2eba2338 Stratos Psomadakis
                           password=self._password,
198 2eba2338 Stratos Psomadakis
                           timeout=SVC_SSH_TIMEOUT,
199 2eba2338 Stratos Psomadakis
                           look_for_keys=False,
200 2eba2338 Stratos Psomadakis
                           allow_agent=False)
201 2eba2338 Stratos Psomadakis
202 2eba2338 Stratos Psomadakis
        transport = self._conn.get_transport()
203 2eba2338 Stratos Psomadakis
        transport.sock.settimeout(None)
204 2eba2338 Stratos Psomadakis
        transport.set_keepalive(SVC_SSH_TIMEOUT)
205 2eba2338 Stratos Psomadakis
206 2eba2338 Stratos Psomadakis
    def run_ssh_cmd(self, cmdlist):
207 2eba2338 Stratos Psomadakis
        """ Connect to host and run the command specified in cmdlist.
208 2eba2338 Stratos Psomadakis

209 2eba2338 Stratos Psomadakis
        @type cmd_list: list of strings
210 2eba2338 Stratos Psomadakis
        @param cmd_list: a list containing the command to be executed plus any
211 2eba2338 Stratos Psomadakis
                         arguments to the command
212 2eba2338 Stratos Psomadakis

213 2eba2338 Stratos Psomadakis
        """
214 2eba2338 Stratos Psomadakis
        self._check_ssh_injection(cmdlist)
215 2eba2338 Stratos Psomadakis
216 2eba2338 Stratos Psomadakis
        cmd = ' '.join(cmdlist)
217 2eba2338 Stratos Psomadakis
218 2eba2338 Stratos Psomadakis
        try:
219 2eba2338 Stratos Psomadakis
            if self._conn:
220 2eba2338 Stratos Psomadakis
                if not self._conn.get_transport().is_active():
221 2eba2338 Stratos Psomadakis
                    self._conn.close()
222 2eba2338 Stratos Psomadakis
                    self._create_connection()
223 2eba2338 Stratos Psomadakis
            else:
224 2eba2338 Stratos Psomadakis
                self._create_connection()
225 2eba2338 Stratos Psomadakis
        except Exception as exc:
226 2eba2338 Stratos Psomadakis
            raise SVCError("%sCannot create SSH connection to SVC: %s"
227 2eba2338 Stratos Psomadakis
                           % (traceback.format_exc(), exc))
228 2eba2338 Stratos Psomadakis
229 2eba2338 Stratos Psomadakis
        conn = self._conn
230 2eba2338 Stratos Psomadakis
        stderr = ''
231 2eba2338 Stratos Psomadakis
232 2eba2338 Stratos Psomadakis
        try:
233 2eba2338 Stratos Psomadakis
            stdin_stream, stdout_stream, stderr_stream = conn.exec_command(cmd)
234 2eba2338 Stratos Psomadakis
            channel = stdout_stream.channel
235 2eba2338 Stratos Psomadakis
236 2eba2338 Stratos Psomadakis
            # NOTE(justinsb): This seems suspicious...
237 2eba2338 Stratos Psomadakis
            # ...other SSH clients have buffering issues with this approach
238 2eba2338 Stratos Psomadakis
            stdout = stdout_stream.read()
239 2eba2338 Stratos Psomadakis
            stderr = stderr_stream.read()
240 2eba2338 Stratos Psomadakis
            stdin_stream.close()
241 2eba2338 Stratos Psomadakis
242 2eba2338 Stratos Psomadakis
            exit_status = channel.recv_exit_status()
243 2eba2338 Stratos Psomadakis
244 2eba2338 Stratos Psomadakis
            if exit_status != 0:
245 2eba2338 Stratos Psomadakis
                raise Exception("Non-zero exit status %s" % exit_status)
246 2eba2338 Stratos Psomadakis
247 2eba2338 Stratos Psomadakis
            return (stdout, stderr)
248 2eba2338 Stratos Psomadakis
        except Exception as exc:
249 2eba2338 Stratos Psomadakis
            raise SVCError("%sCannot run SSH command '%s' on SVC :%s (%s)" %
250 2eba2338 Stratos Psomadakis
                           (traceback.format_exc(), cmd, stderr, exc))
251 2eba2338 Stratos Psomadakis
252 2eba2338 Stratos Psomadakis
253 2eba2338 Stratos Psomadakis
class SVCProvider(object):
254 2eba2338 Stratos Psomadakis
    """ SVC provider class.
255 2eba2338 Stratos Psomadakis

256 2eba2338 Stratos Psomadakis
    """
257 2eba2338 Stratos Psomadakis
258 2eba2338 Stratos Psomadakis
    def __init__(self, config_file):
259 2eba2338 Stratos Psomadakis
        """ Initializes the SVC provider.
260 2eba2338 Stratos Psomadakis

261 2eba2338 Stratos Psomadakis
        The method will read env variables and the SVC ini config file to fill
262 2eba2338 Stratos Psomadakis
        in all the needed info for the SVC provider ops and initialize the
263 2eba2338 Stratos Psomadakis
        SSH connection to the SVC (L{SVCConnection}).
264 2eba2338 Stratos Psomadakis

265 2eba2338 Stratos Psomadakis
        @type config_file: string
266 2eba2338 Stratos Psomadakis
        @param config_file: path to the SVC INI configuration file
267 2eba2338 Stratos Psomadakis

268 2eba2338 Stratos Psomadakis
        """
269 2eba2338 Stratos Psomadakis
        super(SVCProvider, self).__init__()
270 2eba2338 Stratos Psomadakis
271 2eba2338 Stratos Psomadakis
        self._svc_pool = None
272 2eba2338 Stratos Psomadakis
273 2eba2338 Stratos Psomadakis
        self._host_id = None
274 2eba2338 Stratos Psomadakis
275 2eba2338 Stratos Psomadakis
        self._vol_name = None
276 2eba2338 Stratos Psomadakis
        self._vol_size = None
277 2eba2338 Stratos Psomadakis
278 2eba2338 Stratos Psomadakis
        (host, port, user, password) = self._get_params(config_file)
279 2eba2338 Stratos Psomadakis
280 2eba2338 Stratos Psomadakis
        self._svc_connection = SVCConnection(host, port, user, password)
281 2eba2338 Stratos Psomadakis
282 2eba2338 Stratos Psomadakis
    def _parse_svc_config(self, config_file, san):
283 2eba2338 Stratos Psomadakis
        """"Parse the SVC (Ganeti) configuration file.
284 2eba2338 Stratos Psomadakis

285 2eba2338 Stratos Psomadakis
        The config should be in 'INI' format.
286 2eba2338 Stratos Psomadakis

287 2eba2338 Stratos Psomadakis
        @type config_file: string
288 2eba2338 Stratos Psomadakis
        @param config_file: path to the INI config file
289 2eba2338 Stratos Psomadakis
        @type san: string
290 2eba2338 Stratos Psomadakis
        @param san: SVC SAN identifier / section in the INI file
291 2eba2338 Stratos Psomadakis
        @rtype: list of the SVC config file options
292 2eba2338 Stratos Psomadakis
        @return: list of the SVC config file options
293 2eba2338 Stratos Psomadakis

294 2eba2338 Stratos Psomadakis
        """
295 2eba2338 Stratos Psomadakis
        config = ConfigParser.SafeConfigParser()
296 2eba2338 Stratos Psomadakis
297 2eba2338 Stratos Psomadakis
        try:
298 2eba2338 Stratos Psomadakis
            if not config.read(config_file):
299 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error("Unable to read config file")
300 2eba2338 Stratos Psomadakis
301 2eba2338 Stratos Psomadakis
            expected_opts = frozenset(['host', 'port', 'user', 'password',
302 2eba2338 Stratos Psomadakis
                                       'pool', 'host_id'])
303 2eba2338 Stratos Psomadakis
            config_opts = config.options(san)
304 2eba2338 Stratos Psomadakis
305 2eba2338 Stratos Psomadakis
            if frozenset(config_opts) != expected_opts:
306 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error('Malformed config file')
307 2eba2338 Stratos Psomadakis
308 2eba2338 Stratos Psomadakis
            opts = []
309 2eba2338 Stratos Psomadakis
310 2eba2338 Stratos Psomadakis
            host = config.get(san, 'host')
311 2eba2338 Stratos Psomadakis
            if host == '':
312 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error('Bad value for host')
313 2eba2338 Stratos Psomadakis
            opts.append(host)
314 2eba2338 Stratos Psomadakis
315 2eba2338 Stratos Psomadakis
            port = config.get(san, 'port')
316 2eba2338 Stratos Psomadakis
            try:
317 2eba2338 Stratos Psomadakis
                port = int(port)
318 2eba2338 Stratos Psomadakis
                if port <= 0 or port > 65535:
319 2eba2338 Stratos Psomadakis
                    raise ValueError
320 2eba2338 Stratos Psomadakis
            except ValueError:
321 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error('Bad value for port')
322 2eba2338 Stratos Psomadakis
            opts.append(port)
323 2eba2338 Stratos Psomadakis
324 2eba2338 Stratos Psomadakis
            user = config.get(san, 'user')
325 2eba2338 Stratos Psomadakis
            if user == '':
326 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error('Bad value for user')
327 2eba2338 Stratos Psomadakis
            opts.append(user)
328 2eba2338 Stratos Psomadakis
329 2eba2338 Stratos Psomadakis
            password = config.get(san, 'password')
330 2eba2338 Stratos Psomadakis
            if password == '':
331 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error('Bad value for password')
332 2eba2338 Stratos Psomadakis
            opts.append(password)
333 2eba2338 Stratos Psomadakis
334 2eba2338 Stratos Psomadakis
            pool = config.get(san, 'pool')
335 2eba2338 Stratos Psomadakis
            if pool == '':
336 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error('Bad value for pool')
337 2eba2338 Stratos Psomadakis
            self._svc_pool = pool
338 2eba2338 Stratos Psomadakis
339 2eba2338 Stratos Psomadakis
            host_id = config.get(san, 'host_id')
340 2eba2338 Stratos Psomadakis
            try:
341 2eba2338 Stratos Psomadakis
                host_id = int(host_id)
342 2eba2338 Stratos Psomadakis
                if host_id <= 0:
343 2eba2338 Stratos Psomadakis
                    raise ValueError
344 2eba2338 Stratos Psomadakis
            except ValueError:
345 2eba2338 Stratos Psomadakis
                raise ConfigParser.Error('Bad value for host_id')
346 2eba2338 Stratos Psomadakis
            self._host_id = host_id
347 2eba2338 Stratos Psomadakis
        except ConfigParser.Error as config_error:
348 2eba2338 Stratos Psomadakis
            raise SVCError('Error while reading svc config file: %s.\n' %
349 2eba2338 Stratos Psomadakis
                           config_error)
350 2eba2338 Stratos Psomadakis
351 2eba2338 Stratos Psomadakis
        return opts
352 2eba2338 Stratos Psomadakis
353 2eba2338 Stratos Psomadakis
    def _get_params(self, config_file):
354 2eba2338 Stratos Psomadakis
        """ Read env variables and the INI config file.
355 2eba2338 Stratos Psomadakis

356 2eba2338 Stratos Psomadakis
        Return the values of the environmental variables, set by Ganeti, and
357 2eba2338 Stratos Psomadakis
        the SVC configuration parameters set in the SVC (Ganeti) config file.
358 2eba2338 Stratos Psomadakis

359 2eba2338 Stratos Psomadakis
        @type config_file: string
360 2eba2338 Stratos Psomadakis
        @param config_file: path to the INI config file
361 2eba2338 Stratos Psomadakis
        @rtype: list of the SVC config file options
362 2eba2338 Stratos Psomadakis
        @return: list of the SVC config file options
363 2eba2338 Stratos Psomadakis

364 2eba2338 Stratos Psomadakis
        """
365 2eba2338 Stratos Psomadakis
366 2eba2338 Stratos Psomadakis
        self._vol_name = os.getenv("VOL_NAME")
367 2eba2338 Stratos Psomadakis
        if self._vol_name is None:
368 2eba2338 Stratos Psomadakis
            raise SVCError('The environment variable VOL_NAME is missing.\n')
369 2eba2338 Stratos Psomadakis
370 2eba2338 Stratos Psomadakis
        if self._vol_name == '':
371 2eba2338 Stratos Psomadakis
            raise SVCError('Volume name cannot be empty.\n')
372 2eba2338 Stratos Psomadakis
373 2eba2338 Stratos Psomadakis
        self._vol_size = os.getenv("VOL_SIZE")
374 2eba2338 Stratos Psomadakis
        if self._vol_size is not None:
375 2eba2338 Stratos Psomadakis
            try:
376 2eba2338 Stratos Psomadakis
                self._vol_size = int(self._vol_size)
377 2eba2338 Stratos Psomadakis
                if self._vol_size <= 0:
378 2eba2338 Stratos Psomadakis
                    raise ValueError
379 2eba2338 Stratos Psomadakis
            except ValueError:
380 2eba2338 Stratos Psomadakis
                raise SVCError('Volume size must be positive integer')
381 2eba2338 Stratos Psomadakis
382 2eba2338 Stratos Psomadakis
        san = os.getenv("EXTP_SVC_SAN")
383 2eba2338 Stratos Psomadakis
        if san is None:
384 2eba2338 Stratos Psomadakis
            raise SVCError('The environment variable EXTP_SVC_SAN is'
385 2eba2338 Stratos Psomadakis
                           'missing.\n')
386 2eba2338 Stratos Psomadakis
        if san == '':
387 2eba2338 Stratos Psomadakis
            raise SVCError('EXTP_SVC_SAN cannot be none')
388 2eba2338 Stratos Psomadakis
389 2eba2338 Stratos Psomadakis
        return self._parse_svc_config(config_file, san)
390 2eba2338 Stratos Psomadakis
391 2eba2338 Stratos Psomadakis
    @staticmethod
392 2eba2338 Stratos Psomadakis
    def _driver_assert(assert_condition, exception_message):
393 2eba2338 Stratos Psomadakis
        """ Internal assertion mechanism for CLI output.
394 2eba2338 Stratos Psomadakis

395 2eba2338 Stratos Psomadakis
        @type assert_condition: boolean
396 2eba2338 Stratos Psomadakis
        @param assert_condition: condition to assert against
397 2eba2338 Stratos Psomadakis
        @type exception_message: string
398 2eba2338 Stratos Psomadakis
        @param exception_message: exception message to print
399 2eba2338 Stratos Psomadakis

400 2eba2338 Stratos Psomadakis
        """
401 2eba2338 Stratos Psomadakis
        if not assert_condition:
402 2eba2338 Stratos Psomadakis
            raise SVCError(exception_message)
403 2eba2338 Stratos Psomadakis
404 2eba2338 Stratos Psomadakis
    def _assert_ssh_return(self, test, fun, ssh_cmd, out, err):
405 2eba2338 Stratos Psomadakis
        """ Call internal assertion mechanism for SSH cmds.
406 2eba2338 Stratos Psomadakis

407 2eba2338 Stratos Psomadakis
        @type test: boolean
408 2eba2338 Stratos Psomadakis
        @param test: condition to assert against
409 2eba2338 Stratos Psomadakis
        @type fun: object
410 2eba2338 Stratos Psomadakis
        @param fun: function / method which called us
411 2eba2338 Stratos Psomadakis
        @type ssh_cmd: list of strings
412 2eba2338 Stratos Psomadakis
        @param ssh_cmd: list of strings (command plus command arguments)
413 2eba2338 Stratos Psomadakis
        @type out: buffer
414 2eba2338 Stratos Psomadakis
        @param out: stdout buffer
415 2eba2338 Stratos Psomadakis
        @type err: buffer
416 2eba2338 Stratos Psomadakis
        @param err: stderr buffer
417 2eba2338 Stratos Psomadakis

418 2eba2338 Stratos Psomadakis
        """
419 2eba2338 Stratos Psomadakis
        self._driver_assert(
420 2eba2338 Stratos Psomadakis
            test,
421 2eba2338 Stratos Psomadakis
            '%(fun)s: Failed with unexpected CLI output.\n '
422 2eba2338 Stratos Psomadakis
            'Command: %(cmd)s\n stdout: %(out)s\n stderr: %(err)s'
423 2eba2338 Stratos Psomadakis
            % {'fun': fun,
424 2eba2338 Stratos Psomadakis
                'cmd': ssh_cmd,
425 2eba2338 Stratos Psomadakis
                'out': str(out),
426 2eba2338 Stratos Psomadakis
                'err': str(err)})
427 2eba2338 Stratos Psomadakis
428 2eba2338 Stratos Psomadakis
    def _exec_cmd_and_parse_attrs(self, ssh_cmd):
429 2eba2338 Stratos Psomadakis
        """Execute command on the Storwize/SVC and parse attributes.
430 2eba2338 Stratos Psomadakis

431 2eba2338 Stratos Psomadakis
        Exception is raised if the information from the system
432 2eba2338 Stratos Psomadakis
        can not be obtained.
433 2eba2338 Stratos Psomadakis

434 2eba2338 Stratos Psomadakis
        @type ssh_cmd: list of strings
435 2eba2338 Stratos Psomadakis
        @param ssh_cmd: a list of the command and additional arguments
436 2eba2338 Stratos Psomadakis
        @rtype: dict
437 2eba2338 Stratos Psomadakis
        @return: a dictionary containing the attributes returned by ssh cmd
438 2eba2338 Stratos Psomadakis

439 2eba2338 Stratos Psomadakis
        """
440 2eba2338 Stratos Psomadakis
441 2eba2338 Stratos Psomadakis
        out, err = self._svc_connection.run_ssh_cmd(ssh_cmd)
442 2eba2338 Stratos Psomadakis
        self._assert_ssh_return(len(out),
443 2eba2338 Stratos Psomadakis
                                '_exec_cmd_and_parse_attrs',
444 2eba2338 Stratos Psomadakis
                                ssh_cmd, out, err)
445 2eba2338 Stratos Psomadakis
446 2eba2338 Stratos Psomadakis
        attributes = {}
447 2eba2338 Stratos Psomadakis
        for attrib_line in out.split('\n'):
448 2eba2338 Stratos Psomadakis
            # If '!' not found, return the string and two empty strings
449 2eba2338 Stratos Psomadakis
            attrib_name, _, attrib_value = attrib_line.partition('!')
450 2eba2338 Stratos Psomadakis
            if attrib_name is not None and len(attrib_name.strip()):
451 2eba2338 Stratos Psomadakis
                attributes[attrib_name] = attrib_value
452 2eba2338 Stratos Psomadakis
453 2eba2338 Stratos Psomadakis
        return attributes
454 2eba2338 Stratos Psomadakis
455 2eba2338 Stratos Psomadakis
    def _get_hdr_dic(self, header, row, delim):
456 2eba2338 Stratos Psomadakis
        """Return CLI row data as a dictionary indexed by names from header.
457 2eba2338 Stratos Psomadakis

458 2eba2338 Stratos Psomadakis
        The strings are converted to columns using the delimiter in
459 2eba2338 Stratos Psomadakis
        delim.
460 2eba2338 Stratos Psomadakis

461 2eba2338 Stratos Psomadakis
        @type header: string
462 2eba2338 Stratos Psomadakis
        @param header: header string
463 2eba2338 Stratos Psomadakis
        @type row: string
464 2eba2338 Stratos Psomadakis
        @param row: row string
465 2eba2338 Stratos Psomadakis
        @type delim: string
466 2eba2338 Stratos Psomadakis
        @param delim: column delimiter
467 2eba2338 Stratos Psomadakis
        @rtype: dict
468 2eba2338 Stratos Psomadakis
        @return: dictionary containing CLI row data
469 2eba2338 Stratos Psomadakis

470 2eba2338 Stratos Psomadakis
        """
471 2eba2338 Stratos Psomadakis
472 2eba2338 Stratos Psomadakis
        attributes = header.split(delim)
473 2eba2338 Stratos Psomadakis
        values = row.split(delim)
474 2eba2338 Stratos Psomadakis
475 2eba2338 Stratos Psomadakis
        self._driver_assert(
476 2eba2338 Stratos Psomadakis
            len(values) ==
477 2eba2338 Stratos Psomadakis
            len(attributes),
478 2eba2338 Stratos Psomadakis
            '_get_hdr_dic: attribute headers and values do not match.\n '
479 2eba2338 Stratos Psomadakis
            'Headers: %(header)s\n Values: %(row)s'
480 2eba2338 Stratos Psomadakis
            % {'header': str(header),
481 2eba2338 Stratos Psomadakis
               'row': str(row)})
482 2eba2338 Stratos Psomadakis
483 2eba2338 Stratos Psomadakis
        # Is it the same as dict(zip())?
484 2eba2338 Stratos Psomadakis
        #dic = dict((a, v) for a, v in map(None, attributes, values))
485 2eba2338 Stratos Psomadakis
        return dict(zip(attributes, values))
486 2eba2338 Stratos Psomadakis
487 2eba2338 Stratos Psomadakis
    def _get_hostvdisk_mappings(self, host_id):
488 2eba2338 Stratos Psomadakis
        """ Return the defined storage mappings for a host.
489 2eba2338 Stratos Psomadakis

490 2eba2338 Stratos Psomadakis
        @type host_id: integer
491 2eba2338 Stratos Psomadakis
        @param host_id: id of the host in the SVC
492 2eba2338 Stratos Psomadakis
        @rtype: dict
493 2eba2338 Stratos Psomadakis
        @return: dictionary containing the storage mappings for a host
494 2eba2338 Stratos Psomadakis

495 2eba2338 Stratos Psomadakis
        """
496 2eba2338 Stratos Psomadakis
497 2eba2338 Stratos Psomadakis
        return_data = {}
498 2eba2338 Stratos Psomadakis
        ssh_cmd = ['svcinfo', 'lshostvdiskmap', '-delim', '!', str(host_id)]
499 2eba2338 Stratos Psomadakis
        out, _ = self._svc_connection.run_ssh_cmd(ssh_cmd)
500 2eba2338 Stratos Psomadakis
501 2eba2338 Stratos Psomadakis
        mappings = out.strip().split('\n')
502 2eba2338 Stratos Psomadakis
        if len(mappings):
503 2eba2338 Stratos Psomadakis
            header = mappings.pop(0)
504 2eba2338 Stratos Psomadakis
            for mapping_line in mappings:
505 2eba2338 Stratos Psomadakis
                mapping_data = self._get_hdr_dic(header, mapping_line, '!')
506 2eba2338 Stratos Psomadakis
                return_data[mapping_data['vdisk_name']] = mapping_data
507 2eba2338 Stratos Psomadakis
508 2eba2338 Stratos Psomadakis
        return return_data
509 2eba2338 Stratos Psomadakis
510 2eba2338 Stratos Psomadakis
    def _map_vol_to_host(self, volume_name, host_id):
511 2eba2338 Stratos Psomadakis
        """ Create a mapping between a volume to a host.
512 2eba2338 Stratos Psomadakis

513 2eba2338 Stratos Psomadakis
        @type volume_name: string
514 2eba2338 Stratos Psomadakis
        @param volume_name: volume name
515 2eba2338 Stratos Psomadakis
        @type host_id: integer
516 2eba2338 Stratos Psomadakis
        @param host_id: id of the host in the SVC
517 2eba2338 Stratos Psomadakis

518 2eba2338 Stratos Psomadakis
        """
519 2eba2338 Stratos Psomadakis
520 2eba2338 Stratos Psomadakis
        assert(host_id in _SVC_VALID_RANGE)
521 2eba2338 Stratos Psomadakis
        ssh_cmd = ['svctask', 'mkvdiskhostmap', '-host', str(host_id),
522 2eba2338 Stratos Psomadakis
                   volume_name]
523 2eba2338 Stratos Psomadakis
        out, err = self._svc_connection.run_ssh_cmd(ssh_cmd)
524 2eba2338 Stratos Psomadakis
        self._assert_ssh_return('successfully created' in out,
525 2eba2338 Stratos Psomadakis
                                '_map_vol_to_host', ssh_cmd, out, err)
526 2eba2338 Stratos Psomadakis
527 2eba2338 Stratos Psomadakis
    def _unmap_vol_from_host(self, volume_name, host_id):
528 2eba2338 Stratos Psomadakis
        """ Delete a mapping between a volume and a host.
529 2eba2338 Stratos Psomadakis

530 2eba2338 Stratos Psomadakis
        @type volume_name: string
531 2eba2338 Stratos Psomadakis
        @param volume_name: volume name
532 2eba2338 Stratos Psomadakis
        @type host_id: integer
533 2eba2338 Stratos Psomadakis
        @param host_id: id of the host in the SVC
534 2eba2338 Stratos Psomadakis

535 2eba2338 Stratos Psomadakis
        """
536 2eba2338 Stratos Psomadakis
        assert(host_id in _SVC_VALID_RANGE)
537 2eba2338 Stratos Psomadakis
        ssh_cmd = ['svctask', 'rmvdiskhostmap', '-host', str(host_id),
538 2eba2338 Stratos Psomadakis
                   volume_name]
539 2eba2338 Stratos Psomadakis
        out, err = self._svc_connection.run_ssh_cmd(ssh_cmd)
540 2eba2338 Stratos Psomadakis
        # Verify CLI behaviour - no output is returned from
541 2eba2338 Stratos Psomadakis
        # rmvdiskhostmap
542 2eba2338 Stratos Psomadakis
        self._assert_ssh_return(len(out.strip()) == 0,
543 2eba2338 Stratos Psomadakis
                                'unmap_vol_from_host', ssh_cmd, out, err)
544 2eba2338 Stratos Psomadakis
545 2eba2338 Stratos Psomadakis
    def _get_vdisk_attributes(self, vdisk_name):
546 2eba2338 Stratos Psomadakis
        """ Return vdisk attributes, or None if vdisk does not exist.
547 2eba2338 Stratos Psomadakis

548 2eba2338 Stratos Psomadakis
        Exception is raised if the information from system can not be
549 2eba2338 Stratos Psomadakis
        parsed/matched to a single vdisk.
550 2eba2338 Stratos Psomadakis

551 2eba2338 Stratos Psomadakis
        @type vdisk_name: string
552 2eba2338 Stratos Psomadakis
        @param vdisk_name: vdisk name
553 2eba2338 Stratos Psomadakis
        @rtype: dict
554 2eba2338 Stratos Psomadakis
        @return: a dictionary containing the vdisk attributes
555 2eba2338 Stratos Psomadakis

556 2eba2338 Stratos Psomadakis
        """
557 2eba2338 Stratos Psomadakis
558 2eba2338 Stratos Psomadakis
        ssh_cmd = ['svcinfo', 'lsvdisk', '-bytes', '-delim', '!', vdisk_name]
559 2eba2338 Stratos Psomadakis
        return self._exec_cmd_and_parse_attrs(ssh_cmd)
560 2eba2338 Stratos Psomadakis
561 2eba2338 Stratos Psomadakis
    def _is_vdisk_defined(self, vdisk_name):
562 2eba2338 Stratos Psomadakis
        """ Check if vdisk is defined.
563 2eba2338 Stratos Psomadakis

564 2eba2338 Stratos Psomadakis
        @type vdisk_name: string
565 2eba2338 Stratos Psomadakis
        @param vdisk_name: vdisk name
566 2eba2338 Stratos Psomadakis
        @rtype: bool
567 2eba2338 Stratos Psomadakis
        @return: vdisk defined or not
568 2eba2338 Stratos Psomadakis

569 2eba2338 Stratos Psomadakis
        """
570 2eba2338 Stratos Psomadakis
        # Maybe get list of disks and search instead of relying on empty
571 2eba2338 Stratos Psomadakis
        # attr reply
572 2eba2338 Stratos Psomadakis
        try:
573 2eba2338 Stratos Psomadakis
            self._get_vdisk_attributes(vdisk_name)
574 2eba2338 Stratos Psomadakis
            return True
575 2eba2338 Stratos Psomadakis
        except SVCError:
576 2eba2338 Stratos Psomadakis
            return False
577 2eba2338 Stratos Psomadakis
578 2eba2338 Stratos Psomadakis
    def _is_vdisk_mapped(self, vdisk_name, host_id):
579 2eba2338 Stratos Psomadakis
        """ Check if vdisk is mapped to the host. """
580 2eba2338 Stratos Psomadakis
        return vdisk_name in self._get_hostvdisk_mappings(host_id)
581 2eba2338 Stratos Psomadakis
582 2eba2338 Stratos Psomadakis
    @staticmethod
583 2eba2338 Stratos Psomadakis
    def _check_call(cmdlist):
584 2eba2338 Stratos Psomadakis
        """ Wrapper around subprocess.check_call.
585 2eba2338 Stratos Psomadakis

586 2eba2338 Stratos Psomadakis
        @type cmdlist: list of strings
587 2eba2338 Stratos Psomadakis
        @param cmdlist: list of command plus arguments
588 2eba2338 Stratos Psomadakis

589 2eba2338 Stratos Psomadakis
        """
590 2eba2338 Stratos Psomadakis
        try:
591 2eba2338 Stratos Psomadakis
            subprocess.check_call(cmdlist, stderr=subprocess.STDOUT)
592 2eba2338 Stratos Psomadakis
        except subprocess.CalledProcessError as exc:
593 2eba2338 Stratos Psomadakis
            raise SVCError("Command %s failed: %s" % (' '.join(cmdlist),
594 2eba2338 Stratos Psomadakis
                                                      exc.output))
595 2eba2338 Stratos Psomadakis
596 2eba2338 Stratos Psomadakis
    @staticmethod
597 2eba2338 Stratos Psomadakis
    def _check_output(cmdlist):
598 2eba2338 Stratos Psomadakis
        """ Wrapper around subprocess.check_output.
599 2eba2338 Stratos Psomadakis

600 2eba2338 Stratos Psomadakis
        @type cmdlist: list of strings
601 2eba2338 Stratos Psomadakis
        @param cmdlist: list of command plus arguments
602 2eba2338 Stratos Psomadakis
        @rtype: string
603 2eba2338 Stratos Psomadakis
        @return: output of cmdlist
604 2eba2338 Stratos Psomadakis

605 2eba2338 Stratos Psomadakis
        """
606 2eba2338 Stratos Psomadakis
        try:
607 2eba2338 Stratos Psomadakis
            return subprocess.check_output(cmdlist, stderr=subprocess.STDOUT)
608 2eba2338 Stratos Psomadakis
        except subprocess.CalledProcessError as exc:
609 2eba2338 Stratos Psomadakis
            raise SVCError("Command %s failed: %s" % (' '.join(cmdlist),
610 2eba2338 Stratos Psomadakis
                                                      exc.output))
611 2eba2338 Stratos Psomadakis
612 2eba2338 Stratos Psomadakis
    @staticmethod
613 2eba2338 Stratos Psomadakis
    def _get_devices(lun):
614 2eba2338 Stratos Psomadakis
        """ Get the devices mapped to host, corresponding to LUN id lun
615 2eba2338 Stratos Psomadakis

616 2eba2338 Stratos Psomadakis
        @type lun: integer
617 2eba2338 Stratos Psomadakis
        @param lun: LUN id
618 2eba2338 Stratos Psomadakis
        @rtype: list of strings
619 2eba2338 Stratos Psomadakis
        @return: devices discovered by host or not
620 2eba2338 Stratos Psomadakis

621 2eba2338 Stratos Psomadakis
        """
622 2eba2338 Stratos Psomadakis
        return glob.glob("%s/*lun%s" % (_DEV_BYPATH, lun))
623 2eba2338 Stratos Psomadakis
624 2eba2338 Stratos Psomadakis
    def _devices_discovered(self, lun):
625 2eba2338 Stratos Psomadakis
        """ Make sure the devices get created on the host.
626 2eba2338 Stratos Psomadakis

627 2eba2338 Stratos Psomadakis
        @type lun: integer
628 2eba2338 Stratos Psomadakis
        @param lun: LUN id
629 2eba2338 Stratos Psomadakis
        @rtype: bool
630 2eba2338 Stratos Psomadakis
        @return: devices discovered by host or not
631 2eba2338 Stratos Psomadakis

632 2eba2338 Stratos Psomadakis
        """
633 2eba2338 Stratos Psomadakis
        return len(self._get_devices(lun)) == 4
634 2eba2338 Stratos Psomadakis
635 2eba2338 Stratos Psomadakis
    def _devices_add(self, lun):
636 2eba2338 Stratos Psomadakis
        """ Rescan SCSI / FC bus for new devices.
637 2eba2338 Stratos Psomadakis

638 2eba2338 Stratos Psomadakis
        @type lun: integer
639 2eba2338 Stratos Psomadakis
        @param lun: LUN id
640 2eba2338 Stratos Psomadakis

641 2eba2338 Stratos Psomadakis
        """
642 2eba2338 Stratos Psomadakis
        self._check_call([_RESCAN_SCSI_CMD])
643 2eba2338 Stratos Psomadakis
        if self._devices_discovered(lun) is False:
644 2eba2338 Stratos Psomadakis
            raise SVCError("SCSI bus rescan failed to properly add the new "
645 2eba2338 Stratos Psomadakis
                           "devices")
646 2eba2338 Stratos Psomadakis
647 2eba2338 Stratos Psomadakis
    def _devices_remove(self, lun):
648 2eba2338 Stratos Psomadakis
        """ Remove scanned devices.
649 2eba2338 Stratos Psomadakis

650 2eba2338 Stratos Psomadakis
        @type device: string
651 2eba2338 Stratos Psomadakis
        @param device: device string
652 2eba2338 Stratos Psomadakis
        @type lun: integer
653 2eba2338 Stratos Psomadakis
        @param lun: LUN id
654 2eba2338 Stratos Psomadakis

655 2eba2338 Stratos Psomadakis
        """
656 2eba2338 Stratos Psomadakis
        # Which one is better?:
657 2eba2338 Stratos Psomadakis
        #dm_device = os.path.split(os.readlink(device))[-1]
658 2eba2338 Stratos Psomadakis
        #dm_slaves = os.listdir(_SYSFS_SLAVES_PATH % {'blockdev': dm_device})
659 2eba2338 Stratos Psomadakis
        dm_slaves = self._get_devices(lun)
660 2eba2338 Stratos Psomadakis
661 2eba2338 Stratos Psomadakis
        # Delete the disks
662 2eba2338 Stratos Psomadakis
        try:
663 2eba2338 Stratos Psomadakis
            for slave in dm_slaves:
664 2eba2338 Stratos Psomadakis
                delete_path = _SYSFS_FCDELETE_PATH % {'blockdev': slave}
665 2eba2338 Stratos Psomadakis
                with open(delete_path, 'w') as delete_bdev_file:
666 2eba2338 Stratos Psomadakis
                    delete_bdev_file.write('1')
667 2eba2338 Stratos Psomadakis
        except IOError as exc:
668 2eba2338 Stratos Psomadakis
            raise SVCError("Cannot delete FC devices: %s" % exc)
669 2eba2338 Stratos Psomadakis
670 2eba2338 Stratos Psomadakis
        if self._devices_discovered(lun) is True:
671 2eba2338 Stratos Psomadakis
            raise SVCError('Failed to remove FC devices')
672 2eba2338 Stratos Psomadakis
673 2eba2338 Stratos Psomadakis
    def _multipath_ok(self, device):
674 2eba2338 Stratos Psomadakis
        """ Check if multipath device is set up correctly.
675 2eba2338 Stratos Psomadakis

676 2eba2338 Stratos Psomadakis
        @type device: string
677 2eba2338 Stratos Psomadakis
        @param device: device string
678 2eba2338 Stratos Psomadakis
        @rtype: bool
679 2eba2338 Stratos Psomadakis
        @return: multipath device ok or not
680 2eba2338 Stratos Psomadakis

681 2eba2338 Stratos Psomadakis
        """
682 2eba2338 Stratos Psomadakis
        self._check_call([_DMSETUP_CMD, 'info', device])
683 2eba2338 Stratos Psomadakis
        return self._check_output([_MULTIPATH_CMD, '-ll', device]) != ""
684 2eba2338 Stratos Psomadakis
685 2eba2338 Stratos Psomadakis
    def _multipath_setup(self, device):
686 2eba2338 Stratos Psomadakis
        """ Setup multpath / dm for newly discovered devices.
687 2eba2338 Stratos Psomadakis

688 2eba2338 Stratos Psomadakis
        @type device: string
689 2eba2338 Stratos Psomadakis
        @param device: device string
690 2eba2338 Stratos Psomadakis

691 2eba2338 Stratos Psomadakis
        """
692 2eba2338 Stratos Psomadakis
        self._check_call([_MULTIPATH_CMD])
693 2eba2338 Stratos Psomadakis
        if self._multipath_ok(device) is False:
694 2eba2338 Stratos Psomadakis
            raise SVCError("Multipath / DM setup failed")
695 2eba2338 Stratos Psomadakis
696 2eba2338 Stratos Psomadakis
    def _multipath_remove(self, device):
697 2eba2338 Stratos Psomadakis
        """ Tear down multipath.
698 2eba2338 Stratos Psomadakis

699 2eba2338 Stratos Psomadakis
        @type device: string
700 2eba2338 Stratos Psomadakis
        @param device: device string
701 2eba2338 Stratos Psomadakis

702 2eba2338 Stratos Psomadakis
        """
703 2eba2338 Stratos Psomadakis
        self._check_call([_MULTIPATH_CMD, '-f', device])
704 2eba2338 Stratos Psomadakis
        if self._multipath_ok(device) is True:
705 2eba2338 Stratos Psomadakis
            raise SVCError("Unable to reove multipath device")
706 2eba2338 Stratos Psomadakis
707 2eba2338 Stratos Psomadakis
    def _host_device_remove(self, device, lun):
708 2eba2338 Stratos Psomadakis
        """ Tear down multipath and remove the scanned devices.
709 2eba2338 Stratos Psomadakis

710 2eba2338 Stratos Psomadakis
        @type device: string
711 2eba2338 Stratos Psomadakis
        @param device: device string
712 2eba2338 Stratos Psomadakis
        @type lun: integer
713 2eba2338 Stratos Psomadakis
        @type lun: LUN id
714 2eba2338 Stratos Psomadakis

715 2eba2338 Stratos Psomadakis
        """
716 2eba2338 Stratos Psomadakis
        self._multipath_remove(device)
717 2eba2338 Stratos Psomadakis
        self._devices_remove(lun)
718 2eba2338 Stratos Psomadakis
719 2eba2338 Stratos Psomadakis
    def _host_device_setup(self, device, lun):
720 2eba2338 Stratos Psomadakis
        """ Discover and setup the devices on the host machine.
721 2eba2338 Stratos Psomadakis

722 2eba2338 Stratos Psomadakis
        @type device: string
723 2eba2338 Stratos Psomadakis
        @param device: device string
724 2eba2338 Stratos Psomadakis
        @type lun: integer
725 2eba2338 Stratos Psomadakis
        @type lun: LUN id
726 2eba2338 Stratos Psomadakis

727 2eba2338 Stratos Psomadakis
        """
728 2eba2338 Stratos Psomadakis
        try:
729 2eba2338 Stratos Psomadakis
            self._devices_add(lun)
730 2eba2338 Stratos Psomadakis
            self._multipath_setup(device)
731 2eba2338 Stratos Psomadakis
        except:
732 2eba2338 Stratos Psomadakis
            # Cleanup any devices added by SCSI bus rescan
733 2eba2338 Stratos Psomadakis
            # and remove device mappings
734 2eba2338 Stratos Psomadakis
            self._host_device_remove(device, lun)
735 2eba2338 Stratos Psomadakis
            raise
736 2eba2338 Stratos Psomadakis
737 2eba2338 Stratos Psomadakis
    def _get_device_info(self, volume):
738 2eba2338 Stratos Psomadakis
        """ Return device_string and lun id for a volume.
739 2eba2338 Stratos Psomadakis

740 2eba2338 Stratos Psomadakis
        @type volume: string
741 2eba2338 Stratos Psomadakis
        @param volume: volume name
742 2eba2338 Stratos Psomadakis
        @rtype: tuple
743 2eba2338 Stratos Psomadakis
        @return: tuple containing device string and LUN id
744 2eba2338 Stratos Psomadakis

745 2eba2338 Stratos Psomadakis
        """
746 2eba2338 Stratos Psomadakis
        if not self._is_vdisk_defined(volume):
747 2eba2338 Stratos Psomadakis
            raise SVCError("Volume not found")
748 2eba2338 Stratos Psomadakis
749 2eba2338 Stratos Psomadakis
        volume_attributes = self._get_vdisk_attributes(volume)
750 2eba2338 Stratos Psomadakis
751 2eba2338 Stratos Psomadakis
        try:
752 2eba2338 Stratos Psomadakis
            vdisk_id = int(volume_attributes['vdisk_UID'])
753 2eba2338 Stratos Psomadakis
            lun_id = int(volume_attributes['SCSI_id'])
754 2eba2338 Stratos Psomadakis
        except (KeyError, ValueError):
755 2eba2338 Stratos Psomadakis
            raise SVCError("Malformed vdisk attributes")
756 2eba2338 Stratos Psomadakis
757 2eba2338 Stratos Psomadakis
        device_string = "/dev/mapper/%s%s" % (_SCSI_DEV_PREFIX, vdisk_id)
758 2eba2338 Stratos Psomadakis
759 2eba2338 Stratos Psomadakis
        return (device_string, lun_id)
760 2eba2338 Stratos Psomadakis
761 2eba2338 Stratos Psomadakis
    def attach(self):
762 2eba2338 Stratos Psomadakis
        """ Attach vdisk. """
763 2eba2338 Stratos Psomadakis
        if self._vol_size is not None:
764 2eba2338 Stratos Psomadakis
            raise SVCError("Volume size is set, when it shouldn't")
765 2eba2338 Stratos Psomadakis
766 2eba2338 Stratos Psomadakis
        volume_name = self._vol_name
767 2eba2338 Stratos Psomadakis
768 2eba2338 Stratos Psomadakis
        device_string, lun_id = self._get_device_info(volume_name)
769 2eba2338 Stratos Psomadakis
770 2eba2338 Stratos Psomadakis
        check_list = self._verify_checklist()
771 2eba2338 Stratos Psomadakis
        if self._consistent_unmap(check_list):
772 2eba2338 Stratos Psomadakis
            self._map_vol_to_host(volume_name, self._host_id)
773 2eba2338 Stratos Psomadakis
            self._host_device_setup(device_string, lun_id)
774 2eba2338 Stratos Psomadakis
            if not self._consistent_map(check_list):
775 2eba2338 Stratos Psomadakis
                raise SVCError("Attach failed")
776 2eba2338 Stratos Psomadakis
        elif not self._consistent_map(check_list):
777 2eba2338 Stratos Psomadakis
            raise SVCError("Incosistent map state")
778 2eba2338 Stratos Psomadakis
779 2eba2338 Stratos Psomadakis
        sys.stdout.write('%s' % device_string)
780 2eba2338 Stratos Psomadakis
781 2eba2338 Stratos Psomadakis
    def create(self):
782 2eba2338 Stratos Psomadakis
        """ Create a new vdisk. """
783 2eba2338 Stratos Psomadakis
784 2eba2338 Stratos Psomadakis
        if self._vol_size is None:
785 2eba2338 Stratos Psomadakis
            raise SVCError("Missing volume size for create")
786 2eba2338 Stratos Psomadakis
787 2eba2338 Stratos Psomadakis
        if self._is_vdisk_defined(self._vol_name):
788 2eba2338 Stratos Psomadakis
            raise SVCError("Volume exists")
789 2eba2338 Stratos Psomadakis
790 2eba2338 Stratos Psomadakis
        assert(self._svc_pool == _SVC_POOL_NAME)
791 2eba2338 Stratos Psomadakis
        ssh_cmd = ['svctask', 'mkvdisk', '-name', self._vol_name, '-mdiskgrp',
792 2eba2338 Stratos Psomadakis
                   self._svc_pool, '-size', str(self._vol_size)]
793 2eba2338 Stratos Psomadakis
        out, err = self._svc_connection.run_ssh_cmd(ssh_cmd)
794 2eba2338 Stratos Psomadakis
        self._assert_ssh_return(len(out.strip()), '_create_vdisk',
795 2eba2338 Stratos Psomadakis
                                ssh_cmd, out, err)
796 2eba2338 Stratos Psomadakis
797 2eba2338 Stratos Psomadakis
        # Ensure that the output is as expected
798 2eba2338 Stratos Psomadakis
        match_obj = re.search(r'Virtual Disk, id \[([0-9]+)\], '
799 2eba2338 Stratos Psomadakis
                              'successfully created', out)
800 2eba2338 Stratos Psomadakis
        # Make sure we got a "successfully created" message with vdisk id
801 2eba2338 Stratos Psomadakis
        self._driver_assert(
802 2eba2338 Stratos Psomadakis
            match_obj is not None,
803 2eba2338 Stratos Psomadakis
            '_create_vdisk %(name)s - did not find '
804 2eba2338 Stratos Psomadakis
            'success message in CLI output.\n '
805 2eba2338 Stratos Psomadakis
            'stdout: %(out)s\n stderr: %(err)s'
806 2eba2338 Stratos Psomadakis
            % {'name': self._vol_name, 'out': str(out), 'err': str(err)})
807 2eba2338 Stratos Psomadakis
808 2eba2338 Stratos Psomadakis
    def detach(self):
809 2eba2338 Stratos Psomadakis
        """ Detach vdisk. """
810 2eba2338 Stratos Psomadakis
        if self._vol_size is not None:
811 2eba2338 Stratos Psomadakis
            raise SVCError("Volume size is set, when it shouldn't")
812 2eba2338 Stratos Psomadakis
813 2eba2338 Stratos Psomadakis
        volume_name = self._vol_name
814 2eba2338 Stratos Psomadakis
        check_list = self._verify_checklist()
815 2eba2338 Stratos Psomadakis
816 2eba2338 Stratos Psomadakis
        if self._consistent_map(check_list):
817 2eba2338 Stratos Psomadakis
            device_string, lun_id = self._get_device_info(volume_name)
818 2eba2338 Stratos Psomadakis
819 2eba2338 Stratos Psomadakis
            self._host_device_remove(device_string, lun_id)
820 2eba2338 Stratos Psomadakis
            self._unmap_vol_from_host(volume_name, self._host_id)
821 2eba2338 Stratos Psomadakis
            if not self._consistent_unmap(check_list):
822 2eba2338 Stratos Psomadakis
                raise SVCError("Detatch failed")
823 2eba2338 Stratos Psomadakis
        elif not self._consistent_unmap(check_list):
824 2eba2338 Stratos Psomadakis
            raise SVCError("Incosistent map")
825 2eba2338 Stratos Psomadakis
826 2eba2338 Stratos Psomadakis
    def remove(self):
827 2eba2338 Stratos Psomadakis
        """ Deletes existing vdisks. """
828 2eba2338 Stratos Psomadakis
        if self._vol_size is not None:
829 2eba2338 Stratos Psomadakis
            raise SVCError("Volume size is set, when it shouldn't")
830 2eba2338 Stratos Psomadakis
831 2eba2338 Stratos Psomadakis
        volume_name = self._vol_name
832 2eba2338 Stratos Psomadakis
833 2eba2338 Stratos Psomadakis
        if self._is_vdisk_mapped(volume_name, self._host_id):
834 2eba2338 Stratos Psomadakis
            raise SVCError('Tried to delete mapped volume %s' % volume_name)
835 2eba2338 Stratos Psomadakis
836 2eba2338 Stratos Psomadakis
        # Try to delete volume only if found on the storage
837 2eba2338 Stratos Psomadakis
        vdisk_defined = self._is_vdisk_defined(volume_name)
838 2eba2338 Stratos Psomadakis
        if not vdisk_defined:
839 2eba2338 Stratos Psomadakis
            raise SVCError('warning: Tried to delete vdisk %s but it does not '
840 2eba2338 Stratos Psomadakis
                           'exist.' % volume_name)
841 2eba2338 Stratos Psomadakis
842 2eba2338 Stratos Psomadakis
        vdisk_attributes = self._get_vdisk_attributes(volume_name)
843 2eba2338 Stratos Psomadakis
        assert(vdisk_attributes['mdisk_grp_name'] == _SVC_POOL_NAME)
844 2eba2338 Stratos Psomadakis
        ssh_cmd = ['svctask', 'rmvdisk', '-force', volume_name]
845 2eba2338 Stratos Psomadakis
        out, err = self._svc_connection.run_ssh_cmd(ssh_cmd)
846 2eba2338 Stratos Psomadakis
        # No output should be returned from rmvdisk
847 2eba2338 Stratos Psomadakis
        self._assert_ssh_return(len(out.strip()) == 0,
848 2eba2338 Stratos Psomadakis
                                ('_delete_vdisk %(name)s')
849 2eba2338 Stratos Psomadakis
                                % {'name': volume_name},
850 2eba2338 Stratos Psomadakis
                                ssh_cmd, out, err)
851 2eba2338 Stratos Psomadakis
852 2eba2338 Stratos Psomadakis
    def grow(self):
853 2eba2338 Stratos Psomadakis
        """ Resize vdisk. """
854 2eba2338 Stratos Psomadakis
        volume_name = self._vol_name
855 2eba2338 Stratos Psomadakis
856 2eba2338 Stratos Psomadakis
        if self._vol_size is None:
857 2eba2338 Stratos Psomadakis
            raise SVCError("Missing volume size for grow")
858 2eba2338 Stratos Psomadakis
859 2eba2338 Stratos Psomadakis
        new_size = self._vol_size
860 2eba2338 Stratos Psomadakis
861 2eba2338 Stratos Psomadakis
        # size is given in mb, convert to bytes
862 2eba2338 Stratos Psomadakis
        new_size <<= 20
863 2eba2338 Stratos Psomadakis
864 2eba2338 Stratos Psomadakis
        vdisk_attributes = self._get_vdisk_attributes(volume_name)
865 2eba2338 Stratos Psomadakis
        old_size = int(vdisk_attributes['capacity'])
866 2eba2338 Stratos Psomadakis
867 2eba2338 Stratos Psomadakis
        shrink = old_size > new_size
868 2eba2338 Stratos Psomadakis
        diff = abs(new_size - old_size)
869 2eba2338 Stratos Psomadakis
870 2eba2338 Stratos Psomadakis
        assert(vdisk_attributes['mdisk_grp_name'] == _SVC_POOL_NAME)
871 2eba2338 Stratos Psomadakis
        if shrink:
872 2eba2338 Stratos Psomadakis
            ssh_cmd = (['svctask', 'shrinkvdisksize', '-size', str(diff),
873 2eba2338 Stratos Psomadakis
                        '-unit', 'bytes', volume_name])
874 2eba2338 Stratos Psomadakis
            out, err = self._svc_connection.run_ssh_cmd(ssh_cmd)
875 2eba2338 Stratos Psomadakis
            # No output should be returned from expandvdisksize
876 2eba2338 Stratos Psomadakis
            self._assert_ssh_return(len(out.strip()) == 0, 'extend_volume',
877 2eba2338 Stratos Psomadakis
                                    ssh_cmd, out, err)
878 2eba2338 Stratos Psomadakis
        else:
879 2eba2338 Stratos Psomadakis
            ssh_cmd = (['svctask', 'expandvdisksize', '-size', str(diff),
880 2eba2338 Stratos Psomadakis
                        '-unit', 'bytes', volume_name])
881 2eba2338 Stratos Psomadakis
            out, err = self._svc_connection.run_ssh_cmd(ssh_cmd)
882 2eba2338 Stratos Psomadakis
            # No output should be returned from expandvdisksize
883 2eba2338 Stratos Psomadakis
            self._assert_ssh_return(len(out.strip()) == 0, 'extend_volume',
884 2eba2338 Stratos Psomadakis
                                    ssh_cmd, out, err)
885 2eba2338 Stratos Psomadakis
886 2eba2338 Stratos Psomadakis
    def _verify_checklist(self):
887 2eba2338 Stratos Psomadakis
        """ Check for consistent mappings.
888 2eba2338 Stratos Psomadakis

889 2eba2338 Stratos Psomadakis
        @rtype: list of integers
890 2eba2338 Stratos Psomadakis
        @return: verification checks
891 2eba2338 Stratos Psomadakis

892 2eba2338 Stratos Psomadakis
        """
893 2eba2338 Stratos Psomadakis
894 2eba2338 Stratos Psomadakis
        device, lun = self._get_device_info(self._vol_name)
895 2eba2338 Stratos Psomadakis
896 2eba2338 Stratos Psomadakis
        check_list = [self._is_vdisk_defined(self._vol_name),
897 2eba2338 Stratos Psomadakis
                      self._is_vdisk_mapped(self._vol_name, self._host_id),
898 2eba2338 Stratos Psomadakis
                      self._devices_discovered(lun),
899 2eba2338 Stratos Psomadakis
                      self._multipath_ok(device)]
900 2eba2338 Stratos Psomadakis
901 2eba2338 Stratos Psomadakis
        return check_list
902 2eba2338 Stratos Psomadakis
903 2eba2338 Stratos Psomadakis
    @staticmethod
904 2eba2338 Stratos Psomadakis
    def _consistent_map(check_list):
905 2eba2338 Stratos Psomadakis
        """ Check for consistent map.
906 2eba2338 Stratos Psomadakis

907 2eba2338 Stratos Psomadakis
        @type value: integer
908 2eba2338 Stratos Psomadakis
        @param value: verification check sum
909 2eba2338 Stratos Psomadakis
        @rtype: bool
910 2eba2338 Stratos Psomadakis
        @return: volume mapped end-to-end or not
911 2eba2338 Stratos Psomadakis

912 2eba2338 Stratos Psomadakis
        """
913 2eba2338 Stratos Psomadakis
        return all(check_list)
914 2eba2338 Stratos Psomadakis
915 2eba2338 Stratos Psomadakis
    @staticmethod
916 2eba2338 Stratos Psomadakis
    def _consistent_unmap(check_list):
917 2eba2338 Stratos Psomadakis
        """ Check for consistent unmap.
918 2eba2338 Stratos Psomadakis

919 2eba2338 Stratos Psomadakis
        @type value: integer
920 2eba2338 Stratos Psomadakis
        @param value: verification check sum
921 2eba2338 Stratos Psomadakis
        @rtype: bool
922 2eba2338 Stratos Psomadakis
        @return: volume mapped end-to-end or not
923 2eba2338 Stratos Psomadakis

924 2eba2338 Stratos Psomadakis
        """
925 2eba2338 Stratos Psomadakis
        return not any(check_list)
926 2eba2338 Stratos Psomadakis
927 2eba2338 Stratos Psomadakis
    def verify(self):
928 2eba2338 Stratos Psomadakis
        """ Verify op.
929 2eba2338 Stratos Psomadakis

930 2eba2338 Stratos Psomadakis
        """
931 bc598589 Stratos Psomadakis
        pass
932 2eba2338 Stratos Psomadakis
933 343ae85c Dimitris Aragiorgis
    def setinfo(self):
934 bc598589 Stratos Psomadakis
        """ setinfo op.
935 bc598589 Stratos Psomadakis

936 bc598589 Stratos Psomadakis
        """
937 343ae85c Dimitris Aragiorgis
        pass
938 343ae85c Dimitris Aragiorgis
939 343ae85c Dimitris Aragiorgis
    def snapshot(self):
940 bc598589 Stratos Psomadakis
        """ snapshot op.
941 bc598589 Stratos Psomadakis

942 bc598589 Stratos Psomadakis
        """
943 343ae85c Dimitris Aragiorgis
        pass
944 343ae85c Dimitris Aragiorgis
945 343ae85c Dimitris Aragiorgis
946 2eba2338 Stratos Psomadakis
def main():
947 2eba2338 Stratos Psomadakis
    """Read env variables and SAN conf and branch to the requested function.
948 2eba2338 Stratos Psomadakis

949 2eba2338 Stratos Psomadakis
    """
950 2eba2338 Stratos Psomadakis
951 2eba2338 Stratos Psomadakis
    try:
952 2eba2338 Stratos Psomadakis
        svc = SVCProvider(_SVC_CONFIG_PATH)
953 2eba2338 Stratos Psomadakis
954 2eba2338 Stratos Psomadakis
        try:
955 2eba2338 Stratos Psomadakis
            action = {
956 2eba2338 Stratos Psomadakis
                'attach': svc.attach,
957 2eba2338 Stratos Psomadakis
                'create': svc.create,
958 2eba2338 Stratos Psomadakis
                'detach': svc.detach,
959 2eba2338 Stratos Psomadakis
                'grow': svc.grow,
960 2eba2338 Stratos Psomadakis
                'remove': svc.remove,
961 2eba2338 Stratos Psomadakis
                'verify': svc.verify,
962 343ae85c Dimitris Aragiorgis
                'setinfo': svc.setinfo,
963 343ae85c Dimitris Aragiorgis
                'snapshot': svc.snapshot,
964 2eba2338 Stratos Psomadakis
            }[os.path.basename(sys.argv[0])]
965 2eba2338 Stratos Psomadakis
        except KeyError:
966 2eba2338 Stratos Psomadakis
            sys.stderr.write("Op not supported\n")
967 2eba2338 Stratos Psomadakis
            return 1
968 2eba2338 Stratos Psomadakis
969 2eba2338 Stratos Psomadakis
        action()
970 2eba2338 Stratos Psomadakis
        return 0
971 2eba2338 Stratos Psomadakis
    except SVCError as exc:
972 2eba2338 Stratos Psomadakis
        sys.stderr.write("%s\n" % exc)
973 2eba2338 Stratos Psomadakis
    except Exception as exc:
974 2eba2338 Stratos Psomadakis
        sys.stderr.write(traceback.format_exc())
975 2eba2338 Stratos Psomadakis
        sys.stderr.write("Unexpected error: %s\n" % exc)
976 2eba2338 Stratos Psomadakis
977 2eba2338 Stratos Psomadakis
    return 1
978 2eba2338 Stratos Psomadakis
979 2eba2338 Stratos Psomadakis
if __name__ == '__main__':
980 2eba2338 Stratos Psomadakis
    sys.exit(main())