# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Hewlett Packard Development Company, L.P.
# 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.
"""
HPLeftHand REST Client
.. module: HPLeftHandClient
.. moduleauthor: Kurt Martin
:Author: Kurt Martin
:Description: This is the LeftHand/StoreVirtual Client that talks to the
LeftHand OS REST Service.
This client requires and works with version 11.5 of the LeftHand firmware
"""
from hplefthandclient import http
[docs]class HPLeftHandClient:
def __init__(self, api_url):
self.api_url = api_url
self.http = http.HTTPJSONRESTClient(self.api_url)
[docs] def debug_rest(self, flag):
"""
This is useful for debugging requests to LeftHand
:param flag: set to True to enable debugging
:type flag: bool
"""
self.http.set_debug_flag(flag)
[docs] def login(self, username, password):
"""
This authenticates against the LH OS REST server and creates a session.
:param username: The username
:type username: str
:param password: The password
:type password: str
:returns: None
"""
self.http.authenticate(username, password)
[docs] def logout(self):
"""
This destroys the session and logs out from the LH OS server
:returns: None
"""
self.http.unauthenticate()
[docs] def getClusters(self):
"""
Get the list of Clusters
:returns: list of Clusters
"""
response, body = self.http.get('/clusters')
return body
[docs] def getCluster(self, cluster_id):
"""
Get information about a Cluster
:param cluster_id: The id of the cluster to find
:type cluster_id: str
:returns: cluster
"""
response, body = self.http.get('/clusters/%s' % cluster_id)
return body
[docs] def getClusterByName(self, name):
"""
Get information about a cluster by name
:param name: The name of the cluster to find
:type name: str
:returns: cluster
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_CLUSTER - cluster doesn't exist
"""
response, body = self.http.get('/clusters?name=%s' % name)
return body
[docs] def getServers(self):
"""
Get the list of Servers
:returns: list of Servers
"""
response, body = self.http.get('/servers')
return body
[docs] def getServer(self, server_id):
"""
Get information about a server
:param server_id: The id of the server to find
:type server_id: str
:returns: server
:raises: :class:`~hplefthandclient.exceptions.HTTPServerError`
"""
response, body = self.http.get('/servers/%s' % server_id)
return body
[docs] def getServerByName(self, name):
"""
Get information about a server by name
:param name: The name of the server to find
:type name: str
:returns: server
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_SERVER - server doesn't exist
"""
response, body = self.http.get('/servers?name=%s' % name)
return body
[docs] def createServer(self, name, iqn, optional=None):
"""
Create a server by name
:param name: The name of the server to create
:type name: str
:param iqn: The iSCSI qualified name
:type name: str
:param optional: Dictionary of optional params
:type optional: dict
.. code-block:: python
optional = {
'description' : "some comment",
'iscsiEnabled' : True,
'chapName': "some chap name",
'chapAuthenticationRequired': False,
'chapInitiatorSecret': "initiator secret",
'chapTargetSecret': "target secret",
'iscsiLoadBalancingEnabled': True,
'controllingServerName': "server name",
'fibreChannelEnabled': False,
'inServerCluster": True
}
:returns: server
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_SERVER - server doesn't exist
"""
info = {'name': name, 'iscsiIQN': iqn}
if optional:
info = self._mergeDict(info, optional)
response, body = self.http.post('/servers', body=info)
return body
[docs] def deleteServer(self, server_id):
"""
Delete a Server
:param server_id: the server ID to delete
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_SERVER - The server does not exist
"""
response, body = self.http.delete('/servers/%s' % server_id)
return body
[docs] def getSnapshots(self):
"""
Get the list of Snapshots
:returns: list of Snapshots
"""
response, body = self.http.get('/snapshots')
return body
[docs] def getSnapshot(self, snapshot_id):
"""
Get information about a Snapshot
:returns: snapshot
:raises: :class:`~hplefthandclient.exceptions.HTTPServerError`
"""
response, body = self.http.get('/snapshots/%s' % snapshot_id)
return body
[docs] def getSnapshotByName(self, name):
"""
Get information about a snapshot by name
:param name: The name of the snapshot to find
:returns: volume
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_SNAP - shapshot doesn't exist
"""
response, body = self.http.get('/snapshots?name=%s' % name)
return body
[docs] def createSnapshot(self, name, source_volume_id, optional=None):
"""
Create a snapshot of an existing Volume
:param name: Name of the Snapshot
:type name: str
:param source_volume_id: The volume you want to snapshot
:type source_volume_id: int
:param optional: Dictionary of optional params
:type optional: dict
.. code-block:: python
optional = {
'description' : "some comment",
'inheritAccess' : false
}
"""
parameters = {'name': name}
if optional:
parameters = self._mergeDict(parameters, optional)
info = {'action': 'createSnapshot',
'parameters': parameters}
response, body = self.http.post('/volumes/%s' % source_volume_id,
body=info)
return body
[docs] def deleteSnapshot(self, snapshot_id):
"""
Delete a Snapshot
:param snapshot_id: the snapshot ID to delete
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_SNAPSHOT - The snapshot does not exist
"""
response, body = self.http.delete('/snapshots/%s' % snapshot_id)
return body
[docs] def cloneSnapshot(self, name, source_snapshot_id, optional=None):
"""
Create a clone of an existing Shapshot
:param name: Name of the Snapshot clone
:type name: str
:param source_snapshot_id: The snapshot you want to clone
:type source_snapshot_id: int
:param optional: Dictionary of optional params
:type optional: dict
.. code-block:: python
optional = {
'description' : "some comment"
}
"""
parameters = {'name': name}
if optional:
parameters = self._mergeDict(parameters, optional)
info = {'action': 'createSmartClone',
'parameters': parameters}
response, body = self.http.post('/snapshots/%s' % source_snapshot_id,
body=info)
return body
[docs] def getVolumes(self):
"""
Get the list of Volumes
:returns: list of Volumes
"""
response, body = self.http.get('/volumes')
return body
[docs] def getVolume(self, volume_id):
"""
Get information about a volume
:param volume_id: The id of the volume to find
:type volume_id: str
:returns: volume
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_VOL - volume doesn't exist
"""
response, body = self.http.get('/volumes/%s' % volume_id)
return body
[docs] def getVolumeByName(self, name):
"""
Get information about a volume by name
:param name: The name of the volume to find
:type volume_id: str
:returns: volume
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_VOL - volume doesn't exist
"""
response, body = self.http.get('/volumes?name=%s' % name)
return body
[docs] def createVolume(self, name, cluster_id, size, optional=None):
""" Create a new volume
:param name: the name of the volume
:type name: str
:param cluster_id: the cluster Id
:type cluster_id: int
:param sizeKB: size in KB for the volume
:type sizeKB: int
:param optional: dict of other optional items
:type optional: dict
.. code-block:: python
optional = {
'description': 'some comment',
'isThinProvisioned': 'true',
'autogrowSeconds': 200,
'clusterName': 'somename',
'isAdaptiveOptimizationEnabled': 'true',
'dataProtectionLevel': 2,
}
:returns: List of Volumes
:raises: :class:`~hplefthandclient.exceptions.HTTPConflict` -
EXISTENT_SV - Volume Exists already
"""
info = {'name': name, 'clusterID': cluster_id, 'size': size}
if optional:
info = self._mergeDict(info, optional)
response, body = self.http.post('/volumes', body=info)
return body
[docs] def deleteVolume(self, volume_id):
"""
Delete a volume
:param name: the name of the volume
:type name: str
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_VOL - The volume does not exist
"""
response, body = self.http.delete('/volumes/%s' % volume_id)
return body
[docs] def modifyVolume(self, volume_id, optional):
"""Modify an existing volume.
:param volume_id: The id of the volume to find
:type volume_id: str
:returns: volume
:raises: :class:`~hplefthandclient.exceptions.HTTPNotFound` -
NON_EXISTENT_VOL - volume doesn't exist
"""
info = {'volume_id': volume_id}
info = self._mergeDict(info, optional)
response, body = self.http.put('/volumes/%s' % volume_id, body=info)
return body
[docs] def cloneVolume(self, name, source_volume_id, optional=None):
"""
Create a clone of an existing Volume
:param name: Name of the Volume clone
:type name: str
:param source_volume_id: The Volume you want to clone
:type source_volume_id: int
:param optional: Dictionary of optional params
:type optional: dict
.. code-block:: python
optional = {
'description' : "some comment"
}
"""
parameters = {'name': name}
if optional:
parameters = self._mergeDict(parameters, optional)
info = {'action': 'createSmartClone',
'parameters': parameters}
response, body = self.http.post('/volumes/%s' % source_volume_id,
body=info)
return body
[docs] def addServerAccess(self, volume_id, server_id, optional=None):
"""
Assign a Volume to a Server
:param volume_id: Volume ID of the volume
:type name: int
:param server_id: Server ID of the server to add the volume to
:type source_volume_id: int
:param optional: Dictionary of optional params
:type optional: dict
.. code-block:: python
optional = {
'Transport' : 0,
'Lun' : 1,
}
"""
parameters = {'serverID': server_id,
'exclusiveAccess': True,
'readAccess': True,
'writeAccess': True}
if optional:
parameters = self._mergeDict(parameters, optional)
info = {'action': 'addServerAccess',
'parameters': parameters}
response, body = self.http.post('/volumes/%s' % volume_id,
body=info)
return body
[docs] def removeServerAccess(self, volume_id, server_id):
"""
Unassign a Volume from a Server
:param volume_id: Volume ID of the volume
:type name: int
:param server_id: Server ID of the server to remove the volume fom
:type source_volume_id: int
"""
parameters = {'serverID': server_id}
info = {'action': 'removeServerAccess',
'parameters': parameters}
response, body = self.http.post('/volumes/%s' % volume_id,
body=info)
return body
def _mergeDict(self, dict1, dict2):
"""
Safely merge 2 dictionaries together
:param dict1: The first dictionary
:type dict1: dict
:param dict2: The second dictionary
:type dict2: dict
:returns: dict
:raises Exception: dict1, dict2 is not a dictionary
"""
if type(dict1) is not dict:
raise Exception("dict1 is not a dictionary")
if type(dict2) is not dict:
raise Exception("dict2 is not a dictionary")
dict3 = dict1.copy()
dict3.update(dict2)
return dict3