summaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/restapi/RestApiHandler.java
blob: 6cbfa02009efcdb8058664be527f0109507c19e3 (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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.restapi;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.container.logging.AccessLog;
import com.yahoo.vespa.hosted.dockerapi.metrics.DimensionMetrics;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.NodeAdminStateUpdater;
import com.yahoo.vespa.hosted.node.admin.provider.NodeAdminProvider;

import javax.inject.Inject;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executor;

import static com.yahoo.jdisc.http.HttpRequest.Method.GET;
import static com.yahoo.jdisc.http.HttpRequest.Method.PUT;

/**
 * Rest API for suspending and resuming the docker host.
 * There are two non-blocking idempotent calls: /resume and /suspend.
 *
 * There is one debug call: /info
 *
 * @author dybis
 */
public class RestApiHandler extends LoggingRequestHandler{

    private final static ObjectMapper objectMapper = new ObjectMapper();
    private final NodeAdminStateUpdater refresher;
    private final MetricReceiverWrapper metricReceiverWrapper;

    @Inject
    public RestApiHandler(Executor executor, AccessLog accessLog,
                          NodeAdminProvider nodeAdminProvider,
                          MetricReceiverWrapper metricReceiverWrapper) {
        super(executor, accessLog);
        this.refresher = nodeAdminProvider.get();
        this.metricReceiverWrapper = metricReceiverWrapper;
    }

    @Override
    public HttpResponse handle(HttpRequest request) {
        if (request.getMethod() == GET) {
            return handleGet(request);
        }
        if (request.getMethod() == PUT) {
            return handlePut(request);
        }
        return new SimpleResponse(400, "Only PUT and GET are implemented.");
    }

    private HttpResponse handleGet(HttpRequest request) {
        String path = request.getUri().getPath();
        if (path.endsWith("/info")) {
            return new SimpleObjectResponse(200, refresher.getDebugPage());
        }

        if (path.endsWith("/metrics")) {
            return new HttpResponse(200) {
                @Override
                public String getContentType() {
                    return MediaType.APPLICATION_JSON;
                }

                @Override
                public void render(OutputStream outputStream) throws IOException {
                    try (PrintStream printStream = new PrintStream(outputStream)) {
                        for (DimensionMetrics dimensionMetrics : metricReceiverWrapper.getAllMetrics()) {
                            String secretAgentJsonReport = dimensionMetrics.toSecretAgentReport() + "\n";
                            printStream.write(secretAgentJsonReport.getBytes(StandardCharsets.UTF_8.name()));
                        }
                    }
                }
            };
        }
        return new SimpleResponse(400, "unknown path " + path);
    }

    private HttpResponse handlePut(HttpRequest request) {
        String path = request.getUri().getPath();
        // Check paths to disallow illegal state changes
        NodeAdminStateUpdater.State wantedState = null;
        if (path.endsWith("/resume")) {
            wantedState = NodeAdminStateUpdater.State.RESUMED;
        } else if (path.endsWith("/suspend")) {
            wantedState = NodeAdminStateUpdater.State.SUSPENDED;
        } else if (path.endsWith("/suspend/node-admin")) {
            wantedState = NodeAdminStateUpdater.State.SUSPENDED_NODE_ADMIN;
        }

        if (wantedState != null) {
            return refresher.setResumeStateAndCheckIfResumed(wantedState) ?
                    new SimpleResponse(200, "ok") :
                    new SimpleResponse(409, "fail");
        }
        return new SimpleResponse(400, "unknown path " + path);
    }

    private static class SimpleResponse extends HttpResponse {
        private final String jsonMessage;

        SimpleResponse(int code, String message) {
            super(code);
            ObjectNode objectNode = objectMapper.createObjectNode();
            objectNode.put("jsonMessage", message);
            this.jsonMessage = objectNode.toString();
        }

        @Override
        public String getContentType() {
            return MediaType.APPLICATION_JSON;
        }

        @Override
        public void render(OutputStream outputStream) throws IOException {
            outputStream.write(jsonMessage.getBytes(StandardCharsets.UTF_8.name()));
        }
    }

    private static class SimpleObjectResponse extends HttpResponse {
        private final Object response;

        SimpleObjectResponse(int status, Object response) {
            super(status);
            this.response = response;
        }

        @Override
        public String getContentType() {
            return MediaType.APPLICATION_JSON;
        }

        @Override
        public void render(OutputStream outputStream) throws IOException {
            objectMapper.writeValue(outputStream, response);
        }
    }
}