manila/manila/share/drivers/maprfs/driver_util.py

348 lines
13 KiB
Python

# Copyright (c) 2016, MapR Technologies
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility for processing MapR cluster operations
"""
import json
import pipes
import socket
from oslo_concurrency import processutils
from oslo_log import log
import six
from manila.common import constants
from manila import exception
from manila.i18n import _
from manila import utils
LOG = log.getLogger(__name__)
def get_version_handler(configuration):
# here can be choosing DriverUtils depend on cluster version
return BaseDriverUtil(configuration)
class BaseDriverUtil(object):
"""Utility class for MapR-FS specific operations."""
NOT_FOUND_MSG = 'No such'
ERROR_MSG = 'ERROR'
def __init__(self, configuration):
self.configuration = configuration
self.ssh_connections = {}
self.hosts = self.configuration.maprfs_clinode_ip
self.local_hosts = socket.gethostbyname_ex(socket.gethostname())[2]
self.maprcli_bin = '/usr/bin/maprcli'
self.hadoop_bin = '/usr/bin/hadoop'
def _execute(self, *cmd, **kwargs):
for x in range(0, len(self.hosts)):
try:
check_exit_code = kwargs.pop('check_exit_code', True)
host = self.hosts[x]
if host in self.local_hosts:
cmd = self._as_user(cmd,
self.configuration.maprfs_ssh_name)
out, err = utils.execute(*cmd,
check_exit_code=check_exit_code)
else:
out, err = self._run_ssh(host, cmd, check_exit_code)
# move available cldb host to the beginning
if x > 0:
self.hosts[0], self.hosts[x] = self.hosts[x], self.hosts[0]
return out, err
except exception.ProcessExecutionError as e:
if self._check_error(e):
raise
elif x < len(self.hosts) - 1:
msg = ('Error running SSH command. Trying another host')
LOG.error(msg)
else:
raise
except Exception as e:
if x < len(self.hosts) - 1:
msg = ('Error running SSH command. Trying another host')
LOG.error(msg)
else:
raise exception.ProcessExecutionError(six.text_type(e))
def _run_ssh(self, host, cmd_list, check_exit_code=False):
command = ' '.join(pipes.quote(cmd_arg) for cmd_arg in cmd_list)
connection = self.ssh_connections.get(host)
if connection is None:
ssh_name = self.configuration.maprfs_ssh_name
password = self.configuration.maprfs_ssh_pw
private_key = self.configuration.maprfs_ssh_private_key
remote_ssh_port = self.configuration.maprfs_ssh_port
ssh_conn_timeout = self.configuration.ssh_conn_timeout
min_size = self.configuration.ssh_min_pool_conn
max_size = self.configuration.ssh_max_pool_conn
ssh_pool = utils.SSHPool(host,
remote_ssh_port,
ssh_conn_timeout,
ssh_name,
password=password,
privatekey=private_key,
min_size=min_size,
max_size=max_size)
ssh = ssh_pool.create()
self.ssh_connections[host] = (ssh_pool, ssh)
else:
ssh_pool, ssh = connection
if not ssh.get_transport().is_active():
ssh_pool.remove(ssh)
ssh = ssh_pool.create()
self.ssh_connections[host] = (ssh_pool, ssh)
return processutils.ssh_execute(
ssh,
command,
check_exit_code=check_exit_code)
@staticmethod
def _check_error(error):
# check if error was native
return BaseDriverUtil.ERROR_MSG in error.stdout
@staticmethod
def _as_user(cmd, user):
return ['sudo', 'su', '-', user, '-c',
' '.join(pipes.quote(cmd_arg) for cmd_arg in cmd)]
@staticmethod
def _add_params(cmd, **kwargs):
params = []
for x in kwargs.keys():
params.append('-' + x)
params.append(kwargs[x])
return cmd + params
def create_volume(self, name, path, size, **kwargs):
# delete size param as it is set separately
if kwargs.get('quota'):
del kwargs['quota']
sizestr = six.text_type(size) + 'G'
cmd = [self.maprcli_bin, 'volume', 'create', '-name',
name, '-path', path, '-quota',
sizestr, '-readAce', '', '-writeAce', '']
cmd = self._add_params(cmd, **kwargs)
self._execute(*cmd)
def volume_exists(self, volume_name):
cmd = [self.maprcli_bin, 'volume', 'info', '-name', volume_name]
out, __ = self._execute(*cmd, check_exit_code=False)
return self.NOT_FOUND_MSG not in out
def delete_volume(self, name):
cmd = [self.maprcli_bin, 'volume', 'remove', '-name', name, '-force',
'true']
out, __ = self._execute(*cmd, check_exit_code=False)
# if volume does not exist do not raise exception.ProcessExecutionError
if self.ERROR_MSG in out and self.NOT_FOUND_MSG not in out:
raise exception.ProcessExecutionError(out)
def set_volume_size(self, name, size):
sizestr = six.text_type(size) + 'G'
cmd = [self.maprcli_bin, 'volume', 'modify', '-name', name, '-quota',
sizestr]
self._execute(*cmd)
def create_snapshot(self, name, volume_name):
cmd = [self.maprcli_bin, 'volume', 'snapshot', 'create',
'-snapshotname',
name, '-volume', volume_name]
self._execute(*cmd)
def delete_snapshot(self, name, volume_name):
cmd = [self.maprcli_bin, 'volume', 'snapshot', 'remove',
'-snapshotname',
name, '-volume', volume_name]
out, __ = self._execute(*cmd, check_exit_code=False)
# if snapshot does not exist do not raise ProcessExecutionError
if self.ERROR_MSG in out and self.NOT_FOUND_MSG not in out:
raise exception.ProcessExecutionError(out)
def get_volume_info(self, volume_name, columns=None):
cmd = [self.maprcli_bin, 'volume', 'info', '-name', volume_name,
'-json']
if columns:
cmd += ['-columns', ','.join(columns)]
out, __ = self._execute(*cmd)
return json.loads(out)['data'][0]
def get_volume_info_by_path(self, volume_path, columns=None,
check_if_exists=False):
cmd = [self.maprcli_bin, 'volume', 'info', '-path', volume_path,
'-json']
if columns:
cmd += ['-columns', ','.join(columns)]
out, __ = self._execute(*cmd, check_exit_code=not check_if_exists)
if check_if_exists and self.NOT_FOUND_MSG in out:
return None
return json.loads(out)['data'][0]
def get_snapshot_list(self, volume_name=None, volume_path=None):
params = {}
if volume_name:
params['volume'] = volume_name
if volume_path:
params['path'] = volume_name
cmd = [self.maprcli_bin, 'volume', 'snapshot', 'list', '-volume',
'-columns',
'snapshotname', '-json']
cmd = self._add_params(cmd, **params)
out, __ = self._execute(*cmd)
return [x['snapshotname'] for x in json.loads(out)['data']]
def rename_volume(self, name, new_name):
cmd = [self.maprcli_bin, 'volume', 'rename', '-name', name, '-newname',
new_name]
self._execute(*cmd)
def fs_capacity(self):
cmd = [self.hadoop_bin, 'fs', '-df']
out, err = self._execute(*cmd)
lines = out.splitlines()
try:
fields = lines[1].split()
total = int(fields[1])
free = int(fields[3])
except (IndexError, ValueError):
msg = _('Failed to get MapR-FS capacity info.')
LOG.exception(msg)
raise exception.ProcessExecutionError(msg)
return total, free
def maprfs_ls(self, path):
cmd = [self.hadoop_bin, 'fs', '-ls', path]
out, __ = self._execute(*cmd)
return out
def maprfs_cp(self, source, dest):
cmd = [self.hadoop_bin, 'fs', '-cp', '-p', source, dest]
self._execute(*cmd)
def maprfs_chmod(self, dest, mod):
cmd = [self.hadoop_bin, 'fs', '-chmod', mod, dest]
self._execute(*cmd)
def maprfs_du(self, path):
cmd = [self.hadoop_bin, 'fs', '-du', '-s', path]
out, __ = self._execute(*cmd)
return int(out.split(' ')[0])
def check_state(self):
cmd = [self.hadoop_bin, 'fs', '-ls', '/']
out, __ = self._execute(*cmd, check_exit_code=False)
return 'Found' in out
def dir_not_empty(self, path):
cmd = [self.hadoop_bin, 'fs', '-ls', path]
out, __ = self._execute(*cmd, check_exit_code=False)
return 'Found' in out
def set_volume_ace(self, volume_name, access_rules):
read_accesses = []
write_accesses = []
for access_rule in access_rules:
if access_rule['access_level'] == constants.ACCESS_LEVEL_RO:
read_accesses.append(access_rule['access_to'])
elif access_rule['access_level'] == constants.ACCESS_LEVEL_RW:
read_accesses.append(access_rule['access_to'])
write_accesses.append(access_rule['access_to'])
def rule_type(access_to):
if self.group_exists(access_to):
return 'g'
elif self.user_exists(access_to):
return 'u'
else:
# if nor user nor group exits, it should try add group rule
return 'g'
read_accesses_string = '|'.join(
map(lambda x: rule_type(x) + ':' + x, read_accesses))
write_accesses_string = '|'.join(
map(lambda x: rule_type(x) + ':' + x, write_accesses))
cmd = [self.maprcli_bin, 'volume', 'modify', '-name', volume_name,
'-readAce', read_accesses_string, '-writeAce',
write_accesses_string]
self._execute(*cmd)
def add_volume_ace_rules(self, volume_name, access_rules):
if not access_rules:
return
access_rules_map = self.get_access_rules(volume_name)
for access_rule in access_rules:
access_rules_map[access_rule['access_to']] = access_rule
self.set_volume_ace(volume_name, access_rules_map.values())
def remove_volume_ace_rules(self, volume_name, access_rules):
if not access_rules:
return
access_rules_map = self.get_access_rules(volume_name)
for access_rule in access_rules:
if access_rules_map.get(access_rule['access_to']):
del access_rules_map[access_rule['access_to']]
self.set_volume_ace(volume_name, access_rules_map.values())
def get_access_rules(self, volume_name):
info = self.get_volume_info(volume_name)
aces = info['volumeAces']
read_ace = aces['readAce']
write_ace = aces['writeAce']
access_rules_map = {}
self._retrieve_access_rules_from_ace(read_ace, 'r', access_rules_map)
self._retrieve_access_rules_from_ace(write_ace, 'w', access_rules_map)
return access_rules_map
def _retrieve_access_rules_from_ace(self, ace, ace_type, access_rules_map):
access = constants.ACCESS_LEVEL_RW if ace_type == 'w' else (
constants.ACCESS_LEVEL_RO)
if ace not in ['p', '']:
write_rules = [x.strip() for x in ace.split('|')]
for user in write_rules:
rule_type, username = user.split(':')
if rule_type not in ['u', 'g']:
continue
access_rules_map[username] = {
'access_level': access,
'access_to': username,
'access_type': 'user',
}
def user_exists(self, user):
cmd = ['getent', 'passwd', user]
out, __ = self._execute(*cmd, check_exit_code=False)
return out != ''
def group_exists(self, group):
cmd = ['getent', 'group', group]
out, __ = self._execute(*cmd, check_exit_code=False)
return out != ''
def get_cluster_name(self):
cmd = [self.maprcli_bin, 'dashboard', 'info', '-json']
out, __ = self._execute(*cmd)
try:
return json.loads(out)['data'][0]['cluster']['name']
except (IndexError, ValueError) as e:
msg = (_("Failed to parse cluster name. Error: %s") % e)
raise exception.ProcessExecutionError(msg)