Skip to content
This repository has been archived by the owner on Nov 14, 2024. It is now read-only.

ABR1: AtlasBackupService #5708

Merged
merged 47 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
88d8b4c
no
gsheasby Oct 13, 2021
1381ff6
conjurise lockImmutableTimestamp
gsheasby Oct 13, 2021
2eaff3f
start AtlasBackupService
gsheasby Oct 13, 2021
7caa474
basic test
gsheasby Oct 14, 2021
ed17c4e
return imm ts
gsheasby Oct 14, 2021
97b6645
use PrepareBackupResponse
gsheasby Oct 15, 2021
f09979f
getBackupTimestamp: only pass namespace
gsheasby Oct 15, 2021
ebac8c1
cleanup
gsheasby Oct 15, 2021
f95b661
add atlasdb-backups project
gsheasby Oct 18, 2021
e0f9f08
tests
gsheasby Oct 18, 2021
001d370
track locks
gsheasby Oct 18, 2021
b10db24
cleanup
gsheasby Oct 18, 2021
ea2d59f
backup process requires two fresh timestamps
gsheasby Oct 18, 2021
1a26a56
ABS -> ABR
gsheasby Oct 20, 2021
88817a2
fix tests
gsheasby Oct 20, 2021
f4b6d16
api stuff
gsheasby Oct 20, 2021
67262e7
simplify api
gsheasby Oct 20, 2021
6e50fb7
tests
gsheasby Oct 20, 2021
0477db6
async
gsheasby Oct 20, 2021
2df8fad
skeleton for undertow stuff
gsheasby Oct 20, 2021
377f58c
Revert "conjurise lockImmutableTimestamp"
gsheasby Oct 20, 2021
4692100
fix compile
gsheasby Oct 20, 2021
3b4d1fe
futures
gsheasby Oct 21, 2021
01daad9
wiring
gsheasby Nov 1, 2021
5169d21
tidy up
gsheasby Nov 1, 2021
7a4a422
add AtlasBackupTask
gsheasby Nov 1, 2021
d5a09ee
private ctor
gsheasby Nov 1, 2021
d889dac
add serviceName param
gsheasby Nov 1, 2021
b4ff72c
various little CR refactors
gsheasby Nov 4, 2021
bcc700e
missing handleExceptions here
gsheasby Nov 4, 2021
e3605d2
atlasdb-backups -> atlasdb-backup
gsheasby Nov 4, 2021
695779d
unused
gsheasby Nov 4, 2021
5a40e5c
separate tokens
gsheasby Nov 4, 2021
25ea394
renames
gsheasby Nov 4, 2021
5cab1a8
Merge remote-tracking branch 'origin/develop' into gs/abs
gsheasby Nov 4, 2021
2c0a021
more renames
gsheasby Nov 4, 2021
12854d2
AtlasBackupServiceTest
gsheasby Nov 4, 2021
1959f8b
extract util
gsheasby Nov 4, 2021
33177e0
not needed
gsheasby Nov 4, 2021
d734333
more tests
gsheasby Nov 4, 2021
a651ef8
use ImmutableSet.of everywhere
gsheasby Nov 4, 2021
7fef3bb
Add generated changelog entries
svc-changelog Nov 4, 2021
33ffd63
copyOf
gsheasby Nov 4, 2021
072e31b
concurrent map
gsheasby Nov 5, 2021
5c31922
remove if successful
gsheasby Nov 5, 2021
747944b
we must never fail to prepare
gsheasby Nov 5, 2021
5a9d619
complete: always remove
gsheasby Nov 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions atlasdb-backups/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apply from: "../gradle/shared.gradle"

