manila/manila/share/drivers/dell_emc/plugins/vmax/object_manager.py

2060 lines
76 KiB
Python

# Copyright (c) 2016 Dell Inc. or its subsidiaries.
# 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.
import copy
import re
from lxml import builder
from lxml import etree as ET
from oslo_concurrency import processutils
from oslo_log import log
import six
from manila.common import constants as const
from manila import exception
from manila.i18n import _
from manila.share.drivers.dell_emc.common.enas import connector
from manila.share.drivers.dell_emc.common.enas import constants
from manila.share.drivers.dell_emc.common.enas import utils as vmax_utils
from manila.share.drivers.dell_emc.common.enas import xml_api_parser as parser
from manila import utils
LOG = log.getLogger(__name__)
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class StorageObjectManager(object):
def __init__(self, configuration):
self.context = {}
self.connectors = {}
self.connectors['XML'] = connector.XMLAPIConnector(configuration)
self.connectors['SSH'] = connector.SSHConnector(configuration)
elt_maker = builder.ElementMaker(nsmap={None: constants.XML_NAMESPACE})
xml_parser = parser.XMLAPIParser()
obj_types = StorageObject.__subclasses__() # pylint: disable=no-member
for item in obj_types:
key = item.__name__
self.context[key] = eval(key)(self.connectors,
elt_maker,
xml_parser,
self)
def getStorageContext(self, type):
if type in self.context:
return self.context[type]
else:
message = (_("Invalid storage object type %s.") % type)
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
class StorageObject(object):
def __init__(self, conn, elt_maker, xml_parser, manager):
self.conn = conn
self.elt_maker = elt_maker
self.xml_parser = xml_parser
self.manager = manager
self.xml_retry = False
self.ssh_retry_patterns = [
(
constants.SSH_DEFAULT_RETRY_PATTERN,
exception.EMCVmaxLockRequiredException()
),
]
def _translate_response(self, response):
"""Translate different status to ok/error status."""
if (constants.STATUS_OK == response['maxSeverity'] or
constants.STATUS_ERROR == response['maxSeverity']):
return
old_Severity = response['maxSeverity']
if response['maxSeverity'] in (constants.STATUS_DEBUG,
constants.STATUS_INFO):
response['maxSeverity'] = constants.STATUS_OK
LOG.warning("Translated status from %(old)s to %(new)s. "
"Message: %(info)s.",
{'old': old_Severity,
'new': response['maxSeverity'],
'info': response})
def _response_validation(self, response, error_code):
"""Validates whether a response includes a certain error code."""
msg_codes = self._get_problem_message_codes(response['problems'])
for code in msg_codes:
if code == error_code:
return True
return False
def _get_problem_message_codes(self, problems):
message_codes = []
for problem in problems:
if 'messageCode' in problem:
message_codes.append(problem['messageCode'])
return message_codes
def _get_problem_messages(self, problems):
messages = []
for problem in problems:
if 'message' in problem:
messages.append(problem['message'])
return messages
def _get_problem_diags(self, problems):
diags = []
for problem in problems:
if 'Diagnostics' in problem:
diags.append(problem['Diagnostics'])
return diags
def _build_query_package(self, body):
return self.elt_maker.RequestPacket(
self.elt_maker.Request(
self.elt_maker.Query(body)
)
)
def _build_task_package(self, body):
return self.elt_maker.RequestPacket(
self.elt_maker.Request(
self.elt_maker.StartTask(body, timeout='300')
)
)
@utils.retry(exception.EMCVmaxLockRequiredException)
def _send_request(self, req):
req_xml = constants.XML_HEADER + ET.tostring(req).decode('utf-8')
rsp_xml = self.conn['XML'].request(str(req_xml))
response = self.xml_parser.parse(rsp_xml)
self._translate_response(response)
if (response['maxSeverity'] != constants.STATUS_OK and
self._response_validation(response,
constants.MSG_CODE_RETRY)):
raise exception.EMCVmaxLockRequiredException
return response
@utils.retry(exception.EMCVmaxLockRequiredException)
def _execute_cmd(self, cmd, retry_patterns=None, check_exit_code=False):
"""Execute NAS command via SSH.
:param retry_patterns: list of tuples,where each tuple contains a reg
expression and an exception.
:param check_exit_code: Boolean. Raise
processutils.ProcessExecutionError if the command failed to
execute and this parameter is set to True.
"""
if retry_patterns is None:
retry_patterns = self.ssh_retry_patterns
try:
out, err = self.conn['SSH'].run_ssh(cmd, check_exit_code)
except processutils.ProcessExecutionError as e:
for pattern in retry_patterns:
if re.search(pattern[0], e.stdout):
raise pattern[1]
raise
return out, err
def _copy_properties(self, source, target, property_map, deep_copy=True):
for prop in property_map:
if isinstance(prop, tuple):
target_key, src_key = prop
else:
target_key = src_key = prop
if src_key in source:
if deep_copy and isinstance(source[src_key], list):
target[target_key] = copy.deepcopy(source[src_key])
else:
target[target_key] = source[src_key]
else:
target[target_key] = None
def _get_mover_id(self, mover_name, is_vdm):
if is_vdm:
return self.get_context('VDM').get_id(mover_name)
else:
return self.get_context('Mover').get_id(mover_name,
self.xml_retry)
def get_context(self, type):
return self.manager.getStorageContext(type)
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class FileSystem(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(FileSystem, self).__init__(conn, elt_maker, xml_parser, manager)
self.filesystem_map = {}
@utils.retry(exception.EMCVmaxInvalidMoverID)
def create(self, name, size, pool_name, mover_name, is_vdm=True):
pool_id = self.get_context('StoragePool').get_id(pool_name)
mover_id = self._get_mover_id(mover_name, is_vdm)
if is_vdm:
mover = self.elt_maker.Vdm(vdm=mover_id)
else:
mover = self.elt_maker.Mover(mover=mover_id)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.NewFileSystem(
mover,
self.elt_maker.StoragePool(
pool=pool_id,
size=six.text_type(size),
mayContainSlices='true'
),
name=name
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif self._response_validation(
response, constants.MSG_FILESYSTEM_EXIST):
LOG.warning("File system %s already exists. "
"Skip the creation.", name)
return
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to create file system %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get(self, name):
if name not in self.filesystem_map:
request = self._build_query_package(
self.elt_maker.FileSystemQueryParams(
self.elt_maker.AspectSelection(
fileSystems='true',
fileSystemCapacityInfos='true'
),
self.elt_maker.Alias(name=name)
)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
if self._is_filesystem_nonexistent(response):
return constants.STATUS_NOT_FOUND, response['problems']
else:
return response['maxSeverity'], response['problems']
if not response['objects']:
return constants.STATUS_NOT_FOUND, response['problems']
src = response['objects'][0]
filesystem = {}
property_map = (
'name',
('pools_id', 'storagePools'),
('volume_id', 'volume'),
('size', 'volumeSize'),
('id', 'fileSystem'),
'type',
'dataServicePolicies',
)
self._copy_properties(src, filesystem, property_map)
self.filesystem_map[name] = filesystem
return constants.STATUS_OK, self.filesystem_map[name]
def delete(self, name):
status, out = self.get(name)
if constants.STATUS_NOT_FOUND == status:
LOG.warning("File system %s not found. Skip the deletion.",
name)
return
elif constants.STATUS_OK != status:
message = (_("Failed to get file system by name %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
enas_id = self.filesystem_map[name]['id']
request = self._build_task_package(
self.elt_maker.DeleteFileSystem(fileSystem=enas_id)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to delete file system %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
self.filesystem_map.pop(name)
def extend(self, name, pool_name, new_size):
status, out = self.get(name)
if constants.STATUS_OK != status:
message = (_("Failed to get file system by name %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
enas_id = out['id']
size = int(out['size'])
if new_size < size:
message = (_("Failed to extend file system %(name)s because new "
"size %(new_size)d is smaller than old size "
"%(size)d.") %
{'name': name, 'new_size': new_size, 'size': size})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
elif new_size == size:
return
pool_id = self.get_context('StoragePool').get_id(pool_name)
request = self._build_task_package(
self.elt_maker.ExtendFileSystem(
self.elt_maker.StoragePool(
pool=pool_id,
size=six.text_type(new_size - size)
),
fileSystem=enas_id,
)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to extend file system %(name)s to new size "
"%(new_size)d. Reason: %(err)s.") %
{'name': name,
'new_size': new_size,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get_id(self, name):
status, out = self.get(name)
if constants.STATUS_OK != status:
message = (_("Failed to get file system by name %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
return self.filesystem_map[name]['id']
def _is_filesystem_nonexistent(self, response):
"""Translate different status to ok/error status."""
msg_codes = self._get_problem_message_codes(response['problems'])
diags = self._get_problem_diags(response['problems'])
for code, diagnose in zip(msg_codes, diags):
if (code == constants.MSG_FILESYSTEM_NOT_FOUND and
diagnose.find('File system not found.') != -1):
return True
return False
def create_from_snapshot(self, name, snap_name, source_fs_name, pool_name,
mover_name, connect_id):
create_fs_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
'-name', name,
'-type', 'uxfs',
'-create',
'samesize=' + source_fs_name,
'pool=%s' % pool_name,
'storage=SINGLE',
'worm=off',
'-thin', 'no',
'-option', 'slice=y',
]
self._execute_cmd(create_fs_cmd)
ro_mount_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/server_mount', mover_name,
'-option', 'ro',
name,
'/%s' % name,
]
self._execute_cmd(ro_mount_cmd)
session_name = name + ':' + snap_name
copy_ckpt_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_copy',
'-name', session_name[0:63],
'-source', '-ckpt', snap_name,
'-destination', '-fs', name,
'-interconnect',
'id=%s' % connect_id,
'-overwrite_destination',
'-full_copy',
]
try:
self._execute_cmd(copy_ckpt_cmd, check_exit_code=True)
except processutils.ProcessExecutionError as expt:
LOG.error("Failed to copy content from snapshot %(snap)s to "
"file system %(filesystem)s. Reason: %(err)s.",
{'snap': snap_name,
'filesystem': name,
'err': expt})
# When an error happens during nas_copy, we need to continue
# deleting the checkpoint of the target file system if it exists.
query_fs_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
'-info', name,
]
out, err = self._execute_cmd(query_fs_cmd)
re_ckpts = r'ckpts\s*=\s*(.*)\s*'
m = re.search(re_ckpts, out)
if m is not None:
ckpts = m.group(1)
for ckpt in re.split(',', ckpts):
umount_ckpt_cmd = [
'env', 'NAS_DB=/nas',
'/nas/bin/server_umount', mover_name,
'-perm', ckpt,
]
self._execute_cmd(umount_ckpt_cmd)
delete_ckpt_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_fs',
'-delete', ckpt,
'-Force',
]
self._execute_cmd(delete_ckpt_cmd)
rw_mount_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/server_mount', mover_name,
'-option', 'rw',
name,
'/%s' % name,
]
self._execute_cmd(rw_mount_cmd)
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class StoragePool(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(StoragePool, self).__init__(conn, elt_maker, xml_parser, manager)
self.pool_map = {}
def get(self, name, force=False):
if name not in self.pool_map or force:
status, out = self.get_all()
if constants.STATUS_OK != status:
return status, out
if name not in self.pool_map:
return constants.STATUS_NOT_FOUND, None
return constants.STATUS_OK, self.pool_map[name]
def get_all(self):
self.pool_map.clear()
request = self._build_query_package(
self.elt_maker.StoragePoolQueryParams()
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
return response['maxSeverity'], response['problems']
if not response['objects']:
return constants.STATUS_NOT_FOUND, response['problems']
for item in response['objects']:
pool = {}
property_map = (
'name',
('movers_id', 'movers'),
('total_size', 'autoSize'),
('used_size', 'usedSize'),
'diskType',
'dataServicePolicies',
('id', 'pool'),
)
self._copy_properties(item, pool, property_map)
self.pool_map[item['name']] = pool
return constants.STATUS_OK, self.pool_map
def get_id(self, name):
status, out = self.get(name)
if constants.STATUS_OK != status:
message = (_("Failed to get storage pool by name %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
return out['id']
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class MountPoint(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(MountPoint, self).__init__(conn, elt_maker, xml_parser, manager)
@utils.retry(exception.EMCVmaxInvalidMoverID)
def create(self, mount_path, fs_name, mover_name, is_vdm=True):
fs_id = self.get_context('FileSystem').get_id(fs_name)
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.NewMount(
self.elt_maker.MoverOrVdm(
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false',
),
fileSystem=fs_id,
path=mount_path
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif self._is_mount_point_already_existent(response):
LOG.warning("Mount Point %(mount)s already exists. "
"Skip the creation.", {'mount': mount_path})
return
elif constants.STATUS_OK != response['maxSeverity']:
message = (_('Failed to create Mount Point %(mount)s for '
'file system %(fs_name)s. Reason: %(err)s.') %
{'mount': mount_path,
'fs_name': fs_name,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
@utils.retry(exception.EMCVmaxInvalidMoverID)
def get(self, mover_name, is_vdm=True):
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
request = self._build_query_package(
self.elt_maker.MountQueryParams(
self.elt_maker.MoverOrVdm(
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false'
)
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif constants.STATUS_OK != response['maxSeverity']:
return response['maxSeverity'], response['objects']
if not response['objects']:
return constants.STATUS_NOT_FOUND, None
else:
return constants.STATUS_OK, response['objects']
@utils.retry(exception.EMCVmaxInvalidMoverID)
def delete(self, mount_path, mover_name, is_vdm=True):
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.DeleteMount(
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false',
path=mount_path
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif self._is_mount_point_nonexistent(response):
LOG.warning('Mount point %(mount)s on mover %(mover_name)s '
'not found.',
{'mount': mount_path, 'mover_name': mover_name})
return
elif constants.STATUS_OK != response['maxSeverity']:
message = (_('Failed to delete mount point %(mount)s on mover '
'%(mover_name)s. Reason: %(err)s.') %
{'mount': mount_path,
'mover_name': mover_name,
'err': response})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def _is_mount_point_nonexistent(self, response):
"""Translate different status to ok/error status."""
msg_codes = self._get_problem_message_codes(response['problems'])
message = self._get_problem_messages(response['problems'])
for code, msg in zip(msg_codes, message):
if ((code == constants.MSG_GENERAL_ERROR and msg.find(
'No such path or invalid operation') != -1) or
code == constants.MSG_INVALID_VDM_ID or
code == constants.MSG_INVALID_MOVER_ID):
return True
return False
def _is_mount_point_already_existent(self, response):
"""Translate different status to ok/error status."""
msg_codes = self._get_problem_message_codes(response['problems'])
message = self._get_problem_messages(response['problems'])
for code, msg in zip(msg_codes, message):
if ((code == constants.MSG_GENERAL_ERROR and msg.find(
'Mount already exists') != -1)):
return True
return False
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class Mover(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(Mover, self).__init__(conn, elt_maker, xml_parser, manager)
self.mover_map = {}
self.mover_ref_map = {}
def get_ref(self, name, force=False):
if name not in self.mover_ref_map or force:
self.mover_ref_map.clear()
request = self._build_query_package(
self.elt_maker.MoverQueryParams(
self.elt_maker.AspectSelection(movers='true')
)
)
response = self._send_request(request)
if constants.STATUS_ERROR == response['maxSeverity']:
return response['maxSeverity'], response['problems']
for item in response['objects']:
mover = {}
property_map = ('name', ('id', 'mover'))
self._copy_properties(item, mover, property_map)
if mover:
self.mover_ref_map[mover['name']] = mover
if (name not in self.mover_ref_map or
self.mover_ref_map[name]['id'] == ''):
return constants.STATUS_NOT_FOUND, None
return constants.STATUS_OK, self.mover_ref_map[name]
def get(self, name, force=False):
if name not in self.mover_map or force:
if name in self.mover_ref_map and not force:
mover_id = self.mover_ref_map[name]['id']
else:
mover_id = self.get_id(name, force)
if name in self.mover_map:
self.mover_map.pop(name)
request = self._build_query_package(
self.elt_maker.MoverQueryParams(
self.elt_maker.AspectSelection(
moverDeduplicationSettings='true',
moverDnsDomains='true',
moverInterfaces='true',
moverNetworkDevices='true',
moverNisDomains='true',
moverRoutes='true',
movers='true',
moverStatuses='true'
),
mover=mover_id
)
)
response = self._send_request(request)
if constants.STATUS_ERROR == response['maxSeverity']:
return response['maxSeverity'], response['problems']
if not response['objects']:
return constants.STATUS_NOT_FOUND, response['problems']
mover = {}
src = response['objects'][0]
property_map = (
'name',
('id', 'mover'),
('Status', 'maxSeverity'),
'version',
'uptime',
'role',
('interfaces', 'MoverInterface'),
('devices', 'LogicalNetworkDevice'),
('dns_domain', 'MoverDnsDomain'),
)
self._copy_properties(src, mover, property_map)
internal_devices = []
if mover['interfaces']:
for interface in mover['interfaces']:
if self._is_internal_device(interface['device']):
internal_devices.append(interface)
mover['interfaces'] = [var for var in mover['interfaces'] if
var not in internal_devices]
self.mover_map[name] = mover
return constants.STATUS_OK, self.mover_map[name]
def get_id(self, name, force=False):
status, mover_ref = self.get_ref(name, force)
if constants.STATUS_OK != status:
message = (_("Failed to get mover by name %(name)s.") %
{'name': name})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
return mover_ref['id']
def _is_internal_device(self, device):
for device_type in ('mge', 'fxg', 'tks', 'fsn'):
if device.find(device_type) == 0:
return True
return False
def get_interconnect_id(self, source, destination):
header = [
'id',
'name',
'source_server',
'destination_system',
'destination_server',
]
conn_id = None
command_nas_cel = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_cel',
'-interconnect', '-l',
]
out, err = self._execute_cmd(command_nas_cel)
lines = out.strip().split('\n')
for line in lines:
if line.strip().split() == header:
LOG.info('Found the header of the command '
'/nas/bin/nas_cel -interconnect -l.')
else:
interconn = line.strip().split()
if interconn[2] == source and interconn[4] == destination:
conn_id = interconn[0]
return conn_id
def get_physical_devices(self, mover_name):
physical_network_devices = []
cmd_sysconfig = [
'env', 'NAS_DB=/nas', '/nas/bin/server_sysconfig', mover_name,
'-pci'
]
out, err = self._execute_cmd(cmd_sysconfig)
re_pattern = ('0:\s*(?P<name>\S+)\s*IRQ:\s*(?P<irq>\d+)\n'
'.*\n'
'\s*Link:\s*(?P<link>[A-Za-z]+)')
for device in re.finditer(re_pattern, out):
if 'Up' in device.group('link'):
physical_network_devices.append(device.group('name'))
return physical_network_devices
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class VDM(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(VDM, self).__init__(conn, elt_maker, xml_parser, manager)
self.vdm_map = {}
@utils.retry(exception.EMCVmaxInvalidMoverID)
def create(self, name, mover_name):
mover_id = self._get_mover_id(mover_name, False)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.NewVdm(mover=mover_id, name=name)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif self._response_validation(response, constants.MSG_VDM_EXIST):
LOG.warning("VDM %(name)s already exists. Skip the creation.",
{'name': name})
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to create VDM %(name)s on mover "
"%(mover_name)s. Reason: %(err)s.") %
{'name': name,
'mover_name': mover_name,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get(self, name):
if name not in self.vdm_map:
request = self._build_query_package(
self.elt_maker.VdmQueryParams()
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
return response['maxSeverity'], response['problems']
elif not response['objects']:
return constants.STATUS_NOT_FOUND, response['problems']
for item in response['objects']:
vdm = {}
property_map = (
'name',
('id', 'vdm'),
'state',
('host_mover_id', 'mover'),
('interfaces', 'Interfaces'),
)
self._copy_properties(item, vdm, property_map)
self.vdm_map[item['name']] = vdm
if name not in self.vdm_map:
return constants.STATUS_NOT_FOUND, None
return constants.STATUS_OK, self.vdm_map[name]
def delete(self, name):
status, out = self.get(name)
if constants.STATUS_NOT_FOUND == status:
LOG.warning("VDM %s not found. Skip the deletion.",
name)
return
elif constants.STATUS_OK != status:
message = (_("Failed to get VDM by name %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
vdm_id = self.vdm_map[name]['id']
request = self._build_task_package(
self.elt_maker.DeleteVdm(vdm=vdm_id)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to delete VDM %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
self.vdm_map.pop(name)
def get_id(self, name):
status, vdm = self.get(name)
if constants.STATUS_OK != status:
message = (_("Failed to get VDM by name %(name)s.") %
{'name': name})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
return vdm['id']
def attach_nfs_interface(self, vdm_name, if_name):
command_attach_nfs_interface = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
'-vdm', vdm_name,
'-attach', if_name,
]
self._execute_cmd(command_attach_nfs_interface)
def detach_nfs_interface(self, vdm_name, if_name):
command_detach_nfs_interface = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
'-vdm', vdm_name,
'-detach', if_name,
]
try:
self._execute_cmd(command_detach_nfs_interface,
check_exit_code=True)
except processutils.ProcessExecutionError:
interfaces = self.get_interfaces(vdm_name)
if if_name not in interfaces['nfs']:
LOG.debug("Failed to detach interface %(interface)s "
"from mover %(mover_name)s.",
{'interface': if_name, 'mover_name': vdm_name})
else:
message = (_("Failed to detach interface %(interface)s "
"from mover %(mover_name)s.") %
{'interface': if_name, 'mover_name': vdm_name})
LOG.exception(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get_interfaces(self, vdm_name):
interfaces = {
'cifs': [],
'nfs': [],
}
re_pattern = ('Interfaces to services mapping:'
'\s*(?P<interfaces>(\s*interface=.*)*)')
command_get_interfaces = [
'env', 'NAS_DB=/nas', '/nas/bin/nas_server',
'-i',
'-vdm', vdm_name,
]
out, err = self._execute_cmd(command_get_interfaces)
m = re.search(re_pattern, out)
if m:
if_list = m.group('interfaces').split('\n')
for i in if_list:
m_if = re.search('\s*interface=(?P<if>.*)\s*:'
'\s*(?P<type>.*)\s*', i)
if m_if:
if_name = m_if.group('if').strip()
if 'cifs' == m_if.group('type') and if_name != '':
interfaces['cifs'].append(if_name)
elif (m_if.group('type') in ('vdm', 'nfs')
and if_name != ''):
interfaces['nfs'].append(if_name)
return interfaces
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class Snapshot(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(Snapshot, self).__init__(conn, elt_maker, xml_parser, manager)
self.snap_map = {}
def create(self, name, fs_name, pool_id, ckpt_size=None):
fs_id = self.get_context('FileSystem').get_id(fs_name)
if ckpt_size:
elt_pool = self.elt_maker.StoragePool(
pool=pool_id,
size=six.text_type(ckpt_size)
)
else:
elt_pool = self.elt_maker.StoragePool(pool=pool_id)
new_ckpt = self.elt_maker.NewCheckpoint(
self.elt_maker.SpaceAllocationMethod(
elt_pool
),
checkpointOf=fs_id,
name=name
)
request = self._build_task_package(new_ckpt)
response = self._send_request(request)
if self._response_validation(response, constants.MSG_SNAP_EXIST):
LOG.warning("Snapshot %(name)s already exists. "
"Skip the creation.",
{'name': name})
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to create snapshot %(name)s on "
"filesystem %(fs_name)s. Reason: %(err)s.") %
{'name': name,
'fs_name': fs_name,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get(self, name):
if name not in self.snap_map:
request = self._build_query_package(
self.elt_maker.CheckpointQueryParams(
self.elt_maker.Alias(name=name)
)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
return response['maxSeverity'], response['problems']
if not response['objects']:
return constants.STATUS_NOT_FOUND, response['problems']
src = response['objects'][0]
snap = {}
property_map = (
'name',
('id', 'checkpoint'),
'checkpointOf',
'state',
)
self._copy_properties(src, snap, property_map)
self.snap_map[name] = snap
return constants.STATUS_OK, self.snap_map[name]
def delete(self, name):
status, out = self.get(name)
if constants.STATUS_NOT_FOUND == status:
LOG.warning("Snapshot %s not found. Skip the deletion.",
name)
return
elif constants.STATUS_OK != status:
message = (_("Failed to get snapshot by name %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
chpt_id = self.snap_map[name]['id']
request = self._build_task_package(
self.elt_maker.DeleteCheckpoint(checkpoint=chpt_id)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to delete snapshot %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
self.snap_map.pop(name)
def get_id(self, name):
status, out = self.get(name)
if constants.STATUS_OK != status:
message = (_("Failed to get snapshot by %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
return self.snap_map[name]['id']
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class MoverInterface(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(MoverInterface, self).__init__(conn, elt_maker, xml_parser,
manager)
@utils.retry(exception.EMCVmaxInvalidMoverID)
def create(self, interface):
# Maximum of 32 characters for mover interface name
name = interface['name']
if len(name) > 32:
name = name[0:31]
device_name = interface['device_name']
ip_addr = interface['ip']
mover_name = interface['mover_name']
net_mask = interface['net_mask']
vlan_id = interface['vlan_id'] if interface['vlan_id'] else -1
mover_id = self._get_mover_id(mover_name, False)
params = dict(device=device_name,
ipAddress=six.text_type(ip_addr),
mover=mover_id,
name=name,
netMask=net_mask,
vlanid=six.text_type(vlan_id))
if interface.get('ip_version') == 6:
params['ipVersion'] = 'IPv6'
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.NewMoverInterface(**params))
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif self._response_validation(
response, constants.MSG_INTERFACE_NAME_EXIST):
LOG.warning("Mover interface name %s already exists. "
"Skip the creation.", name)
elif self._response_validation(
response, constants.MSG_INTERFACE_EXIST):
LOG.warning("Mover interface IP %s already exists. "
"Skip the creation.", ip_addr)
elif self._response_validation(
response, constants.MSG_INTERFACE_INVALID_VLAN_ID):
# When fail to create a mover interface with the specified
# vlan id, VMAX will leave an interface with vlan id 0 in the
# backend. So we should explicitly remove the interface.
try:
self.delete(six.text_type(ip_addr), mover_name)
except exception.EMCVmaxXMLAPIError:
pass
message = (_("Invalid vlan id %s. Other interfaces on this "
"subnet are in a different vlan.") % vlan_id)
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to create mover interface %(interface)s. "
"Reason: %(err)s.") %
{'interface': interface,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get(self, name, mover_name):
# Maximum of 32 characters for mover interface name
if len(name) > 32:
name = name[0:31]
status, mover = self.manager.getStorageContext('Mover').get(
mover_name, True)
if constants.STATUS_OK == status:
for interface in mover['interfaces']:
if name == interface['name']:
return constants.STATUS_OK, interface
return constants.STATUS_NOT_FOUND, None
@utils.retry(exception.EMCVmaxInvalidMoverID)
def delete(self, ip_addr, mover_name):
mover_id = self._get_mover_id(mover_name, False)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.DeleteMoverInterface(
ipAddress=six.text_type(ip_addr),
mover=mover_id
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif self._response_validation(
response, constants.MSG_INTERFACE_NON_EXISTENT):
LOG.warning("Mover interface %s not found. "
"Skip the deletion.", ip_addr)
return
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to delete mover interface %(ip)s on mover "
"%(mover)s. Reason: %(err)s.") %
{'ip': ip_addr,
'mover': mover_name,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class DNSDomain(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(DNSDomain, self).__init__(conn, elt_maker, xml_parser, manager)
@utils.retry(exception.EMCVmaxInvalidMoverID)
def create(self, mover_name, name, servers, protocol='udp'):
mover_id = self._get_mover_id(mover_name, False)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.NewMoverDnsDomain(
mover=mover_id,
name=name,
servers=servers,
protocol=protocol
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to create DNS domain %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
@utils.retry(exception.EMCVmaxInvalidMoverID)
def delete(self, mover_name, name):
mover_id = self._get_mover_id(mover_name, False)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.DeleteMoverDnsDomain(
mover=mover_id,
name=name
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif constants.STATUS_OK != response['maxSeverity']:
LOG.warning("Failed to delete DNS domain %(name)s. "
"Reason: %(err)s.",
{'name': name, 'err': response['problems']})
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class CIFSServer(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(CIFSServer, self).__init__(conn, elt_maker, xml_parser, manager)
self.cifs_server_map = {}
@utils.retry(exception.EMCVmaxInvalidMoverID)
def create(self, server_args):
compName = server_args['name']
# Maximum of 14 characters for netBIOS name
name = server_args['name'][-14:]
# Maximum of 12 characters for alias name
alias_name = server_args['name'][-12:]
interfaces = server_args['interface_ip']
domain_name = server_args['domain_name']
user_name = server_args['user_name']
password = server_args['password']
mover_name = server_args['mover_name']
is_vdm = server_args['is_vdm']
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
alias_name_list = [self.elt_maker.li(alias_name)]
request = self._build_task_package(
self.elt_maker.NewW2KCifsServer(
self.elt_maker.MoverOrVdm(
mover=mover_id,
moverIdIsVdm='true' if server_args['is_vdm'] else 'false'
),
self.elt_maker.Aliases(*alias_name_list),
self.elt_maker.JoinDomain(userName=user_name,
password=password),
compName=compName,
domain=domain_name,
interfaces=interfaces,
name=name
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
if constants.STATUS_OK != response['maxSeverity']:
status, out = self.get(compName, mover_name, is_vdm)
if constants.STATUS_OK == status and out['domainJoined'] == 'true':
return
else:
message = (_("Failed to create CIFS server %(name)s. "
"Reason: %(err)s.") %
{'name': name,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
@utils.retry(exception.EMCVmaxInvalidMoverID)
def get_all(self, mover_name, is_vdm=True):
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
request = self._build_query_package(
self.elt_maker.CifsServerQueryParams(
self.elt_maker.MoverOrVdm(
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false'
)
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif constants.STATUS_OK != response['maxSeverity']:
return response['maxSeverity'], response['objects']
if mover_name in self.cifs_server_map:
self.cifs_server_map.pop(mover_name)
self.cifs_server_map[mover_name] = {}
for item in response['objects']:
self.cifs_server_map[mover_name][item['compName'].lower()] = item
return constants.STATUS_OK, self.cifs_server_map[mover_name]
def get(self, name, mover_name, is_vdm=True, force=False):
# name is compName
name = name.lower()
if (mover_name in self.cifs_server_map and
name in self.cifs_server_map[mover_name]) and not force:
return constants.STATUS_OK, self.cifs_server_map[mover_name][name]
self.get_all(mover_name, is_vdm)
if mover_name in self.cifs_server_map:
for compName, server in self.cifs_server_map[mover_name].items():
if name == compName:
return constants.STATUS_OK, server
return constants.STATUS_NOT_FOUND, None
@utils.retry(exception.EMCVmaxInvalidMoverID)
def modify(self, server_args):
"""Make CIFS server join or un-join the domain.
:param server_args: Dictionary for CIFS server modification
name: CIFS server name instead of compName
join_domain: True for joining the domain, false for un-joining
user_name: User name under which the domain is joined
password: Password associated with the user name
mover_name: mover or VDM name
is_vdm: Boolean to indicate mover or VDM
:raises exception.EMCVmaxXMLAPIError: if modification fails.
"""
name = server_args['name']
join_domain = server_args['join_domain']
user_name = server_args['user_name']
password = server_args['password']
mover_name = server_args['mover_name']
if 'is_vdm' in server_args.keys():
is_vdm = server_args['is_vdm']
else:
is_vdm = True
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
request = self._build_task_package(
self.elt_maker.ModifyW2KCifsServer(
self.elt_maker.DomainSetting(
joinDomain='true' if join_domain else 'false',
password=password,
userName=user_name,
),
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false',
name=name
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif self._ignore_modification_error(response, join_domain):
return
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to modify CIFS server %(name)s. "
"Reason: %(err)s.") %
{'name': name,
'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def _ignore_modification_error(self, response, join_domain):
if self._response_validation(response, constants.MSG_JOIN_DOMAIN):
return join_domain
elif self._response_validation(response, constants.MSG_UNJOIN_DOMAIN):
return not join_domain
return False
def delete(self, computer_name, mover_name, is_vdm=True):
try:
status, out = self.get(
computer_name.lower(), mover_name, is_vdm, self.xml_retry)
if constants.STATUS_NOT_FOUND == status:
LOG.warning("CIFS server %(name)s on mover %(mover_name)s "
"not found. Skip the deletion.",
{'name': computer_name, 'mover_name': mover_name})
return
except exception.EMCVmaxXMLAPIError:
LOG.warning("CIFS server %(name)s on mover %(mover_name)s "
"not found. Skip the deletion.",
{'name': computer_name, 'mover_name': mover_name})
return
server_name = out['name']
mover_id = self._get_mover_id(mover_name, is_vdm)
request = self._build_task_package(
self.elt_maker.DeleteCifsServer(
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false',
name=server_name
)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to delete CIFS server %(name)s. "
"Reason: %(err)s.") %
{'name': computer_name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
self.cifs_server_map[mover_name].pop(computer_name)
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class CIFSShare(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(CIFSShare, self).__init__(conn, elt_maker, xml_parser, manager)
self.cifs_share_map = {}
@utils.retry(exception.EMCVmaxInvalidMoverID)
def create(self, name, server_name, mover_name, is_vdm=True):
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
share_path = '/' + name
request = self._build_task_package(
self.elt_maker.NewCifsShare(
self.elt_maker.MoverOrVdm(
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false'
),
self.elt_maker.CifsServers(self.elt_maker.li(server_name)),
name=name,
path=share_path
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to create file share %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get(self, name):
if name not in self.cifs_share_map:
request = self._build_query_package(
self.elt_maker.CifsShareQueryParams(name=name)
)
response = self._send_request(request)
if constants.STATUS_OK != response['maxSeverity']:
return response['maxSeverity'], response['problems']
if not response['objects']:
return constants.STATUS_NOT_FOUND, None
self.cifs_share_map[name] = response['objects'][0]
return constants.STATUS_OK, self.cifs_share_map[name]
@utils.retry(exception.EMCVmaxInvalidMoverID)
def delete(self, name, mover_name, is_vdm=True):
status, out = self.get(name)
if constants.STATUS_NOT_FOUND == status:
LOG.warning("CIFS share %s not found. Skip the deletion.",
name)
return
elif constants.STATUS_OK != status:
message = (_("Failed to get CIFS share by name %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': out})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
mover_id = self._get_mover_id(mover_name, is_vdm)
if self.xml_retry:
self.xml_retry = False
netbios_names = self.cifs_share_map[name]['CifsServers']
request = self._build_task_package(
self.elt_maker.DeleteCifsShare(
self.elt_maker.CifsServers(*map(lambda a: self.elt_maker.li(a),
netbios_names)),
mover=mover_id,
moverIdIsVdm='true' if is_vdm else 'false',
name=name
)
)
response = self._send_request(request)
if (self._response_validation(response,
constants.MSG_INVALID_MOVER_ID) and
not self.xml_retry):
self.xml_retry = True
raise exception.EMCVmaxInvalidMoverID(id=mover_id)
elif constants.STATUS_OK != response['maxSeverity']:
message = (_("Failed to delete file system %(name)s. "
"Reason: %(err)s.") %
{'name': name, 'err': response['problems']})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
self.cifs_share_map.pop(name)
def disable_share_access(self, share_name, mover_name):
cmd_str = 'sharesd %s set noaccess' % share_name
disable_access = [
'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
'-v', "%s" % cmd_str,
]
try:
self._execute_cmd(disable_access, check_exit_code=True)
except processutils.ProcessExecutionError:
message = (_('Failed to disable the access to CIFS share '
'%(name)s.') %
{'name': share_name})
LOG.exception(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def allow_share_access(self, mover_name, share_name, user_name, domain,
access=constants.CIFS_ACL_FULLCONTROL):
account = user_name + "@" + domain
allow_str = ('sharesd %(share_name)s grant %(account)s=%(access)s'
% {'share_name': share_name,
'account': account,
'access': access})
allow_access = [
'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
'-v', "%s" % allow_str,
]
try:
self._execute_cmd(allow_access, check_exit_code=True)
except processutils.ProcessExecutionError as expt:
dup_msg = re.compile(r'ACE for %(domain)s\\%(user)s unchanged' %
{'domain': domain, 'user': user_name}, re.I)
if re.search(dup_msg, expt.stdout):
LOG.warning("Duplicate access control entry, "
"skipping allow...")
else:
message = (_('Failed to allow the access %(access)s to '
'CIFS share %(name)s. Reason: %(err)s.') %
{'access': access, 'name': share_name, 'err': expt})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def deny_share_access(self, mover_name, share_name, user_name, domain,
access=constants.CIFS_ACL_FULLCONTROL):
account = user_name + "@" + domain
revoke_str = ('sharesd %(share_name)s revoke %(account)s=%(access)s'
% {'share_name': share_name,
'account': account,
'access': access})
allow_access = [
'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
'-v', "%s" % revoke_str,
]
try:
self._execute_cmd(allow_access, check_exit_code=True)
except processutils.ProcessExecutionError as expt:
not_found_msg = re.compile(
r'No ACE found for %(domain)s\\%(user)s'
% {'domain': domain, 'user': user_name}, re.I)
user_err_msg = re.compile(
r'Cannot get mapping for %(domain)s\\%(user)s'
% {'domain': domain, 'user': user_name}, re.I)
if re.search(not_found_msg, expt.stdout):
LOG.warning("No access control entry found, "
"skipping deny...")
elif re.search(user_err_msg, expt.stdout):
LOG.warning("User not found on domain, skipping deny...")
else:
message = (_('Failed to deny the access %(access)s to '
'CIFS share %(name)s. Reason: %(err)s.') %
{'access': access, 'name': share_name, 'err': expt})
LOG.exception(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def get_share_access(self, mover_name, share_name):
get_str = 'sharesd %s dump' % share_name
get_access = [
'env', 'NAS_DB=/nas', '/nas/bin/.server_config', mover_name,
'-v', "%s" % get_str,
]
try:
out, err = self._execute_cmd(get_access, check_exit_code=True)
except processutils.ProcessExecutionError:
msg = _('Failed to get access list of CIFS share %s.') % share_name
LOG.exception(msg)
raise exception.EMCVmaxXMLAPIError(err=msg)
ret = {}
name_pattern = re.compile(r"Unix user '(.+?)'")
access_pattern = re.compile(r"ALLOWED:(.+?):")
name = None
for line in out.splitlines():
if name is None:
names = name_pattern.findall(line)
if names:
name = names[0].lower()
else:
accesses = access_pattern.findall(line)
if accesses:
ret[name] = accesses[0].lower()
name = None
return ret
def clear_share_access(self, mover_name, share_name, domain,
white_list_users):
existing_users = self.get_share_access(mover_name, share_name)
white_list_users_set = set(user.lower() for user in white_list_users)
users_to_remove = set(existing_users.keys()) - white_list_users_set
for user in users_to_remove:
self.deny_share_access(mover_name, share_name, user, domain,
existing_users[user])
return users_to_remove
@vmax_utils.decorate_all_methods(vmax_utils.log_enter_exit,
debug_only=True)
class NFSShare(StorageObject):
def __init__(self, conn, elt_maker, xml_parser, manager):
super(NFSShare, self).__init__(conn, elt_maker, xml_parser, manager)
self.nfs_share_map = {}
def create(self, name, mover_name):
share_path = '/' + name
create_nfs_share_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
'-option', 'access=-0.0.0.0/0.0.0.0',
share_path,
]
try:
self._execute_cmd(create_nfs_share_cmd, check_exit_code=True)
except processutils.ProcessExecutionError as expt:
message = (_('Failed to create NFS share %(name)s on mover '
'%(mover_name)s. Reason: %(err)s.') %
{'name': name, 'mover_name': mover_name, 'err': expt})
LOG.exception(message)
raise exception.EMCVmaxXMLAPIError(err=message)
def delete(self, name, mover_name):
path = '/' + name
status, out = self.get(name, mover_name)
if constants.STATUS_NOT_FOUND == status:
LOG.warning("NFS share %s not found. Skip the deletion.",
path)
return
delete_nfs_share_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
'-unexport',
'-perm',
path,
]
try:
self._execute_cmd(delete_nfs_share_cmd, check_exit_code=True)
except processutils.ProcessExecutionError as expt:
message = (_('Failed to delete NFS share %(name)s on '
'%(mover_name)s. Reason: %(err)s.') %
{'name': name, 'mover_name': mover_name, 'err': expt})
LOG.exception(message)
raise exception.EMCVmaxXMLAPIError(err=message)
self.nfs_share_map.pop(name)
def get(self, name, mover_name, force=False, check_exit_code=False):
if name in self.nfs_share_map and not force:
return constants.STATUS_OK, self.nfs_share_map[name]
path = '/' + name
nfs_share = {
"mover_name": '',
"path": '',
'AccessHosts': [],
'RwHosts': [],
'RoHosts': [],
'RootHosts': [],
'readOnly': '',
}
nfs_query_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
'-P', 'nfs',
'-list', path,
]
try:
out, err = self._execute_cmd(nfs_query_cmd,
check_exit_code=check_exit_code)
except processutils.ProcessExecutionError as expt:
dup_msg = (r'%(mover_name)s : No such file or directory' %
{'mover_name': mover_name})
if re.search(dup_msg, expt.stdout):
LOG.warning("NFS share %s not found.", name)
return constants.STATUS_NOT_FOUND, None
else:
message = (_('Failed to list NFS share %(name)s on '
'%(mover_name)s. Reason: %(err)s.') %
{'name': name,
'mover_name': mover_name,
'err': expt})
LOG.exception(message)
raise exception.EMCVmaxXMLAPIError(err=message)
re_exports = '%s\s*:\s*\nexport\s*(.*)\n' % mover_name
m = re.search(re_exports, out)
if m is not None:
nfs_share['path'] = path
nfs_share['mover_name'] = mover_name
export = m.group(1)
fields = export.split(" ")
for field in fields:
field = field.strip()
if field.startswith('rw='):
nfs_share['RwHosts'] = vmax_utils.parse_ipaddr(field[3:])
elif field.startswith('access='):
nfs_share['AccessHosts'] = vmax_utils.parse_ipaddr(
field[7:])
elif field.startswith('root='):
nfs_share['RootHosts'] = vmax_utils.parse_ipaddr(field[5:])
elif field.startswith('ro='):
nfs_share['RoHosts'] = vmax_utils.parse_ipaddr(field[3:])
self.nfs_share_map[name] = nfs_share
else:
return constants.STATUS_NOT_FOUND, None
return constants.STATUS_OK, self.nfs_share_map[name]
def allow_share_access(self, share_name, host_ip, mover_name,
access_level=const.ACCESS_LEVEL_RW):
@utils.synchronized('emc-shareaccess-' + share_name)
def do_allow_access(share_name, host_ip, mover_name, access_level):
status, share = self.get(share_name, mover_name)
if constants.STATUS_NOT_FOUND == status:
message = (_('NFS share %s not found.') % share_name)
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
changed = False
rwhosts = share['RwHosts']
rohosts = share['RoHosts']
host_ip = vmax_utils.convert_ipv6_format_if_needed(host_ip)
if access_level == const.ACCESS_LEVEL_RW:
if host_ip not in rwhosts:
rwhosts.append(host_ip)
changed = True
if host_ip in rohosts:
rohosts.remove(host_ip)
changed = True
if access_level == const.ACCESS_LEVEL_RO:
if host_ip not in rohosts:
rohosts.append(host_ip)
changed = True
if host_ip in rwhosts:
rwhosts.remove(host_ip)
changed = True
roothosts = share['RootHosts']
if host_ip not in roothosts:
roothosts.append(host_ip)
changed = True
accesshosts = share['AccessHosts']
if host_ip not in accesshosts:
accesshosts.append(host_ip)
changed = True
if not changed:
LOG.debug("%(host)s is already in access list of share "
"%(name)s.", {'host': host_ip, 'name': share_name})
else:
path = '/' + share_name
self._set_share_access(path,
mover_name,
rwhosts,
rohosts,
roothosts,
accesshosts)
# Update self.nfs_share_map
self.get(share_name, mover_name, force=True,
check_exit_code=True)
do_allow_access(share_name, host_ip, mover_name, access_level)
def deny_share_access(self, share_name, host_ip, mover_name):
@utils.synchronized('emc-shareaccess-' + share_name)
def do_deny_access(share_name, host_ip, mover_name):
status, share = self.get(share_name, mover_name)
if constants.STATUS_OK != status:
message = (_('Query nfs share %(path)s failed. '
'Reason %(err)s.') %
{'path': share_name, 'err': share})
LOG.error(message)
raise exception.EMCVmaxXMLAPIError(err=message)
changed = False
rwhosts = set(share['RwHosts'])
if host_ip in rwhosts:
rwhosts.remove(host_ip)
changed = True
roothosts = set(share['RootHosts'])
if host_ip in roothosts:
roothosts.remove(host_ip)
changed = True
accesshosts = set(share['AccessHosts'])
if host_ip in accesshosts:
accesshosts.remove(host_ip)
changed = True
rohosts = set(share['RoHosts'])
if host_ip in rohosts:
rohosts.remove(host_ip)
changed = True
if not changed:
LOG.debug("%(host)s is already in access list of share "
"%(name)s.", {'host': host_ip, 'name': share_name})
else:
path = '/' + share_name
self._set_share_access(path,
mover_name,
rwhosts,
rohosts,
roothosts,
accesshosts)
# Update self.nfs_share_map
self.get(share_name, mover_name, force=True,
check_exit_code=True)
do_deny_access(share_name, host_ip, mover_name)
def clear_share_access(self, share_name, mover_name, white_list_hosts):
@utils.synchronized('emc-shareaccess-' + share_name)
def do_clear_access(share_name, mover_name, white_list_hosts):
def hosts_to_remove(orig_list):
if white_list_hosts is None:
ret = set()
else:
ret = set(white_list_hosts).intersection(set(orig_list))
return ret
status, share = self.get(share_name, mover_name)
if constants.STATUS_OK != status:
message = (_('Query nfs share %(path)s failed. '
'Reason %(err)s.') %
{'path': share_name, 'err': status})
raise exception.EMCVmaxXMLAPIError(err=message)
self._set_share_access('/' + share_name,
mover_name,
hosts_to_remove(share['RwHosts']),
hosts_to_remove(share['RoHosts']),
hosts_to_remove(share['RootHosts']),
hosts_to_remove(share['AccessHosts']))
# Update self.nfs_share_map
self.get(share_name, mover_name, force=True,
check_exit_code=True)
do_clear_access(share_name, mover_name, white_list_hosts)
def _set_share_access(self, path, mover_name, rw_hosts, ro_hosts,
root_hosts, access_hosts):
if access_hosts is None:
access_hosts = set()
if '-0.0.0.0/0.0.0.0' not in access_hosts:
access_hosts.add('-0.0.0.0/0.0.0.0')
access_str = ('access=%(access)s'
% {'access': ':'.join(access_hosts)})
if root_hosts:
access_str += (',root=%(root)s' % {'root': ':'.join(root_hosts)})
if rw_hosts:
access_str += ',rw=%(rw)s' % {'rw': ':'.join(rw_hosts)}
if ro_hosts:
access_str += ',ro=%(ro)s' % {'ro': ':'.join(ro_hosts)}
set_nfs_share_access_cmd = [
'env', 'NAS_DB=/nas', '/nas/bin/server_export', mover_name,
'-ignore',
'-option', access_str,
path,
]
try:
self._execute_cmd(set_nfs_share_access_cmd, check_exit_code=True)
except processutils.ProcessExecutionError as expt:
message = (_('Failed to set NFS share %(name)s access on '
'%(mover_name)s. Reason: %(err)s.') %
{'name': path[1:],
'mover_name': mover_name,
'err': expt})
LOG.exception(message)
raise exception.EMCVmaxXMLAPIError(err=message)