Skip to content

Commit 0c81d23

Browse files
authored
Saimon/support fusion direct (#355)
1 parent 8e6deb1 commit 0c81d23

6 files changed

Lines changed: 198 additions & 39 deletions

File tree

cterasdk/core/cloudfs.py

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .base_command import BaseCommand
66
from . import query, devices
77
from .enum import ListFilter, PolicyType
8-
from .types import ComplianceSettingsBuilder, ExtendedAttributesBuilder
8+
from .types import ArchiveSettingsBuilder, ComplianceSettingsBuilder, ExtendedAttributesBuilder
99
from ..common import union, Object
1010
from ..exceptions import CTERAException, ObjectNotFoundException
1111

@@ -151,35 +151,70 @@ def __init__(self, core):
151151
def _get_entire_object(self, name, owner):
152152
return self._core.api.get(f'{self.find(name, owner, include=["baseObjectRef"]).baseObjectRef}')
153153

154-
def add(self, name, group, owner, winacls=True, description=None, # pylint: disable=too-many-arguments
155-
quota=None, compliance_settings=None, xattrs=None, gfl=False, lock_extensions=None):
154+
def add(self, name, group=None, owner=None, winacls=True, description=None, # pylint: disable=too-many-arguments, too-many-locals
155+
quota=None, archive_settings=None, compliance_settings=None, native_format_settings=None,
156+
xattrs=None, gfl=False, lock_extensions=None):
156157
"""
157158
Create a new Cloud Drive Folder (Cloud Volume)
158159
159-
:param str name: Name of the new folder
160-
:param str group: Folder Group to assign this folder to
160+
:param str name: Name of the new cloud folder
161+
:param str group,optional: Folder Group to assign this folder to
161162
:param cterasdk.core.types.UserAccount owner: User account, the owner of the new folder
162163
:param bool,optional winacls: Use Windows ACLs, defaults to True
163164
:param str,optional description: Cloud drive folder description
164165
:param str,optional quota: Cloud drive folder quota in GB
166+
:param cterasdk.common.object.Object,optional archive_settings: Archive settings.
167+
Use :func:`cterasdk.core.types.ArchiveSettingsBuilder` to build the archive settings object
165168
:param cterasdk.common.object.Object,optional compliance_settings: Compliance settings, defaults to disabled.
166169
Use :func:`cterasdk.core.types.ComplianceSettingsBuilder` to build the compliance settings object
170+
:param cterasdk.common.object.Object,optional native_format_settings: Native format settings, defaults to disabled.
171+
Use :func:`cterasdk.core.types.NativeFormatSettingsBuilder` to build the native format settings object
167172
:param cterasdk.common.object.Object,optional xattrs: Extended attributes, defaults to MacOS.
168173
Use :func:`cterasdk.core.types.ExtendedAttributesBuilder` to build the extended attributes object
169174
:param bool,optional gfl: Enable global file locking
170175
:param list[str],optional lock_extensions: List of file extensions (without leading dot) for which global file locking is enforced.
171176
:returns: Path to the Cloud Drive folder
172177
:rtype: str
173178
"""
174-
param = Object()
179+
param = Object(owner=self._core.users.get(owner, ['baseObjectRef']).baseObjectRef)
180+
181+
self._configure_native_or_encrypted(param, group, native_format_settings)
182+
183+
CloudDrives._configure_attributes(param, name, winacls, quota, description, xattrs)
184+
185+
CloudDrives._configure_archive(param, archive_settings)
186+
187+
CloudDrives._configure_compliance(param, compliance_settings)
188+
189+
if gfl:
190+
CloudDrives._configure_locking(param, lock_extensions)
191+
192+
try:
193+
response = self._core.api.execute('', 'addCloudDrive', param)
194+
logger.info('Cloud drive folder created. %s',
195+
{'name': name, 'owner': param.owner, 'folder_group': group, 'winacls': winacls})
196+
return re.search(r'/Users\/(.+)', response).group()
197+
except CTERAException as error:
198+
logger.error('Cloud drive folder creation failed. %s',
199+
{'name': name, 'folder_group': group, 'owner': str(owner), 'win_acls': winacls})
200+
raise error
201+
202+
def _configure_native_or_encrypted(self, param, group, native_format_settings):
203+
if native_format_settings:
204+
param.openStorageEnabled = True
205+
param.openFabricSettings = native_format_settings
206+
param.group = None
207+
else:
208+
param.group = self._core.cloudfs.groups.get(group, ['baseObjectRef']).baseObjectRef
209+
210+
@staticmethod
211+
def _configure_attributes(param, name, winacls, quota, description, xattrs):
175212
param.name = name
176-
param.owner = self._core.users.get(owner, ['baseObjectRef']).baseObjectRef
177-
param.group = self._core.cloudfs.groups.get(group, ['baseObjectRef']).baseObjectRef
178213
param.enableSyncWinNtExtendedAttributes = winacls
179214
param.folderQuota = quota
180215
if description:
181216
param.description = description
182-
param.wormSettings = compliance_settings if compliance_settings else ComplianceSettingsBuilder.default().build()
217+
183218
if xattrs:
184219
param.extendedAttributes = xattrs
185220
elif not winacls: # Only override default when winacls is False
@@ -193,31 +228,27 @@ def add(self, name, group, owner, winacls=True, description=None, # pylint: dis
193228
else:
194229
param.extendedAttributes = ExtendedAttributesBuilder.default().build()
195230

196-
if gfl:
197-
param.globalFileLockSettings = Object()
198-
param.globalFileLockSettings._classname = 'GlobalFileLockSettings' # pylint: disable=protected-access
199-
param.globalFileLockSettings.enabled = True
200-
param.globalFileLockSettings.globalFileLockExtensions = (
201-
lock_extensions if lock_extensions else CloudDrives.default_extensions
202-
)
231+
@staticmethod
232+
def _configure_archive(param, archive_settings):
233+
param.archiveSettings = archive_settings if archive_settings else ArchiveSettingsBuilder.default().build()
203234

204-
try:
205-
response = self._core.api.execute('', 'addCloudDrive', param)
206-
logger.info(
207-
'Cloud drive folder created. %s',
208-
{'name': name, 'owner': param.owner, 'folder_group': group, 'winacls': winacls}
209-
)
210-
return re.search(r'/Users\/(.+)', response).group()
211-
except CTERAException as error:
212-
logger.error(
213-
'Cloud drive folder creation failed. %s',
214-
{'name': name, 'folder_group': group, 'owner': str(owner), 'win_acls': winacls}
215-
)
216-
raise error
235+
@staticmethod
236+
def _configure_compliance(param, compliance_settings):
237+
param.wormSettings = compliance_settings if compliance_settings else ComplianceSettingsBuilder.default().build()
217238

218-
def modify(self, current_name, owner, new_name=None, # pylint: disable=too-many-arguments, too-many-locals
219-
new_owner=None, new_group=None, description=None, winacls=None, quota=None, compliance_settings=None, xattrs=None,
220-
gfl=None, lock_extensions=None):
239+
@staticmethod
240+
def _configure_locking(param, lock_extensions):
241+
param.globalFileLockSettings = Object()
242+
param.globalFileLockSettings._classname = 'GlobalFileLockSettings' # pylint: disable=protected-access
243+
param.globalFileLockSettings.enabled = True
244+
param.globalFileLockSettings.globalFileLockExtensions = (
245+
lock_extensions if lock_extensions else CloudDrives.default_extensions
246+
)
247+
248+
def modify(self, current_name, owner, new_name=None, # pylint: disable=too-many-arguments, too-many-locals, too-many-branches
249+
new_owner=None, new_group=None, description=None, winacls=None, quota=None,
250+
archive_settings=None, compliance_settings=None, native_format_settings=None,
251+
xattrs=None, gfl=None, lock_extensions=None):
221252
"""
222253
Modify a Cloud Drive Folder (Cloud Volume)
223254
@@ -229,8 +260,12 @@ def modify(self, current_name, owner, new_name=None, # pylint: disable=too-many
229260
:param str,optional description: Folder description
230261
:param bool,optional winacls: Enable or disable Windows ACLs
231262
:param str,optional quota: Folder quota in GB
263+
:param cterasdk.common.object.Object,optional archive_settings: Archive settings.
264+
Use :func:`cterasdk.core.types.ArchiveSettingsBuilder` to build the archive settings object
232265
:param cterasdk.common.object.Object,optional compliance_settings: Compliance settings.
233266
Use :func:`cterasdk.core.types.ComplianceSettingsBuilder` to build the compliance settings object
267+
:param cterasdk.common.object.Object,optional native_format_settings: Native format settings.
268+
Use :func:`cterasdk.core.types.NativeFormatSettingsBuilder` to build the native format settings object
234269
:param cterasdk.common.object.Object,optional xattrs: Extended attributes.
235270
Use :func:`cterasdk.core.types.ExtendedAttributesBuilder` to build the extended attributes object
236271
:param bool,optional gfl: Enable global file locking
@@ -249,8 +284,12 @@ def modify(self, current_name, owner, new_name=None, # pylint: disable=too-many
249284
param.enableSyncWinNtExtendedAttributes = winacls
250285
if quota:
251286
param.folderQuota = quota
287+
if archive_settings:
288+
param.archiveSettings = archive_settings
252289
if compliance_settings:
253290
param.wormSettings = compliance_settings
291+
if native_format_settings:
292+
param.openFabricSettings = native_format_settings
254293
if xattrs:
255294
param.extendedAttributes = xattrs
256295
if gfl is not None:

cterasdk/core/enum.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ class BucketType:
439439
:ivar str Wasabi: Wasabi S3
440440
:ivar str Google: Google S3
441441
:ivar str NetAppStorageGRID: NetApp StorageGRID WebScale (S3)
442+
:ivar str Cloudian: Cloudian (S3)
442443
"""
443444
Azure = 'Azure'
444445
Scality = 'ScalityS3'
@@ -449,6 +450,7 @@ class BucketType:
449450
Wasabi = 'WasabiS3'
450451
Google = 'GoogleS3'
451452
NetAppStorageGRID = 'NTAP'
453+
Cloudian = 'CD'
452454

453455

454456
class EnvironmentVariables:
@@ -737,3 +739,16 @@ class ResourceError:
737739
FileWithTheSameNameExist = "FileWithTheSameNameExist"
738740
InvalidName = "InvalidName"
739741
ReservedName = "ReservedName"
742+
743+
744+
class NativeFormat:
745+
"""
746+
Native Format - Fusion Direct
747+
748+
:ivar str Filesystem: Read-write through the filesystem only. Bucket access is read-only.
749+
:ivar str Bucket: Read-write through the bucket only. Filesystem access is read-only.
750+
:ivar str Bidirectional: Read-write through both the filesystem and the bucket.
751+
"""
752+
Filesystem = 'Filesystem'
753+
Bucket = 'Bucket'
754+
Bidirectional = 'Bidirectional'

cterasdk/core/servers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def enable(self, bucket, interval):
130130
:param int interval: Backup interval in minutes
131131
"""
132132
database = self._core.servers.system_database
133-
database.backupToBucket = Object(enabled=True, exportSchedulePeriod=interval, details=bucket.database_backup_server_object())
133+
database.backupToBucket = Object(enabled=True, exportSchedulePeriod=interval, details=bucket.to_database_backup_server_object())
134134
logger.info("Enabling database backup. %s", {'server': database.name})
135135
response = self._core.api.put(f'/servers/{database.name}', database)
136136
logger.info("Database backup enabled. %s", {'server': database.name})

cterasdk/core/types.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ..lib.storage import commonfs
55

66
from .enum import PortalAccountType, CollaboratorType, FileAccessMode, PlanCriteria, TemplateCriteria, ProtectionLevel, \
7-
BucketType, LocationType, Platform, RetentionMode, Duration, ExtendedAttributes, ConflictHandler
7+
BucketType, LocationType, Platform, RetentionMode, Duration, ExtendedAttributes, ConflictHandler, NativeFormat
88

99

1010
CloudFSFolderFindingHelper = namedtuple('CloudFSFolderFindingHelper', ('name', 'owner'))
@@ -331,7 +331,21 @@ def __init__(self, bucket, driver, access_key, secret_key, endpoint, https, dire
331331
def trust_all_certificates(self):
332332
return not self.verify_ssl
333333

334-
def database_backup_server_object(self):
334+
def to_native_format_server_object(self):
335+
return Object(
336+
_classname='OpenFabricS3DataStorage',
337+
storage=self.driver,
338+
bucket=self.bucket,
339+
accessKey=self.access_key,
340+
secretKey=self.secret_key,
341+
endPoint=self.endpoint,
342+
useHttps=self.https,
343+
trustAllCertificates=self.trust_all_certificates,
344+
masterHost=None,
345+
usePathStyleAddressing=False
346+
)
347+
348+
def to_database_backup_server_object(self):
335349
return Object(
336350
storage=self.driver,
337351
bucket=self.bucket,
@@ -400,6 +414,13 @@ def __init__(self, bucket, access_key, secret_key,
400414
super().__init__(bucket, BucketType.Nutanix, access_key, secret_key, endpoint, https, direct, verify_ssl)
401415

402416

417+
class Cloudian(S3Compatible):
418+
419+
def __init__(self, bucket, access_key, secret_key,
420+
endpoint, https=False, direct=False, verify_ssl=True):
421+
super().__init__(bucket, BucketType.Cloudian, access_key, secret_key, endpoint, https, direct, verify_ssl)
422+
423+
403424
class Wasabi(S3Compatible):
404425

405426
def __init__(self, bucket, access_key, secret_key,
@@ -741,6 +762,58 @@ def build(self):
741762
return self.settings
742763

743764

765+
class ArchiveSettingsBuilder:
766+
767+
def __init__(self, enabled, mode, retain_for):
768+
self.settings = Object()
769+
self.settings._classname = 'ArchiveSettings' # pylint: disable=protected-access
770+
self.settings.archive = enabled
771+
self.settings.retentionMode = mode
772+
self.settings.retentionPeriod = retain_for
773+
self.settings.gracePeriod = ComplianceSettingsBuilder._get_retention_period(0, Duration.Minutes)
774+
775+
@staticmethod
776+
def default():
777+
return ArchiveSettingsBuilder(False, RetentionMode.Enterprise, None)
778+
779+
@staticmethod
780+
def enterprise(amount, duration):
781+
return ArchiveSettingsBuilder(True, RetentionMode.Enterprise,
782+
ComplianceSettingsBuilder._get_retention_period(amount, duration)) # pylint: disable=protected-access
783+
784+
@staticmethod
785+
def compliance(amount, duration):
786+
return ArchiveSettingsBuilder(True, RetentionMode.Compliance,
787+
ComplianceSettingsBuilder._get_retention_period(amount, duration)) # pylint: disable=protected-access
788+
789+
def build(self):
790+
return self.settings
791+
792+
793+
class NativeFormatSettingsBuilder:
794+
795+
def __init__(self, mode, bucket, sqs, region):
796+
self.settings = Object(_classname='OpenFabricSettings', storageMode=mode)
797+
self.settings.dataStorage = bucket.to_native_format_server_object()
798+
self.settings.dataStorage.sqsUrl = sqs
799+
self.settings.dataStorage.region = region
800+
801+
@staticmethod
802+
def filesystem(bucket, region):
803+
return NativeFormatSettingsBuilder(NativeFormat.Filesystem, bucket, None, region)
804+
805+
@staticmethod
806+
def bucket(bucket, sqs, region):
807+
return NativeFormatSettingsBuilder(NativeFormat.Bucket, bucket, sqs, region)
808+
809+
@staticmethod
810+
def bidirectional(bucket, sqs, region):
811+
return NativeFormatSettingsBuilder(NativeFormat.Bidirectional, bucket, sqs, region)
812+
813+
def build(self):
814+
return self.settings
815+
816+
744817
class ExtendedAttribute(Object):
745818

746819
def __init__(self, name, supported):

docs/source/UserGuides/Portal/Administration.rst

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1425,10 +1425,13 @@ Cloud Drive Folders
14251425
wbruce = core_types.UserAccount('wbruce', 'ctera.local')
14261426
admin.cloudfs.drives.add('DIR-002', 'FG-002', wbruce)
14271427
1428+
Code examples to create immutable Cloud Drive folders
1429+
1430+
.. code:: python
1431+
14281432
"""Create immutable Cloud Drive folders"""
14291433
14301434
svc_account = core_types.UserAccount('svc_account')
1431-
14321435
"""
14331436
Mode: Enterprise (i.e., allow privileged delete by the CTERA Compliance Officer role)
14341437
Retention Period: 7 Years.
@@ -1447,6 +1450,34 @@ Cloud Drive Folders
14471450
settings = core_types.ComplianceSettingsBuilder.enterprise(1, core_enum.Duration.Years).grace_period(1, core_enum.Duration.Hours).build()
14481451
admin.cloudfs.drives.add('Compliance', 'FG-Compliance', svc_account, compliance_settings=settings)
14491452
1453+
Code examples to create archive Cloud Drive folders
1454+
1455+
.. code:: python
1456+
1457+
archive_settings = core_types.ArchiveSettingsBuilder.enterprise(30, core_enum.Duration.Days).build()
1458+
admin.cloudfs.groups.add(
1459+
'ARCHIVE-DIR-001',
1460+
'FG-001',
1461+
core_types.UserAccount('svc_account'),
1462+
archive_settings=archive_settings
1463+
)
1464+
1465+
Code examples to create Cloud Drive folders using Fusion Direct
1466+
1467+
.. code:: python
1468+
1469+
name, access, secret = 'target-s3-bucket-name', 'access-key', 'secret-access-key'
1470+
endpoint, https = 's3.eu-west-1.amazonaws.com', True
1471+
bucket = core_types.AmazonS3(name, access, secret, endpoint, https) # use verify_ssl=False to trust all certificates
1472+
1473+
sqs = 'https://sqs.ctera.local:18090/7ed20456b4eebb2c21c0079edcefe2ce/bucket'
1474+
native_format_settings = core_types.NativeFormatSettingsBuilder.bidirectional(bucket, sqs).build()
1475+
1476+
user.cloudfs.drives.add(
1477+
'DIR-001-Native',
1478+
owner=core_types.UserAccount('svc_account'),
1479+
native_format_settings=native_format_settings)
1480+
)
14501481
14511482
.. automethod:: cterasdk.core.cloudfs.CloudDrives.modify
14521483
:noindex:

tests/ut/core/admin/test_cloudfs_cloud_drives.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from cterasdk import exceptions
55
from cterasdk.core import cloudfs
6-
from cterasdk.core.types import UserAccount, ComplianceSettingsBuilder, ExtendedAttributesBuilder
6+
from cterasdk.core.types import UserAccount, ArchiveSettingsBuilder, ComplianceSettingsBuilder, ExtendedAttributesBuilder
77
from cterasdk.core import query
88
from cterasdk.common import Object, union
99
from tests.ut.core.admin import base_admin
@@ -191,8 +191,8 @@ def test_undelete_with_local_owner(self):
191191
self._global_admin.users.get.assert_called_once_with(self._local_user_account, ['displayName'])
192192
self._global_admin.files.undelete.assert_called_once_with(f'Users/{self._owner}/{self._name}')
193193

194-
def _get_add_cloud_drive_object(self, winacls=True, description=None, quota=None, compliance_settings=None, xattrs=None,
195-
gfl=None, lock_extensions=None):
194+
def _get_add_cloud_drive_object(self, winacls=True, description=None, quota=None, archive_settings=None,
195+
compliance_settings=None, xattrs=None, gfl=None, lock_extensions=None):
196196
add_cloud_drive_param = Object()
197197
add_cloud_drive_param.name = self._name
198198
add_cloud_drive_param.owner = self._owner
@@ -202,6 +202,7 @@ def _get_add_cloud_drive_object(self, winacls=True, description=None, quota=None
202202
if description:
203203
add_cloud_drive_param.description = description
204204
add_cloud_drive_param.wormSettings = compliance_settings if compliance_settings else ComplianceSettingsBuilder.default().build()
205+
add_cloud_drive_param.archiveSettings = archive_settings if archive_settings else ArchiveSettingsBuilder.default().build()
205206
add_cloud_drive_param.extendedAttributes = xattrs if xattrs else ExtendedAttributesBuilder.default().build()
206207
if gfl:
207208
add_cloud_drive_param.globalFileLockSettings = Object()

0 commit comments

Comments
 (0)