dependencies {
implementation project(':atlasdb-config')
implementation project(':timelock-api:timelock-api-objects')

implementation 'com.palantir.safe-logging:safe-logging'

testImplementation 'org.mockito:mockito-core'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.atlasdb.backup;

import com.palantir.atlasdb.backup.api.AtlasBackupServiceBlocking;
import com.palantir.atlasdb.timelock.api.BackupToken;
import com.palantir.atlasdb.timelock.api.CompleteBackupRequest;
import com.palantir.atlasdb.timelock.api.CompleteBackupResponse;
import com.palantir.atlasdb.timelock.api.Namespace;
import com.palantir.atlasdb.timelock.api.PrepareBackupRequest;
import com.palantir.atlasdb.timelock.api.PrepareBackupResponse;
import com.palantir.conjure.java.api.config.service.ServicesConfigBlock;
import com.palantir.dialogue.clients.DialogueClients;
import com.palantir.dialogue.clients.DialogueClients.ReloadingFactory;
import com.palantir.refreshable.Refreshable;
import com.palantir.tokens.auth.AuthHeader;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public final class AtlasBackupTask {
private final AtlasBackupServiceBlocking atlasBackupServiceBlocking;
private final BackupTokenPersister backupTokenPersister;

private AtlasBackupTask(AtlasBackupServiceBlocking atlasBackupServiceBlocking) {
this.atlasBackupServiceBlocking = atlasBackupServiceBlocking;
this.backupTokenPersister = new InMemoryBackupTokenPersister();
gsheasby marked this conversation as resolved.
Show resolved Hide resolved
}

public static AtlasBackupTask create(Refreshable<ServicesConfigBlock> servicesConfigBlock, String serviceName) {
ReloadingFactory reloadingFactory = DialogueClients.create(servicesConfigBlock);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can go with an AtlasBackupService that has a fixed client and the same API instead of recreating the client for each task

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the naming is misleading you here - renaming to AtlasBackupService makes sense. The serviceName is whatever the timelock service name is configured to be internally (not connected to the namespace), and the class will be created once (on internal service startup), and reused for multiple backup operations.

AtlasBackupServiceBlocking atlasBackupServiceBlocking =
reloadingFactory.get(AtlasBackupServiceBlocking.class, serviceName);
return new AtlasBackupTask(atlasBackupServiceBlocking);
}

public Set<Namespace> prepareBackup(AuthHeader authHeader, Set<Namespace> namespaces) {
gsheasby marked this conversation as resolved.
Show resolved Hide resolved
PrepareBackupRequest request = PrepareBackupRequest.of(namespaces);
PrepareBackupResponse response = atlasBackupServiceBlocking.prepareBackup(authHeader, request);

return response.getSuccessful().stream()
.filter(backupTokenPersister::storeBackupToken)
gsheasby marked this conversation as resolved.
Show resolved Hide resolved
.map(BackupToken::getNamespace)
.collect(Collectors.toSet());
}

public Set<Namespace> completeBackup(AuthHeader authHeader, Set<Namespace> namespaces) {
Set<BackupToken> tokens = namespaces.stream()
.map(backupTokenPersister::retrieveBackupToken)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
CompleteBackupRequest request = CompleteBackupRequest.of(tokens);
CompleteBackupResponse response = atlasBackupServiceBlocking.completeBackup(authHeader, request);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: actually persist the tokens using a persister that is passed in to this class, then we have an atlas-side implementation of the persister that conforms with the current backup story in internal tooling

return response.getSuccessfulBackups().stream()
.map(BackupToken::getNamespace)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.atlasdb.backup;

import com.palantir.atlasdb.timelock.api.BackupToken;
import com.palantir.atlasdb.timelock.api.Namespace;
import java.util.Optional;

public interface BackupTokenPersister {
boolean storeBackupToken(BackupToken backupToken);

Optional<BackupToken> retrieveBackupToken(Namespace namespace);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* (c) Copyright 2021 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.atlasdb.backup;

import com.palantir.atlasdb.timelock.api.BackupToken;
import com.palantir.atlasdb.timelock.api.Namespace;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public final class InMemoryBackupTokenPersister implements BackupTokenPersister {
private final Map<Namespace, BackupToken> storedTokens;

public InMemoryBackupTokenPersister() {
storedTokens = new HashMap<>();
}

@Override
public boolean storeBackupToken(BackupToken backupToken) {
storedTokens.put(backupToken.getNamespace(), backupToken);
return true;
}

@Override
public Optional<BackupToken> retrieveBackupToken(Namespace namespace) {
return Optional.ofNullable(storedTokens.get(namespace));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ List<TrackedLockEvent> getLocalLockHistory() {
void logLockResponse(Set<ConjureLockDescriptor> lockDescriptors, ConjureLockResponse response) {
TrackedLockEvent event = getTimestampedLockEventBuilder()
.eventType(EventType.LOCK)
.eventDescription(response.accept(new ConjureLockResponse.Visitor<String>() {
.eventDescription(response.accept(new ConjureLockResponse.Visitor<>() {
@Override
public String visitSuccessful(SuccessfulLockResponse value) {
return "SUCCESS - locked " + lockDescriptors + "; obtained " + value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ default boolean isInitialized() {

TimestampRange getFreshTimestamps(@Safe @QueryParam("number") int numTimestampsRequested);

// TODO (jkong): Can this be deprecated? Are there users outside of Atlas transactions?
LockImmutableTimestampResponse lockImmutableTimestamp();
gsheasby marked this conversation as resolved.
Show resolved Hide resolved

@DoNotDelegate
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
rootProject.name = 'atlasdb'
include ":atlasdb-api"
include ":atlasdb-autobatch"
include ":atlasdb-backups"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: altas-backup?

Copy link
Contributor Author

@gsheasby gsheasby Nov 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

atlasdb-backup for consistency?

include ":atlasdb-cassandra"
include ":atlasdb-cassandra-integration-tests"
include ":atlasdb-cassandra-multinode-tests"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
import com.palantir.atlasdb.AtlasDbConstants;
import com.palantir.atlasdb.backup.AtlasBackupResource;
import com.palantir.atlasdb.config.AuxiliaryRemotingParameters;
import com.palantir.atlasdb.config.ImmutableLeaderConfig;
import com.palantir.atlasdb.config.ImmutableServerListConfig;
Expand Down Expand Up @@ -332,30 +333,34 @@ private void createAndRegisterResources() {
Function<String, LockService> lockServiceGetter =
namespace -> namespaces.get(namespace).getLockService();

RedirectRetryTargeter redirectRetryTargeter = redirectRetryTargeter();
if (undertowRegistrar.isPresent()) {
Consumer<UndertowService> presentUndertowRegistrar = undertowRegistrar.get();
registerCorruptionHandlerWrappedService(
presentUndertowRegistrar,
ConjureTimelockResource.undertow(redirectRetryTargeter(), asyncTimelockServiceGetter));
ConjureTimelockResource.undertow(redirectRetryTargeter, asyncTimelockServiceGetter));
registerCorruptionHandlerWrappedService(
presentUndertowRegistrar,
ConjureLockWatchingResource.undertow(redirectRetryTargeter(), asyncTimelockServiceGetter));
ConjureLockWatchingResource.undertow(redirectRetryTargeter, asyncTimelockServiceGetter));
registerCorruptionHandlerWrappedService(
presentUndertowRegistrar,
ConjureLockV1Resource.undertow(redirectRetryTargeter(), lockServiceGetter));
presentUndertowRegistrar, ConjureLockV1Resource.undertow(redirectRetryTargeter, lockServiceGetter));
registerCorruptionHandlerWrappedService(
presentUndertowRegistrar,
TimeLockPaxosHistoryProviderResource.undertow(corruptionComponents.localHistoryLoader()));
registerCorruptionHandlerWrappedService(
presentUndertowRegistrar,
MultiClientConjureTimelockResource.undertow(redirectRetryTargeter(), asyncTimelockServiceGetter));
MultiClientConjureTimelockResource.undertow(redirectRetryTargeter, asyncTimelockServiceGetter));
registerCorruptionHandlerWrappedService(
presentUndertowRegistrar,
AtlasBackupResource.undertow(redirectRetryTargeter, asyncTimelockServiceGetter));
} else {
registrar.accept(ConjureTimelockResource.jersey(redirectRetryTargeter(), asyncTimelockServiceGetter));
registrar.accept(ConjureLockWatchingResource.jersey(redirectRetryTargeter(), asyncTimelockServiceGetter));
registrar.accept(ConjureLockV1Resource.jersey(redirectRetryTargeter(), lockServiceGetter));
registrar.accept(ConjureTimelockResource.jersey(redirectRetryTargeter, asyncTimelockServiceGetter));
registrar.accept(ConjureLockWatchingResource.jersey(redirectRetryTargeter, asyncTimelockServiceGetter));
registrar.accept(ConjureLockV1Resource.jersey(redirectRetryTargeter, lockServiceGetter));
registrar.accept(TimeLockPaxosHistoryProviderResource.jersey(corruptionComponents.localHistoryLoader()));
registrar.accept(
MultiClientConjureTimelockResource.jersey(redirectRetryTargeter(), asyncTimelockServiceGetter));
MultiClientConjureTimelockResource.jersey(redirectRetryTargeter, asyncTimelockServiceGetter));
registrar.accept(AtlasBackupResource.jersey(redirectRetryTargeter, asyncTimelockServiceGetter));
}
}

Expand Down
40 changes: 40 additions & 0 deletions timelock-api/src/main/conjure/timelock-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ types:
base-type: any
external:
java: com.palantir.lock.v2.Lease
LockToken:
base-type: any
external:
java: com.palantir.lock.v2.LockToken
LockWatchStateUpdate:
base-type: any
external:
Expand Down Expand Up @@ -82,6 +86,26 @@ types:
union:
successful: SuccessfulLockResponse
unsuccessful: UnsuccessfulLockResponse
# in-progress backup information may be kept in TimeLock/Atlas somewhere.
BackupToken: # separate objects for in-progress vs complete backups?
gsheasby marked this conversation as resolved.
Show resolved Hide resolved
fields:
namespace: Namespace
lockToken: LockToken
immutableTimestamp: Long
backupStartTimestamp: Long
backupEndTimestamp: optional<Long>
PrepareBackupRequest:
fields:
namespaces: set<Namespace>
PrepareBackupResponse:
fields:
successful: set<BackupToken>
CompleteBackupRequest:
fields:
backupTokens: set<BackupToken>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly wasteful, but probably fine for API simplicity

CompleteBackupResponse:
fields:
successfulBackups: set<BackupToken>
ConjureWaitForLocksResponse:
fields:
wasSuccessful: boolean
Expand Down Expand Up @@ -117,6 +141,22 @@ types:
leaderTimes: map<Namespace, LeaderTime>

services:
AtlasBackupService:
name: Backup service
default-auth: header
package: com.palantir.atlasdb.backup.api
base-path: /backup
endpoints:
prepareBackup:
http: POST /prepare
args:
request: PrepareBackupRequest
returns: PrepareBackupResponse
completeBackup:
http: POST /complete
args:
request: CompleteBackupRequest
returns: CompleteBackupResponse
ConjureTimelockService:
name: Timelock service
default-auth: header
Expand Down
Loading