aboutsummaryrefslogtreecommitdiffstats
path: root/routing-generator/src/main/java/com/yahoo/vespa/hosted/routing/restapi/AkamaiHandler.java
blob: c3e9666adb6bfc7f537f65f25e70904d28c6f790 (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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.routing.restapi;

import com.yahoo.component.annotation.Inject;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
import com.yahoo.restapi.ErrorResponse;
import com.yahoo.restapi.Path;
import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.routing.RoutingGenerator;
import com.yahoo.vespa.hosted.routing.RoutingTable;
import com.yahoo.vespa.hosted.routing.status.HealthStatus;
import com.yahoo.vespa.hosted.routing.status.RoutingStatus;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * This handler implements the /akamai health check.
 *
 * The global routing service polls /akamai to determine if a deployment should receive requests via its global
 * endpoint.
 *
 * @author oyving
 * @author mpolden
 * @author Torbjorn Smorgrav
 * @author Wacek Kusnierczyk
 */
public class AkamaiHandler extends ThreadedHttpRequestHandler {

    public static final String
            ROTATION_UNKNOWN_MESSAGE = "Rotation not found",
            ROTATION_UNAVAILABLE_MESSAGE = "Rotation set unavailable",
            ROTATION_UNHEALTHY_MESSAGE = "Rotation unhealthy",
            ROTATION_INACTIVE_MESSAGE = "Rotation not available",
            ROTATION_OK_MESSAGE = "Rotation OK";

    private final RoutingStatus routingStatus;
    private final HealthStatus healthStatus;
    private final Supplier<Optional<RoutingTable>> tableSupplier;

    @Inject
    public AkamaiHandler(Context parentCtx,
                         RoutingGenerator routingGenerator,
                         RoutingStatus routingStatus,
                         HealthStatus healthStatus) {
        this(parentCtx, routingGenerator::routingTable, routingStatus, healthStatus);
    }

    AkamaiHandler(Context parentCtx,
                  Supplier<Optional<RoutingTable>> tableSupplier,
                  RoutingStatus routingStatus,
                  HealthStatus healthStatus) {
        super(parentCtx);
        this.routingStatus = Objects.requireNonNull(routingStatus);
        this.healthStatus = Objects.requireNonNull(healthStatus);
        this.tableSupplier = Objects.requireNonNull(tableSupplier);
    }

    @Override
    public HttpResponse handle(HttpRequest request) {
        Path path = new Path(request.getUri());
        if (path.matches("/akamai/v1/status")) {
            return status(request);
        }
        return ErrorResponse.notFoundError("Nothing at " + path);
    }

    private HttpResponse status(HttpRequest request) {
        String hostHeader = request.getHeader("host");
        String hostname = withoutPort(hostHeader);
        Optional<RoutingTable.Target> target = tableSupplier.get().flatMap(table -> table.targetOf(hostname, RoutingMethod.sharedLayer4));

        if (target.isEmpty())
            return response(404, hostHeader, "", ROTATION_UNKNOWN_MESSAGE);

        if (!target.get().active())
            return response(404, hostHeader, "", ROTATION_INACTIVE_MESSAGE);

        String upstreamName = target.get().id();

        if (!routingStatus.isActive(upstreamName))
            return response(404, hostHeader, upstreamName, ROTATION_UNAVAILABLE_MESSAGE);

        if (!healthStatus.servers().isHealthy(upstreamName))
            return response(502, hostHeader, upstreamName, ROTATION_UNHEALTHY_MESSAGE);

        return response(200, hostHeader, upstreamName, ROTATION_OK_MESSAGE);
    }

    private static HttpResponse response(int status, String hostname, String name, String message) {
        Slime slime = new Slime();
        Cursor root = slime.setObject();
        root.setString("hostname", hostname);
        root.setString("upstream", name);
        root.setString("message", message);
        return new SlimeJsonResponse(status, slime);
    }

    private static String withoutPort(String hostHeader) {
        return hostHeader.replaceFirst("(:[\\d]+)?$", "");
    }

}