@@ -657,6 +657,12 @@ class CephRGWTest(test_utils.BaseCharmTest):
657
657
primary_rgw_unit = 'ceph-radosgw/0'
658
658
secondary_rgw_app = 'secondary-ceph-radosgw'
659
659
secondary_rgw_unit = 'secondary-ceph-radosgw/0'
660
+ cloud_sync_rgw_app = 'cloud-sync-ceph-radosgw'
661
+ # These S3 Juju apps are used for the cloud sync tests. They are deployed
662
+ # using the following Juju charm: https://charmhub.io/minio-test
663
+ # Their purpose is to provide S3 destinations for the cloud sync tests.
664
+ cloud_sync_default_s3_app = 's3-default'
665
+ cloud_sync_dev_s3_app = 's3-dev'
660
666
661
667
@classmethod
662
668
def setUpClass (cls ):
@@ -685,6 +691,15 @@ def multisite(self):
685
691
except KeyError :
686
692
return False
687
693
694
+ @property
695
+ def cloud_sync (self ):
696
+ """Determine whether Ceph cloud sync application is present."""
697
+ try :
698
+ zaza_model .get_application (self .cloud_sync_rgw_app )
699
+ return True
700
+ except KeyError :
701
+ return False
702
+
688
703
def get_rgwadmin_cmd_skeleton (self , unit_name ):
689
704
"""
690
705
Get radosgw-admin cmd skeleton with rgw.hostname populated key.
@@ -855,6 +870,35 @@ def get_rgw_endpoint(self, unit_name: str):
855
870
except KeyError :
856
871
return "http://{}:80" .format (unit_address )
857
872
873
+ def get_minio_boto3_client (self , app_name : str ):
874
+ """Get boto3 client for MinIO application.
875
+
876
+ :param app_name: MinIO Juju app name.
877
+ :type app_name: str
878
+ """
879
+ leader_unit = zaza_model .get_lead_unit (app_name )
880
+ unit_address = zaza_model .get_unit_public_address (
881
+ leader_unit ,
882
+ self .model_name
883
+ )
884
+
885
+ logging .debug ("Minio Leader Unit: {}, Endpoint: {}" .format (
886
+ leader_unit .entity_id , unit_address ))
887
+ if unit_address is None :
888
+ return None
889
+
890
+ app_config = zaza_model .get_application_config (app_name )
891
+ port = app_config ['port' ].get ('value' )
892
+ access_key = app_config ['root-user' ].get ('value' )
893
+ access_secret = app_config ['root-password' ].get ('value' )
894
+
895
+ return boto3 .resource (
896
+ "s3" ,
897
+ verify = False ,
898
+ endpoint_url = "http://{}:{}" .format (unit_address , port ),
899
+ aws_access_key_id = access_key ,
900
+ aws_secret_access_key = access_secret )
901
+
858
902
def configure_rgw_apps_for_multisite (self ):
859
903
"""Configure Multisite values on primary and secondary apps."""
860
904
realm = 'zaza_realm'
@@ -876,6 +920,15 @@ def configure_rgw_apps_for_multisite(self):
876
920
'zone' : 'zaza_secondary'
877
921
}
878
922
)
923
+ if self .cloud_sync :
924
+ zaza_model .set_application_config (
925
+ self .cloud_sync_rgw_app ,
926
+ {
927
+ 'realm' : realm ,
928
+ 'zonegroup' : zonegroup ,
929
+ 'zone' : 'zaza_cloud_sync'
930
+ }
931
+ )
879
932
880
933
def configure_rgw_multisite_relation (self ):
881
934
"""Configure multi-site relation between primary and secondary apps."""
@@ -1074,6 +1127,13 @@ def test_003_object_storage_and_secondary_block(self):
1074
1127
"Non-Pristine RGW site can't be used as secondary"
1075
1128
}
1076
1129
}
1130
+ if self .cloud_sync :
1131
+ assert_state [self .cloud_sync_rgw_app ] = {
1132
+ "workload-status" : "blocked" ,
1133
+ "workload-status-message-prefix" :
1134
+ "multi-site configuration but primary/secondary "
1135
+ "relation missing" ,
1136
+ }
1077
1137
zaza_model .wait_for_application_states (states = assert_state ,
1078
1138
timeout = 900 )
1079
1139
@@ -1294,6 +1354,99 @@ def test_004_multisite_directional_sync_policy(self):
1294
1354
)
1295
1355
zaza_model .wait_for_unit_idle (self .primary_rgw_unit )
1296
1356
1357
+ def test_005_object_storage_cloud_sync (self ):
1358
+ """Verify Ceph RGW Cloud Sync functionality."""
1359
+ # Skip cloud sync tests if not compatible with bundle.
1360
+ if not self .cloud_sync :
1361
+ raise unittest .SkipTest ('Skipping Cloud Sync Test' )
1362
+
1363
+ obj_name = 'testfile'
1364
+ # Syncs to default S3 target.
1365
+ default_container_name = 'zaza-cloud-sync-container'
1366
+ default_obj_data = 'Test data from Zaza'
1367
+ # Syncs to dev S3 target.
1368
+ dev_container_name = 'dev-zaza-cloud-sync-container'
1369
+ dev_obj_data = 'Test dev data from Zaza'
1370
+
1371
+ # Configure cloud-sync multi-site relation.
1372
+ logging .info ('Configuring Cloud Sync Multisite' )
1373
+ self .configure_rgw_apps_for_multisite ()
1374
+ zaza_model .add_relation (
1375
+ self .primary_rgw_app ,
1376
+ self .primary_rgw_app + ":primary" ,
1377
+ self .cloud_sync_rgw_app + ":cloud-sync"
1378
+ )
1379
+ assert_state = {
1380
+ self .secondary_rgw_app : {
1381
+ "workload-status" : "blocked" ,
1382
+ "workload-status-message-prefix" :
1383
+ "multi-site configuration but primary/secondary "
1384
+ "relation missing" ,
1385
+ }
1386
+ }
1387
+ zaza_model .wait_for_application_states (states = assert_state ,
1388
+ timeout = 900 )
1389
+
1390
+ logging .info ('Verifying Ceph RGW Cloud Sync functionality' )
1391
+
1392
+ # Fetch Primary Endpoint Details.
1393
+ primary_endpoint = self .get_rgw_endpoint (self .primary_rgw_unit )
1394
+ self .assertNotEqual (primary_endpoint , None )
1395
+
1396
+ # Create RGW client and perform IO to be synced to both S3 targets.
1397
+ access_key , secret_key = self .get_client_keys ()
1398
+ primary_client = boto3 .resource ("s3" ,
1399
+ verify = False ,
1400
+ endpoint_url = primary_endpoint ,
1401
+ aws_access_key_id = access_key ,
1402
+ aws_secret_access_key = secret_key )
1403
+ default_container = primary_client .Bucket (default_container_name )
1404
+ default_container .create ()
1405
+ default_obj = primary_client .Object (default_container_name , obj_name )
1406
+ default_obj .put (Body = default_obj_data )
1407
+ dev_container = primary_client .Bucket (dev_container_name )
1408
+ dev_container .create ()
1409
+ dev_obj = primary_client .Object (dev_container_name , obj_name )
1410
+ dev_obj .put (Body = dev_obj_data )
1411
+
1412
+ # Wait for sync to complete.
1413
+ logging .info ('Waiting for Cloud Sync Data and Metadata to Synchronize' )
1414
+ self .wait_for_status (self .cloud_sync_rgw_app , is_primary = False )
1415
+
1416
+ # Create clients for the cloud-sync S3 targets.
1417
+ default_s3_client = self .get_minio_boto3_client (
1418
+ self .cloud_sync_default_s3_app
1419
+ )
1420
+ self .assertNotEqual (default_s3_client , None )
1421
+ dev_s3_client = self .get_minio_boto3_client (self .cloud_sync_dev_s3_app )
1422
+ self .assertNotEqual (dev_s3_client , None )
1423
+
1424
+ # Verify that data was properly synced.
1425
+ logging .info ('Verifying Synced Data on S3 Targets' )
1426
+ test_data = self .fetch_rgw_object (default_s3_client ,
1427
+ default_container_name ,
1428
+ obj_name )
1429
+ self .assertEqual (test_data , default_obj_data )
1430
+ test_data = self .fetch_rgw_object (dev_s3_client ,
1431
+ dev_container_name ,
1432
+ obj_name )
1433
+ self .assertEqual (test_data , dev_obj_data )
1434
+
1435
+ # Perform cleanup.
1436
+ logging .info ('Performing Cleanup' )
1437
+ self .purge_bucket (self .primary_rgw_app , default_container_name )
1438
+ self .purge_bucket (self .primary_rgw_app , dev_container_name )
1439
+
1440
+ # Wait for sync to complete.
1441
+ self .wait_for_status (self .cloud_sync_rgw_app , is_primary = False )
1442
+
1443
+ # Validate that synced data was removed from the S3 targets.
1444
+ logging .info ('Verifying that data was deleted on the S3 targets' )
1445
+ with self .assertRaises (botocore .exceptions .ClientError ):
1446
+ default_s3_client .Object (default_container_name , obj_name ).get ()
1447
+ with self .assertRaises (botocore .exceptions .ClientError ):
1448
+ dev_s3_client .Object (dev_container_name , obj_name ).get ()
1449
+
1297
1450
def test_100_migration_and_multisite_failover (self ):
1298
1451
"""Perform multisite migration and verify failover."""
1299
1452
container_name = 'zaza-container'
0 commit comments