diff options
author | Harald Musum <musum@yahoo-inc.com> | 2017-01-02 15:21:41 +0100 |
---|---|---|
committer | Harald Musum <musum@yahoo-inc.com> | 2017-01-02 15:21:41 +0100 |
commit | 7f401834a66493d938bb450e84dd5eb9d70e88fc (patch) | |
tree | 10d53c4b4800a9991af01d1da95d4961e160dc05 /configserver | |
parent | 84be0d9c8601649485dbfb9d8747ca584070c9c0 (diff) |
Refactor session creation
Diffstat (limited to 'configserver')
12 files changed, 291 insertions, 367 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java index f78a52e5db4..802942c5833 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java @@ -24,6 +24,7 @@ import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.vespa.config.server.session.LocalSession; import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.session.RemoteSession; +import com.yahoo.vespa.config.server.session.SessionFactory; import com.yahoo.vespa.config.server.session.SilentDeployLogger; import com.yahoo.vespa.config.server.tenant.ActivateLock; import com.yahoo.vespa.config.server.tenant.Rotations; @@ -31,6 +32,7 @@ import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.Curator; +import java.io.File; import java.io.IOException; import java.net.URI; import java.time.Clock; @@ -241,4 +243,27 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return applicationRepo.listApplications(); } + public long createSessionFromExisting(Tenant tenant, DeployLogger logger, + TimeoutBudget timeoutBudget, ApplicationId applicationId) { + LocalSessionRepo localSessionRepo = tenant.getLocalSessionRepo(); + SessionFactory sessionFactory = tenant.getSessionFactory(); + LocalSession fromSession = getExistingSession(tenant, applicationId); + LocalSession session = sessionFactory.createSessionFromExisting(fromSession, logger, timeoutBudget); + localSessionRepo.addSession(session); + return session.getSessionId(); + } + + public long createSession(Tenant tenant, TimeoutBudget timeoutBudget, File applicationDirectory, String applicationName) { + LocalSessionRepo localSessionRepo = tenant.getLocalSessionRepo(); + SessionFactory sessionFactory = tenant.getSessionFactory(); + LocalSession session = sessionFactory.createSession(applicationDirectory, applicationName, timeoutBudget); + localSessionRepo.addSession(session); + return session.getSessionId(); + } + + private LocalSession getExistingSession(Tenant tenant, ApplicationId applicationId) { + TenantApplications applicationRepo = tenant.getApplicationRepo(); + return getLocalSession(tenant, applicationRepo.getSessionIdForApplication(applicationId)); + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStream.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStream.java index b83d8383f44..21ea03912a7 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStream.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/CompressedApplicationInputStream.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server.http; import com.google.common.io.ByteStreams; import com.google.common.io.Files; import com.yahoo.log.LogLevel; +import com.yahoo.vespa.config.server.http.v2.SessionCreateHandler; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; @@ -47,10 +48,10 @@ public class CompressedApplicationInputStream implements AutoCloseable { private static ArchiveInputStream getArchiveInputStream(InputStream is, String contentTypeHeader) throws IOException { ArchiveInputStream ais; switch (contentTypeHeader) { - case SessionCreate.APPLICATION_X_GZIP: + case SessionCreateHandler.APPLICATION_X_GZIP: ais = new TarArchiveInputStream(new GZIPInputStream(is)); break; - case SessionCreate.APPLICATION_ZIP: + case SessionCreateHandler.APPLICATION_ZIP: ais = new ZipArchiveInputStream(is); break; default: diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/SessionCreate.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/SessionCreate.java deleted file mode 100644 index 402c323d547..00000000000 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/SessionCreate.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http; - -import com.google.common.io.Files; -import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.TenantName; -import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.io.IOUtils; -import com.yahoo.log.LogLevel; -import com.yahoo.slime.Slime; -import com.yahoo.vespa.config.server.TimeoutBudget; -import com.yahoo.vespa.config.server.http.v2.SessionCreateResponse; -import com.yahoo.vespa.config.server.session.LocalSession; -import com.yahoo.vespa.config.server.session.LocalSessionRepo; -import com.yahoo.vespa.config.server.session.SessionFactory; - -import java.io.File; -import java.io.IOException; - -/** - * Creates a session from an application package, - * or creates a new session from a previous session (with id or the "active" session). - * - * @author lulf - * @author hmusum - * @since 5.1.27 - */ -// TODO Rename class -public class SessionCreate { - public final static String APPLICATION_X_GZIP = "application/x-gzip"; - public final static String APPLICATION_ZIP = "application/zip"; - public final static String contentTypeHeader = "Content-Type"; - - private final SessionFactory sessionFactory; - private final LocalSessionRepo localSessionRepo; - private final SessionCreateResponse responseCreator; - - public SessionCreate(SessionFactory sessionFactory, LocalSessionRepo localSessionRepo, SessionCreateResponse responseCreator) { - this.sessionFactory = sessionFactory; - this.localSessionRepo = localSessionRepo; - this.responseCreator = responseCreator; - } - - public HttpResponse createFromExisting(HttpRequest request, Slime deployLog, LocalSession fromSession, TenantName tenant, TimeoutBudget timeoutBudget) { - DeployLogger logger = SessionHandler.createLogger(deployLog, request, - new ApplicationId.Builder().tenant(tenant).applicationName("-").build()); - LocalSession session = sessionFactory.createSessionFromExisting(fromSession, logger, timeoutBudget); - localSessionRepo.addSession(session); - return createResponse(request, session); - } - - public HttpResponse create(HttpRequest request, Slime deployLog, TenantName tenant, TimeoutBudget timeoutBudget) { - validateDataAndHeader(request); - return createSession(request, deployLog, sessionFactory, localSessionRepo, tenant, timeoutBudget); - } - - private HttpResponse createSession(HttpRequest request, Slime deployLog, SessionFactory sessionFactory, LocalSessionRepo localSessionRepo, TenantName tenant, TimeoutBudget timeoutBudget) { - File tempDir = Files.createTempDir(); - File applicationDirectory = decompressApplication(request, tempDir); - DeployLogger logger = SessionHandler.createLogger(deployLog, request, - new ApplicationId.Builder().tenant(tenant).applicationName("-").build()); - String name = getNameProperty(request, logger); - LocalSession session = sessionFactory.createSession(applicationDirectory, name, timeoutBudget); - localSessionRepo.addSession(session); - HttpResponse response = createResponse(request, session); - cleanupApplicationDirectory(tempDir, logger); - return response; - } - - private 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; - } - - private File decompressApplication(HttpRequest request, File tempDir) { - try (CompressedApplicationInputStream application = CompressedApplicationInputStream.createFromCompressedStream(request.getData(), request.getHeader(contentTypeHeader))) { - return application.decompress(tempDir); - } catch (IOException e) { - throw new InternalServerException("Unable to decompress data in body", e); - } - } - - private void cleanupApplicationDirectory(File tempDir, DeployLogger logger) { - logger.log(LogLevel.DEBUG, "Deleting tmp dir '" + tempDir + "'"); - if (!IOUtils.recursiveDeleteDir(tempDir)) { - logger.log(LogLevel.WARNING, "Not able to delete tmp dir '" + tempDir + "'"); - } - } - - - private static void validateDataAndHeader(HttpRequest request) { - if (request.getData() == null) { - throw new BadRequestException("Request contains no data"); - } - String header = request.getHeader(contentTypeHeader); - if (header == null) { - throw new BadRequestException("Request contains no " + contentTypeHeader + " header"); - } else if (!(header.equals(APPLICATION_X_GZIP) || header.equals(APPLICATION_ZIP))) { - throw new BadRequestException("Request contains invalid " + contentTypeHeader + " header, only '" + - APPLICATION_X_GZIP + "' and '" + APPLICATION_ZIP + "' are supported"); - } - } - - private HttpResponse createResponse(HttpRequest request, LocalSession session) { - return responseCreator.createResponse(request.getHost(), request.getPort(), session.getSessionId()); - } -} diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/SessionHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/SessionHandler.java index bdd16b5f7ee..74b4eea2ee0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/SessionHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/SessionHandler.java @@ -82,7 +82,7 @@ public class SessionHandler extends HttpHandler { } } - static DeployHandlerLogger createLogger(Slime deployLog, HttpRequest request, ApplicationId app) { + public static DeployHandlerLogger createLogger(Slime deployLog, HttpRequest request, ApplicationId app) { return createLogger(deployLog, request.getBooleanProperty("verbose"), app); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java index d21d2c613bd..14e164e14f0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandler.java @@ -1,26 +1,32 @@ // Copyright 2016 Yahoo Inc. 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.common.io.Files; 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.TenantName; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.logging.AccessLog; +import com.yahoo.io.IOUtils; 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.http.CompressedApplicationInputStream; +import com.yahoo.vespa.config.server.http.InternalServerException; import com.yahoo.vespa.config.server.tenant.Tenant; import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.config.server.TimeoutBudget; -import com.yahoo.vespa.config.server.application.TenantApplications; import com.yahoo.vespa.config.server.http.BadRequestException; -import com.yahoo.vespa.config.server.http.SessionCreate; import com.yahoo.vespa.config.server.http.SessionHandler; import com.yahoo.vespa.config.server.http.Utils; -import com.yahoo.vespa.config.server.session.LocalSession; +import java.io.File; +import java.io.IOException; import java.net.URI; import java.time.Duration; import java.util.concurrent.Executor; @@ -34,8 +40,12 @@ import java.util.concurrent.Executor; * @since 5.1 */ public class SessionCreateHandler extends SessionHandler { - private final Tenants tenants; + public final static String APPLICATION_X_GZIP = "application/x-gzip"; + public final static String APPLICATION_ZIP = "application/zip"; + public final static String contentTypeHeader = "Content-Type"; private static final String fromPattern = "http://*/application/v2/tenant/*/application/*/environment/*/region/*/instance/*"; + + private final Tenants tenants; private final Duration zookeeperBarrierTimeout; @Inject @@ -55,24 +65,24 @@ public class SessionCreateHandler extends SessionHandler { final TenantName tenantName = Utils.getTenantNameFromSessionRequest(request); Utils.checkThatTenantExists(tenants, tenantName); Tenant tenant = tenants.getTenant(tenantName); - final SessionCreate sessionCreate = new SessionCreate(tenant.getSessionFactory(), tenant.getLocalSessionRepo(), - new SessionCreateResponse(tenantName, deployLog, deployLog.get())); TimeoutBudget timeoutBudget = SessionHandler.getTimeoutBudget(request, zookeeperBarrierTimeout); + DeployLogger logger = createLogger(request, deployLog, tenantName); + long sessionId; if (request.hasProperty("from")) { - LocalSession fromSession = getExistingSession(tenant, request); - return sessionCreate.createFromExisting(request, deployLog, fromSession, tenantName, timeoutBudget); + ApplicationId applicationId = getFromApplicationId(request); + sessionId = applicationRepository.createSessionFromExisting(tenant, logger, timeoutBudget, applicationId); } else { - return sessionCreate.create(request, deployLog, tenantName, timeoutBudget); + validateDataAndHeader(request); + File tempDir = Files.createTempDir(); + File applicationDirectory = decompressApplication(request, tempDir); + String name = getNameProperty(request, logger); + sessionId = applicationRepository.createSession(tenant, timeoutBudget, applicationDirectory, name); + cleanupApplicationDirectory(tempDir, logger); } + return createResponse(request, tenantName, deployLog, sessionId); } - private LocalSession getExistingSession(Tenant tenant, HttpRequest request) { - TenantApplications applicationRepo = tenant.getApplicationRepo(); - ApplicationId applicationId = getFromProperty(request); - return applicationRepository.getLocalSession(tenant, applicationRepo.getSessionIdForApplication(applicationId)); - } - - private static ApplicationId getFromProperty(HttpRequest request) { + 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 + "'"); @@ -90,4 +100,53 @@ public class SessionCreateHandler extends SessionHandler { .applicationName(match.group(3)) .instanceName(match.group(6)).build(); } + + private DeployHandlerLogger createLogger(HttpRequest request, Slime deployLog, TenantName tenant) { + return SessionHandler.createLogger(deployLog, request, + new ApplicationId.Builder().tenant(tenant).applicationName("-").build()); + } + + private 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; + } + + private File decompressApplication(HttpRequest request, File tempDir) { + try (CompressedApplicationInputStream application = CompressedApplicationInputStream.createFromCompressedStream(request.getData(), request + .getHeader(contentTypeHeader))) { + return application.decompress(tempDir); + } catch (IOException e) { + throw new InternalServerException("Unable to decompress data in body", e); + } + } + + private void cleanupApplicationDirectory(File tempDir, DeployLogger logger) { + logger.log(LogLevel.DEBUG, "Deleting tmp dir '" + tempDir + "'"); + if (!IOUtils.recursiveDeleteDir(tempDir)) { + logger.log(LogLevel.WARNING, "Not able to delete tmp dir '" + tempDir + "'"); + } + } + + private static void validateDataAndHeader(HttpRequest request) { + if (request.getData() == null) { + throw new BadRequestException("Request contains no data"); + } + String header = request.getHeader(contentTypeHeader); + if (header == null) { + throw new BadRequestException("Request contains no " + contentTypeHeader + " header"); + } else if (!(header.equals(APPLICATION_X_GZIP) || header.equals(APPLICATION_ZIP))) { + throw new BadRequestException("Request contains invalid " + contentTypeHeader + " header, only '" + + APPLICATION_X_GZIP + "' and '" + 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); + } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionCreateHandlerTestBase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionCreateHandlerTestBase.java deleted file mode 100644 index 0623ad15bda..00000000000 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionCreateHandlerTestBase.java +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.config.server.http; - -import com.google.common.io.Files; -import com.yahoo.config.application.api.DeployLogger; -import com.yahoo.config.model.application.provider.FilesApplicationPackage; -import com.yahoo.container.jdisc.HttpRequest; -import com.yahoo.container.jdisc.HttpResponse; -import com.yahoo.io.IOUtils; -import com.yahoo.vespa.config.server.TimeoutBudget; -import com.yahoo.vespa.config.server.application.TenantApplications; -import com.yahoo.vespa.config.server.session.LocalSession; -import com.yahoo.vespa.config.server.session.LocalSessionRepo; -import com.yahoo.vespa.config.server.session.SessionFactory; - -import org.junit.Ignore; -import org.junit.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import static com.yahoo.jdisc.Response.Status.*; -import static com.yahoo.jdisc.http.HttpRequest.Method.GET; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -/** - * Tests for session create handlers, to make it easier to have - * similar tests for more than one version of the API. - * - * @author hmusum - * @since 5.1.28 - */ -public abstract class SessionCreateHandlerTestBase extends SessionHandlerTest { - - public static final HashMap<String, String> postHeaders = new HashMap<>(); - - protected String pathPrefix = "/application/v2/session/"; - protected String createdMessage = " created.\""; - protected String tenantMessage = ""; - - public File testApp = new File("src/test/apps/app"); - public LocalSessionRepo localSessionRepo; - public TenantApplications applicationRepo; - - static { - postHeaders.put(SessionCreate.contentTypeHeader, SessionCreate.APPLICATION_X_GZIP); - } - - @Ignore - @Test - public void require_that_from_parameter_cannot_be_set_if_data_in_request() throws IOException { - HttpRequest request = post(Collections.singletonMap("from", "active")); - HttpResponse response = createHandler().handle(request); - HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' is illegal for POST"); - } - - @Test - public void require_that_post_request_must_contain_data() throws IOException { - HttpResponse response = createHandler().handle(post()); - HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Request contains no data"); - } - - @Test - public void require_that_post_request_must_have_correct_content_type() throws IOException { - HashMap<String, String> headers = new HashMap<>(); // no Content-Type header - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - HttpResponse response = createHandler().handle(post(outFile, headers, null)); - HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Request contains no Content-Type header"); - } - - @Test - public void require_that_application_name_is_given_from_parameter() throws IOException { - Map<String, String> params = Collections.singletonMap("name", "ulfio"); - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - MockSessionFactory factory = new MockSessionFactory(); - createHandler(factory).handle(post(outFile, postHeaders, params)); - assertTrue(factory.createCalled); - assertThat(factory.applicationName, is("ulfio")); - } - - protected void assertFromParameter(String expected, String from) throws IOException { - HttpRequest request = post(Collections.singletonMap("from", from)); - MockSessionFactory factory = new MockSessionFactory(); - factory.applicationPackage = testApp; - HttpResponse response = createHandler(factory).handle(request); - assertNotNull(response); - assertThat(response.getStatus(), is(OK)); - assertTrue(factory.createFromCalled); - assertThat(SessionHandlerTest.getRenderedString(response), - is("{\"log\":[]" + tenantMessage + ",\"session-id\":\"" + expected + "\",\"prepared\":\"http://" + hostname + ":" + port + pathPrefix + - expected + "/prepared\",\"content\":\"http://" + hostname + ":" + port + pathPrefix + - expected + "/content/\",\"message\":\"Session " + expected + createdMessage + "}")); - } - - protected void assertIllegalFromParameter(String fromValue) throws IOException { - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - HttpRequest request = post(outFile, postHeaders, Collections.singletonMap("from", fromValue)); - HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(createHandler().handle(request), BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' has illegal value '" + fromValue + "'"); - } - - @Test - public void require_that_prepare_url_is_returned_on_success() throws IOException { - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - Map<String, String> parameters = Collections.singletonMap("name", "foo"); - HttpResponse response = createHandler().handle(post(outFile, postHeaders, parameters)); - assertNotNull(response); - assertThat(response.getStatus(), is(OK)); - assertThat(SessionHandlerTest.getRenderedString(response), - is("{\"log\":[]" + tenantMessage + ",\"session-id\":\"0\",\"prepared\":\"http://" + - hostname + ":" + port + pathPrefix + "0/prepared\",\"content\":\"http://" + - hostname + ":" + port + pathPrefix + "0/content/\",\"message\":\"Session 0" + createdMessage + "}")); - } - - @Test - public void require_that_session_factory_is_called() throws IOException { - MockSessionFactory sessionFactory = new MockSessionFactory(); - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - createHandler(sessionFactory).handle(post(outFile)); - assertTrue(sessionFactory.createCalled); - } - - @Test - public void require_that_handler_does_not_support_get() throws IOException { - HttpResponse response = createHandler().handle(HttpRequest.createTestRequest(pathPrefix, GET)); - HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, METHOD_NOT_ALLOWED, - HttpErrorResponse.errorCodes.METHOD_NOT_ALLOWED, - "Method 'GET' is not supported"); - } - - @Test - public void require_internal_error_when_exception() throws IOException { - MockSessionFactory factory = new MockSessionFactory(); - factory.doThrow = true; - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - HttpResponse response = createHandler(factory).handle(post(outFile)); - HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, INTERNAL_SERVER_ERROR, - HttpErrorResponse.errorCodes.INTERNAL_SERVER_ERROR, - "foo"); - } - - @Test - public void require_that_handler_unpacks_application() throws IOException { - MockSessionFactory sessionFactory = new MockSessionFactory(); - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - createHandler(sessionFactory).handle(post(outFile)); - assertTrue(sessionFactory.createCalled); - final File applicationPackage = sessionFactory.applicationPackage; - assertNotNull(applicationPackage); - assertTrue(applicationPackage.exists()); - final File[] files = applicationPackage.listFiles(); - assertNotNull(files); - assertThat(files.length, is(2)); - } - - @Test - public void require_that_session_is_stored_in_repo() throws IOException { - File outFile = CompressedApplicationInputStreamTest.createTarFile(); - createHandler(new MockSessionFactory()).handle(post(outFile)); - assertNotNull(localSessionRepo.getSession(0l)); - } - - public abstract SessionHandler createHandler(); - - public abstract SessionHandler createHandler(SessionFactory sessionFactory); - - public abstract HttpRequest post() throws FileNotFoundException; - - public abstract HttpRequest post(File file) throws FileNotFoundException; - - public abstract HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException; - - public abstract HttpRequest post(Map<String, String> parameters) throws FileNotFoundException; - - public static class MockSessionFactory implements SessionFactory { - public boolean createCalled = false; - public boolean createFromCalled = false; - public boolean doThrow = false; - public File applicationPackage; - public String applicationName; - - @Override - public LocalSession createSession(File applicationDirectory, String applicationName, TimeoutBudget timeoutBudget) { - createCalled = true; - this.applicationName = applicationName; - if (doThrow) { - throw new RuntimeException("foo"); - } - final File tempDir = Files.createTempDir(); - try { - IOUtils.copyDirectory(applicationDirectory, tempDir); - } catch (IOException e) { - e.printStackTrace(); - } - this.applicationPackage = tempDir; - return new SessionHandlerTest.MockSession(0, FilesApplicationPackage.fromFile(applicationPackage)); - } - - @Override - public LocalSession createSessionFromExisting(LocalSession existingSession, DeployLogger logger, TimeoutBudget timeoutBudget) { - if (doThrow) { - throw new RuntimeException("foo"); - } - createFromCalled = true; - return new SessionHandlerTest.MockSession(existingSession.getSessionId() + 1, FilesApplicationPackage.fromFile(applicationPackage)); - } - } -} - diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java index 1f1791fd70f..04d7703a82c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java @@ -1,16 +1,20 @@ // Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.config.server.http; +import com.google.common.io.Files; import com.yahoo.config.application.api.ApplicationFile; import com.yahoo.config.application.api.ApplicationPackage; import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.io.IOUtils; import com.yahoo.transaction.NestedTransaction; import com.yahoo.transaction.Transaction; import com.yahoo.log.LogLevel; import com.yahoo.path.Path; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.vespa.config.server.TimeoutBudget; import com.yahoo.vespa.config.server.application.ApplicationSet; import com.yahoo.vespa.config.server.host.HostRegistry; import com.yahoo.config.provision.ApplicationId; @@ -170,4 +174,40 @@ public class SessionHandlerTest { return name; } } + + public static class MockSessionFactory implements SessionFactory { + public boolean createCalled = false; + public boolean createFromCalled = false; + public boolean doThrow = false; + public File applicationPackage; + public String applicationName; + + @Override + public LocalSession createSession(File applicationDirectory, String applicationName, TimeoutBudget timeoutBudget) { + createCalled = true; + this.applicationName = applicationName; + if (doThrow) { + throw new RuntimeException("foo"); + } + final File tempDir = Files.createTempDir(); + try { + IOUtils.copyDirectory(applicationDirectory, tempDir); + } catch (IOException e) { + e.printStackTrace(); + } + this.applicationPackage = tempDir; + return new SessionHandlerTest.MockSession(0, FilesApplicationPackage.fromFile(applicationPackage)); + } + + @Override + public LocalSession createSessionFromExisting(LocalSession existingSession, DeployLogger logger, TimeoutBudget timeoutBudget) { + if (doThrow) { + throw new RuntimeException("foo"); + } + createFromCalled = true; + return new SessionHandlerTest.MockSession(existingSession.getSessionId() + 1, FilesApplicationPackage.fromFile(applicationPackage)); + } + } + + } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java index 5d364de28d3..dda5ecb897e 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionActiveHandlerTest.java @@ -23,6 +23,7 @@ import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.session.RemoteSession; import com.yahoo.vespa.config.server.session.RemoteSessionRepo; import com.yahoo.vespa.config.server.session.Session; +import com.yahoo.vespa.config.server.session.SessionFactory; import com.yahoo.vespa.config.server.session.SessionZooKeeperClient; import com.yahoo.vespa.curator.mock.MockCurator; @@ -38,7 +39,6 @@ import com.yahoo.path.Path; import com.yahoo.vespa.config.server.application.MemoryTenantApplications; import com.yahoo.vespa.config.server.http.SessionActiveHandlerTestBase; import com.yahoo.vespa.config.server.http.SessionHandler; -import com.yahoo.vespa.config.server.http.SessionCreateHandlerTestBase.MockSessionFactory; public class SessionActiveHandlerTest extends SessionActiveHandlerTestBase { @@ -150,7 +150,7 @@ public class SessionActiveHandlerTest extends SessionActiveHandlerTestBase { @Override protected SessionHandler createHandler() throws Exception { - final MockSessionFactory sessionFactory = new MockSessionFactory(); + final SessionFactory sessionFactory = new MockSessionFactory(); TestTenantBuilder testTenantBuilder = new TestTenantBuilder(); testTenantBuilder.createTenant(tenant) .withSessionFactory(sessionFactory) diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java index d053b2ca6a3..908f4481a95 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionCreateHandlerTest.java @@ -5,26 +5,34 @@ import com.yahoo.cloud.config.ConfigserverConfig; import com.yahoo.config.model.application.provider.FilesApplicationPackage; import com.yahoo.config.provision.TenantName; import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.logging.AccessLog; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.config.server.ApplicationRepository; import com.yahoo.vespa.config.server.application.ApplicationConvergenceChecker; import com.yahoo.vespa.config.server.application.LogServerLogGrabber; import com.yahoo.vespa.config.server.application.MemoryTenantApplications; -import com.yahoo.vespa.config.server.http.SessionCreateHandlerTestBase; +import com.yahoo.vespa.config.server.application.TenantApplications; +import com.yahoo.vespa.config.server.http.CompressedApplicationInputStreamTest; +import com.yahoo.vespa.config.server.http.HandlerTest; +import com.yahoo.vespa.config.server.http.HttpErrorResponse; import com.yahoo.vespa.config.server.http.SessionHandlerTest; import com.yahoo.vespa.config.server.provision.HostProvisionerProvider; import com.yahoo.vespa.config.server.session.*; import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.mock.MockCurator; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.io.*; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; +import static com.yahoo.jdisc.Response.Status.*; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static com.yahoo.jdisc.http.HttpRequest.Method.*; @@ -33,10 +41,24 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.*; * @author hmusum * @since 5.1 */ -public class SessionCreateHandlerTest extends SessionCreateHandlerTestBase { +public class SessionCreateHandlerTest extends SessionHandlerTest { private static final TenantName tenant = TenantName.from("test"); + public static final HashMap<String, String> postHeaders = new HashMap<>(); + + protected String pathPrefix = "/application/v2/session/"; + protected String createdMessage = " created.\""; + protected String tenantMessage = ""; + + public File testApp = new File("src/test/apps/app"); + public LocalSessionRepo localSessionRepo; + public TenantApplications applicationRepo; + + static { + postHeaders.put(SessionCreateHandler.contentTypeHeader, SessionCreateHandler.APPLICATION_X_GZIP); + } + @Before public void setupRepo() throws Exception { applicationRepo = new MemoryTenantApplications(); @@ -46,6 +68,120 @@ public class SessionCreateHandlerTest extends SessionCreateHandlerTestBase { tenantMessage = ",\"tenant\":\"test\""; } + @Ignore + @Test + public void require_that_from_parameter_cannot_be_set_if_data_in_request() throws IOException { + HttpRequest request = post(Collections.singletonMap("from", "active")); + HttpResponse response = createHandler().handle(request); + HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' is illegal for POST"); + } + + @Test + public void require_that_post_request_must_contain_data() throws IOException { + HttpResponse response = createHandler().handle(post()); + HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Request contains no data"); + } + + @Test + public void require_that_post_request_must_have_correct_content_type() throws IOException { + HashMap<String, String> headers = new HashMap<>(); // no Content-Type header + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + HttpResponse response = createHandler().handle(post(outFile, headers, null)); + HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Request contains no Content-Type header"); + } + + @Test + public void require_that_application_name_is_given_from_parameter() throws IOException { + Map<String, String> params = Collections.singletonMap("name", "ulfio"); + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + MockSessionFactory factory = new MockSessionFactory(); + createHandler(factory).handle(post(outFile, postHeaders, params)); + assertTrue(factory.createCalled); + assertThat(factory.applicationName, is("ulfio")); + } + + protected void assertFromParameter(String expected, String from) throws IOException { + HttpRequest request = post(Collections.singletonMap("from", from)); + MockSessionFactory factory = new MockSessionFactory(); + factory.applicationPackage = testApp; + HttpResponse response = createHandler(factory).handle(request); + assertNotNull(response); + assertThat(response.getStatus(), is(OK)); + assertTrue(factory.createFromCalled); + assertThat(SessionHandlerTest.getRenderedString(response), + is("{\"log\":[]" + tenantMessage + ",\"session-id\":\"" + expected + "\",\"prepared\":\"http://" + hostname + ":" + port + pathPrefix + + expected + "/prepared\",\"content\":\"http://" + hostname + ":" + port + pathPrefix + + expected + "/content/\",\"message\":\"Session " + expected + createdMessage + "}")); + } + + protected void assertIllegalFromParameter(String fromValue) throws IOException { + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + HttpRequest request = post(outFile, postHeaders, Collections.singletonMap("from", fromValue)); + HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(createHandler().handle(request), BAD_REQUEST, HttpErrorResponse.errorCodes.BAD_REQUEST, "Parameter 'from' has illegal value '" + fromValue + "'"); + } + + @Test + public void require_that_prepare_url_is_returned_on_success() throws IOException { + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + Map<String, String> parameters = Collections.singletonMap("name", "foo"); + HttpResponse response = createHandler().handle(post(outFile, postHeaders, parameters)); + assertNotNull(response); + assertThat(response.getStatus(), is(OK)); + assertThat(SessionHandlerTest.getRenderedString(response), + is("{\"log\":[]" + tenantMessage + ",\"session-id\":\"0\",\"prepared\":\"http://" + + hostname + ":" + port + pathPrefix + "0/prepared\",\"content\":\"http://" + + hostname + ":" + port + pathPrefix + "0/content/\",\"message\":\"Session 0" + createdMessage + "}")); + } + + @Test + public void require_that_session_factory_is_called() throws IOException { + MockSessionFactory sessionFactory = new MockSessionFactory(); + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + createHandler(sessionFactory).handle(post(outFile)); + assertTrue(sessionFactory.createCalled); + } + + @Test + public void require_that_handler_does_not_support_get() throws IOException { + HttpResponse response = createHandler().handle(HttpRequest.createTestRequest(pathPrefix, GET)); + HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, METHOD_NOT_ALLOWED, + HttpErrorResponse.errorCodes.METHOD_NOT_ALLOWED, + "Method 'GET' is not supported"); + } + + @Test + public void require_internal_error_when_exception() throws IOException { + MockSessionFactory factory = new MockSessionFactory(); + factory.doThrow = true; + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + HttpResponse response = createHandler(factory).handle(post(outFile)); + HandlerTest.assertHttpStatusCodeErrorCodeAndMessage(response, INTERNAL_SERVER_ERROR, + HttpErrorResponse.errorCodes.INTERNAL_SERVER_ERROR, + "foo"); + } + + @Test + public void require_that_handler_unpacks_application() throws IOException { + MockSessionFactory sessionFactory = new MockSessionFactory(); + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + createHandler(sessionFactory).handle(post(outFile)); + assertTrue(sessionFactory.createCalled); + final File applicationPackage = sessionFactory.applicationPackage; + assertNotNull(applicationPackage); + assertTrue(applicationPackage.exists()); + final File[] files = applicationPackage.listFiles(); + assertNotNull(files); + assertThat(files.length, is(2)); + } + + @Test + public void require_that_session_is_stored_in_repo() throws IOException { + File outFile = CompressedApplicationInputStreamTest.createTarFile(); + createHandler(new MockSessionFactory()).handle(post(outFile)); + assertNotNull(localSessionRepo.getSession(0l)); + } + + @Test public void require_that_application_urls_can_be_given_as_from_parameter() throws Exception { localSessionRepo.addSession(new SessionHandlerTest.MockSession(2l, FilesApplicationPackage.fromFile(testApp))); @@ -76,7 +212,6 @@ public class SessionCreateHandlerTest extends SessionCreateHandlerTestBase { assertIllegalFromParameter("http://host:4013/application/v2/tenant/" + tenant + "/application/foo/environment/prod/region/baz/instance"); } - @Override public SessionCreateHandler createHandler() { try { return createHandler(new MockSessionFactory()); @@ -87,7 +222,6 @@ public class SessionCreateHandlerTest extends SessionCreateHandlerTestBase { return null; } - @Override public SessionCreateHandler createHandler(SessionFactory sessionFactory) { try { TestTenantBuilder testBuilder = new TestTenantBuilder(); @@ -118,17 +252,14 @@ public class SessionCreateHandlerTest extends SessionCreateHandlerTestBase { new ApplicationConvergenceChecker())); } - @Override public HttpRequest post() throws FileNotFoundException { return post(null, postHeaders, new HashMap<>()); } - @Override public HttpRequest post(File file) throws FileNotFoundException { return post(file, postHeaders, new HashMap<>()); } - @Override public HttpRequest post(File file, Map<String, String> headers, Map<String, String> parameters) throws FileNotFoundException { HttpRequest request = HttpRequest.createTestRequest("http://" + hostname + ":" + port + "/application/v2/tenant/" + tenant + "/session", POST, @@ -140,7 +271,6 @@ public class SessionCreateHandlerTest extends SessionCreateHandlerTestBase { return request; } - @Override public HttpRequest post(Map<String, String> parameters) throws FileNotFoundException { return post(null, new HashMap<>(), parameters); } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java index 7e0edf3a446..d88de383eef 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/SessionPrepareHandlerTest.java @@ -84,7 +84,7 @@ public class SessionPrepareHandlerTest extends SessionPrepareHandlerTestBase { TenantApplications applicationRepoDefault = new MemoryTenantApplications(); LocalSessionRepo localRepoDefault = new LocalSessionRepo(applicationRepoDefault); final TenantName tenantName = TenantName.defaultName(); - addTenant(tenantName, localRepoDefault, new RemoteSessionRepo(), new SessionCreateHandlerTestBase.MockSessionFactory()); + addTenant(tenantName, localRepoDefault, new RemoteSessionRepo(), new MockSessionFactory()); addTestTenant(); final SessionHandler handler = createHandler(builder); @@ -172,13 +172,11 @@ public class SessionPrepareHandlerTest extends SessionPrepareHandlerTestBase { @Override public SessionHandler createHandler(RemoteSessionRepo remoteSessionRepo) { - return createHandler(addTenant(tenant, localRepo, remoteSessionRepo, - new SessionCreateHandlerTestBase.MockSessionFactory())); + return createHandler(addTenant(tenant, localRepo, remoteSessionRepo, new MockSessionFactory())); } private TestTenantBuilder addTestTenant() { - return addTenant(tenant, localRepo, new RemoteSessionRepo(), - new SessionCreateHandlerTestBase.MockSessionFactory()); + return addTenant(tenant, localRepo, new RemoteSessionRepo(), new MockSessionFactory()); } static SessionHandler createHandler(TestTenantBuilder builder) { diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java index cc417ab69e2..eaddd7c65b7 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TestTenantBuilder.java @@ -7,7 +7,6 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.path.Path; import com.yahoo.vespa.config.server.*; import com.yahoo.vespa.config.server.application.MemoryTenantApplications; -import com.yahoo.vespa.config.server.http.SessionCreateHandlerTestBase; import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.config.server.session.LocalSessionRepo; import com.yahoo.vespa.config.server.session.RemoteSessionRepo; @@ -35,7 +34,7 @@ public class TestTenantBuilder { public TenantBuilder createTenant(TenantName tenantName) { MemoryTenantApplications applicationRepo = new MemoryTenantApplications(); TenantBuilder builder = TenantBuilder.create(componentRegistry, tenantName, Path.createRoot().append(tenantName.value())) - .withSessionFactory(new SessionCreateHandlerTestBase.MockSessionFactory()) + .withSessionFactory(new SessionCreateHandlerTest.MockSessionFactory()) .withLocalSessionRepo(new LocalSessionRepo(applicationRepo)) .withRemoteSessionRepo(new RemoteSessionRepo()) .withApplicationRepo(applicationRepo); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java index 8a56e742bc0..430747d5f4b 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionFactoryTest.java @@ -9,8 +9,8 @@ import com.yahoo.path.Path; import com.yahoo.vespa.config.server.*; import com.yahoo.vespa.config.server.http.CompressedApplicationInputStream; import com.yahoo.vespa.config.server.http.CompressedApplicationInputStreamTest; -import com.yahoo.vespa.config.server.http.SessionCreate; +import com.yahoo.vespa.config.server.http.v2.SessionCreateHandler; import com.yahoo.vespa.config.server.tenant.TestWithTenant; import org.json.JSONException; import org.json.JSONObject; @@ -76,7 +76,8 @@ public class SessionFactoryTest extends TestWithTenant { } private LocalSession getLocalSession(String appName) throws IOException { - CompressedApplicationInputStream app = CompressedApplicationInputStream.createFromCompressedStream(new FileInputStream(CompressedApplicationInputStreamTest.createTarFile()), SessionCreate.APPLICATION_X_GZIP); + CompressedApplicationInputStream app = CompressedApplicationInputStream.createFromCompressedStream( + new FileInputStream(CompressedApplicationInputStreamTest.createTarFile()), SessionCreateHandler.APPLICATION_X_GZIP); return factory.createSession(app.decompress(Files.createTempDir()), appName, TimeoutBudgetTest.day()); } } |