aboutsummaryrefslogtreecommitdiffstats
path: root/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/RotationsCache.java
blob: 38d990da2486961c56478481de5db8e8e764e521 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Copyright 2019 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.tenant;

import com.yahoo.config.provision.ApplicationId;
import com.yahoo.path.Path;
import com.yahoo.slime.ObjectTraverser;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.applicationmodel.ClusterId;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.curator.Curator;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;


/**
 * Persists assignment of rotations to an application to ZooKeeper.
 * The entries are RotationAssignments, which keep track of the container
 * cluster that is the target, the endpoint name, and the rotation used to
 * give availability to that cluster.
 *
 * This is v2 of that storage in a new directory.  Previously we only stored
 * the name of the rotation, since all the other information could be
 * calculated runtime.
 *
 * @author ogronnesby
 */
public class RotationsCache {
    private final Path cachePath;
    private final Curator curator;

    public RotationsCache(Path tenantPath, Curator curator) {
        this.cachePath = tenantPath.append("rotationsCache-v2/");
        this.curator = curator;
    }

    public Map<ClusterId, RotationAssignment> getRotationAssignment(ApplicationId applicationId) {
        final var optionalData = curator.getData(applicationPath(applicationId));
        return optionalData
                .map(SlimeUtils::jsonToSlime)
                .map(RotationsCache::entryFromSlime)
                .orElse(Collections.emptyMap());
    }

    public void putRotationAssignment(ApplicationId applicationId, Map<ClusterId, RotationAssignment> assignments) {
        if (assignments.isEmpty()) return;
        try {
            curator.set(
                    applicationPath(applicationId),
                    SlimeUtils.toJsonBytes(entryToSlime(assignments))
            );
        } catch (IOException e) {
            throw new UncheckedIOException("Error writing rotations of: " + applicationId, e);
        }
    }

    static Map<ClusterId, RotationAssignment> entryFromSlime(Slime slime) {
        final var assignmentMap = new HashMap<ClusterId, RotationAssignment>();

        slime.get().traverse((ObjectTraverser) (name, inspector) -> {
            final var containerId = new ClusterId(name);
            final var assignment = RotationAssignment.fromSlime(inspector);
            assignmentMap.put(containerId, assignment);
        });

        return Map.copyOf(assignmentMap);
    }

    static Slime entryToSlime(Map<ClusterId, RotationAssignment> assignments) {
        final var slime = new Slime();
        final var cursor = slime.setObject();

        assignments.forEach((clusterId, assignment) -> {
            final var assignmentCursor = cursor.setObject(clusterId.toString());
            assignment.toSlime(assignmentCursor);
        });

        return slime;
    }

    private Path applicationPath(ApplicationId applicationId) {
        return cachePath.append(applicationId.serializedForm());
    }
}