From 4ddaeeb41d4d875e360bac047da569b0f86b7f21 Mon Sep 17 00:00:00 2001 From: Radovan Date: Thu, 30 Nov 2023 16:41:26 +0200 Subject: [PATCH] Expose sum of backup object count and size via gRPC methods (#686) * Expose sum of backup object count and size via gRPC methods * Fix various issues with ITs and grpc * Use k8ssandra-operator main branch in ITs * Simplify gRPC way of working out backup statuses * Use k8ssandra-operator compatible with changes in this PR * Build the k8s cluster with the checked out k8ssandra operator * Do not return a data structure if we modify it * Properly read k8s enablement setting * Switch ITs to use k8ssandra-operator main --- .github/workflows/ci.yml | 6 +-- medusa/config.py | 9 ++++ medusa/service/grpc/medusa.proto | 8 +-- medusa/service/grpc/medusa_pb2.py | 32 +++++------ medusa/service/grpc/server.py | 51 ++++++++++++------ tests/config_test.py | 14 +++++ .../features/steps/integration_steps.py | 28 ++++++---- tests/service/grpc/server_test.py | 53 +++++++++++-------- 8 files changed, 131 insertions(+), 70 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 623a0b3bc..7e66169ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -343,9 +343,7 @@ jobs: - uses: actions/checkout@v3 with: repository: k8ssandra/k8ssandra-operator - # Modify this line to use a different branch when making changes to the operator affecting Medusa - # Change to main once the operator changes are merged - ref: add-medusa-non-regression-test + ref: main path: k8ssandra-operator - name: Set up Go uses: actions/setup-go@v3 @@ -377,7 +375,7 @@ jobs: - name: Setup kind cluster working-directory: k8ssandra-operator run: | - make single-create + make single-up - name: Run e2e test ( ${{ matrix.e2e_test }} ) working-directory: k8ssandra-operator run: | diff --git a/medusa/config.py b/medusa/config.py index 6d5f425cf..94235eb58 100644 --- a/medusa/config.py +++ b/medusa/config.py @@ -205,6 +205,15 @@ def parse_config(args, config_file): if value is not None }}) + # the k8s mode and grpc server overlap in a keyword 'enabled' + # so we need to reconcile them explicitly + k8s_enabled = evaluate_boolean(config['kubernetes']['enabled']) + if args.get('k8s_enabled', 'False') == 'True' or k8s_enabled: + config.set('kubernetes', 'enabled', 'True') + grpc_enabled = evaluate_boolean(config['grpc']['enabled']) + if args.get('grpc_enabled', "False") == 'True' or grpc_enabled: + config.set('grpc', 'enabled', 'True') + if evaluate_boolean(config['kubernetes']['enabled']): if evaluate_boolean(config['cassandra']['use_sudo']): logging.warning('Forcing use_sudo to False because Kubernetes mode is enabled') diff --git a/medusa/service/grpc/medusa.proto b/medusa/service/grpc/medusa.proto index 9d4f5fc3c..718c1ba9f 100644 --- a/medusa/service/grpc/medusa.proto +++ b/medusa/service/grpc/medusa.proto @@ -36,7 +36,7 @@ message BackupRequest { message BackupResponse { string backupName = 1; - StatusType status = 2; + StatusType status = 2; } message BackupStatusRequest { @@ -82,8 +82,10 @@ message BackupSummary { int32 totalNodes = 4; int32 finishedNodes = 5; repeated BackupNode nodes = 6; - StatusType status = 7; - string backupType = 8; + StatusType status = 7; + string backupType = 8; + int64 totalSize = 9; + int64 totalObjects = 10; } message BackupNode { diff --git a/medusa/service/grpc/medusa_pb2.py b/medusa/service/grpc/medusa_pb2.py index 63496a5bb..b063f1c0e 100644 --- a/medusa/service/grpc/medusa_pb2.py +++ b/medusa/service/grpc/medusa_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cmedusa.proto\"d\n\rBackupRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12!\n\x04mode\x18\x02 \x01(\x0e\x32\x13.BackupRequest.Mode\"\"\n\x04Mode\x12\x10\n\x0c\x44IFFERENTIAL\x10\x00\x12\x08\n\x04\x46ULL\x10\x01\"A\n\x0e\x42\x61\x63kupResponse\x12\x12\n\nbackupName\x18\x01 \x01(\t\x12\x1b\n\x06status\x18\x02 \x01(\x0e\x32\x0b.StatusType\")\n\x13\x42\x61\x63kupStatusRequest\x12\x12\n\nbackupName\x18\x01 \x01(\t\"Z\n\x14\x42\x61\x63kupStatusResponse\x12\x11\n\tstartTime\x18\x01 \x01(\t\x12\x12\n\nfinishTime\x18\x02 \x01(\t\x12\x1b\n\x06status\x18\x03 \x01(\x0e\x32\x0b.StatusType\"#\n\x13\x44\x65leteBackupRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"A\n\x14\x44\x65leteBackupResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x06status\x18\x02 \x01(\x0e\x32\x0b.StatusType\"&\n\x10GetBackupRequest\x12\x12\n\nbackupName\x18\x01 \x01(\t\"P\n\x11GetBackupResponse\x12\x1e\n\x06\x62\x61\x63kup\x18\x01 \x01(\x0b\x32\x0e.BackupSummary\x12\x1b\n\x06status\x18\x02 \x01(\x0e\x32\x0b.StatusType\"\x13\n\x11GetBackupsRequest\"Y\n\x12GetBackupsResponse\x12\x1f\n\x07\x62\x61\x63kups\x18\x01 \x03(\x0b\x32\x0e.BackupSummary\x12\"\n\roverallStatus\x18\x02 \x01(\x0e\x32\x0b.StatusType\"\xc2\x01\n\rBackupSummary\x12\x12\n\nbackupName\x18\x01 \x01(\t\x12\x11\n\tstartTime\x18\x02 \x01(\x03\x12\x12\n\nfinishTime\x18\x03 \x01(\x03\x12\x12\n\ntotalNodes\x18\x04 \x01(\x05\x12\x15\n\rfinishedNodes\x18\x05 \x01(\x05\x12\x1a\n\x05nodes\x18\x06 \x03(\x0b\x32\x0b.BackupNode\x12\x1b\n\x06status\x18\x07 \x01(\x0e\x32\x0b.StatusType\x12\x12\n\nbackupType\x18\x08 \x01(\t\"L\n\nBackupNode\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0e\n\x06tokens\x18\x02 \x03(\x03\x12\x12\n\ndatacenter\x18\x03 \x01(\t\x12\x0c\n\x04rack\x18\x04 \x01(\t\"\x15\n\x13PurgeBackupsRequest\"\x84\x01\n\x14PurgeBackupsResponse\x12\x17\n\x0fnbBackupsPurged\x18\x01 \x01(\x05\x12\x17\n\x0fnbObjectsPurged\x18\x02 \x01(\x05\x12\x17\n\x0ftotalPurgedSize\x18\x03 \x01(\x03\x12!\n\x19totalObjectsWithinGcGrace\x18\x04 \x01(\x05\"S\n\x15PrepareRestoreRequest\x12\x12\n\nbackupName\x18\x01 \x01(\t\x12\x12\n\ndatacenter\x18\x02 \x01(\t\x12\x12\n\nrestoreKey\x18\x03 \x01(\t\"\x18\n\x16PrepareRestoreResponse*C\n\nStatusType\x12\x0f\n\x0bIN_PROGRESS\x10\x00\x12\x0b\n\x07SUCCESS\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x12\x0b\n\x07UNKNOWN\x10\x03\x32\xc8\x03\n\x06Medusa\x12)\n\x06\x42\x61\x63kup\x12\x0e.BackupRequest\x1a\x0f.BackupResponse\x12.\n\x0b\x41syncBackup\x12\x0e.BackupRequest\x1a\x0f.BackupResponse\x12;\n\x0c\x42\x61\x63kupStatus\x12\x14.BackupStatusRequest\x1a\x15.BackupStatusResponse\x12;\n\x0c\x44\x65leteBackup\x12\x14.DeleteBackupRequest\x1a\x15.DeleteBackupResponse\x12\x32\n\tGetBackup\x12\x11.GetBackupRequest\x1a\x12.GetBackupResponse\x12\x35\n\nGetBackups\x12\x12.GetBackupsRequest\x1a\x13.GetBackupsResponse\x12;\n\x0cPurgeBackups\x12\x14.PurgeBackupsRequest\x1a\x15.PurgeBackupsResponse\x12\x41\n\x0ePrepareRestore\x12\x16.PrepareRestoreRequest\x1a\x17.PrepareRestoreResponseb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cmedusa.proto\"d\n\rBackupRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12!\n\x04mode\x18\x02 \x01(\x0e\x32\x13.BackupRequest.Mode\"\"\n\x04Mode\x12\x10\n\x0c\x44IFFERENTIAL\x10\x00\x12\x08\n\x04\x46ULL\x10\x01\"A\n\x0e\x42\x61\x63kupResponse\x12\x12\n\nbackupName\x18\x01 \x01(\t\x12\x1b\n\x06status\x18\x02 \x01(\x0e\x32\x0b.StatusType\")\n\x13\x42\x61\x63kupStatusRequest\x12\x12\n\nbackupName\x18\x01 \x01(\t\"Z\n\x14\x42\x61\x63kupStatusResponse\x12\x11\n\tstartTime\x18\x01 \x01(\t\x12\x12\n\nfinishTime\x18\x02 \x01(\t\x12\x1b\n\x06status\x18\x03 \x01(\x0e\x32\x0b.StatusType\"#\n\x13\x44\x65leteBackupRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"A\n\x14\x44\x65leteBackupResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x06status\x18\x02 \x01(\x0e\x32\x0b.StatusType\"&\n\x10GetBackupRequest\x12\x12\n\nbackupName\x18\x01 \x01(\t\"P\n\x11GetBackupResponse\x12\x1e\n\x06\x62\x61\x63kup\x18\x01 \x01(\x0b\x32\x0e.BackupSummary\x12\x1b\n\x06status\x18\x02 \x01(\x0e\x32\x0b.StatusType\"\x13\n\x11GetBackupsRequest\"Y\n\x12GetBackupsResponse\x12\x1f\n\x07\x62\x61\x63kups\x18\x01 \x03(\x0b\x32\x0e.BackupSummary\x12\"\n\roverallStatus\x18\x02 \x01(\x0e\x32\x0b.StatusType\"\xeb\x01\n\rBackupSummary\x12\x12\n\nbackupName\x18\x01 \x01(\t\x12\x11\n\tstartTime\x18\x02 \x01(\x03\x12\x12\n\nfinishTime\x18\x03 \x01(\x03\x12\x12\n\ntotalNodes\x18\x04 \x01(\x05\x12\x15\n\rfinishedNodes\x18\x05 \x01(\x05\x12\x1a\n\x05nodes\x18\x06 \x03(\x0b\x32\x0b.BackupNode\x12\x1b\n\x06status\x18\x07 \x01(\x0e\x32\x0b.StatusType\x12\x12\n\nbackupType\x18\x08 \x01(\t\x12\x11\n\ttotalSize\x18\t \x01(\x03\x12\x14\n\x0ctotalObjects\x18\n \x01(\x03\"L\n\nBackupNode\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x0e\n\x06tokens\x18\x02 \x03(\x03\x12\x12\n\ndatacenter\x18\x03 \x01(\t\x12\x0c\n\x04rack\x18\x04 \x01(\t\"\x15\n\x13PurgeBackupsRequest\"\x84\x01\n\x14PurgeBackupsResponse\x12\x17\n\x0fnbBackupsPurged\x18\x01 \x01(\x05\x12\x17\n\x0fnbObjectsPurged\x18\x02 \x01(\x05\x12\x17\n\x0ftotalPurgedSize\x18\x03 \x01(\x03\x12!\n\x19totalObjectsWithinGcGrace\x18\x04 \x01(\x05\"S\n\x15PrepareRestoreRequest\x12\x12\n\nbackupName\x18\x01 \x01(\t\x12\x12\n\ndatacenter\x18\x02 \x01(\t\x12\x12\n\nrestoreKey\x18\x03 \x01(\t\"\x18\n\x16PrepareRestoreResponse*C\n\nStatusType\x12\x0f\n\x0bIN_PROGRESS\x10\x00\x12\x0b\n\x07SUCCESS\x10\x01\x12\n\n\x06\x46\x41ILED\x10\x02\x12\x0b\n\x07UNKNOWN\x10\x03\x32\xc8\x03\n\x06Medusa\x12)\n\x06\x42\x61\x63kup\x12\x0e.BackupRequest\x1a\x0f.BackupResponse\x12.\n\x0b\x41syncBackup\x12\x0e.BackupRequest\x1a\x0f.BackupResponse\x12;\n\x0c\x42\x61\x63kupStatus\x12\x14.BackupStatusRequest\x1a\x15.BackupStatusResponse\x12;\n\x0c\x44\x65leteBackup\x12\x14.DeleteBackupRequest\x1a\x15.DeleteBackupResponse\x12\x32\n\tGetBackup\x12\x11.GetBackupRequest\x1a\x12.GetBackupResponse\x12\x35\n\nGetBackups\x12\x12.GetBackupsRequest\x1a\x13.GetBackupsResponse\x12;\n\x0cPurgeBackups\x12\x14.PurgeBackupsRequest\x1a\x15.PurgeBackupsResponse\x12\x41\n\x0ePrepareRestore\x12\x16.PrepareRestoreRequest\x1a\x17.PrepareRestoreResponseb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -21,8 +21,8 @@ if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_STATUSTYPE']._serialized_start=1202 - _globals['_STATUSTYPE']._serialized_end=1269 + _globals['_STATUSTYPE']._serialized_start=1243 + _globals['_STATUSTYPE']._serialized_end=1310 _globals['_BACKUPREQUEST']._serialized_start=16 _globals['_BACKUPREQUEST']._serialized_end=116 _globals['_BACKUPREQUEST_MODE']._serialized_start=82 @@ -46,17 +46,17 @@ _globals['_GETBACKUPSRESPONSE']._serialized_start=567 _globals['_GETBACKUPSRESPONSE']._serialized_end=656 _globals['_BACKUPSUMMARY']._serialized_start=659 - _globals['_BACKUPSUMMARY']._serialized_end=853 - _globals['_BACKUPNODE']._serialized_start=855 - _globals['_BACKUPNODE']._serialized_end=931 - _globals['_PURGEBACKUPSREQUEST']._serialized_start=933 - _globals['_PURGEBACKUPSREQUEST']._serialized_end=954 - _globals['_PURGEBACKUPSRESPONSE']._serialized_start=957 - _globals['_PURGEBACKUPSRESPONSE']._serialized_end=1089 - _globals['_PREPARERESTOREREQUEST']._serialized_start=1091 - _globals['_PREPARERESTOREREQUEST']._serialized_end=1174 - _globals['_PREPARERESTORERESPONSE']._serialized_start=1176 - _globals['_PREPARERESTORERESPONSE']._serialized_end=1200 - _globals['_MEDUSA']._serialized_start=1272 - _globals['_MEDUSA']._serialized_end=1728 + _globals['_BACKUPSUMMARY']._serialized_end=894 + _globals['_BACKUPNODE']._serialized_start=896 + _globals['_BACKUPNODE']._serialized_end=972 + _globals['_PURGEBACKUPSREQUEST']._serialized_start=974 + _globals['_PURGEBACKUPSREQUEST']._serialized_end=995 + _globals['_PURGEBACKUPSRESPONSE']._serialized_start=998 + _globals['_PURGEBACKUPSRESPONSE']._serialized_end=1130 + _globals['_PREPARERESTOREREQUEST']._serialized_start=1132 + _globals['_PREPARERESTOREREQUEST']._serialized_end=1215 + _globals['_PREPARERESTORERESPONSE']._serialized_start=1217 + _globals['_PREPARERESTORERESPONSE']._serialized_end=1241 + _globals['_MEDUSA']._serialized_start=1313 + _globals['_MEDUSA']._serialized_end=1769 # @@protoc_insertion_point(module_scope) diff --git a/medusa/service/grpc/server.py b/medusa/service/grpc/server.py index f2f2fcb30..52c7e166a 100644 --- a/medusa/service/grpc/server.py +++ b/medusa/service/grpc/server.py @@ -170,19 +170,18 @@ def BackupStatus(self, request, context): response = medusa_pb2.BackupStatusResponse() try: with Storage(config=self.storage_config) as storage: - + # find the backup backup = storage.get_node_backup(fqdn=storage.config.fqdn, name=request.backupName) if backup.started is None: raise KeyError - + # work out the timings response.startTime = datetime.fromtimestamp(backup.started).strftime(TIMESTAMP_FORMAT) if backup.finished: response.finishTime = datetime.fromtimestamp(backup.finished).strftime(TIMESTAMP_FORMAT) else: response.finishTime = "" - + # record the status record_status_in_response(response, request.backupName) - except KeyError: context.set_details("backup <{}> does not exist".format(request.backupName)) context.set_code(grpc.StatusCode.NOT_FOUND) @@ -192,13 +191,12 @@ def BackupStatus(self, request, context): def GetBackup(self, request, context): response = medusa_pb2.GetBackupResponse() - last_status = medusa_pb2.StatusType.UNKNOWN try: with Storage(config=self.storage_config) as connected_storage: backup = connected_storage.get_cluster_backup(request.backupName) - summary, response.status = get_backup_summary(backup, last_status) + summary = get_backup_summary(backup) response.backup.CopyFrom(summary) - record_status_in_response(response, request.backupName) + response.status = summary.status except Exception as e: context.set_details("Failed to get backup due to error: {}".format(e)) context.set_code(grpc.StatusCode.INTERNAL) @@ -208,14 +206,14 @@ def GetBackup(self, request, context): def GetBackups(self, request, context): response = medusa_pb2.GetBackupsResponse() - last_status = medusa_pb2.StatusType.UNKNOWN try: # cluster backups with Storage(config=self.storage_config) as connected_storage: backups = get_backups(connected_storage, self.config, True) for backup in backups: - summary, last_status = get_backup_summary(backup, last_status) + summary = get_backup_summary(backup) response.backups.append(summary) + set_overall_status(response) except Exception as e: context.set_details("Failed to get backups due to error: {}".format(e)) @@ -282,27 +280,50 @@ def PrepareRestore(self, request, context): return response -def get_backup_summary(backup, last_status): +def set_overall_status(get_backups_response): + get_backups_response.overallStatus = medusa_pb2.StatusType.UNKNOWN + backups = get_backups_response.backups + if len(backups) == 0: + return + if all(backup.status == medusa_pb2.StatusType.SUCCESS for backup in backups): + get_backups_response.overallStatus = medusa_pb2.StatusType.SUCCESS + if any(backup.status == medusa_pb2.StatusType.IN_PROGRESS for backup in backups): + get_backups_response.overallStatus = medusa_pb2.StatusType.IN_PROGRESS + if any(backup.status == medusa_pb2.StatusType.FAILED for backup in backups): + get_backups_response.overallStatus = medusa_pb2.StatusType.FAILED + if any(backup.status == medusa_pb2.StatusType.UNKNOWN for backup in backups): + get_backups_response.overallStatus = medusa_pb2.StatusType.UNKNOWN + + +def get_backup_summary(backup): summary = medusa_pb2.BackupSummary() + summary.backupName = backup.name + if backup.started is None: summary.startTime = 0 else: - summary.startTime = 1234 + summary.startTime = backup.started + if backup.finished is None: summary.finishTime = 0 summary.status = medusa_pb2.StatusType.IN_PROGRESS - last_status = medusa_pb2.StatusType.IN_PROGRESS else: summary.finishTime = backup.finished - if last_status != medusa_pb2.StatusType.IN_PROGRESS: - summary.status = medusa_pb2.StatusType.SUCCESS + summary.status = medusa_pb2.StatusType.SUCCESS + summary.totalNodes = len(backup.tokenmap) summary.finishedNodes = len(backup.complete_nodes()) + for node in backup.tokenmap: summary.nodes.append(create_token_map_node(backup, node)) + summary.backupType = backup.backup_type - return summary, last_status + + summary.totalSize = backup.size() + summary.totalObjects = backup.num_objects() + + return summary # Callback function for recording unique backup results diff --git a/tests/config_test.py b/tests/config_test.py index fde48b212..4a85a6f1a 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -187,6 +187,20 @@ def test_use_sudo_kubernetes_enabled(self): config = medusa.config.parse_config(args, medusa_k8s_config) assert config['cassandra']['use_sudo'] == 'False' + def test_use_sudo_kubernetes_enabled_without_config_file(self): + kubernetes_args = { + "k8s_enabled": 'True', + "cassandra_url": 'https://foo:8080', + "use_mgmt_api": 'True' + } + args = {**kubernetes_args} + medusa_basic_config = pathlib.Path(__file__).parent / "resources/config/medusa.ini" + config = medusa.config.parse_config(args, medusa_basic_config) + assert config['kubernetes']['enabled'] == 'True' + assert config['kubernetes']['cassandra_url'] == 'https://foo:8080' + assert config['kubernetes']['use_mgmt_api'] == 'True' + assert config['cassandra']['use_sudo'] == 'False' + def test_overridden_fqdn(self): """Ensure that a overridden fqdn in config is honored""" args = {'fqdn': 'overridden-fqdn'} diff --git a/tests/integration/features/steps/integration_steps.py b/tests/integration/features/steps/integration_steps.py index 21c7e7d23..d56ff2ff4 100644 --- a/tests/integration/features/steps/integration_steps.py +++ b/tests/integration/features/steps/integration_steps.py @@ -353,8 +353,7 @@ def i_am_using_storage_provider(context, storage_provider, client_encryption): @given(r'I am using "{storage_provider}" as storage provider in ccm cluster "{client_encryption}" with gRPC server') def i_am_using_storage_provider_with_grpc_server(context, storage_provider, client_encryption): config = parse_medusa_config(context, storage_provider, client_encryption, - "http://127.0.0.1:8778/jolokia/", grpc=1, use_mgmt_api=1) - + "http://127.0.0.1:8778/jolokia/", grpc='True', use_mgmt_api='False') context.storage_provider = storage_provider context.client_encryption = client_encryption context.grpc_server = GRPCServer(config) @@ -383,8 +382,14 @@ def i_am_using_storage_provider_with_grpc_server(context, storage_provider, clie @given(r'I am using "{storage_provider}" as storage provider in ccm cluster "{client_encryption}" with mgmt api') def i_am_using_storage_provider_with_grpc_server_and_mgmt_api(context, storage_provider, client_encryption): - config = parse_medusa_config(context, storage_provider, client_encryption, - "http://127.0.0.1:8080/api/v0/ops/node/snapshots", use_mgmt_api=1, grpc=1) + config = parse_medusa_config( + context, + storage_provider, + client_encryption, + cassandra_url="http://127.0.0.1:8080/api/v0/ops/node/snapshots", + use_mgmt_api='True', + grpc='True' + ) context.storage_provider = storage_provider context.client_encryption = client_encryption @@ -434,7 +439,7 @@ def i_am_using_storage_provider_with_grpc_server_and_mgmt_api(context, storage_p time.sleep(1) -def get_args(context, storage_provider, client_encryption, cassandra_url, use_mgmt_api=0, grpc=0): +def get_args(context, storage_provider, client_encryption, cassandra_url, use_mgmt_api='False', grpc='False'): logging.info(STARTING_TESTS_MSG) if not hasattr(context, "cluster_name"): context.cluster_name = "test" @@ -479,11 +484,11 @@ def get_args(context, storage_provider, client_encryption, cassandra_url, use_mg ) grpc_args = { - "enabled": grpc + "grpc_enabled": grpc } kubernetes_args = { - "enabled": use_mgmt_api, + "k8s_enabled": use_mgmt_api, "cassandra_url": cassandra_url, "use_mgmt_api": use_mgmt_api } @@ -492,7 +497,7 @@ def get_args(context, storage_provider, client_encryption, cassandra_url, use_mg return args -def get_medusa_config(context, storage_provider, client_encryption, cassandra_url, use_mgmt_api=0, grpc=0): +def get_medusa_config(context, storage_provider, client_encryption, cassandra_url, use_mgmt_api='False', grpc='False'): args = get_args(context, storage_provider, client_encryption, cassandra_url, use_mgmt_api, grpc) config_file = Path(os.path.join(os.path.abspath("."), f'resources/config/medusa-{storage_provider}.ini')) create_storage_specific_resources(storage_provider) @@ -500,7 +505,9 @@ def get_medusa_config(context, storage_provider, client_encryption, cassandra_ur return config -def parse_medusa_config(context, storage_provider, client_encryption, cassandra_url, use_mgmt_api=0, grpc=0): +def parse_medusa_config( + context, storage_provider, client_encryption, cassandra_url, use_mgmt_api='False', grpc='False' +): args = get_args(context, storage_provider, client_encryption, cassandra_url, use_mgmt_api, grpc) config_file = Path(os.path.join(os.path.abspath("."), f'resources/config/medusa-{storage_provider}.ini')) create_storage_specific_resources(storage_provider) @@ -585,6 +592,8 @@ def _i_verify_over_grpc_backup_exists(context, backup_name, backup_type): backup = context.grpc_client.get_backup(backup_name=backup_name) assert backup.backupName == backup_name assert backup.backupType == backup_type + assert backup.totalSize > 0 + assert backup.totalObjects > 0 @then(r'I sleep for {num_secs} seconds') @@ -607,6 +616,7 @@ def _i_verify_over_grpc_backup_has_status_unknown(context, backup_name): @then(r'I verify over gRPC that the backup "{backup_name}" has expected status SUCCESS') def _i_verify_over_grpc_backup_has_status_success(context, backup_name): status = context.grpc_client.get_backup_status(backup_name) + logging.info(f'status={status}') assert status == medusa_pb2.StatusType.SUCCESS diff --git a/tests/service/grpc/server_test.py b/tests/service/grpc/server_test.py index 723354ece..b048499af 100644 --- a/tests/service/grpc/server_test.py +++ b/tests/service/grpc/server_test.py @@ -103,12 +103,14 @@ def test_get_known_incomplete_backup(self): nodes=['node1'], differential=True ) + cluster_backup.size = lambda: 0 + cluster_backup.num_objects = lambda: 0 tokenmap_dict = { "node1": {"tokens": [-1094266504216117253], "is_up": True, "rack": "r1", "dc": "dc1"}, "node2": {"tokens": [1094266504216117253], "is_up": True, "rack": "r1", "dc": "dc1"} } BackupMan.register_backup('backup1', True) - BackupMan.update_backup_status('backup1', BackupMan.STATUS_SUCCESS) + BackupMan.update_backup_status('backup1', BackupMan.STATUS_IN_PROGRESS) # patches a call to the tokenmap, thus avoiding access to the storage with patch('medusa.storage.ClusterBackup.tokenmap', return_value=tokenmap_dict) as tokenmap: @@ -116,28 +118,33 @@ def test_get_known_incomplete_backup(self): tokenmap.__iter__ = lambda _: list(tokenmap_dict.keys()).__iter__() tokenmap.__len__ = lambda _: len(tokenmap_dict.keys()) # we don't ever create any file, so we won't get a timestamp to get the finish time from - with patch('medusa.storage.ClusterBackup.finished', return_value=123456): - # prevent calls to the storage by faking the get_cluster_backup method - with patch('medusa.storage.Storage.get_cluster_backup', return_value=cluster_backup): - request = medusa_pb2.BackupStatusRequest(backupName='backup1') - context = Mock(spec=ServicerContext) - get_backup_response = service.GetBackup(request, context) - - self.assertEqual(medusa_pb2.StatusType.SUCCESS, get_backup_response.status) - - self.assertEqual('backup1', get_backup_response.backup.backupName) - self.assertEqual(1234, get_backup_response.backup.startTime) - # the finishTime is 1 because it's the proto's default value. the magic mock does not set this - self.assertEqual(1, get_backup_response.backup.finishTime) - self.assertEqual(2, get_backup_response.backup.totalNodes) - self.assertEqual(1, get_backup_response.backup.finishedNodes) - # the BackupNode records ought to be more populated than this, but we test that in ITs instead - self.assertEqual( - [medusa_pb2.BackupNode(host='node1'), medusa_pb2.BackupNode(host='node2')], - get_backup_response.backup.nodes - ) - self.assertEqual(medusa_pb2.StatusType.SUCCESS, get_backup_response.backup.status) - self.assertEqual('differential', get_backup_response.backup.backupType) + with patch('medusa.storage.ClusterBackup.started', return_value=12345): + with patch('medusa.storage.ClusterBackup.finished', return_value=123456): + # prevent calls to the storage by faking the get_cluster_backup method + with patch('medusa.storage.Storage.get_cluster_backup', return_value=cluster_backup): + request = medusa_pb2.BackupStatusRequest(backupName='backup1') + context = Mock(spec=ServicerContext) + get_backup_response = service.GetBackup(request, context) + + self.assertEqual(medusa_pb2.StatusType.SUCCESS, get_backup_response.status) + + self.assertEqual('backup1', get_backup_response.backup.backupName) + + # because of MagicMock-ing and @property annotation, we cannot make this work properly + self.assertEqual(1, get_backup_response.backup.startTime) + self.assertEqual(1, get_backup_response.backup.finishTime) + + self.assertEqual(2, get_backup_response.backup.totalNodes) + self.assertEqual(1, get_backup_response.backup.finishedNodes) + # the BackupNode records ought to be more populated than this, but we test that in ITs instead + self.assertEqual( + [medusa_pb2.BackupNode(host='node1'), medusa_pb2.BackupNode(host='node2')], + get_backup_response.backup.nodes + ) + # this should also be IN_PROGRESS but because the ClusterBackup.finished is a mock + # we cannot correctly make it be 'None' when needed (some other things break) + self.assertEqual(medusa_pb2.StatusType.SUCCESS, get_backup_response.backup.status) + self.assertEqual('differential', get_backup_response.backup.backupType) def test_get_backup_status_unknown_backup(self): # start the Medusa service