summaryrefslogtreecommitdiffstats
path: root/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java
blob: 26ef79ebe0286c1321b8bd229a63d29c70e89318 (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
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.server.http.v2;

import com.google.inject.Inject;
import com.yahoo.cloud.config.ConfigserverConfig;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.jdisc.application.UriPattern;
import com.yahoo.log.LogLevel;
import com.yahoo.slime.Slime;
import com.yahoo.vespa.config.server.ApplicationRepository;
import com.yahoo.vespa.config.server.deploy.DeployHandlerLogger;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
import com.yahoo.vespa.config.server.TimeoutBudget;
import com.yahoo.vespa.config.server.http.BadRequestException;
import com.yahoo.vespa.config.server.http.SessionHandler;
import com.yahoo.vespa.config.server.http.Utils;

import java.net.URI;
import java.time.Duration;

/**
 * A handler that is able to create a session from an application package,
 * or create a new session from a previous session (with id or the "active" session).
 * Handles /application/v2/ requests
 *
 * @author hmusum
 */
public class SessionCreateHandler extends SessionHandler {

    private static final String fromPattern = "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*";

    private final TenantRepository tenantRepository;
    private final Duration zookeeperBarrierTimeout;

    @Inject
    public SessionCreateHandler(SessionHandler.Context ctx,
                                ApplicationRepository applicationRepository,
                                TenantRepository tenantRepository,
                                ConfigserverConfig configserverConfig) {
        super(ctx, applicationRepository);
        this.tenantRepository = tenantRepository;
        this.zookeeperBarrierTimeout = Duration.ofSeconds(configserverConfig.zookeeper().barrierTimeout());
    }

    @Override
    protected HttpResponse handlePOST(HttpRequest request) {
        Slime deployLog = applicationRepository.createDeployLog();
        final TenantName tenantName = Utils.getTenantNameFromSessionRequest(request);
        Utils.checkThatTenantExists(tenantRepository, tenantName);
        TimeoutBudget timeoutBudget = SessionHandler.getTimeoutBudget(request, zookeeperBarrierTimeout);
        DeployLogger logger = createLogger(request, deployLog, tenantName);
        long sessionId;
        if (request.hasProperty("from")) {
            ApplicationId applicationId = getFromApplicationId(request);
            sessionId = applicationRepository.createSessionFromExisting(applicationId, logger, false, timeoutBudget);
        } else {
            validateDataAndHeader(request);
            String name = getNameProperty(request, logger);
            // TODO: we are always using instance name 'default' here, fix
            ApplicationId applicationId = ApplicationId.from(tenantName, ApplicationName.from(name), InstanceName.defaultName());
            sessionId = applicationRepository.createSession(applicationId, timeoutBudget, request.getData(), request.getHeader(ApplicationApiHandler.contentTypeHeader));
        }
        return createResponse(request, tenantName, deployLog, sessionId);
    }

    private static ApplicationId getFromApplicationId(HttpRequest request) {
        String from = request.getProperty("from");
        if (from == null || "".equals(from)) {
            throw new BadRequestException("Parameter 'from' has illegal value '" + from + "'");
        }
        return getAndValidateFromParameter(URI.create(from));
    }

    private static ApplicationId getAndValidateFromParameter(URI from) {
        UriPattern.Match match = new UriPattern(fromPattern).match(from);
        if (match == null || match.groupCount() < 7) {
            throw new BadRequestException("Parameter 'from' has illegal value '" + from + "'");
        }
        return new ApplicationId.Builder()
            .tenant(match.group(2))
            .applicationName(match.group(3))
            .instanceName(match.group(6)).build();
    }

    private static DeployHandlerLogger createLogger(HttpRequest request, Slime deployLog, TenantName tenant) {
        return SessionHandler.createLogger(deployLog, request,
                                           new ApplicationId.Builder().tenant(tenant).applicationName("-").build());
    }

    private static String getNameProperty(HttpRequest request, DeployLogger logger) {
        String name = request.getProperty("name");
        // TODO: Do we need validation of this parameter?
        if (name == null) {
            name = "default";
            logger.log(LogLevel.INFO, "No application name given, using '" + name + "'");
        }
        return name;
    }

    static void validateDataAndHeader(HttpRequest request) {
        if (request.getData() == null) {
            throw new BadRequestException("Request contains no data");
        }
        String header = request.getHeader(ApplicationApiHandler.contentTypeHeader);
        if (header == null) {
            throw new BadRequestException("Request contains no " + ApplicationApiHandler.contentTypeHeader + " header");
        } else if (!(header.equals(ApplicationApiHandler.APPLICATION_X_GZIP) || header.equals(ApplicationApiHandler.APPLICATION_ZIP))) {
            throw new BadRequestException("Request contains invalid " + ApplicationApiHandler.contentTypeHeader + " header, only '" +
                                                  ApplicationApiHandler.APPLICATION_X_GZIP + "' and '" + ApplicationApiHandler.APPLICATION_ZIP + "' are supported");
        }
    }

    private HttpResponse createResponse(HttpRequest request, TenantName tenantName, Slime deployLog, long sessionId) {
        return new SessionCreateResponse(tenantName, deployLog, deployLog.get())
                .createResponse(request.getHost(), request.getPort(), sessionId);
    }
}