diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 00000000..52b1a71d --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,25 @@ +############################################################################# +# +# The first-level GNUmakefile +# +# Please copy this GNUmakefile to your new project, and modify the +# path to the Makefile.detect below. +# +# Please do NOT add anything else to this file. Customize the +# second-level Makefile instead. +# +# Two levels of makefiles are required to do the auto-detection. +# They can be avoided only by doing a pre-configuration like it happens +# in every GNU distribution. Unfortunately, this approach, though very +# beneficial, will require some major changes, and I exect it will be +# difficult to get approval for this. +# +############################################################################# + +# Please do not rename these variables. They're used in Makefile.detect +# to avoid invocation of "%:" pattern to "update" the makefiles +# (they don't need to be updated, do they?) +MAKEFILE := GNUmakefile +MAKEFILE_DETECT := ../../makefiles/Makefile.detect + +include $(MAKEFILE_DETECT) diff --git a/cvpysdk/commcell_migration.py b/cvpysdk/commcell_migration.py new file mode 100644 index 00000000..70224ffb --- /dev/null +++ b/cvpysdk/commcell_migration.py @@ -0,0 +1,327 @@ +# -*- coding: utf-8 -*- +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +"""Class to perform all the CommCell Migration operations on commcell + +CommCellMigration is the only class defined in this file. + +CommCellMigration: Helper class to perform CommCell Import & Export operations. + +CommCellMigration: + + __init__() -- initializes CommCellMigration helper object. + + commcell_export() -- function to run CCM Export operation. + + commcell_import() -- function to run CCM Import operation. + +""" +from base64 import b64encode +from past.builtins import basestring + +from .exception import SDKException + +class CommCellMigration(object): + """Class for representing the commcell export & import operations from commcell. """ + def __init__(self, commcell_object): + """Initializes object of the CommCellMigration class. + + Args: + commcell_object (object) -instance of the commcell class + + Returns: + object - instance of the CommCellMigration class + """ + + self._commcell_object = commcell_object + self._cvpysdk_object = self._commcell_object._cvpysdk_object + self._services = self._commcell_object._services + self._update_response_ = self._commcell_object._update_response_ + self._commcell_name = self._commcell_object.commserv_name + self._path_type = 0 + + def commcell_export(self, export_location, client_list, options_dictionary): + """ Starts the Commcell Export job. + + Args: + export_location ( str ) -- Location to export generated dumps. + + client_list ( list ) -- Contains list of clients used for export. + [ + "Server_1","Client1","Client2" + ] + + options_dictionary ( dict ) -- Contains options used to perform CCM Export. + { + "pathType":"Local", + + "otherSqlInstance":True + } + + Returns: + int -- returns the CCM Export job id. + + Raises: + SDKException: + if type of the input is not valid. + + if all the required inputs are not provided. + + if invalid inputs are passed. + """ + + path_type = options_dictionary.get("pathType", "Local") + network_user_name = options_dictionary.get("userName", "") + network_user_password = options_dictionary.get("password", "") + other_sql_instance = options_dictionary.get("otherSqlInstance", False) + sql_instance_name = options_dictionary.get("sqlInstanceName", "") + sql_user_name = options_dictionary.get("sqlUserName", "") + sql_password = options_dictionary.get("sqlPassword", "") + database = options_dictionary.get("Database", "commserv") + capture_ma = options_dictionary.get("captureMediaAgents", True) + auto_pick_cluster = options_dictionary.get("autopickCluster", False) + + if not (isinstance(path_type, basestring) + and isinstance(network_user_name, basestring) + and isinstance(network_user_password, basestring) + and isinstance(other_sql_instance, bool) + and isinstance(sql_instance_name,basestring) + and isinstance(export_location, basestring) + and isinstance(sql_user_name, basestring) + and isinstance(sql_password, basestring) + and isinstance(database, basestring) + and isinstance(capture_ma, bool) + and isinstance(auto_pick_cluster, bool)): + raise SDKException('CommCellMigration', '101') + + if path_type.lower() == 'local': + self._path_type = 0 + elif path_type.lower() == 'network': + self._path_type = 1 + else: + raise SDKException('CommCellMigration', '104') + + if other_sql_instance: + if sql_instance_name == "" or sql_user_name == "" or sql_password == "": + raise SDKException('CommCellMigration', '103') + sql_password = b64encode(sql_password.encode()).decode() + + if self._path_type == 1: + if network_user_name == "" or network_user_password == "": + raise SDKException('CommCellMigration', '103') + network_user_password = b64encode(network_user_password.encode()).decode() + + export_json = { + "taskInfo": { + "task": { + "taskType": 1, + "isEditing": False, + "initiatedFrom": 2, + "policyType": 0, + "taskFlags": { + "disabled": False + } + }, + "appGroup": { + }, + "subTasks": [ + { + "subTask": { + "subTaskType": 1, + "operationType": 4029 + }, + "options": { + "adminOpts": { + "ccmOption": { + "commonOptions": { + "otherSqlInstance": other_sql_instance, + "pathType": self._path_type, + "dumpFolder": export_location, + "splitCSDB": 1, + "userAccount": { + "password": network_user_password, + "userName": network_user_name + }, + "sqlLinkedServer": { + "sqlServerName": sql_instance_name, + "sqlUserAccount": { + "userName": sql_user_name, + "password": sql_password + } + } + }, + "captureOptions": { + "captureMediaAgents": capture_ma, + "lastHours": 60, + "remoteDumpDir": "", + "remoteCSName": "", + "pruneExportedDump": False, + "autopickCluster": auto_pick_cluster, + "copyDumpToRemoteCS": False, + "useJobResultsDirForExport": False, + "captureFromDB": { + "csName": self._commcell_name, + "csDbName": database + }, + "entities": [ + ], + "timeRange": { + "_type_": 54, + } + } + } + } + } + } + ] + } + } + + sub_dict = export_json['taskInfo']['subTasks'][0]['options']['adminOpts']['ccmOption'] \ + ['captureOptions']['entities'] + + for i in range(len(client_list)): + temp_dic = {'clientName': client_list[i], 'commCellName': self._commcell_name} + sub_dict.append(temp_dic) + + flag, response = self._cvpysdk_object.make_request('POST', + self._services['RESTORE'], + export_json) + + if flag: + if response.json() and 'jobIds' in response.json(): + return response.json()['jobIds'][0] + elif response.json and 'errorCode' in response.json(): + return response.json()['errorCode'] + return 0 + else: + response_string = self._update_response_(response.text) + raise SDKException('Response', '101', response_string) + + def commcell_import(self, import_location, options_dictionary): + """ Starts the Commcell Import job. + + Args: + import_location ( str ) -- Location to import the generated dumps. + + options_dictionary ( dict ) -- Contains list of options used for CCMImport. + { + "pathType":"Local", + + "userName":"sa" + + "password":"password" + } + + Returns: + int -- returns CCM Import job id or error code. + + Raises: + SDKException: + if type of the input is not valid. + + if all the required inputs are not provided. + + if invalid inputs are passed. + """ + path_type = options_dictionary.get("pathType", "Local") + network_user_name = options_dictionary.get("userName", "") + network_user_password = options_dictionary.get("password", "") + + if not (isinstance(path_type, basestring) and isinstance(import_location, basestring)): + raise SDKException('CommCellMigration', '101') + + if path_type.lower() == 'local': + self._path_type = 0 + elif path_type.lower() == 'network': + self._path_type = 1 + else: + raise SDKException('CommCellMigration', '104') + + if self._path_type == 1: + if network_user_name == "" or network_user_password == "": + raise SDKException('CommCellMigration', '103') + + import_json = { + "taskInfo":{ + "associations":[ + { + "type":0, + "clientSidePackage": True, + "consumeLicense": True + } + ], + "task":{ + "taskType":1, + "initiatedFrom":2, + "taskFlags":{ + "disabled": False + } + }, + "subTasks":[ + { + "subTask":{ + "subTaskType":1, + "operationType":4030 + }, + "options":{ + "adminOpts":{ + "ccmOption":{ + "mergeOptions":{ + "deleteEntitiesIfOnlyfromSource": False, + "forceOverwriteHolidays": False, + "reuseTapes": False, + "specifyStagingPath": False, + "forceOverwriteOperationWindow": False, + "fallbackSpareGroup":"", + "mergeOperationWindow": False, + "pruneImportedDump": False, + "alwaysUseFallbackDataPath":True, + "deleteEntitiesNotPresent":False, + "forceOverwrite":False, + "mergeHolidays":True, + "forceOverwriteSchedule":False, + "fallbackDrivePool":"", + "mergeActivityControl":True, + "fallbackMediaAgent":"", + "mergeSchedules":True, + "failIfEntityAlreadyExists":False, + "fallbackLibrary":"", + "skipConflictMedia":False, + "stagingPath":"" + }, + "commonOptions": { + "bRoboJob": False, + "databaseConfiguredRemote": False, + "pathType": self._path_type, + "dumpFolder": import_location, + "splitCSDB": 0, + "userAccount": { + "password": network_user_password, + "userName": network_user_name + } + } + } + } + } + } + ] + } + } + flag, response = self._cvpysdk_object.make_request('POST', + self._services['RESTORE'], + import_json) + + if flag: + if response.json() and 'jobIds' in response.json(): + return response.json()['jobIds'][0] + elif response.json and 'errorCode' in response.json(): + return response.json()['errorCode'] + return 0 + else: + response_string = self._update_response_(response.text) + raise SDKException('Response', '101', response_string) diff --git a/cvpysdk/instances/cloudapps/cloud_storage_instance.py b/cvpysdk/instances/cloudapps/cloud_storage_instance.py new file mode 100644 index 00000000..30dfe7df --- /dev/null +++ b/cvpysdk/instances/cloudapps/cloud_storage_instance.py @@ -0,0 +1,441 @@ +# FIXME : https://engweb.commvault.com/engtools/defect/215230 +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +"""File for operating on a cloud storage instance. + +CloudStorageInstance is the only class defined in this file. + +CloudStorageInstance: Derived class from CloudAppsInstance Base class, representing a + Cloud Storage instance(S3,Azure,Oraclecloud and Openstack), and to + perform operations on that instance + +CloudStorageInstance: + + _get_instance_properties() -- Instance class method overwritten to add cloud apps + instance properties as well + + _restore_in_place() -- restores the files/folders specified in the + input paths list to the same location. + + _restore_out_of_place() -- restores the files/folders specified in the input + paths list to the input client, at the specified destination location. + + _restore_to_fs() -- restores the files/folders specified in the input + paths list to the input fs client, at the specified destination location. + +""" +from past.builtins import basestring +from cvpysdk.instances.cainstance import CloudAppsInstance +from cvpysdk.client import Client +from cvpysdk.exception import SDKException + + +class CloudStorageInstance(CloudAppsInstance): + """ Class for representing an Instance of the cloud storage instance type. """ + + def _get_instance_properties(self): + """ Gets the properties of this instance """ + super(CloudStorageInstance, self)._get_instance_properties() + + self._ca_instance_type = None + self._host_url = None + self._access_keyid = None + self._secret_accesskey = None + self._account_name = None + self._access_key = None + self._server_name = None + self._username = None + self._endpointurl = None + self._access_node = None + + if 'cloudAppsInstance' in self._properties: + cloud_apps_instance = self._properties['cloudAppsInstance'] + self._ca_instance_type = cloud_apps_instance['instanceType'] + + if 's3Instance' in cloud_apps_instance: + s3instance = cloud_apps_instance['s3Instance'] + + self._host_url = s3instance['hostURL'] + self._access_keyid = s3instance['accessKeyId'] + self._secret_accesskey = s3instance['secretAccessKey'] + + if 'azureInstance' in cloud_apps_instance: + azureinstance = cloud_apps_instance['azureInstance'] + + self._host_url = azureinstance['hostURL'] + self._account_name = azureinstance['accountName'] + self._access_key = azureinstance['accessKey'] + + if 'openStackInstance' in cloud_apps_instance: + + self._server_name = cloud_apps_instance['openStackInstance']['serverName'] + self._username = cloud_apps_instance['openStackInstance']['credentials'][ + 'userName'] + + if 'oraCloudInstance' in cloud_apps_instance: + oraclecloudinstance = cloud_apps_instance['oraCloudInstance'] + + self._endpointurl = oraclecloudinstance['endpointURL'] + self._username = oraclecloudinstance['user']['userName'] + + if 'generalCloudProperties' in cloud_apps_instance: + self._access_node = cloud_apps_instance[ + 'generalCloudProperties']['proxyServers'][0]['clientName'] + + @property + def ca_instance_type(self): + """ Returns the CloudApps instance type as a read-only attribute. """ + return self._ca_instance_type + + @property + def host_url(self): + """ Returns the host URL property as a read-only attribute. """ + return self._host_url + + @property + def access_key(self): + """ Returns the access key property as a read-only attribute. """ + return self._access_key + + @property + def account_name(self): + """ Returns the account name as a read-only attribute. """ + return self._account_name + + @property + def access_keyid(self): + """ Returns the access key ID property as a read-only attribute. """ + return self._access_keyid + + @property + def secret_accesskey(self): + """ Returns the secret access key as a read-only attribute. """ + return self._secret_accesskey + + @property + def server_name(self): + """ Returns the server name property as a read-only attribute. """ + return self._server_name + + @property + def username(self): + """ Returns the username property as a read-only attribute. """ + return self._username + + @property + def endpointurl(self): + """ Returns the endpoint URL property as a read-only attribute. """ + return self._endpointurl + + @property + def access_node(self): + """ Returns the access node of this instance as a read-only attribute. """ + return self._access_node + + def restore_in_place( + self, + paths, + overwrite=True, + copy_precedence=None): + """ Restores the files/folders specified in the input paths list to the same location. + + Args: + paths (list) -- list of full paths of files/folders to restore + + overwrite (bool) -- unconditional overwrite files during restore + default: True + + copy_precedence (int) -- copy precedence value of storage policy copy + default: None + + Returns: + object - instance of the Job class for this restore job + + Raises: + SDKException: + if paths is not a list + + if failed to initialize job + + if response is empty + + if response is not success + + """ + + if not (isinstance(paths, list) and + isinstance(overwrite, bool)): + + raise SDKException('Instance', '101') + + if paths == []: + raise SDKException('Instance', '104') + + dest_client = self._agent_object._client_object.client_name + dest_instance = self.instance_name + + dclient = self._commcell_object.clients.get(dest_client) + dagent = dclient.agents.get('Cloud Apps') + dinstance = dagent.instances.get(dest_instance) + + request_json = self._restore_json( + paths=paths, + destination_client=None, + destination_instance_name=None, + overwrite=overwrite, + in_place=True, + copy_precedence=copy_precedence, + restore_To_FileSystem=False) + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['cloudAppsRestoreOptions'] = { + "instanceType": int(self.ca_instance_type), + "cloudStorageRestoreOptions": { + "restoreToFileSystem": False, + "overrideCloudLogin": False, + "restoreDestination": { + "instanceType": int(self.ca_instance_type) + } + } + } + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['destination'] = { + "isLegalHold": False, + "inPlace": True, + "destClient": { + "clientName": dest_client, + "clientId": int(dclient.client_id) + + }, + "destinationInstance": { + "instanceName": dest_instance, + "instanceId": int(dinstance.instance_id) + } + } + + return self._process_restore_response(request_json) + + def restore_out_of_place( + self, + paths, + destination_client, + destination_instance_name, + destination_path, + overwrite=True, + copy_precedence=None): + """ Restores the files/folders specified in the input paths list to the input client, + at the specified destination location. + + Args: + paths (list) -- list of full paths of files/folders to restore + + destination_client (str) -- name of the client to which the files + are to be restored. + + destination_instance_name(str) -- name of the instance to which the files + are to be restored. + + destination_path (str) -- location where the files are to be restored + in the destination instance. + + overwrite (bool) -- unconditional overwrite files during restore + default: True + + copy_precedence (int) -- copy precedence value of storage policy copy + default: None + + + Returns: + object - instance of the Job class for this restore job + + Raises: + SDKException: + if client is not a string or Client instance + + if destination_path is not a string + + if paths is not a list + + if failed to initialize job + + if response is empty + + if response is not success + + """ + + if not ((isinstance(destination_client, basestring) or + isinstance(destination_client, Client)) and + isinstance(destination_instance_name, basestring) and + isinstance(destination_path, basestring) and + isinstance(paths, list) and + isinstance(overwrite, bool)): + + raise SDKException('Instance', '101') + + if paths == []: + raise SDKException('Instance', '104') + + if destination_client is None: + dest_client = self._agent_object._client_object.client_name + + else: + dest_client = destination_client + + if destination_instance_name is None: + dest_instance = self.instance_name + + else: + dest_instance = destination_instance_name + + dclient = self._commcell_object.clients.get(dest_client) + dagent = dclient.agents.get('Cloud Apps') + dinstance = dagent.instances.get(dest_instance) + + request_json = self._restore_json( + paths=paths, + destination_client=destination_client, + destination_instance_name=destination_instance_name, + destination_path=destination_path, + overwrite=overwrite, + in_place=False, + copy_precedence=copy_precedence, + restore_To_FileSystem=False + ) + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['destination'] = { + "isLegalHold": False, + "inPlace": False, + "destPath": [destination_path], + "destClient": { + "clientName": destination_client, + "clientId": int(dclient.client_id) + + }, + "destinationInstance": { + "instanceName": destination_instance_name, + "instanceId": int(dinstance.instance_id) + } + } + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['cloudAppsRestoreOptions'] = { + "instanceType": int(self.ca_instance_type), + "cloudStorageRestoreOptions": { + "restoreToFileSystem": False, + "overrideCloudLogin": False, + "restoreDestination": { + "instanceType": int(self.ca_instance_type) + } + } + } + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['commonOptions'] = { + "stripLevelType": 1 + } + + return self._process_restore_response(request_json) + + def restore_to_fs( + self, + paths, + destination_path, + destination_client=None, + overwrite=True, + copy_precedence=None): + """ Restores the files/folders specified in the input paths list to the input client, + at the specified destination location. + + Args: + paths (list) -- list of full paths of files/folders to restore + + destination_path (str) -- location where the files are to be restored + in the destination instance. + + destination_client (str) -- name of the fs client to which the files + are to be restored. + default: None for restores to backup or proxy client. + + overwrite (bool) -- unconditional overwrite files during restore + default: True + + copy_precedence (int) -- copy precedence value of storage policy copy + default: None + + Returns: + object - instance of the Job class for this restore job + + Raises: + SDKException: + if client is not a string or Client instance + + if destination_path is not a string + + if paths is not a list + + if failed to initialize job + + if response is empty + + if response is not success + + """ + + if not ((isinstance(destination_client, basestring) or + isinstance(destination_client, Client)) and + isinstance(destination_path, basestring) and + isinstance(paths, list) and + isinstance(overwrite, bool)): + + raise SDKException('Instance', '101') + + if destination_client is None: + dest_client = self._agent_object._client_object.client_name + + else: + dest_client = destination_client + + request_json = self._restore_json( + paths=paths, + destination_path=destination_path, + destination_client=destination_client, + overwrite=overwrite, + in_place=False, + copy_precedence=copy_precedence, + restore_To_FileSystem=True + ) + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['destination'] = { + "isLegalHold": False, + "inPlace": False, + "destPath": [destination_path], + "destClient": { + "clientName": dest_client + } + } + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['cloudAppsRestoreOptions'] = { + "instanceType": int(self.ca_instance_type), + "cloudStorageRestoreOptions": { + "restoreToFileSystem": True, + "overrideCloudLogin": False + } + } + + request_json["taskInfo"]["subTasks"][0]["options"][ + "restoreOptions"]['commonOptions'] = { + "stripLevelType": 1 + } + + return self._process_restore_response(request_json) diff --git a/cvpysdk/instances/virtualserver/oraclecloudinstance.py b/cvpysdk/instances/virtualserver/oraclecloudinstance.py new file mode 100644 index 00000000..9af21ba0 --- /dev/null +++ b/cvpysdk/instances/virtualserver/oraclecloudinstance.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +"""File for operating on a Virtual Server Oracle Cloud Instance. + +OracleCloudInstance is the only class defined in this file. + +OracleCloudInstance: Derived class from VirtualServer Base class, representing a + Oracle Cloud instance, and to perform operations on that instance + +OracleCloudInstance: + + __init__(agent_object,instance_name,instance_id) -- initialize object of Oracle Cloud + Instance object associated with the + VirtualServer Instance + + + _get_instance_properties() -- VirtualServer Instance class method + overwritten to get Oracle Cloud + Specific instance properties as well + + _set_instance_properties() -- Oracle Cloud Instance class method + to set Oracle Cloud + Specific instance properties + + +""" + +from ..vsinstance import VirtualServerInstance + + +class OracleCloudInstance(VirtualServerInstance): + """Class for representing an Hyper-V of the Virtual Server agent.""" + + def __init__(self, agent, instance_name, instance_id=None): + """Initialize the Instance object for the given Virtual Server instance. + + Args: + agent (object) -- the instance of the agent class + + instance_name (str) -- the name of the instance + + instance_id (int) -- the instance id + + """ + self._vendor_id = 13 + self._server_name = [] + self._server_host_name = None + self._username = None + super(OracleCloudInstance, self).__init__(agent, instance_name, instance_id) + + def _get_instance_properties(self): + """ + Get the properties of this instance + + Raise: + SDK Exception: + if response is not empty + if response is not success + """ + + super(OracleCloudInstance, self)._get_instance_properties() + if "vmwareVendor" in self._virtualserverinstance: + self._server_host_name = [self._virtualserverinstance['vmwareVendor'][ + 'virtualCenter']['domainName']] + + self._username = self._virtualserverinstance['vmwareVendor'][ + 'virtualCenter']['userName'] + + for _each_client in self._asscociatedclients['memberServers']: + client = _each_client['client'] + if 'clientName' in client.keys(): + self._server_name.append(str(client['clientName'])) + + def _get_instance_properties_json(self): + """get the all instance related properties of this subclient. + + Returns: + instance_json (dict) -- all instance properties put inside a dict + + """ + instance_json = { + "instanceProperties":{ + "isDeleted": False, + "instance": self._instance, + "instanceActivityControl": self._instanceActivityControl, + "virtualServerInstance": { + "vsInstanceType": self._virtualserverinstance['vsInstanceType'], + "associatedClients": self._virtualserverinstance['associatedClients'], + "vmwareVendor": self._virtualserverinstance['vmwareVendor'], + "xenServer": {} + } + } + } + return instance_json + + @property + def server_host_name(self): + """return the Oracle Cloud endpoint + + Returns: + _server_host_name (str) -- the hostname of the oracle cloud server + """ + return self._server_host_name + + @property + def server_name(self): + """ + returns the list of all associated clients with the instance + + Returns: + _server_name (str) -- the list of all proxies associated to the instance + """ + return self._server_name + + @property + def instance_username(self): + """returns the username of the instance + + Returns: + _username (str) -- the user name of the oracle cloud endpoint + """ + return self._username diff --git a/cvpysdk/subclients/cloudapps/cloud_storage_subclient.py b/cvpysdk/subclients/cloudapps/cloud_storage_subclient.py new file mode 100644 index 00000000..21c2256f --- /dev/null +++ b/cvpysdk/subclients/cloudapps/cloud_storage_subclient.py @@ -0,0 +1,248 @@ +# FIXME : https://engweb.commvault.com/engtools/defect/215230 +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +"""File for operating on a Cloud Storage Subclient. + +CloudStorageSubclient is the only class defined in this file. + +CloudStorageSubclient: Derived class from CloudAppsSubclient Base class, representing a + Cloud Storage subclient(S3,Azure,Oraclecloud and Openstack), and + to perform operations on that subclient + +CloudStorageSubclient: + + _get_subclient_properties() -- gets the properties of Cloud Storage Subclient + + _get_subclient_properties_json() -- gets the properties JSON of Cloud Storage Subclient + + content() -- gets the content of the subclient + + _set_content() -- sets the content of the subclient + + restore_in_place() -- Restores the files/folders specified in the + input paths list to the same location + + restore_out_of_place() -- Restores the files/folders specified in the + input paths list to the input client, at the specified destination location + + restore_to_fs() -- Restores the files/folders specified in the + input paths list to the input fs client, at the specified destination location. + +""" +from past.builtins import basestring +from ..casubclient import CloudAppsSubclient +from ...exception import SDKException + + +class CloudStorageSubclient(CloudAppsSubclient): + """ Derived class from Subclient Base class, representing a Cloud Storage subclient, + and to perform operations on that subclient. """ + + def _get_subclient_properties(self): + """ Gets the subclient related properties of Cloud Storage subclient. """ + super(CloudStorageSubclient, self)._get_subclient_properties() + if 'content' in self._subclient_properties: + self._content = self._subclient_properties['content'] + + def _get_subclient_properties_json(self): + """ Gets the properties JSON of Cloud Storage Subclient. + + Returns: + dict - all subclient properties put inside a dict + + """ + subclient_json = { + "subClientProperties": + { + "proxyClient": self._proxyClient, + "subClientEntity": self._subClientEntity, + "cloudAppsSubClientProp": { + "instanceType": self._backupset_object._instance_object.ca_instance_type + }, + "content": self._content, + "commonProperties": self._commonProperties, + "contentOperationType": 1 + } + } + return subclient_json + + def _set_content(self, + content=None): + """ Sets the subclient content + + Args: + content (list) -- list of subclient content + + """ + if content is None: + content = self.content + + update_content = [] + for path in content: + cloud_dict = { + "path": path + } + update_content.append(cloud_dict) + + self._set_subclient_properties("_content", update_content) + + @property + def content(self): + """ Gets the appropriate content from the Subclient relevant to the user. + + Returns: + list - list of content associated with the subclient + + """ + content = [] + + for path in self._content: + if 'path' in path: + content.append(path["path"]) + + return content + + @content.setter + def content(self, subclient_content): + """ Creates the list of content JSON to pass to the API to add/update content of a + Cloud Storage Subclient. + + Args: + subclient_content (list) -- list of the content to add to the subclient + + Returns: + list - list of the appropriate JSON for an agent to send to the POST Subclient API + + Raises : + SDKException : if the subclient content is not a list value and if it is empty + + """ + if isinstance(subclient_content, list) and subclient_content != []: + self._set_content(content=subclient_content) + else: + raise SDKException( + 'Subclient', '102', 'Subclient content should be a list value and not empty' + ) + + def restore_in_place( + self, + paths, + overwrite=True, + copy_precedence=None): + """ Restores the files/folders specified in the input paths list to the same location. + + Args: + paths (list) -- list of full paths of files/folders + to restore + + overwrite (bool) -- unconditional overwrite files during restore + default: True + + copy_precedence (int) -- copy precedence value of storage policy copy + default: None + + Returns: + object - instance of the Job class for this restore job + + """ + + self._instance_object._restore_association = self._subClientEntity + + return self._instance_object.restore_in_place( + paths=paths, + overwrite=overwrite, + copy_precedence=copy_precedence) + + def restore_out_of_place( + self, + paths, + destination_client, + destination_instance_name, + destination_path, + overwrite=True, + copy_precedence=None): + """ Restores the files/folders specified in the input paths list to the input client, + at the specified destionation location. + + Args: + paths (list) -- list of full paths of files/folders to restore + + destination_client (str) -- name of the client to which the files are + to be restored. + default: None for in place restores + + destination_instance_name(str) -- name of the instance to which the files are + to be restored. + default: None for in place restores + + destination_path (str) -- location where the files are to be restored + in the destination instance. + + overwrite (bool) -- unconditional overwrite files during restore + default: True + + copy_precedence (int) -- copy precedence value of storage policy copy + default: None + + Returns: + object - instance of the Job class for this restore job + + """ + + self._instance_object._restore_association = self._subClientEntity + + return self._instance_object.restore_out_of_place( + paths=paths, + destination_client=destination_client, + destination_instance_name=destination_instance_name, + destination_path=destination_path, + overwrite=overwrite, + copy_precedence=copy_precedence) + + def restore_to_fs( + self, + paths, + destination_path, + destination_client=None, + overwrite=True, + copy_precedence=None): + """ Restores the files/folders specified in the input paths list to the fs client + + Args: + paths (list) -- list of full paths of files/folders to restore + + destination_path (str) -- location where the files are to be restored + in the destination instance. + + destination_client (str) -- name of the fs client to which the files + are to be restored. + default: None for restores to backup or proxy client. + + overwrite (bool) -- unconditional overwrite files during restore + default: True + + copy_precedence (int) -- copy precedence value of storage policy copy + default: None + + Returns: + object - instance of the Job class for this restore job + + """ + + self._instance_object._restore_association = self._subClientEntity + + if destination_client is None: + destination_client = self._instance_object.backup_client + + return self._instance_object.restore_to_fs( + paths=paths, + destination_path=destination_path, + destination_client=destination_client, + overwrite=overwrite, + copy_precedence=copy_precedence) diff --git a/cvpysdk/subclients/virtualserver/oraclecloudsubclient.py b/cvpysdk/subclients/virtualserver/oraclecloudsubclient.py new file mode 100644 index 00000000..44419dab --- /dev/null +++ b/cvpysdk/subclients/virtualserver/oraclecloudsubclient.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +"""File for operating on a Virtual Server OracleCloud Subclient. + +OracleCloudeVirtualServerSubclient is the only class defined in this file. + +OracleCloudVirtualServerSubclient: Derived class from VirtualServerSubClient Base class, + representing a OracleCloud Subclient, and + to perform operations on that Subclient + +OracleCloudVirtualServerSubclient: + + full_vm_restore_out_of_place() -- restores the VM specified in to the specified client, + at the specified destination location + +""" + +from ..vssubclient import VirtualServerSubclient + + +class OracleCloudVirtualServerSubclient(VirtualServerSubclient): + """Derived class from VirtualServerSubclient Base class. + This represents a OracleCloud virtual server subclient, + and can perform restore operations on only that subclient. + + """ + + def full_vm_restore_out_of_place( + self, + vm_to_restore=None, + destination_client=None, + proxy_client=None, + new_name=None, + host=None, + power_on=True, + copy_precedence=0, + restore_option=None): + """Restores the FULL Virtual machine specified in the input list + to the provided vcenter client along with the ESX and the datastores. + If the provided client name is none then it restores the Full Virtual + Machine to the source client and corresponding ESX and datastore. + + Args: + vm_to_restore (list) -- list of all VMs to restore + + destination_client (str) -- name of the pseudo client where VM should be + restored + + proxy_client (str) -- the proxy to be used for restore + + new_name (str) -- new name to be given to the restored VM + + host (str) -- destination host or cluster; restores to the + source VM ESX if this value is not + specified + + power_on (bool) -- power on the restored VM + default: True + + copy_precedence (int) -- copy precedence to restored from + default: 0 + + restore_option (dict) -- dictionary with all the advanced restore + options. + + Returns: + object - instance of the Job class for this restore job + + Raises: + SDKException: + if inputs are not of correct type as per definition + + if failed to initialize job + + if response is empty + + if response is not success + + """ + if not restore_option: + restore_option = {} + + # set attr for all the option in restore xml from user inputs + self._set_restore_inputs( + restore_option, + vm_to_restore=self._set_vm_to_restore(vm_to_restore), + power_on=power_on, + copy_preceedence=copy_precedence, + volume_level_restore=1, + client_name=proxy_client, + vcenter_client=destination_client, + esx_host=host, + out_place=True, + restore_new_name=new_name + ) + + request_json = self._prepare_fullvm_restore_json(restore_option) + return self._process_restore_response(request_json) diff --git a/tests/input.json b/tests/input.json new file mode 100644 index 00000000..6df12670 --- /dev/null +++ b/tests/input.json @@ -0,0 +1,8 @@ +{ + "commcell": { + "webconsole_hostname": "", + "commcell_username": "", + "commcell_password": "" + }, + "password1": "" +} \ No newline at end of file diff --git a/tests/test_all.py b/tests/test_all.py new file mode 100644 index 00000000..f51614de --- /dev/null +++ b/tests/test_all.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + + +"""Runs all Python unit tests for cvpysdk.""" + +import os +try: + import unittest2 as unittest +except ImportError: + import unittest + +os.chdir(os.path.dirname(os.path.abspath(__file__))) +suite = unittest.defaultTestLoader.discover('.') + +if __name__ == '__main__': + unittest.TextTestRunner().run(suite) diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 00000000..d85b3c75 --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import testlib + +try: + import unittest2 as unittest +except ImportError: + import unittest + +from cvpysdk.client import Client +from cvpysdk.exception import SDKException + + +class ClientTest(testlib.SDKTestCase): + def setUp(self): + super(ClientTest, self).setUp() + self.client_name = self.commcell_object.commserv_name + self.client = self.commcell_object.clients.get(self.client_name) + + def tearDown(self): + super(ClientTest, self).tearDown() + + def test_client_init(self): + self.assertRaises( + SDKException, + self.commcell_object.clients.get, + 'abc123') + self.assertIsInstance( + self.commcell_object.clients.get( + self.client_name), Client) + + def test_backup_activity(self): + + if self.client.is_backup_enabled: + self.assertIsNone(self.client.disable_backup()) + self.assertFalse(self.client.is_backup_enabled) + else: + self.assertIsNone(self.client.enable_backup()) + self.assertTrue(self.client.is_backup_enabled) + + def test_restore_activity(self): + + if self.client.is_restore_enabled: + self.assertIsNone(self.client.disable_restore()) + self.assertFalse(self.client.is_restore_enabled) + else: + self.assertIsNone(self.client.enable_restore()) + self.assertTrue(self.client.is_restore_enabled) + + def test_data_aging(self): + + if self.client.is_data_aging_enabled: + self.assertIsNone(self.client.disable_data_aging()) + self.assertFalse(self.client.is_data_aging_enabled) + else: + self.assertIsNone(self.client.enable_data_aging()) + self.assertTrue(self.client.is_data_aging_enabled) + + def test_execute_command(self): + """ Execute mkdir command with wrong input folder path. Check if output matches + regex specified + """ + import re + self.assertRegexpMatches(self.client.execute_command("mkdir !:")[1], + re.compile("\w*cannot find\w*")) + + def test_readiness(self): + self.assertTrue(self.client.is_ready) + + def test_file_upload(self): + import os + # testing for CS client , hence assuming windows. + destination_folder = "C:\\" + destination_file = destination_folder + os.path.basename(__file__) + self.client.execute_command("del " + destination_file) + self.client.upload_file(__file__, destination_folder) + assertNotEqual(self.client.execute_command( + "if exists " + destination_file + " echo success"), + "success") + + +if __name__ == "__main__": + + import unittest + unittest.main() diff --git a/tests/test_domain.py b/tests/test_domain.py new file mode 100644 index 00000000..31716f41 --- /dev/null +++ b/tests/test_domain.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +import testlib + + +try: + import unittest2 as unittest +except ImportError: + import unittest + +from cvpysdk.client import Client +from cvpysdk.exception import SDKException + + +class DomainTest(testlib.SDKTestCase): + def setUp(self): + super(DomainTest, self).setUp() + self.client_name = self.commcell_object.commserv_name + self.client = self.commcell_object.clients.get(self.client_name) + + def tearDown(self): + super(DomainTest, self).tearDown() + + def test_add_domian(self): + + self.assertRaises( + SDKException, + self.commcell_object.domains.get, + 'abc123') + self.commcell_object.domains.add( + "automation_pyunittest", + "automation", "automation\\administrator", + self.data['password1'] , + ["magic_test"] + ) + self.assertEqual( + u"automation_pyunittest", + self.commcell_object.domains.get("automation_pyunittest")["shortName"]["domainName"] + ) + self.commcell_object.domains.delete("automation_pyunittest") + self.assertRaises( + SDKException, + self.commcell_object.domains.get, + 'automation_pyunittest') + + +if __name__ == "__main__": + + import unittest + unittest.main() diff --git a/tests/testlib.py b/tests/testlib.py new file mode 100644 index 00000000..d1a447d1 --- /dev/null +++ b/tests/testlib.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# -------------------------------------------------------------------------- +# Copyright Commvault Systems, Inc. +# See LICENSE.txt in the project root for +# license information. +# -------------------------------------------------------------------------- + +"""Shared unit test utilities.""" +import json +import os +import time + +try: + import unittest2 as unittest +except ImportError: + import unittest + +from cvpysdk import commcell +from time import sleep +from datetime import datetime, timedelta + +import logging +logging.basicConfig( + filename='unit_test.log', + level=logging.DEBUG, + format="%(asctime)s:%(levelname)s:%(message)s") + + +class SDKTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + with open('input.json', 'r') as data_file: + cls.data = json.load(data_file) + + def setUp(self): + unittest.TestCase.setUp(self) + self.test_json() + self.commcell_object = commcell.Commcell(**self.data['commcell']) + + def tearDown(self): + self.commcell_object.logout() + + def test_json(self): + self.assertIsInstance(self.data, dict) + self.assertIsInstance(self.data['commcell'], dict) + self.assertIn('webconsole_hostname', self.data['commcell'].keys()) + self.assertIn('commcell_username', self.data['commcell'].keys()) + self.assertIn('commcell_password', self.data['commcell'].keys()) + self.assertNotIn("", self.data['commcell'].values()) + + +if __name__ == "__main__": + import unittest + unittest.main()