Module cvpysdk.subclients.cloudapps.dynamics365_subclient
File for operating on a Dynamics 365 CRM Subclient.
MSDynamics365Subclient is the only class defined in this file.
MSDynamics365Subclient: Derived class from O365AppsSubclient Base class, representing a Dynamics 365 subclient, and to perform operations on that subclient
Msdynamics365Subclient
** Methods **
_get_subclient_properties() – Gets the subclient related properties of a MS Dynamics 365 subclient
_get_subclient_properties_json() – get the all subclient related properties of this subclient.
get_discovered_tables() – Method to get the tables discovered from the MS Dynamics 365 CRM subclient get_discovered_environments() – Method to get the environments discovered from the Dynamics 365 CRM subclient _get_associated_content() – Method to get the content associated with a Dynamics 365 CRM subclient get_associated_tables() – Method to get the tables associated with a Dynamics 365 CRM client get_associated_environments() – Method to get the environments associated with a Dynamics 365 CRM client _set_association_json() – JSON to set the content association for a Dynamics 365 CRM client _set_content_association() – Method to associate some content to a Dynamics 365 CRM client… _table_association_info_json() – Private Method to create the association JSON for associating tables to a Dynamics 365 CRM client. set_table_associations() – Method to add table associations to a Dynamics 365 CRM client. _environment_association_info_json() – Method to create the association JSON for associating environments to a Dynamics 365 CRM client. set_environment_associations() – Method to add environment associations to a Dynamics 365 CRM client. _json_for_backup_task() – Method to create the association JSON for backing up content for a Dynamics 365 subclient _backup_content_json() – Method to fetch the metadata properties for backing up content for a Dynamics 365 subclient _run_backup() – Method to run backup for the content of a Dynamics 365 subclient backup_tables() – Method to run backup for the specified tables of a Dynamics 365 subclient backup_environments() – Method to run backup for the specified environments of a Dynamics 365 subclient _restore_content_json() – Restore JSON for restoring content for a Dynamics 365 subclient _get_restore_item_path() – Get the complete path of the content for running a restore job _prepare_restore_json() – Method to prepare JSON/ Python dict for in- place restore for the content specified. restore_in_place() – Method to run in- place restore for the content specified. launch_d365_licensing() – Method to launch Licensing API call. _get_environment_id_for_oop_restore() – Get the Environment ID for an environment for Out of Place Restore restore_out_of_place() – Method to run out-of-place restore for the content specified. browse() – Browse for the backed up content for a Dynamics 365 subclient _get_guid_for_path() – Method to get the browse GUID corresponding to the path _perform_browse() – Perform a browse of the backed up content _get_dynamics365_browse_params() – Default dictionary for the browse parameters for a Dynamics 365 browse query.
** Properties **
discovered_environments – Property to get the tables discovered by the Dynamics 365 subclient. discovered_tables – Dictionary of tables discovered by the subclient browse_item_type() – Dynamics 365 item type
Expand source code Browse git
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------
# Copyright Commvault Systems, Inc.
#
# 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.
# --------------------------------------------------------------------------
"""
File for operating on a Dynamics 365 CRM Subclient.
MSDynamics365Subclient is the only class defined in this file.
MSDynamics365Subclient: Derived class from O365AppsSubclient Base class, representing a
Dynamics 365 subclient, and to perform operations on that subclient
MSDynamics365Subclient:
***************** Methods *****************
_get_subclient_properties() -- Gets the subclient related properties of a MS Dynamics 365 subclient
_get_subclient_properties_json() -- get the all subclient related properties of this subclient.
get_discovered_tables() -- Method to get the tables discovered from the MS Dynamics 365 CRM subclient
get_discovered_environments() -- Method to get the environments discovered from the Dynamics 365 CRM subclient
_get_associated_content() -- Method to get the content associated with a Dynamics 365 CRM subclient
get_associated_tables() -- Method to get the tables associated with a Dynamics 365 CRM client
get_associated_environments() -- Method to get the environments associated with a Dynamics 365 CRM client
_set_association_json() -- JSON to set the content association for a Dynamics 365 CRM client
_set_content_association() -- Method to associate some content to a Dynamics 365 CRM client...
_table_association_info_json() -- Private Method to create the association JSON for associating tables
to a Dynamics 365 CRM client.
set_table_associations() -- Method to add table associations to a Dynamics 365 CRM client.
_environment_association_info_json() -- Method to create the association JSON for associating environments
to a Dynamics 365 CRM client.
set_environment_associations() -- Method to add environment associations to a Dynamics 365 CRM client.
_json_for_backup_task() -- Method to create the association JSON for backing up content for a Dynamics 365 subclient
_backup_content_json() -- Method to fetch the metadata properties for backing up content for a Dynamics 365 subclient
_run_backup() -- Method to run backup for the content of a Dynamics 365 subclient
backup_tables() -- Method to run backup for the specified tables of a Dynamics 365 subclient
backup_environments() -- Method to run backup for the specified environments of a Dynamics 365 subclient
_restore_content_json() -- Restore JSON for restoring content for a Dynamics 365 subclient
_get_restore_item_path() -- Get the complete path of the content for running a restore job
_prepare_restore_json() -- Method to prepare JSON/ Python dict for in- place restore for the content specified.
restore_in_place() -- Method to run in- place restore for the content specified.
launch_d365_licensing() -- Method to launch Licensing API call.
_get_environment_id_for_oop_restore() -- Get the Environment ID for an environment for Out of Place Restore
restore_out_of_place() -- Method to run out-of-place restore for the content specified.
browse() -- Browse for the backed up content for a Dynamics 365 subclient
_get_guid_for_path() -- Method to get the browse GUID corresponding to the path
_perform_browse() -- Perform a browse of the backed up content
_get_dynamics365_browse_params() -- Default dictionary for the browse parameters for a Dynamics 365 browse query.
***************** Properties *****************
discovered_environments -- Property to get the tables discovered by the Dynamics 365 subclient.
discovered_tables -- Dictionary of tables discovered by the subclient
browse_item_type() -- Dynamics 365 item type
"""
import copy
import json
from ...exception import SDKException
from ..o365apps_subclient import O365AppsSubclient
from ..casubclient import CloudAppsSubclient
class MSDynamics365Subclient(O365AppsSubclient):
"""
Class representing a MS Dynamics 365 subclient.
Class has been derived from the O365AppsSubclient.
"""
def __init__(self, backupset_object, subclient_name, subclient_id=None):
"""Initialize the Subclient object for the given MSDynamics365 Subclient.
Args:
backupset_object (object) -- instance of the backup-set class
subclient_name (str) -- subclient name
subclient_id (int) -- subclient id
"""
super(MSDynamics365Subclient, self).__init__(
backupset_object, subclient_name, subclient_id)
self._instance_object = backupset_object._instance_object
self._client_object = self._instance_object._agent_object._client_object
self._associated_tables: dict = dict()
self._associated_environments: dict = dict()
self._discovered_environments: dict = dict()
self._discovered_tables: dict = dict()
self._instance_type: int = 35
self._app_id: int = 134
# App ID for cloud apps
self._Dynamics365_SET_USER_POLICY_ASSOCIATION = self._commcell_object._services['SET_USER_POLICY_ASSOCIATION']
def _get_subclient_properties(self):
"""Gets the subclient related properties of a MS Dynamics 365 subclient"""
super(MSDynamics365Subclient, self)._get_subclient_properties()
def _get_subclient_properties_json(self):
"""get the all subclient related properties of this subclient.
Returns:
dict - all subclient properties put inside a dict
"""
return {'subClientProperties': self._subclient_properties}
def discover_tables(self):
"""
Method to get the tables discovered from the MS Dynamics 365 CRM subclient
Returns:
discovered_tables (dict)-- Dictionary of returned tables
"""
self._discovered_tables = self._instance_object.discover_content(environment_discovery=False)
return self._discovered_tables
def discover_environments(self):
"""
Method to get the environments discovered from the Dynamics 365 CRM subclient
Returns:
discovered_environments (dict)-- Dictionary of discovered environments
"""
self._discovered_environments = self._instance_object.discover_content(environment_discovery=True)
return self._discovered_environments
@property
def discovered_environments(self):
"""
Property to get the environments discovered by the Dynamics 365 subclient.
If updated list is required, call refresh method prior to using this property.
Returns:
discovered_environments (dict)-- Dictionary of discovered environments
"""
if not bool(self._discovered_environments):
self.discover_environments()
return self._discovered_environments
@property
def discovered_tables(self):
"""
Property to get the tables discovered by the Dynamics 365 subclient.
If updated list is required, call refresh method prior to using this property.
Returns:
discovered_tables (dict)-- Dictionary of discovered tables
"""
if not bool(self._discovered_tables):
self.discover_tables()
return self._discovered_tables
def _get_associated_content(self, is_environment=False):
"""
Method to get the content associated with a Dynamics 365 CRM subclient
Arguments:
is_environment (bool)-- Whether to get the associated environments or tables
Default Value:
False
Returns the associated tables
Returns:
associated_content_list (list)-- List of content associated with the client
Format:
Each list element will be a dictionary denoting that particular environment/ table
"""
discover_by_type: int
if is_environment is True:
discover_by_type = 5
else:
discover_by_type = 14
_GET_ASSOCIATED_CONTENT = self._services['USER_POLICY_ASSOCIATION']
request_json = {
"discoverByType": discover_by_type,
"bIncludeDeleted": False,
"cloudAppAssociation": {
"subclientEntity": {
"subclientId": int(self.subclient_id)
}
}
}
flag, response = self._cvpysdk_object.make_request(
'POST', _GET_ASSOCIATED_CONTENT, request_json
)
if flag:
if response and response.json():
no_of_records = int()
if 'associations' in response.json():
no_of_records = response.json().get('associations', [])[0].get('pagingInfo', {}). \
get('totalRecords', -1)
associations = response.json().get('associations', [])
content_list = list()
if discover_by_type == 5:
for environment in associations:
environment_name = environment.get("groups", {}).get("name")
env_dict = {
"name": environment_name,
"id": environment.get("groups", {}).get("id"),
"userAccountInfo": environment.get("userAccountInfo", {}),
"plan": environment.get("plan", {}),
"is_environment": True
}
content_list.append(env_dict)
elif discover_by_type == 14:
for table in associations:
table_name = table.get("userAccountInfo", {}).get("displayName")
table_dict = {
"name": table_name,
"environment_name": table.get("userAccountInfo", {}).get("ParentWebGuid", ""),
"userAccountInfo": table.get("userAccountInfo", {}),
"plan": table.get("plan", {}),
"is_environment": False
}
content_list.append(table_dict)
return content_list
# return content_list, no_of_records
return {}, 0
raise SDKException('Response', '101', self._update_response_(response.text))
def get_associated_tables(self, refresh: bool = False):
"""
Method to get the tables associated with a Dynamics 365 CRM client
Arguments:
refresh (bool)-- Whether to refresh the dictionary contents
If True
get associated environments, will fetch the latest associations and return them
Returns:
associated_tables (list)-- List of tables associated with the client
Format:
Each list element will be a dictionary denoting that particular table
Dictionary keys/ format will be:
name : name of the table
environment_name : name of the environment to which the table belongs to
plan: Dynamics 365 plan used for content association
is_environment: False for a Table
userAccountInfo: Metadata info for that table
Sample Response:
{
'name': 'account',
'environment_name': 'sample-environment-name',
'userAccountInfo':
{
'aliasName': 'https://<org-url-name>.crm.dynamics.com/api/data/v9.1/account',
'displayName': 'Account,
'ParentWebGuid': 'org-environment-name',
'lastBackupJobRanTime': {'time': <last-backup-time>},
'IdxCollectionTime': {'time': <last-index-time>},
'user': {
'_type_': 13,
'userGUID': '<table-GUID>>'
}
},
'plan': {
'planName': '<PLAN-NAME>', 'planId': <plan-id>},
'is_environment': False
}
Environment name/ URL in the sample response is for description purpose only
"""
if refresh is True:
self._associated_tables = self._get_associated_content(is_environment=False)
return self._associated_tables
def get_associated_environments(self, refresh: bool = False):
"""
Method to get the environments associated with a Dynamics 365 CRM client
Arguments:
refresh (bool)-- Whether to refresh the dictionary contents
If True
get associated environments, will fetch the latest associations and return them
Returns:
associated_environments (list)-- List of environments associated with the client
Format:
Each list element will be a dictionary denoting that particular environment
Dictionary keys/ format will be:
name : name of the table
plan: Dynamics 365 plan used for content association
is_environment: True for an environment
userAccountInfo: Metadata info for that environment
Sample Response:
{
'name': 'sample-environment-name',
'id': '<environment-ID>>',
'userAccountInfo':
{
'aliasName': 'https://<org-url-name>.crm.dynamics.com',
'itemType': 0,
'ItemClassification': 0,
'displayName': 'org-environment-display-name',
'BackupSetId': 0,
'isAutoDiscoveredUser': False,
'lastBackupJobRanTime': 'time': <last-backup-time>,
'IdxCollectionTime': {'time': <last-index-playback-time>},
user': {
'_type_': 13,
'userGUID': '<env-GUID>'
}
},
'plan': {'planName': '<name-of-plan>', 'planId': <id-of-plan>},
'is_environment': True}
"""
if refresh is True:
self._associated_environments = self._get_associated_content(is_environment=True)
return self._associated_environments
def _set_association_json(self, is_environment: bool = False):
"""
JSON to set the content association for a Dynamics 365 CRM client
Arguments:
is_environment (bool): Whether the content to be associated is an environment
Default Value:
False
Returns:
set_content_association_json (dict)-- Content Association JSON
"""
set_content_association_json = {"LaunchAutoDiscovery": is_environment,
"cloudAppAssociation": {
"accountStatus": 0,
"cloudAppDiscoverinfo": {
"userAccounts": [
],
"groups": [],
"discoverByType": 14 if is_environment is False else 15
}
, "subclientEntity": self._subClientEntity}}
return set_content_association_json
def _set_content_association(self, content_json: dict):
"""
Method to associate some content to a Dynamics 365 CRM client...
Arguments:
content_json (dict)-- Association JSON to be used for the content
"""
flag, response = self._commcell_object._cvpysdk_object.make_request(
'POST', self._Dynamics365_SET_USER_POLICY_ASSOCIATION, content_json
)
if flag:
try:
if response.json():
if response.json().get('resp', {}).get('errorCode', 0) != 0:
error_message = response.json()['errorMessage']
output_string = 'Failed to Create Association for a Dynamics 365 CRM client\nError: "{0}"'
raise SDKException(
'Subclient', '102', output_string.format(error_message)
)
else:
self.refresh()
except ValueError:
raise SDKException('Response', '102')
else:
response_string = self._commcell_object._update_response_(response.text)
raise SDKException('Response', '101', response_string)
def _table_association_info_json(self, tables_list: list):
"""
Private Method to create the association JSON for associating tables
to a Dynamics 365 CRM client.
Arguments:
tables_list (list)-- List of tables to be associated to the content
List Format:
Each list element should be a tuple of the format:
("table_name", "environment_name")
environment_name is the name of the environment to which the table belongs to
table_name is the name of the table to be associated
Returns:
tables_info (list)-- List of metadata info for the tables to be used for associating content
"""
tables_info: list = list()
_discovered_tables = self.discovered_tables
if not bool(_discovered_tables):
raise SDKException('Subclient', '101',
"Discovered Tables is Empty.")
for _table in _discovered_tables:
_table_name, _parent_env_name = _table["displayName"], _table["ParentWebGuid"]
try:
if (_table_name, _parent_env_name) in tables_list:
_table_assoc_info = _table
_table_assoc_info["user.userGUID"] = _table.get("user").get("userGUID")
tables_info.append(_table_assoc_info)
except TypeError:
raise SDKException('Subclient', '101',
"For Associating tables, content list should be a list of tuples")
if len(tables_info) != len(tables_list):
raise SDKException("Subclient", "101", "All of the input tables were in the list of discovered tables")
return tables_info
def set_table_associations(self, tables_list: list, plan_name: str = str()):
"""
Method to add table associations
to a Dynamics 365 CRM client.
Arguments:
tables_list (list)-- List of tables to be associated to the content
List Format:
Each list element should be a tuple of the format:
("table_name", "environment_name")
environment_name is the name of the environment to which the table belongs to
table_name is the name of the table to be associated
Sample input:
[ ("account", "testenv1") , ("note", "testenv2") , ("attachments", "testenv1")]
plan_name (str)-- Name of the Dynamics 365 Plan to be used for content association
"""
plan_id = int(self._commcell_object.plans[plan_name.lower()])
tables_info = self._table_association_info_json(tables_list=tables_list)
_table_association_json = self._set_association_json(is_environment=False)
_table_association_json["cloudAppAssociation"]["plan"] = {
"planId": plan_id
}
_table_association_json["cloudAppAssociation"]["cloudAppDiscoverinfo"]["userAccounts"] = tables_info
self._set_content_association(content_json=_table_association_json)
def _environment_association_info_json(self, environments_name: list):
"""
Method to create the association JSON for associating environments
to a Dynamics 365 CRM client.
Arguments:
environments_name (list)-- List of environments to be associated to the content
List Format:
Each list element should be a string of the name of the environment
Returns:
environments_info (list)-- List of metadata info for the environments to
be used for associating content
"""
environments_info: list = list()
_discovered_envs = self.discovered_environments
if not bool(_discovered_envs):
raise SDKException('Subclient', '101',
"Discovered Environments List is Empty")
for environment in _discovered_envs:
if environment["displayName"] in environments_name:
_env_assoc_info = environment
_env_assoc_info["user.userGUID"] = environment.get("user").get("userGUID")
_env_assoc_info["rawCommonFlag"] = environment.get("commonFlags", 0)
environments_info.append(_env_assoc_info)
if len(environments_info) == 0:
raise SDKException("Subclient", "101",
"None of the input environments were in the list of discovered environments")
return environments_info
def set_environment_associations(self, environments_name: list, plan_name: str = str()):
"""
Method to add environment associations
to a Dynamics 365 CRM client.
Arguments:
environments_name (list)-- List of environments to be associated to the content
List Format:
Each list element should be a string of the name of the environment
Sample Values:
['testenv1' , 'testenv2', 'testenv3']
plan_name (str)-- Name of the Dynamics 365 Plan to be used for content association
"""
environments_info: list = self._environment_association_info_json(environments_name=environments_name)
if self._commcell_object.plans.has_plan(plan_name.lower()):
plan_id = int(self._commcell_object.plans[plan_name.lower()])
else:
raise SDKException("Subclient", "101",
"Dynamics 365 Plan does not exist")
_env_association_json = self._set_association_json(is_environment=True)
_env_association_json["cloudAppAssociation"]["plan"] = {
"planId": plan_id
}
_env_association_json["cloudAppAssociation"]["cloudAppDiscoverinfo"]["userAccounts"] = environments_info
self._set_content_association(content_json=_env_association_json)
def _json_for_backup_task(self, content_list: list, is_environment: bool = False):
"""
Method to create the association JSON for backing up content for a Dynamics 365 subclient
Arguments:
content_list (list)-- List of content to be backed up
List Format, if content to be backed up is tables:
Each list element should be a tuple of the format:
("environment_name","table_name")
environment_name is the name of the environment to which the table belongs to
table_name is the name of the table to be backed up
List Format, if content to be associated is environments:
Each list element should be a string of the name of the environment
is_environment (bool)-- Content passed to be backed up is environment type content or table type
Returns:
_backup_task_json (list)-- JSON for backing up the content
"""
_backup_task_json = self._backup_json('Full', False, '')
_sub_client_content_json = self._backup_content_json(content_list=content_list, is_environment=is_environment)
backup_options = {
'backupLevel': 2, # Incremental
'cloudAppOptions': {
'userAccounts': _sub_client_content_json
}
}
_backup_task_json['taskInfo']['subTasks'][0]['options']['backupOpts'] = backup_options
return _backup_task_json
def _backup_content_json(self, content_list: list, is_environment: bool = False):
"""
Method to fetch the metadata properties for backing up content for a Dynamics 365 subclient
Arguments:
content_list (list)-- List of content to be backed up
List Format, if content to be backed up is tables:
Each list element should be a tuple of the format:
("environment_name","table_name")
environment_name is the name of the environment to which the table belongs to
table_name is the name of the table to be backed up
List Format, if content to be associated is environments:
Each list element should be a string of the name of the environment
is_environment (bool)-- Content passed to be backed up is environment type content or table type
Returns:
_bkp_content_json (list)-- Metadata JSON for backing up that content
"""
_bkp_content_json = list()
if is_environment is True:
for environment in self.get_associated_environments(refresh=True):
if environment["name"] in content_list:
_env_bkp_info = environment["userAccountInfo"]
_bkp_content_json.append(_env_bkp_info)
elif is_environment is False:
for _table in self.get_associated_tables(refresh=True):
_table_name, _parent_env_name = _table["name"].lower(), _table["environment_name"].lower()
try:
if (_parent_env_name, _table_name) in content_list:
_table_bkp_info = _table["userAccountInfo"]
_bkp_content_json.append(_table_bkp_info)
except TypeError:
raise SDKException('Subclient', '101',
"For backing up tables, content list should be a list of tuples")
return _bkp_content_json
def _run_backup(self, backup_content: list, is_environment: bool = False):
"""
Method to run backup for the content of a Dynamics 365 subclient
Arguments:
backup_content (list)-- List of content to be backed up
List Format, if content to be backed up is tables:
Each list element should be a tuple of the format:
("environment_name","table_name")
environment_name is the name of the environment to which the table belongs to
table_name is the name of the table to be backed up
List Format, if content to be associated is environments:
Each list element should be a string of the name of the environment
is_environment (bool)-- Content passed to be backed up is environment type content or table type
Returns:
backup_job (Job)-- CVPySDK.Job class instance for that particular backup job
"""
_backup_json = self._json_for_backup_task(content_list=backup_content, is_environment=is_environment)
backup_endpoint = self._services['CREATE_TASK']
flag, response = self._commcell_object._cvpysdk_object.make_request("POST", backup_endpoint, _backup_json)
return self._process_backup_response(flag, response)
def backup_tables(self, tables_list: list):
"""
Method to run backup for the specified tables of a Dynamics 365 subclient
Arguments:
tables_list (list)-- List of tables to be backed up
List Format
Each list element should be a tuple of the format:
("environment_name","table_name")
environment_name is the name of the environment to which the table belongs to
table_name is the name of the table to be backed up
Sample input:
[ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
Returns:
backup_job (Job)-- CVPySDK.Job class instance for that particular backup job
"""
return self._run_backup(backup_content=tables_list, is_environment=False)
def backup_environments(self, environments_list: list):
"""
Method to run backup for the specified environments of a Dynamics 365 subclient
Arguments:
environments_list (list)-- List of environments to be backed up
List Format, for backing up specified environments:
Each list element should be a string of the name of the environment
Sample List:
['testenv1','testenv2','testenv3']
Returns:
backup_job (Job)-- CVPySDK.Job class instance for that particular backup job
"""
return self._run_backup(backup_content=environments_list, is_environment=True)
def _restore_content_json(self):
"""
Restore JSON for restoring content for a Dynamics 365 subclient
Returns:
_restore_task_json (dict)-- JSON to be used for running a restore task
"""
_restore_task_json = {
"taskInfo": {
"associations": [self._subclient_properties['subClientEntity']],
"task": {
"taskType": 1,
"initiatedFrom": 2,
"policyType": 0
},
"subTasks": [
{
"subTask": {
"subTaskType": 3,
"operationType": 1001
},
"options": {
"restoreOptions": {
"browseOption": {
"timeRange": {}
},
"commonOptions": {
"skip": True,
"overwriteFiles": False,
"unconditionalOverwrite": False
},
"destination": {
"destAppId": self._app_id,
"inPlace": True,
"destClient": {
"clientId": int(self._client_object.client_id),
"clientName": self._client_object.client_name
},
"destPath": []
},
"fileOption": {
"sourceItem": list()
},
"cloudAppsRestoreOptions": {
"instanceType": self._instance_type,
"d365RestoreOptions": {
"restoreAllMatching": False,
"restoreToDynamics365": True,
"overWriteItems": False,
"destLocation": "",
"restoreUsingFindQuery": False
}
}
}
}
}
]
}
}
return _restore_task_json
def _get_restore_item_path(self, content_list: list, is_environment: bool = False):
"""
Get the complete path of the content for running a restore job
Arguments:
content_list (list)-- List of content ot be restored
If content is environment,
List format:
list of strings, with each string corresponding to the environments display name, in lower case
If content is tables:
List format:
list of tuples, with each tuple, of the form: "environment_name","table_name"
where environment name if the name of the environment to which the table belongs to
Sample input:
[ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
is_environment (bool)-- Whether the content is environment or tables
Returns:
__restore_content_list (list)-- List of complete path for running restore job for the specifiec content
"""
__restore_content_list = list()
if is_environment is True:
for environment in self.get_associated_environments(refresh=True):
if environment["name"] in content_list:
_restore_id = environment["id"]
__restore_content_list.append(_restore_id)
elif is_environment is False:
for _table in self.get_associated_tables(refresh=True):
_table_name, _parent_env_name = _table["name"].lower(), _table["environment_name"].lower()
try:
if (_parent_env_name, _table_name) in content_list:
_id = _table.get("userAccountInfo").get("smtpAddress").split('/')
_table_id = _id[-1]
_env_id = _id[-2]
_restore_id = f"{_env_id}/{_table_id}"
__restore_content_list.append(_restore_id)
except TypeError:
raise SDKException('Subclient', '101',
"For restoring the tables, content list should be a list of tuples")
__restore_content_list = list(
map(lambda _restore_id: f"/tenant/{_restore_id}", __restore_content_list)
)
return __restore_content_list
def _prepare_restore_json(self,
restore_content: list,
restore_path: list = None,
overwrite: bool = True,
job_id: int = None,
is_environment: bool = False,
is_out_of_place_restore: bool = False,
destination_environment: str = str()
) -> dict:
"""
Method to prepare JSON/ Python dict for in- place restore for the content specified.
Arguments:
restore_content (str)-- List of the content to restore
If content is environment,
List format:
list of strings, with each string corresponding to the environments display name, in lower case
If content is tables:
List format:
list of tuples, with each tuple, of the form: "environment_name","table_name"
where environment name if the name of the environment to which the table belongs to
Sample input:
[ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
restore_path (list)-- List of the paths of the items to restore
Instead of passing, the restore content, restore path can be passed
Restore path, is the path for each item, that is to be restored.
Path is returned by the browse operation
is_environment (bool)-- Whether to content to be restored is a table or an environment
overwrite (bool)-- Skip or overwrite content
job_id (int)-- Job ID for point in time restores
destination_environment (Str)-- Destination environment for OOP restore.
is_out_of_place_restore (bool)-- Is Out of Place Restore?
Returns:
_restore_content_json (dict)-- Python dict to be used for restore content request
"""
_restore_content_json = self._restore_content_json()
if restore_path is None:
restore_path = self._get_restore_item_path(content_list=restore_content, is_environment=is_environment)
_restore_content_json["taskInfo"]["subTasks"][0]["options"]["restoreOptions"]["fileOption"][
"sourceItem"] = restore_path
if job_id is not None:
_job = self._commcell_object.job_controller.get(job_id)
_restore_content_json["taskInfo"]["subTasks"][0]["options"]["restoreOptions"]["browseOption"]["timeRange"][
"toTime"] = _job.end_timestamp
if overwrite is True:
_restore_content_json["taskInfo"]["subTasks"][0]["options"] \
["restoreOptions"]["commonOptions"]["overwriteFiles"] = True
_restore_content_json["taskInfo"]["subTasks"][0]["options"] \
["restoreOptions"]["commonOptions"][
"skip"] = False
_restore_content_json["taskInfo"]["subTasks"][0]["options"] \
["restoreOptions"]["commonOptions"][
"unconditionalOverwrite"] = True
_restore_content_json["taskInfo"]["subTasks"][0]["options"] \
["restoreOptions"]["cloudAppsRestoreOptions"] \
["d365RestoreOptions"]["overWriteItems"] = True
if is_out_of_place_restore:
_restore_content_json["taskInfo"]["subTasks"][0]["options"] \
["restoreOptions"]["destination"]["destPath"] = [destination_environment]
_instance_id = self._get_environment_id_for_oop_restore(environment_name=destination_environment)
_restore_content_json["taskInfo"]["subTasks"][0]["options"] \
["restoreOptions"]["cloudAppsRestoreOptions"] \
["d365RestoreOptions"]["destLocation"] = _instance_id
_restore_content_json["taskInfo"]["subTasks"][0]["options"] \
["restoreOptions"]["destination"]["inPlace"] = False
return _restore_content_json
def restore_in_place(
self,
restore_content: list = None,
restore_path: list = None,
is_environment: bool = False,
overwrite: bool = True,
job_id: int = None):
"""
Method to run in- place restore for the content specified.
Arguments:
restore_content (str)-- List of the content to restore
If content is environment,
List format:
list of strings, with each string corresponding to the environments display name, in lower case
Sample Input:
[ 'testenv1' , 'testenv2' , 'testenv3' ]
If content is tables:
List format:
list of tuples, with each tuple, of the form: "environment_name","table_name"
where environment name if the name of the environment to which the table belongs to
Sample input:
[ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
restore_path (list)-- List of the paths of the items to restore
Instead of passing, the restore content, restore path can be passed
Restore path, is the path for each item, that is to be restored.
Path is returned by the browse operation
is_environment (bool)-- Whether to content to be restored is a table or an environment
overwrite (bool)-- Skip or overwrite content
job_id (int)-- Job ID for point in time restores
Returns:
restore_job (job)-- Instance of CVPySDK.Job for the restore job
"""
if restore_content is None and restore_path is None:
raise SDKException("Subclient", "101", "Need to have either of restore content or restore path")
_restore_json = self._prepare_restore_json(
restore_content=restore_content,
restore_path=restore_path,
is_environment=is_environment,
job_id=job_id,
overwrite=overwrite,
is_out_of_place_restore=False)
return self._process_restore_response(_restore_json)
def launch_d365_licensing(self, run_for_all_clients=False):
"""
Method to launch Licensing API call.
Arguments:
run_for_all_clients(bool) -- True if thread is to be run on all clients, False otherwise
default: False
"""
_LAUNCH_LICENSING = self._services['LAUNCH_O365_LICENSING']
request_json = {
"subClient": {
"clientId": int(self._client_object.client_id)
},
"runForAllClients": run_for_all_clients,
"appType": 6
}
flag, response = self._cvpysdk_object.make_request(
'POST', _LAUNCH_LICENSING, request_json
)
if flag:
try:
if response.json():
if response.json().get('resp', {}).get('errorCode', 0) != 0:
error_message = response.json()['errorMessage']
output_string = 'Failed to Launch Licensing Thread\nError: "{0}"'
raise SDKException('Subclient', '102', output_string.format(error_message))
else:
self.refresh()
except ValueError:
raise SDKException('Response', '102')
else:
response_string = self._commcell_object._update_response_(response.text)
raise SDKException('Response', '101', response_string)
def _get_environment_id_for_oop_restore(self, environment_name: str) -> str:
"""
Get the Environment ID for an environment for Out of Place Restore
Arguments:
environment_name (str)-- Name of the environment
Returns:
environment_id (str)-- ID for the environment
"""
for environment in self.discovered_environments:
if environment['displayName'] == environment_name:
_env_xml = environment.get("xmlGeneric")
_env_json = json.loads(_env_xml)
_env_id = _env_json.get("_instanceName")
return _env_id
def restore_out_of_place(
self,
restore_content: list = None,
restore_path: list = None,
is_environment: bool = False,
overwrite: bool = True,
job_id: int = None,
destination_environment: str = str()):
"""
Method to run out-of-place restore for the content specified.
Arguments:
restore_content (str)-- List of the content to restore
If content is environment,
List format:
list of strings, with each string corresponding to the environments display name, in lower case
Sample Input:
[ 'testenv1' , 'testenv2' , 'testenv3' ]
If content is tables:
List format:
list of tuples, with each tuple, of the form: "environment_name","table_name"
where environment name if the name of the environment to which the table belongs to
Sample input:
[ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
restore_path (list)-- List of the paths of the items to restore
Instead of passing, the restore content, restore path can be passed
Restore path, is the path for each item, that is to be restored.
Path is returned by the browse operation
is_environment (bool)-- Whether to content to be restored is a table or an environment
overwrite (bool)-- Skip or overwrite content
job_id (int)-- Job ID for point in time restores
destination_environment (str)-- Destination environment name
Returns:
restore_job (job)-- Instance of CVPySDK.Job for the restore job
"""
if restore_content is None and restore_path is None:
raise SDKException("Subclient", "101", "Need to have either of restore content or restore path")
_restore_json = self._prepare_restore_json(
restore_content=restore_content,
restore_path=restore_path,
is_environment=is_environment,
job_id=job_id,
overwrite=overwrite,
is_out_of_place_restore=True,
destination_environment=destination_environment)
restore_endpoint = self._services['CREATE_TASK']
flag, response = self._commcell_object._cvpysdk_object.make_request("POST", restore_endpoint, _restore_json)
return self._process_restore_response(flag, response)
def browse(self,
browse_path: list[str] = None,
include_deleted_items: bool = False,
till_time: int = -1):
"""
Browse for the backed up content for a Dynamics 365 subclient
Arguments:
browse_path (list) -- Path to be browsed
Sample Value:
["environment-name" , "table-name"]
include_deleted_items
(bool) -- Whether to include deleted items in the browse response
till_time (int) -- Time-stamp for point in time browse
"""
_parent_path = str()
_environments = self._perform_browse(parent_path=_parent_path, till_time=till_time, item_type=2,
include_deleted_items=include_deleted_items)
_browse_response = copy.deepcopy(_environments)
if browse_path:
_item_type: int = 3
for path in browse_path:
_parent_path = self._get_guid_for_path(_browse_response, path)
if not _parent_path:
raise SDKException('Subclient', '101',
f"Path: {path} not found in browse content: {_browse_response}")
_browse_response = self._perform_browse(parent_path=_parent_path, till_time=till_time,
item_type=_item_type,
include_deleted_items=include_deleted_items)
_item_type += 1
return _browse_response
def _get_guid_for_path(self, browse_response: dict, path: str) -> str:
"""
Method to get the browse GUID corresponding to the path
Arguments:
browse_reponse (dict)-- Response from the browse query
path (str)-- Path for which GUID is to be fetched
Example:
from the browse for "d365-env", find the GUID for the "Accounts" table
The GUID would be used im the subsequent browse requests
"""
guid: str = str()
for item in browse_response:
if item.get("appSpecific").get("d365Item").get("displayName") == path:
guid = item.get("cvObjectGuid")
return guid
def _perform_browse(self, parent_path: str = str(), till_time: int = -1, item_type: int = 2,
include_deleted_items: bool = False):
"""
Perform a browse of the backed up content
Arguments:
parent_path (str) -- GUID for the parent path
include_deleted_items
(bool) -- Whether to include deleted items in the browse response
till_time (int) -- Time-stamp for point in time browse
item_type (int) -- Item type to be browsed
"""
_browse_default_params = self._get_dynamics365_browse_params(item_type=item_type)
_query_params = _browse_default_params.get("query_params")
_file_filter = _browse_default_params.get("file_filter")
_sort_params = _browse_default_params.get("sort_param")
_common_filters = _browse_default_params.get("common_filters")
if parent_path:
_parent_path_filter = {
"field": "PARENT_GUID",
"intraFieldOp": 0,
"fieldValues": {
"values": [
f"{parent_path}"
]
}
}
_file_filter.append(_parent_path_filter)
if till_time != -1:
_backup_time_filter = {
"field": "BACKUPTIME",
"intraFieldOp": 0,
"fieldValues": {
"values": [
"0",
f"{till_time}"
]
}
}
_file_filter.append(_backup_time_filter)
if include_deleted_items:
_common_filter = {
"groupType": 0,
"field": "CISTATE",
"intraFieldOp": 0,
"fieldValues": {
"values": [
"1",
"3333",
"3334",
"3335"
]
}
}
_common_filters[0] = _common_filter
return self.do_web_search(query_params=_query_params, file_filter=_file_filter, sort_param=_sort_params,
common_filters=_common_filters)
def _get_dynamics365_browse_params(self, item_type: int = 2) -> dict:
"""
Default dictionary for the browse parameters for a Dynamics 365 browse query.
Arguments:
item_type (int) -- Item type to br browsed for
"""
_query_params: list = [
{
"param": "ENABLE_MIXEDVIEW",
"value": "true"
},
{
"param": "RESPONSE_FIELD_LIST",
"value": "D365_ENTITY_DISP_NAME,D365_ENTITY_DISP_NAME,CONTENTID,CV_OBJECT_GUID,CV_TURBO_GUID,"
"PARENT_GUID,AFILEID,AFILEOFFSET,COMMCELLNO,APPID,D365_ID,D365_DISPLAYNAME,"
"FILE_CREATEDTIME,MODIFIEDTIME,BACKUPTIME,D365_OBJECT_TYPE,D365_CONTENTHASH,D365_FLAGS,"
"D365_ENTITYSET_ID,D365_ENTITYSET_NAME,D365_INSTANCE_ID,D365_INSTANCE_NAME,"
"D365_CREATEDBY_GUID,D365_CREATEDBY_NAME,D365_MODIFIEDBY_GUID,D365_MODIFIEDBY_NAME,"
"D365_OWNER_GUID,D365_OWNER_NAME,DATE_DELETED,CISTATE "
},
{
"param": "COLLAPSE_FIELD",
"value": "CV_OBJECT_GUID"
}]
_sort_params: list = [
{
"sortDirection": 0,
"sortField": "D365_DISPLAYNAME"
}
]
_common_filters: list = [
{
"groupType": 0,
"field": "CISTATE",
"intraFieldOp": 0,
"fieldValues": {
"values": [
"1"
]
}
},
{
"field": "IS_VISIBLE",
"intraFieldOpStr": "None",
"intraFieldOp": 0,
"fieldValues": {
"isMoniker": False,
"isRange": False,
"values": [
"true"
]
}
}
]
_file_filter: list = [
{
"field": "D365_OBJECT_TYPE",
"intraFieldOp": 0,
"fieldValues": {
"values": [
f"{item_type}"
]
}
}
]
return {"query_params": _query_params, "file_filter": _file_filter, "sort_param": _sort_params,
"common_filters": _common_filters}
@property
def browse_item_type(self):
"""Dynamics 365 item types"""
_browse_item_type = {"environment": 2,
"table": 3,
"record": 4}
return _browse_item_type
Classes
class MSDynamics365Subclient (backupset_object, subclient_name, subclient_id=None)
-
Class representing a MS Dynamics 365 subclient. Class has been derived from the O365AppsSubclient.
Initialize the Subclient object for the given MSDynamics365 Subclient.
Args
backupset_object (object) – instance of the backup-set class
subclient_name (str) – subclient name
subclient_id (int) – subclient id
Expand source code Browse git
class MSDynamics365Subclient(O365AppsSubclient): """ Class representing a MS Dynamics 365 subclient. Class has been derived from the O365AppsSubclient. """ def __init__(self, backupset_object, subclient_name, subclient_id=None): """Initialize the Subclient object for the given MSDynamics365 Subclient. Args: backupset_object (object) -- instance of the backup-set class subclient_name (str) -- subclient name subclient_id (int) -- subclient id """ super(MSDynamics365Subclient, self).__init__( backupset_object, subclient_name, subclient_id) self._instance_object = backupset_object._instance_object self._client_object = self._instance_object._agent_object._client_object self._associated_tables: dict = dict() self._associated_environments: dict = dict() self._discovered_environments: dict = dict() self._discovered_tables: dict = dict() self._instance_type: int = 35 self._app_id: int = 134 # App ID for cloud apps self._Dynamics365_SET_USER_POLICY_ASSOCIATION = self._commcell_object._services['SET_USER_POLICY_ASSOCIATION'] def _get_subclient_properties(self): """Gets the subclient related properties of a MS Dynamics 365 subclient""" super(MSDynamics365Subclient, self)._get_subclient_properties() def _get_subclient_properties_json(self): """get the all subclient related properties of this subclient. Returns: dict - all subclient properties put inside a dict """ return {'subClientProperties': self._subclient_properties} def discover_tables(self): """ Method to get the tables discovered from the MS Dynamics 365 CRM subclient Returns: discovered_tables (dict)-- Dictionary of returned tables """ self._discovered_tables = self._instance_object.discover_content(environment_discovery=False) return self._discovered_tables def discover_environments(self): """ Method to get the environments discovered from the Dynamics 365 CRM subclient Returns: discovered_environments (dict)-- Dictionary of discovered environments """ self._discovered_environments = self._instance_object.discover_content(environment_discovery=True) return self._discovered_environments @property def discovered_environments(self): """ Property to get the environments discovered by the Dynamics 365 subclient. If updated list is required, call refresh method prior to using this property. Returns: discovered_environments (dict)-- Dictionary of discovered environments """ if not bool(self._discovered_environments): self.discover_environments() return self._discovered_environments @property def discovered_tables(self): """ Property to get the tables discovered by the Dynamics 365 subclient. If updated list is required, call refresh method prior to using this property. Returns: discovered_tables (dict)-- Dictionary of discovered tables """ if not bool(self._discovered_tables): self.discover_tables() return self._discovered_tables def _get_associated_content(self, is_environment=False): """ Method to get the content associated with a Dynamics 365 CRM subclient Arguments: is_environment (bool)-- Whether to get the associated environments or tables Default Value: False Returns the associated tables Returns: associated_content_list (list)-- List of content associated with the client Format: Each list element will be a dictionary denoting that particular environment/ table """ discover_by_type: int if is_environment is True: discover_by_type = 5 else: discover_by_type = 14 _GET_ASSOCIATED_CONTENT = self._services['USER_POLICY_ASSOCIATION'] request_json = { "discoverByType": discover_by_type, "bIncludeDeleted": False, "cloudAppAssociation": { "subclientEntity": { "subclientId": int(self.subclient_id) } } } flag, response = self._cvpysdk_object.make_request( 'POST', _GET_ASSOCIATED_CONTENT, request_json ) if flag: if response and response.json(): no_of_records = int() if 'associations' in response.json(): no_of_records = response.json().get('associations', [])[0].get('pagingInfo', {}). \ get('totalRecords', -1) associations = response.json().get('associations', []) content_list = list() if discover_by_type == 5: for environment in associations: environment_name = environment.get("groups", {}).get("name") env_dict = { "name": environment_name, "id": environment.get("groups", {}).get("id"), "userAccountInfo": environment.get("userAccountInfo", {}), "plan": environment.get("plan", {}), "is_environment": True } content_list.append(env_dict) elif discover_by_type == 14: for table in associations: table_name = table.get("userAccountInfo", {}).get("displayName") table_dict = { "name": table_name, "environment_name": table.get("userAccountInfo", {}).get("ParentWebGuid", ""), "userAccountInfo": table.get("userAccountInfo", {}), "plan": table.get("plan", {}), "is_environment": False } content_list.append(table_dict) return content_list # return content_list, no_of_records return {}, 0 raise SDKException('Response', '101', self._update_response_(response.text)) def get_associated_tables(self, refresh: bool = False): """ Method to get the tables associated with a Dynamics 365 CRM client Arguments: refresh (bool)-- Whether to refresh the dictionary contents If True get associated environments, will fetch the latest associations and return them Returns: associated_tables (list)-- List of tables associated with the client Format: Each list element will be a dictionary denoting that particular table Dictionary keys/ format will be: name : name of the table environment_name : name of the environment to which the table belongs to plan: Dynamics 365 plan used for content association is_environment: False for a Table userAccountInfo: Metadata info for that table Sample Response: { 'name': 'account', 'environment_name': 'sample-environment-name', 'userAccountInfo': { 'aliasName': 'https://<org-url-name>.crm.dynamics.com/api/data/v9.1/account', 'displayName': 'Account, 'ParentWebGuid': 'org-environment-name', 'lastBackupJobRanTime': {'time': <last-backup-time>}, 'IdxCollectionTime': {'time': <last-index-time>}, 'user': { '_type_': 13, 'userGUID': '<table-GUID>>' } }, 'plan': { 'planName': '<PLAN-NAME>', 'planId': <plan-id>}, 'is_environment': False } Environment name/ URL in the sample response is for description purpose only """ if refresh is True: self._associated_tables = self._get_associated_content(is_environment=False) return self._associated_tables def get_associated_environments(self, refresh: bool = False): """ Method to get the environments associated with a Dynamics 365 CRM client Arguments: refresh (bool)-- Whether to refresh the dictionary contents If True get associated environments, will fetch the latest associations and return them Returns: associated_environments (list)-- List of environments associated with the client Format: Each list element will be a dictionary denoting that particular environment Dictionary keys/ format will be: name : name of the table plan: Dynamics 365 plan used for content association is_environment: True for an environment userAccountInfo: Metadata info for that environment Sample Response: { 'name': 'sample-environment-name', 'id': '<environment-ID>>', 'userAccountInfo': { 'aliasName': 'https://<org-url-name>.crm.dynamics.com', 'itemType': 0, 'ItemClassification': 0, 'displayName': 'org-environment-display-name', 'BackupSetId': 0, 'isAutoDiscoveredUser': False, 'lastBackupJobRanTime': 'time': <last-backup-time>, 'IdxCollectionTime': {'time': <last-index-playback-time>}, user': { '_type_': 13, 'userGUID': '<env-GUID>' } }, 'plan': {'planName': '<name-of-plan>', 'planId': <id-of-plan>}, 'is_environment': True} """ if refresh is True: self._associated_environments = self._get_associated_content(is_environment=True) return self._associated_environments def _set_association_json(self, is_environment: bool = False): """ JSON to set the content association for a Dynamics 365 CRM client Arguments: is_environment (bool): Whether the content to be associated is an environment Default Value: False Returns: set_content_association_json (dict)-- Content Association JSON """ set_content_association_json = {"LaunchAutoDiscovery": is_environment, "cloudAppAssociation": { "accountStatus": 0, "cloudAppDiscoverinfo": { "userAccounts": [ ], "groups": [], "discoverByType": 14 if is_environment is False else 15 } , "subclientEntity": self._subClientEntity}} return set_content_association_json def _set_content_association(self, content_json: dict): """ Method to associate some content to a Dynamics 365 CRM client... Arguments: content_json (dict)-- Association JSON to be used for the content """ flag, response = self._commcell_object._cvpysdk_object.make_request( 'POST', self._Dynamics365_SET_USER_POLICY_ASSOCIATION, content_json ) if flag: try: if response.json(): if response.json().get('resp', {}).get('errorCode', 0) != 0: error_message = response.json()['errorMessage'] output_string = 'Failed to Create Association for a Dynamics 365 CRM client\nError: "{0}"' raise SDKException( 'Subclient', '102', output_string.format(error_message) ) else: self.refresh() except ValueError: raise SDKException('Response', '102') else: response_string = self._commcell_object._update_response_(response.text) raise SDKException('Response', '101', response_string) def _table_association_info_json(self, tables_list: list): """ Private Method to create the association JSON for associating tables to a Dynamics 365 CRM client. Arguments: tables_list (list)-- List of tables to be associated to the content List Format: Each list element should be a tuple of the format: ("table_name", "environment_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be associated Returns: tables_info (list)-- List of metadata info for the tables to be used for associating content """ tables_info: list = list() _discovered_tables = self.discovered_tables if not bool(_discovered_tables): raise SDKException('Subclient', '101', "Discovered Tables is Empty.") for _table in _discovered_tables: _table_name, _parent_env_name = _table["displayName"], _table["ParentWebGuid"] try: if (_table_name, _parent_env_name) in tables_list: _table_assoc_info = _table _table_assoc_info["user.userGUID"] = _table.get("user").get("userGUID") tables_info.append(_table_assoc_info) except TypeError: raise SDKException('Subclient', '101', "For Associating tables, content list should be a list of tuples") if len(tables_info) != len(tables_list): raise SDKException("Subclient", "101", "All of the input tables were in the list of discovered tables") return tables_info def set_table_associations(self, tables_list: list, plan_name: str = str()): """ Method to add table associations to a Dynamics 365 CRM client. Arguments: tables_list (list)-- List of tables to be associated to the content List Format: Each list element should be a tuple of the format: ("table_name", "environment_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be associated Sample input: [ ("account", "testenv1") , ("note", "testenv2") , ("attachments", "testenv1")] plan_name (str)-- Name of the Dynamics 365 Plan to be used for content association """ plan_id = int(self._commcell_object.plans[plan_name.lower()]) tables_info = self._table_association_info_json(tables_list=tables_list) _table_association_json = self._set_association_json(is_environment=False) _table_association_json["cloudAppAssociation"]["plan"] = { "planId": plan_id } _table_association_json["cloudAppAssociation"]["cloudAppDiscoverinfo"]["userAccounts"] = tables_info self._set_content_association(content_json=_table_association_json) def _environment_association_info_json(self, environments_name: list): """ Method to create the association JSON for associating environments to a Dynamics 365 CRM client. Arguments: environments_name (list)-- List of environments to be associated to the content List Format: Each list element should be a string of the name of the environment Returns: environments_info (list)-- List of metadata info for the environments to be used for associating content """ environments_info: list = list() _discovered_envs = self.discovered_environments if not bool(_discovered_envs): raise SDKException('Subclient', '101', "Discovered Environments List is Empty") for environment in _discovered_envs: if environment["displayName"] in environments_name: _env_assoc_info = environment _env_assoc_info["user.userGUID"] = environment.get("user").get("userGUID") _env_assoc_info["rawCommonFlag"] = environment.get("commonFlags", 0) environments_info.append(_env_assoc_info) if len(environments_info) == 0: raise SDKException("Subclient", "101", "None of the input environments were in the list of discovered environments") return environments_info def set_environment_associations(self, environments_name: list, plan_name: str = str()): """ Method to add environment associations to a Dynamics 365 CRM client. Arguments: environments_name (list)-- List of environments to be associated to the content List Format: Each list element should be a string of the name of the environment Sample Values: ['testenv1' , 'testenv2', 'testenv3'] plan_name (str)-- Name of the Dynamics 365 Plan to be used for content association """ environments_info: list = self._environment_association_info_json(environments_name=environments_name) if self._commcell_object.plans.has_plan(plan_name.lower()): plan_id = int(self._commcell_object.plans[plan_name.lower()]) else: raise SDKException("Subclient", "101", "Dynamics 365 Plan does not exist") _env_association_json = self._set_association_json(is_environment=True) _env_association_json["cloudAppAssociation"]["plan"] = { "planId": plan_id } _env_association_json["cloudAppAssociation"]["cloudAppDiscoverinfo"]["userAccounts"] = environments_info self._set_content_association(content_json=_env_association_json) def _json_for_backup_task(self, content_list: list, is_environment: bool = False): """ Method to create the association JSON for backing up content for a Dynamics 365 subclient Arguments: content_list (list)-- List of content to be backed up List Format, if content to be backed up is tables: Each list element should be a tuple of the format: ("environment_name","table_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be backed up List Format, if content to be associated is environments: Each list element should be a string of the name of the environment is_environment (bool)-- Content passed to be backed up is environment type content or table type Returns: _backup_task_json (list)-- JSON for backing up the content """ _backup_task_json = self._backup_json('Full', False, '') _sub_client_content_json = self._backup_content_json(content_list=content_list, is_environment=is_environment) backup_options = { 'backupLevel': 2, # Incremental 'cloudAppOptions': { 'userAccounts': _sub_client_content_json } } _backup_task_json['taskInfo']['subTasks'][0]['options']['backupOpts'] = backup_options return _backup_task_json def _backup_content_json(self, content_list: list, is_environment: bool = False): """ Method to fetch the metadata properties for backing up content for a Dynamics 365 subclient Arguments: content_list (list)-- List of content to be backed up List Format, if content to be backed up is tables: Each list element should be a tuple of the format: ("environment_name","table_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be backed up List Format, if content to be associated is environments: Each list element should be a string of the name of the environment is_environment (bool)-- Content passed to be backed up is environment type content or table type Returns: _bkp_content_json (list)-- Metadata JSON for backing up that content """ _bkp_content_json = list() if is_environment is True: for environment in self.get_associated_environments(refresh=True): if environment["name"] in content_list: _env_bkp_info = environment["userAccountInfo"] _bkp_content_json.append(_env_bkp_info) elif is_environment is False: for _table in self.get_associated_tables(refresh=True): _table_name, _parent_env_name = _table["name"].lower(), _table["environment_name"].lower() try: if (_parent_env_name, _table_name) in content_list: _table_bkp_info = _table["userAccountInfo"] _bkp_content_json.append(_table_bkp_info) except TypeError: raise SDKException('Subclient', '101', "For backing up tables, content list should be a list of tuples") return _bkp_content_json def _run_backup(self, backup_content: list, is_environment: bool = False): """ Method to run backup for the content of a Dynamics 365 subclient Arguments: backup_content (list)-- List of content to be backed up List Format, if content to be backed up is tables: Each list element should be a tuple of the format: ("environment_name","table_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be backed up List Format, if content to be associated is environments: Each list element should be a string of the name of the environment is_environment (bool)-- Content passed to be backed up is environment type content or table type Returns: backup_job (Job)-- CVPySDK.Job class instance for that particular backup job """ _backup_json = self._json_for_backup_task(content_list=backup_content, is_environment=is_environment) backup_endpoint = self._services['CREATE_TASK'] flag, response = self._commcell_object._cvpysdk_object.make_request("POST", backup_endpoint, _backup_json) return self._process_backup_response(flag, response) def backup_tables(self, tables_list: list): """ Method to run backup for the specified tables of a Dynamics 365 subclient Arguments: tables_list (list)-- List of tables to be backed up List Format Each list element should be a tuple of the format: ("environment_name","table_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be backed up Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] Returns: backup_job (Job)-- CVPySDK.Job class instance for that particular backup job """ return self._run_backup(backup_content=tables_list, is_environment=False) def backup_environments(self, environments_list: list): """ Method to run backup for the specified environments of a Dynamics 365 subclient Arguments: environments_list (list)-- List of environments to be backed up List Format, for backing up specified environments: Each list element should be a string of the name of the environment Sample List: ['testenv1','testenv2','testenv3'] Returns: backup_job (Job)-- CVPySDK.Job class instance for that particular backup job """ return self._run_backup(backup_content=environments_list, is_environment=True) def _restore_content_json(self): """ Restore JSON for restoring content for a Dynamics 365 subclient Returns: _restore_task_json (dict)-- JSON to be used for running a restore task """ _restore_task_json = { "taskInfo": { "associations": [self._subclient_properties['subClientEntity']], "task": { "taskType": 1, "initiatedFrom": 2, "policyType": 0 }, "subTasks": [ { "subTask": { "subTaskType": 3, "operationType": 1001 }, "options": { "restoreOptions": { "browseOption": { "timeRange": {} }, "commonOptions": { "skip": True, "overwriteFiles": False, "unconditionalOverwrite": False }, "destination": { "destAppId": self._app_id, "inPlace": True, "destClient": { "clientId": int(self._client_object.client_id), "clientName": self._client_object.client_name }, "destPath": [] }, "fileOption": { "sourceItem": list() }, "cloudAppsRestoreOptions": { "instanceType": self._instance_type, "d365RestoreOptions": { "restoreAllMatching": False, "restoreToDynamics365": True, "overWriteItems": False, "destLocation": "", "restoreUsingFindQuery": False } } } } } ] } } return _restore_task_json def _get_restore_item_path(self, content_list: list, is_environment: bool = False): """ Get the complete path of the content for running a restore job Arguments: content_list (list)-- List of content ot be restored If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] is_environment (bool)-- Whether the content is environment or tables Returns: __restore_content_list (list)-- List of complete path for running restore job for the specifiec content """ __restore_content_list = list() if is_environment is True: for environment in self.get_associated_environments(refresh=True): if environment["name"] in content_list: _restore_id = environment["id"] __restore_content_list.append(_restore_id) elif is_environment is False: for _table in self.get_associated_tables(refresh=True): _table_name, _parent_env_name = _table["name"].lower(), _table["environment_name"].lower() try: if (_parent_env_name, _table_name) in content_list: _id = _table.get("userAccountInfo").get("smtpAddress").split('/') _table_id = _id[-1] _env_id = _id[-2] _restore_id = f"{_env_id}/{_table_id}" __restore_content_list.append(_restore_id) except TypeError: raise SDKException('Subclient', '101', "For restoring the tables, content list should be a list of tuples") __restore_content_list = list( map(lambda _restore_id: f"/tenant/{_restore_id}", __restore_content_list) ) return __restore_content_list def _prepare_restore_json(self, restore_content: list, restore_path: list = None, overwrite: bool = True, job_id: int = None, is_environment: bool = False, is_out_of_place_restore: bool = False, destination_environment: str = str() ) -> dict: """ Method to prepare JSON/ Python dict for in- place restore for the content specified. Arguments: restore_content (str)-- List of the content to restore If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] restore_path (list)-- List of the paths of the items to restore Instead of passing, the restore content, restore path can be passed Restore path, is the path for each item, that is to be restored. Path is returned by the browse operation is_environment (bool)-- Whether to content to be restored is a table or an environment overwrite (bool)-- Skip or overwrite content job_id (int)-- Job ID for point in time restores destination_environment (Str)-- Destination environment for OOP restore. is_out_of_place_restore (bool)-- Is Out of Place Restore? Returns: _restore_content_json (dict)-- Python dict to be used for restore content request """ _restore_content_json = self._restore_content_json() if restore_path is None: restore_path = self._get_restore_item_path(content_list=restore_content, is_environment=is_environment) _restore_content_json["taskInfo"]["subTasks"][0]["options"]["restoreOptions"]["fileOption"][ "sourceItem"] = restore_path if job_id is not None: _job = self._commcell_object.job_controller.get(job_id) _restore_content_json["taskInfo"]["subTasks"][0]["options"]["restoreOptions"]["browseOption"]["timeRange"][ "toTime"] = _job.end_timestamp if overwrite is True: _restore_content_json["taskInfo"]["subTasks"][0]["options"] \ ["restoreOptions"]["commonOptions"]["overwriteFiles"] = True _restore_content_json["taskInfo"]["subTasks"][0]["options"] \ ["restoreOptions"]["commonOptions"][ "skip"] = False _restore_content_json["taskInfo"]["subTasks"][0]["options"] \ ["restoreOptions"]["commonOptions"][ "unconditionalOverwrite"] = True _restore_content_json["taskInfo"]["subTasks"][0]["options"] \ ["restoreOptions"]["cloudAppsRestoreOptions"] \ ["d365RestoreOptions"]["overWriteItems"] = True if is_out_of_place_restore: _restore_content_json["taskInfo"]["subTasks"][0]["options"] \ ["restoreOptions"]["destination"]["destPath"] = [destination_environment] _instance_id = self._get_environment_id_for_oop_restore(environment_name=destination_environment) _restore_content_json["taskInfo"]["subTasks"][0]["options"] \ ["restoreOptions"]["cloudAppsRestoreOptions"] \ ["d365RestoreOptions"]["destLocation"] = _instance_id _restore_content_json["taskInfo"]["subTasks"][0]["options"] \ ["restoreOptions"]["destination"]["inPlace"] = False return _restore_content_json def restore_in_place( self, restore_content: list = None, restore_path: list = None, is_environment: bool = False, overwrite: bool = True, job_id: int = None): """ Method to run in- place restore for the content specified. Arguments: restore_content (str)-- List of the content to restore If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case Sample Input: [ 'testenv1' , 'testenv2' , 'testenv3' ] If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] restore_path (list)-- List of the paths of the items to restore Instead of passing, the restore content, restore path can be passed Restore path, is the path for each item, that is to be restored. Path is returned by the browse operation is_environment (bool)-- Whether to content to be restored is a table or an environment overwrite (bool)-- Skip or overwrite content job_id (int)-- Job ID for point in time restores Returns: restore_job (job)-- Instance of CVPySDK.Job for the restore job """ if restore_content is None and restore_path is None: raise SDKException("Subclient", "101", "Need to have either of restore content or restore path") _restore_json = self._prepare_restore_json( restore_content=restore_content, restore_path=restore_path, is_environment=is_environment, job_id=job_id, overwrite=overwrite, is_out_of_place_restore=False) return self._process_restore_response(_restore_json) def launch_d365_licensing(self, run_for_all_clients=False): """ Method to launch Licensing API call. Arguments: run_for_all_clients(bool) -- True if thread is to be run on all clients, False otherwise default: False """ _LAUNCH_LICENSING = self._services['LAUNCH_O365_LICENSING'] request_json = { "subClient": { "clientId": int(self._client_object.client_id) }, "runForAllClients": run_for_all_clients, "appType": 6 } flag, response = self._cvpysdk_object.make_request( 'POST', _LAUNCH_LICENSING, request_json ) if flag: try: if response.json(): if response.json().get('resp', {}).get('errorCode', 0) != 0: error_message = response.json()['errorMessage'] output_string = 'Failed to Launch Licensing Thread\nError: "{0}"' raise SDKException('Subclient', '102', output_string.format(error_message)) else: self.refresh() except ValueError: raise SDKException('Response', '102') else: response_string = self._commcell_object._update_response_(response.text) raise SDKException('Response', '101', response_string) def _get_environment_id_for_oop_restore(self, environment_name: str) -> str: """ Get the Environment ID for an environment for Out of Place Restore Arguments: environment_name (str)-- Name of the environment Returns: environment_id (str)-- ID for the environment """ for environment in self.discovered_environments: if environment['displayName'] == environment_name: _env_xml = environment.get("xmlGeneric") _env_json = json.loads(_env_xml) _env_id = _env_json.get("_instanceName") return _env_id def restore_out_of_place( self, restore_content: list = None, restore_path: list = None, is_environment: bool = False, overwrite: bool = True, job_id: int = None, destination_environment: str = str()): """ Method to run out-of-place restore for the content specified. Arguments: restore_content (str)-- List of the content to restore If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case Sample Input: [ 'testenv1' , 'testenv2' , 'testenv3' ] If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] restore_path (list)-- List of the paths of the items to restore Instead of passing, the restore content, restore path can be passed Restore path, is the path for each item, that is to be restored. Path is returned by the browse operation is_environment (bool)-- Whether to content to be restored is a table or an environment overwrite (bool)-- Skip or overwrite content job_id (int)-- Job ID for point in time restores destination_environment (str)-- Destination environment name Returns: restore_job (job)-- Instance of CVPySDK.Job for the restore job """ if restore_content is None and restore_path is None: raise SDKException("Subclient", "101", "Need to have either of restore content or restore path") _restore_json = self._prepare_restore_json( restore_content=restore_content, restore_path=restore_path, is_environment=is_environment, job_id=job_id, overwrite=overwrite, is_out_of_place_restore=True, destination_environment=destination_environment) restore_endpoint = self._services['CREATE_TASK'] flag, response = self._commcell_object._cvpysdk_object.make_request("POST", restore_endpoint, _restore_json) return self._process_restore_response(flag, response) def browse(self, browse_path: list[str] = None, include_deleted_items: bool = False, till_time: int = -1): """ Browse for the backed up content for a Dynamics 365 subclient Arguments: browse_path (list) -- Path to be browsed Sample Value: ["environment-name" , "table-name"] include_deleted_items (bool) -- Whether to include deleted items in the browse response till_time (int) -- Time-stamp for point in time browse """ _parent_path = str() _environments = self._perform_browse(parent_path=_parent_path, till_time=till_time, item_type=2, include_deleted_items=include_deleted_items) _browse_response = copy.deepcopy(_environments) if browse_path: _item_type: int = 3 for path in browse_path: _parent_path = self._get_guid_for_path(_browse_response, path) if not _parent_path: raise SDKException('Subclient', '101', f"Path: {path} not found in browse content: {_browse_response}") _browse_response = self._perform_browse(parent_path=_parent_path, till_time=till_time, item_type=_item_type, include_deleted_items=include_deleted_items) _item_type += 1 return _browse_response def _get_guid_for_path(self, browse_response: dict, path: str) -> str: """ Method to get the browse GUID corresponding to the path Arguments: browse_reponse (dict)-- Response from the browse query path (str)-- Path for which GUID is to be fetched Example: from the browse for "d365-env", find the GUID for the "Accounts" table The GUID would be used im the subsequent browse requests """ guid: str = str() for item in browse_response: if item.get("appSpecific").get("d365Item").get("displayName") == path: guid = item.get("cvObjectGuid") return guid def _perform_browse(self, parent_path: str = str(), till_time: int = -1, item_type: int = 2, include_deleted_items: bool = False): """ Perform a browse of the backed up content Arguments: parent_path (str) -- GUID for the parent path include_deleted_items (bool) -- Whether to include deleted items in the browse response till_time (int) -- Time-stamp for point in time browse item_type (int) -- Item type to be browsed """ _browse_default_params = self._get_dynamics365_browse_params(item_type=item_type) _query_params = _browse_default_params.get("query_params") _file_filter = _browse_default_params.get("file_filter") _sort_params = _browse_default_params.get("sort_param") _common_filters = _browse_default_params.get("common_filters") if parent_path: _parent_path_filter = { "field": "PARENT_GUID", "intraFieldOp": 0, "fieldValues": { "values": [ f"{parent_path}" ] } } _file_filter.append(_parent_path_filter) if till_time != -1: _backup_time_filter = { "field": "BACKUPTIME", "intraFieldOp": 0, "fieldValues": { "values": [ "0", f"{till_time}" ] } } _file_filter.append(_backup_time_filter) if include_deleted_items: _common_filter = { "groupType": 0, "field": "CISTATE", "intraFieldOp": 0, "fieldValues": { "values": [ "1", "3333", "3334", "3335" ] } } _common_filters[0] = _common_filter return self.do_web_search(query_params=_query_params, file_filter=_file_filter, sort_param=_sort_params, common_filters=_common_filters) def _get_dynamics365_browse_params(self, item_type: int = 2) -> dict: """ Default dictionary for the browse parameters for a Dynamics 365 browse query. Arguments: item_type (int) -- Item type to br browsed for """ _query_params: list = [ { "param": "ENABLE_MIXEDVIEW", "value": "true" }, { "param": "RESPONSE_FIELD_LIST", "value": "D365_ENTITY_DISP_NAME,D365_ENTITY_DISP_NAME,CONTENTID,CV_OBJECT_GUID,CV_TURBO_GUID," "PARENT_GUID,AFILEID,AFILEOFFSET,COMMCELLNO,APPID,D365_ID,D365_DISPLAYNAME," "FILE_CREATEDTIME,MODIFIEDTIME,BACKUPTIME,D365_OBJECT_TYPE,D365_CONTENTHASH,D365_FLAGS," "D365_ENTITYSET_ID,D365_ENTITYSET_NAME,D365_INSTANCE_ID,D365_INSTANCE_NAME," "D365_CREATEDBY_GUID,D365_CREATEDBY_NAME,D365_MODIFIEDBY_GUID,D365_MODIFIEDBY_NAME," "D365_OWNER_GUID,D365_OWNER_NAME,DATE_DELETED,CISTATE " }, { "param": "COLLAPSE_FIELD", "value": "CV_OBJECT_GUID" }] _sort_params: list = [ { "sortDirection": 0, "sortField": "D365_DISPLAYNAME" } ] _common_filters: list = [ { "groupType": 0, "field": "CISTATE", "intraFieldOp": 0, "fieldValues": { "values": [ "1" ] } }, { "field": "IS_VISIBLE", "intraFieldOpStr": "None", "intraFieldOp": 0, "fieldValues": { "isMoniker": False, "isRange": False, "values": [ "true" ] } } ] _file_filter: list = [ { "field": "D365_OBJECT_TYPE", "intraFieldOp": 0, "fieldValues": { "values": [ f"{item_type}" ] } } ] return {"query_params": _query_params, "file_filter": _file_filter, "sort_param": _sort_params, "common_filters": _common_filters} @property def browse_item_type(self): """Dynamics 365 item types""" _browse_item_type = {"environment": 2, "table": 3, "record": 4} return _browse_item_type
Ancestors
Instance variables
var browse_item_type
-
Dynamics 365 item types
Expand source code Browse git
@property def browse_item_type(self): """Dynamics 365 item types""" _browse_item_type = {"environment": 2, "table": 3, "record": 4} return _browse_item_type
var discovered_environments
-
Property to get the environments discovered by the Dynamics 365 subclient.
If updated list is required, call refresh method prior to using this property.
Returns
discovered_environments (dict)– Dictionary of discovered environments
Expand source code Browse git
@property def discovered_environments(self): """ Property to get the environments discovered by the Dynamics 365 subclient. If updated list is required, call refresh method prior to using this property. Returns: discovered_environments (dict)-- Dictionary of discovered environments """ if not bool(self._discovered_environments): self.discover_environments() return self._discovered_environments
var discovered_tables
-
Property to get the tables discovered by the Dynamics 365 subclient.
If updated list is required, call refresh method prior to using this property.
Returns
discovered_tables (dict)– Dictionary of discovered tables
Expand source code Browse git
@property def discovered_tables(self): """ Property to get the tables discovered by the Dynamics 365 subclient. If updated list is required, call refresh method prior to using this property. Returns: discovered_tables (dict)-- Dictionary of discovered tables """ if not bool(self._discovered_tables): self.discover_tables() return self._discovered_tables
Methods
def backup_environments(self, environments_list: list)
-
Method to run backup for the specified environments of a Dynamics 365 subclient
Arguments
environments_list (list)– List of environments to be backed up List Format, for backing up specified environments: Each list element should be a string of the name of the environment Sample List: ['testenv1','testenv2','testenv3']
Returns
backup_job (Job)– CVPySDK.Job class instance for that particular backup job
Expand source code Browse git
def backup_environments(self, environments_list: list): """ Method to run backup for the specified environments of a Dynamics 365 subclient Arguments: environments_list (list)-- List of environments to be backed up List Format, for backing up specified environments: Each list element should be a string of the name of the environment Sample List: ['testenv1','testenv2','testenv3'] Returns: backup_job (Job)-- CVPySDK.Job class instance for that particular backup job """ return self._run_backup(backup_content=environments_list, is_environment=True)
def backup_tables(self, tables_list: list)
-
Method to run backup for the specified tables of a Dynamics 365 subclient
Arguments
tables_list (list)– List of tables to be backed up List Format Each list element should be a tuple of the format: ("environment_name","table_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be backed up
Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
Returns
backup_job (Job)– CVPySDK.Job class instance for that particular backup job
Expand source code Browse git
def backup_tables(self, tables_list: list): """ Method to run backup for the specified tables of a Dynamics 365 subclient Arguments: tables_list (list)-- List of tables to be backed up List Format Each list element should be a tuple of the format: ("environment_name","table_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be backed up Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] Returns: backup_job (Job)-- CVPySDK.Job class instance for that particular backup job """ return self._run_backup(backup_content=tables_list, is_environment=False)
def browse(self, browse_path: list[str] = None, include_deleted_items: bool = False, till_time: int = -1)
-
Browse for the backed up content for a Dynamics 365 subclient
Arguments
browse_path (list) – Path to be browsed Sample Value: ["environment-name" , "table-name"] include_deleted_items (bool) – Whether to include deleted items in the browse response till_time (int) – Time-stamp for point in time browse
Expand source code Browse git
def browse(self, browse_path: list[str] = None, include_deleted_items: bool = False, till_time: int = -1): """ Browse for the backed up content for a Dynamics 365 subclient Arguments: browse_path (list) -- Path to be browsed Sample Value: ["environment-name" , "table-name"] include_deleted_items (bool) -- Whether to include deleted items in the browse response till_time (int) -- Time-stamp for point in time browse """ _parent_path = str() _environments = self._perform_browse(parent_path=_parent_path, till_time=till_time, item_type=2, include_deleted_items=include_deleted_items) _browse_response = copy.deepcopy(_environments) if browse_path: _item_type: int = 3 for path in browse_path: _parent_path = self._get_guid_for_path(_browse_response, path) if not _parent_path: raise SDKException('Subclient', '101', f"Path: {path} not found in browse content: {_browse_response}") _browse_response = self._perform_browse(parent_path=_parent_path, till_time=till_time, item_type=_item_type, include_deleted_items=include_deleted_items) _item_type += 1 return _browse_response
def discover_environments(self)
-
Method to get the environments discovered from the Dynamics 365 CRM subclient
Returns
discovered_environments (dict)– Dictionary of discovered environments
Expand source code Browse git
def discover_environments(self): """ Method to get the environments discovered from the Dynamics 365 CRM subclient Returns: discovered_environments (dict)-- Dictionary of discovered environments """ self._discovered_environments = self._instance_object.discover_content(environment_discovery=True) return self._discovered_environments
def discover_tables(self)
-
Method to get the tables discovered from the MS Dynamics 365 CRM subclient
Returns
discovered_tables (dict)– Dictionary of returned tables
Expand source code Browse git
def discover_tables(self): """ Method to get the tables discovered from the MS Dynamics 365 CRM subclient Returns: discovered_tables (dict)-- Dictionary of returned tables """ self._discovered_tables = self._instance_object.discover_content(environment_discovery=False) return self._discovered_tables
def get_associated_environments(self, refresh: bool = False)
-
Method to get the environments associated with a Dynamics 365 CRM client
Arguments
refresh (bool)– Whether to refresh the dictionary contents If True get associated environments, will fetch the latest associations and return them
Returns
- associated_environments (list)– List of environments associated with the client
- Format:
- Each list element will be a dictionary denoting that particular environment
- Dictionary keys/ format will be:
name
-
name of the table plan: Dynamics 365 plan used for content association is_environment: True for an environment userAccountInfo: Metadata info for that environment
Sample Response: { 'name': 'sample-environment-name', 'id': '
>', 'userAccountInfo': { 'aliasName': 'https:// .crm.dynamics.com', 'itemType': 0, 'ItemClassification': 0, 'displayName': 'org-environment-display-name', 'BackupSetId': 0, 'isAutoDiscoveredUser': False, 'lastBackupJobRanTime': 'time': , 'IdxCollectionTime': {'time': }, user': { 'type': 13, 'userGUID': ' ' } }, 'plan': {'planName': ' ', 'planId': }, 'is_environment': True}
Expand source code Browse git
def get_associated_environments(self, refresh: bool = False): """ Method to get the environments associated with a Dynamics 365 CRM client Arguments: refresh (bool)-- Whether to refresh the dictionary contents If True get associated environments, will fetch the latest associations and return them Returns: associated_environments (list)-- List of environments associated with the client Format: Each list element will be a dictionary denoting that particular environment Dictionary keys/ format will be: name : name of the table plan: Dynamics 365 plan used for content association is_environment: True for an environment userAccountInfo: Metadata info for that environment Sample Response: { 'name': 'sample-environment-name', 'id': '<environment-ID>>', 'userAccountInfo': { 'aliasName': 'https://<org-url-name>.crm.dynamics.com', 'itemType': 0, 'ItemClassification': 0, 'displayName': 'org-environment-display-name', 'BackupSetId': 0, 'isAutoDiscoveredUser': False, 'lastBackupJobRanTime': 'time': <last-backup-time>, 'IdxCollectionTime': {'time': <last-index-playback-time>}, user': { '_type_': 13, 'userGUID': '<env-GUID>' } }, 'plan': {'planName': '<name-of-plan>', 'planId': <id-of-plan>}, 'is_environment': True} """ if refresh is True: self._associated_environments = self._get_associated_content(is_environment=True) return self._associated_environments
def get_associated_tables(self, refresh: bool = False)
-
Method to get the tables associated with a Dynamics 365 CRM client
Arguments
refresh (bool)– Whether to refresh the dictionary contents If True get associated environments, will fetch the latest associations and return them
Returns
- associated_tables (list)– List of tables associated with the client
- Format:
- Each list element will be a dictionary denoting that particular table
- Dictionary keys/ format will be:
name
-
name of the table environment_name : name of the environment to which the table belongs to plan: Dynamics 365 plan used for content association is_environment: False for a Table userAccountInfo: Metadata info for that table
Sample Response: { 'name': 'account', 'environment_name': 'sample-environment-name', 'userAccountInfo': { 'aliasName': 'https://
.crm.dynamics.com/api/data/v9.1/account', 'displayName': 'Account, 'ParentWebGuid': 'org-environment-name', 'lastBackupJobRanTime': {'time': }, 'IdxCollectionTime': {'time': }, 'user': { 'type': 13, 'userGUID': ' >' } }, 'plan': { 'planName': ' ', 'planId': }, 'is_environment': False } Environment name/ URL in the sample response is for description purpose only
Expand source code Browse git
def get_associated_tables(self, refresh: bool = False): """ Method to get the tables associated with a Dynamics 365 CRM client Arguments: refresh (bool)-- Whether to refresh the dictionary contents If True get associated environments, will fetch the latest associations and return them Returns: associated_tables (list)-- List of tables associated with the client Format: Each list element will be a dictionary denoting that particular table Dictionary keys/ format will be: name : name of the table environment_name : name of the environment to which the table belongs to plan: Dynamics 365 plan used for content association is_environment: False for a Table userAccountInfo: Metadata info for that table Sample Response: { 'name': 'account', 'environment_name': 'sample-environment-name', 'userAccountInfo': { 'aliasName': 'https://<org-url-name>.crm.dynamics.com/api/data/v9.1/account', 'displayName': 'Account, 'ParentWebGuid': 'org-environment-name', 'lastBackupJobRanTime': {'time': <last-backup-time>}, 'IdxCollectionTime': {'time': <last-index-time>}, 'user': { '_type_': 13, 'userGUID': '<table-GUID>>' } }, 'plan': { 'planName': '<PLAN-NAME>', 'planId': <plan-id>}, 'is_environment': False } Environment name/ URL in the sample response is for description purpose only """ if refresh is True: self._associated_tables = self._get_associated_content(is_environment=False) return self._associated_tables
def launch_d365_licensing(self, run_for_all_clients=False)
-
Method to launch Licensing API call.
Arguments
run_for_all_clients(bool) – True if thread is to be run on all clients, False otherwise default: False
Expand source code Browse git
def launch_d365_licensing(self, run_for_all_clients=False): """ Method to launch Licensing API call. Arguments: run_for_all_clients(bool) -- True if thread is to be run on all clients, False otherwise default: False """ _LAUNCH_LICENSING = self._services['LAUNCH_O365_LICENSING'] request_json = { "subClient": { "clientId": int(self._client_object.client_id) }, "runForAllClients": run_for_all_clients, "appType": 6 } flag, response = self._cvpysdk_object.make_request( 'POST', _LAUNCH_LICENSING, request_json ) if flag: try: if response.json(): if response.json().get('resp', {}).get('errorCode', 0) != 0: error_message = response.json()['errorMessage'] output_string = 'Failed to Launch Licensing Thread\nError: "{0}"' raise SDKException('Subclient', '102', output_string.format(error_message)) else: self.refresh() except ValueError: raise SDKException('Response', '102') else: response_string = self._commcell_object._update_response_(response.text) raise SDKException('Response', '101', response_string)
def restore_in_place(self, restore_content: list = None, restore_path: list = None, is_environment: bool = False, overwrite: bool = True, job_id: int = None)
-
Method to run in- place restore for the content specified.
Arguments
restore_content (str)– List of the content to restore If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case Sample Input: [ 'testenv1' , 'testenv2' , 'testenv3' ]
If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
restore_path (list)– List of the paths of the items to restore Instead of passing, the restore content, restore path can be passed Restore path, is the path for each item, that is to be restored. Path is returned by the browse operation
is_environment (bool)– Whether to content to be restored is a table or an environment overwrite (bool)– Skip or overwrite content job_id (int)– Job ID for point in time restores
Returns
restore_job (job)– Instance of CVPySDK.Job for the restore job
Expand source code Browse git
def restore_in_place( self, restore_content: list = None, restore_path: list = None, is_environment: bool = False, overwrite: bool = True, job_id: int = None): """ Method to run in- place restore for the content specified. Arguments: restore_content (str)-- List of the content to restore If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case Sample Input: [ 'testenv1' , 'testenv2' , 'testenv3' ] If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] restore_path (list)-- List of the paths of the items to restore Instead of passing, the restore content, restore path can be passed Restore path, is the path for each item, that is to be restored. Path is returned by the browse operation is_environment (bool)-- Whether to content to be restored is a table or an environment overwrite (bool)-- Skip or overwrite content job_id (int)-- Job ID for point in time restores Returns: restore_job (job)-- Instance of CVPySDK.Job for the restore job """ if restore_content is None and restore_path is None: raise SDKException("Subclient", "101", "Need to have either of restore content or restore path") _restore_json = self._prepare_restore_json( restore_content=restore_content, restore_path=restore_path, is_environment=is_environment, job_id=job_id, overwrite=overwrite, is_out_of_place_restore=False) return self._process_restore_response(_restore_json)
def restore_out_of_place(self, restore_content: list = None, restore_path: list = None, is_environment: bool = False, overwrite: bool = True, job_id: int = None, destination_environment: str = '')
-
Method to run out-of-place restore for the content specified.
Arguments
restore_content (str)– List of the content to restore If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case Sample Input: [ 'testenv1' , 'testenv2' , 'testenv3' ]
If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")]
restore_path (list)– List of the paths of the items to restore Instead of passing, the restore content, restore path can be passed Restore path, is the path for each item, that is to be restored. Path is returned by the browse operation
is_environment (bool)– Whether to content to be restored is a table or an environment overwrite (bool)– Skip or overwrite content job_id (int)– Job ID for point in time restores destination_environment (str)– Destination environment name
Returns
restore_job (job)– Instance of CVPySDK.Job for the restore job
Expand source code Browse git
def restore_out_of_place( self, restore_content: list = None, restore_path: list = None, is_environment: bool = False, overwrite: bool = True, job_id: int = None, destination_environment: str = str()): """ Method to run out-of-place restore for the content specified. Arguments: restore_content (str)-- List of the content to restore If content is environment, List format: list of strings, with each string corresponding to the environments display name, in lower case Sample Input: [ 'testenv1' , 'testenv2' , 'testenv3' ] If content is tables: List format: list of tuples, with each tuple, of the form: "environment_name","table_name" where environment name if the name of the environment to which the table belongs to Sample input: [ ("testenv1" , "account") , ("testenv2","note") , ("testenv1","attachments")] restore_path (list)-- List of the paths of the items to restore Instead of passing, the restore content, restore path can be passed Restore path, is the path for each item, that is to be restored. Path is returned by the browse operation is_environment (bool)-- Whether to content to be restored is a table or an environment overwrite (bool)-- Skip or overwrite content job_id (int)-- Job ID for point in time restores destination_environment (str)-- Destination environment name Returns: restore_job (job)-- Instance of CVPySDK.Job for the restore job """ if restore_content is None and restore_path is None: raise SDKException("Subclient", "101", "Need to have either of restore content or restore path") _restore_json = self._prepare_restore_json( restore_content=restore_content, restore_path=restore_path, is_environment=is_environment, job_id=job_id, overwrite=overwrite, is_out_of_place_restore=True, destination_environment=destination_environment) restore_endpoint = self._services['CREATE_TASK'] flag, response = self._commcell_object._cvpysdk_object.make_request("POST", restore_endpoint, _restore_json) return self._process_restore_response(flag, response)
def set_environment_associations(self, environments_name: list, plan_name: str = '')
-
Method to add environment associations to a Dynamics 365 CRM client.
Arguments
environments_name (list)– List of environments to be associated to the content List Format: Each list element should be a string of the name of the environment Sample Values: ['testenv1' , 'testenv2', 'testenv3']
plan_name (str)– Name of the Dynamics 365 Plan to be used for content association
Expand source code Browse git
def set_environment_associations(self, environments_name: list, plan_name: str = str()): """ Method to add environment associations to a Dynamics 365 CRM client. Arguments: environments_name (list)-- List of environments to be associated to the content List Format: Each list element should be a string of the name of the environment Sample Values: ['testenv1' , 'testenv2', 'testenv3'] plan_name (str)-- Name of the Dynamics 365 Plan to be used for content association """ environments_info: list = self._environment_association_info_json(environments_name=environments_name) if self._commcell_object.plans.has_plan(plan_name.lower()): plan_id = int(self._commcell_object.plans[plan_name.lower()]) else: raise SDKException("Subclient", "101", "Dynamics 365 Plan does not exist") _env_association_json = self._set_association_json(is_environment=True) _env_association_json["cloudAppAssociation"]["plan"] = { "planId": plan_id } _env_association_json["cloudAppAssociation"]["cloudAppDiscoverinfo"]["userAccounts"] = environments_info self._set_content_association(content_json=_env_association_json)
def set_table_associations(self, tables_list: list, plan_name: str = '')
-
Method to add table associations to a Dynamics 365 CRM client.
Arguments
tables_list (list)– List of tables to be associated to the content List Format: Each list element should be a tuple of the format: ("table_name", "environment_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be associated Sample input: [ ("account", "testenv1") , ("note", "testenv2") , ("attachments", "testenv1")]
plan_name (str)– Name of the Dynamics 365 Plan to be used for content association
Expand source code Browse git
def set_table_associations(self, tables_list: list, plan_name: str = str()): """ Method to add table associations to a Dynamics 365 CRM client. Arguments: tables_list (list)-- List of tables to be associated to the content List Format: Each list element should be a tuple of the format: ("table_name", "environment_name") environment_name is the name of the environment to which the table belongs to table_name is the name of the table to be associated Sample input: [ ("account", "testenv1") , ("note", "testenv2") , ("attachments", "testenv1")] plan_name (str)-- Name of the Dynamics 365 Plan to be used for content association """ plan_id = int(self._commcell_object.plans[plan_name.lower()]) tables_info = self._table_association_info_json(tables_list=tables_list) _table_association_json = self._set_association_json(is_environment=False) _table_association_json["cloudAppAssociation"]["plan"] = { "planId": plan_id } _table_association_json["cloudAppAssociation"]["cloudAppDiscoverinfo"]["userAccounts"] = tables_info self._set_content_association(content_json=_table_association_json)
Inherited members
O365AppsSubclient
:allow_multiple_readers
backup
data_readers
deduplication_options
description
disable_backup
disable_intelli_snap
display_name
do_web_search
enable_backup
enable_backup_at_time
enable_intelli_snap
enable_trueup
enable_trueup_days
encryption_flag
exclude_from_sla
find
find_latest_job
get_ma_associated_storagepolicy
is_backup_enabled
is_blocklevel_backup_enabled
is_default_subclient
is_intelli_snap_enabled
is_on_demand_subclient
is_trueup_enabled
last_backup_time
list_media
name
network_agent
next_backup_time
plan
process_index_retention
properties
read_buffer_size
refresh
set_backup_nodes
set_proxy_for_snap
snapshot_engine_name
software_compression
storage_ma
storage_ma_id
storage_policy
subclient_guid
subclient_id
subclient_name
unset_proxy_for_snap
update_properties