273 lines
10 KiB
Python
273 lines
10 KiB
Python
# Copyright (c) 2015 EMC Corporation.
|
|
# 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.
|
|
|
|
from oslo_serialization import jsonutils
|
|
import requests
|
|
import six
|
|
|
|
from manila import exception
|
|
from manila.i18n import _
|
|
|
|
|
|
class IsilonApi(object):
|
|
|
|
def __init__(self, api_url, auth, verify_ssl_cert=True):
|
|
self.host_url = api_url
|
|
self.session = requests.session()
|
|
self.session.auth = auth
|
|
self.verify_ssl_cert = verify_ssl_cert
|
|
|
|
def create_directory(self, container_path, recursive=False):
|
|
"""Create a directory."""
|
|
|
|
headers = {"x-isi-ifs-target-type": "container"}
|
|
url = (self.host_url + "/namespace" + container_path + '?recursive='
|
|
+ six.text_type(recursive))
|
|
r = self.request('PUT', url,
|
|
headers=headers)
|
|
return r.status_code == 200
|
|
|
|
def clone_snapshot(self, snapshot_name, fq_target_dir):
|
|
self.create_directory(fq_target_dir)
|
|
snapshot = self.get_snapshot(snapshot_name)
|
|
snapshot_path = snapshot['path']
|
|
# remove /ifs from start of path
|
|
relative_snapshot_path = snapshot_path[4:]
|
|
fq_snapshot_path = ('/ifs/.snapshot/' + snapshot_name +
|
|
relative_snapshot_path)
|
|
self._clone_directory_contents(fq_snapshot_path, fq_target_dir,
|
|
snapshot_name, relative_snapshot_path)
|
|
|
|
def _clone_directory_contents(self, fq_source_dir, fq_target_dir,
|
|
snapshot_name, relative_path):
|
|
dir_listing = self.get_directory_listing(fq_source_dir)
|
|
for item in dir_listing['children']:
|
|
name = item['name']
|
|
source_item_path = fq_source_dir + '/' + name
|
|
new_relative_path = relative_path + '/' + name
|
|
dest_item_path = fq_target_dir + '/' + name
|
|
if item['type'] == 'container':
|
|
# create the container name in the target dir & clone dir
|
|
self.create_directory(dest_item_path)
|
|
self._clone_directory_contents(source_item_path,
|
|
dest_item_path,
|
|
snapshot_name,
|
|
new_relative_path)
|
|
elif item['type'] == 'object':
|
|
self.clone_file_from_snapshot('/ifs' + new_relative_path,
|
|
dest_item_path, snapshot_name)
|
|
|
|
def clone_file_from_snapshot(self, fq_file_path, fq_dest_path,
|
|
snapshot_name):
|
|
headers = {'x-isi-ifs-copy-source': '/namespace' + fq_file_path}
|
|
snapshot_suffix = '&snapshot=' + snapshot_name
|
|
url = (self.host_url + '/namespace' + fq_dest_path + '?clone=true' +
|
|
snapshot_suffix)
|
|
self.request('PUT', url, headers=headers)
|
|
|
|
def get_directory_listing(self, fq_dir_path):
|
|
url = self.host_url + '/namespace' + fq_dir_path + '?detail=default'
|
|
r = self.request('GET', url)
|
|
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
def is_path_existent(self, resource_path):
|
|
url = self.host_url + '/namespace' + resource_path
|
|
r = self.request('HEAD', url)
|
|
if r.status_code == 200:
|
|
return True
|
|
elif r.status_code == 404:
|
|
return False
|
|
else:
|
|
r.raise_for_status()
|
|
|
|
def get_snapshot(self, snapshot_name):
|
|
r = self.request('GET',
|
|
self.host_url + '/platform/1/snapshot/snapshots/' +
|
|
snapshot_name)
|
|
snapshot_json = r.json()
|
|
if r.status_code == 200:
|
|
return snapshot_json['snapshots'][0]
|
|
elif r.status_code == 404:
|
|
return None
|
|
else:
|
|
r.raise_for_status()
|
|
|
|
def get_snapshots(self):
|
|
r = self.request('GET',
|
|
self.host_url + '/platform/1/snapshot/snapshots')
|
|
if r.status_code == 200:
|
|
return r.json()
|
|
else:
|
|
r.raise_for_status()
|
|
|
|
def lookup_nfs_export(self, share_path):
|
|
response = self.session.get(
|
|
self.host_url + '/platform/1/protocols/nfs/exports',
|
|
verify=self.verify_ssl_cert)
|
|
nfs_exports_json = response.json()
|
|
for export in nfs_exports_json['exports']:
|
|
for path in export['paths']:
|
|
if path == share_path:
|
|
return export['id']
|
|
return None
|
|
|
|
def get_nfs_export(self, export_id):
|
|
response = self.request('GET',
|
|
self.host_url +
|
|
'/platform/1/protocols/nfs/exports/' +
|
|
six.text_type(export_id))
|
|
if response.status_code == 200:
|
|
return response.json()['exports'][0]
|
|
else:
|
|
return None
|
|
|
|
def lookup_smb_share(self, share_name):
|
|
response = self.session.get(
|
|
self.host_url + '/platform/1/protocols/smb/shares/' + share_name)
|
|
if response.status_code == 200:
|
|
return response.json()['shares'][0]
|
|
else:
|
|
return None
|
|
|
|
def create_nfs_export(self, export_path):
|
|
"""Creates an NFS export using the Platform API.
|
|
|
|
:param export_path: a string specifying the desired export path
|
|
:return: "True" if created successfully; "False" otherwise
|
|
"""
|
|
|
|
data = {'paths': [export_path]}
|
|
url = self.host_url + '/platform/1/protocols/nfs/exports'
|
|
response = self.request('POST', url, data=data)
|
|
return response.status_code == 201
|
|
|
|
def create_smb_share(self, share_name, share_path):
|
|
"""Creates an SMB/CIFS share.
|
|
|
|
:param share_name: the name of the CIFS share
|
|
:param share_path: the path associated with the CIFS share
|
|
:return: "True" if the share created successfully; returns "False"
|
|
otherwise
|
|
"""
|
|
|
|
data = {}
|
|
data['name'] = share_name
|
|
data['path'] = share_path
|
|
url = self.host_url + '/platform/1/protocols/smb/shares'
|
|
response = self.request('POST', url, data=data)
|
|
return response.status_code == 201
|
|
|
|
def create_snapshot(self, snapshot_name, snapshot_path):
|
|
"""Creates a snapshot."""
|
|
|
|
data = {'name': snapshot_name, 'path': snapshot_path}
|
|
r = self.request('POST',
|
|
self.host_url + '/platform/1/snapshot/snapshots',
|
|
data=data)
|
|
if r.status_code == 201:
|
|
return True
|
|
else:
|
|
r.raise_for_status()
|
|
|
|
def delete(self, fq_resource_path, recursive=False):
|
|
"""Deletes a file or folder."""
|
|
|
|
r = self.request('DELETE',
|
|
self.host_url + '/namespace' + fq_resource_path +
|
|
'?recursive=' + six.text_type(recursive))
|
|
r.raise_for_status()
|
|
|
|
def delete_nfs_share(self, share_number):
|
|
response = self.session.delete(
|
|
self.host_url + '/platform/1/protocols/nfs/exports' + '/' +
|
|
six.text_type(share_number))
|
|
return response.status_code == 204
|
|
|
|
def delete_smb_share(self, share_name):
|
|
url = self.host_url + '/platform/1/protocols/smb/shares/' + share_name
|
|
response = self.request('DELETE', url)
|
|
return response.status_code == 204
|
|
|
|
def delete_snapshot(self, snapshot_name):
|
|
response = self.request(
|
|
'DELETE', '{0}/platform/1/snapshot/snapshots/{1}'
|
|
.format(self.host_url, snapshot_name))
|
|
response.raise_for_status()
|
|
|
|
def quota_create(self, path, quota_type, size):
|
|
thresholds = {'hard': size}
|
|
data = {
|
|
'path': path,
|
|
'type': quota_type,
|
|
'include_snapshots': False,
|
|
'thresholds_include_overhead': False,
|
|
'enforced': True,
|
|
'thresholds': thresholds,
|
|
}
|
|
response = self.request(
|
|
'POST', '{0}/platform/1/quota/quotas'.format(self.host_url),
|
|
data=data)
|
|
response.raise_for_status()
|
|
|
|
def quota_get(self, path, quota_type):
|
|
response = self.request(
|
|
'GET',
|
|
'{0}/platform/1/quota/quotas?path={1}'.format(self.host_url, path),
|
|
)
|
|
if response.status_code == 404:
|
|
return None
|
|
elif response.status_code != 200:
|
|
response.raise_for_status()
|
|
|
|
json = response.json()
|
|
len_returned_quotas = len(json['quotas'])
|
|
if len_returned_quotas == 0:
|
|
return None
|
|
elif len_returned_quotas == 1:
|
|
return json['quotas'][0]
|
|
else:
|
|
message = (_('Greater than one quota returned when querying '
|
|
'quotas associated with share path: %(path)s .') %
|
|
{'path': path})
|
|
raise exception.ShareBackendException(msg=message)
|
|
|
|
def quota_modify_size(self, quota_id, new_size):
|
|
data = {'thresholds': {'hard': new_size}}
|
|
response = self.request(
|
|
'PUT',
|
|
'{0}/platform/1/quota/quotas/{1}'.format(self.host_url, quota_id),
|
|
data=data
|
|
)
|
|
response.raise_for_status()
|
|
|
|
def quota_set(self, path, quota_type, size):
|
|
"""Sets a quota of the given type and size on the given path."""
|
|
quota_json = self.quota_get(path, quota_type)
|
|
if quota_json is None:
|
|
self.quota_create(path, quota_type, size)
|
|
else:
|
|
# quota already exists, modify it's size
|
|
quota_id = quota_json['id']
|
|
self.quota_modify_size(quota_id, size)
|
|
|
|
def request(self, method, url, headers=None, data=None, params=None):
|
|
if data is not None:
|
|
data = jsonutils.dumps(data)
|
|
r = self.session.request(method, url, headers=headers, data=data,
|
|
verify=self.verify_ssl_cert, params=params)
|
|
return r
|