summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java92
-rw-r--r--config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java56
-rw-r--r--config-model/src/main/resources/schema/deployment.rnc7
-rw-r--r--config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml11
-rw-r--r--config-model/src/test/cfg/application/invalid_parallel_deployment_xml/hosts.xml10
-rw-r--r--config-model/src/test/cfg/application/invalid_parallel_deployment_xml/services.xml16
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java20
-rw-r--r--config-model/src/test/schema-test-files/deployment.xml8
-rw-r--r--jdisc_http_service/pom.xml4
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java155
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java7
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java2
-rw-r--r--pom.xml5
-rw-r--r--zkfacade/pom.xml5
14 files changed, 266 insertions, 132 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
index 68c1a897dc3..ba68404b7ca 100644
--- a/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
+++ b/config-model-api/src/main/java/com/yahoo/config/application/api/DeploymentSpec.java
@@ -15,10 +15,12 @@ import java.io.Reader;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -57,6 +59,7 @@ public class DeploymentSpec {
this.upgradePolicy = upgradePolicy;
this.steps = ImmutableList.copyOf(completeSteps(new ArrayList<>(steps)));
this.xmlForm = xmlForm;
+ validateZones(this.steps);
}
/** Throw an IllegalArgumentException if the total delay exceeds 24 hours */
@@ -68,6 +71,33 @@ public class DeploymentSpec {
throw new IllegalArgumentException("The total delay specified is " + Duration.ofSeconds(totalDelaySeconds) +
" but max 24 hours is allowed");
}
+
+ /** Throw an IllegalArgumentException if any production zone is declared multiple times */
+ private static void validateZones(List<Step> steps) {
+ // Collect both non-parallel and parallel zones
+ List<DeclaredZone> zones = new ArrayList<>();
+ steps.stream()
+ .filter(step -> step instanceof DeclaredZone)
+ .map(DeclaredZone.class::cast)
+ .forEach(zones::add);
+ steps.stream()
+ .filter(step -> step instanceof ParallelZones)
+ .map(ParallelZones.class::cast)
+ .flatMap(parallelZones -> parallelZones.zones().stream())
+ .forEach(zones::add);
+
+
+ // Detect duplicates
+ Set<DeclaredZone> unique = new HashSet<>();
+ List<RegionName> duplicates = zones.stream()
+ .filter(z -> z.environment() == Environment.prod && !unique.add(z))
+ .map(z -> z.region().get())
+ .collect(Collectors.toList());
+ if (!duplicates.isEmpty()) {
+ throw new IllegalArgumentException("All declared regions must be unique, but found these " +
+ "duplicated regions: " + duplicates);
+ }
+ }
/** Adds missing required steps and reorders steps to a permissible order */
private static List<Step> completeSteps(List<Step> steps) {
@@ -123,7 +153,7 @@ public class DeploymentSpec {
public List<Step> steps() { return steps; }
/** Returns only the DeclaredZone deployment steps of this in the order they will be performed */
- public List<DeclaredZone> zones() {
+ public List<DeclaredZone> zones() {
return steps.stream().filter(step -> step instanceof DeclaredZone).map(DeclaredZone.class::cast)
.collect(Collectors.toList());
}
@@ -168,17 +198,21 @@ public class DeploymentSpec {
if (environment == Environment.prod) {
for (Element stepTag : XML.getChildren(environmentTag)) {
- if (stepTag.getTagName().equals("delay"))
- steps.add(new Delay(Duration.ofSeconds(longAttribute("hours", stepTag) * 60 * 60 +
- longAttribute("minutes", stepTag) * 60 +
- longAttribute("seconds", stepTag))));
- else // a region: deploy step
- steps.add(new DeclaredZone(environment,
- Optional.of(RegionName.from(XML.getValue(stepTag).trim())),
- readActive(stepTag)));
+ if (stepTag.getTagName().equals("delay")) {
+ steps.add(new Delay(Duration.ofSeconds(longAttribute("hours", stepTag) * 60 * 60 +
+ longAttribute("minutes", stepTag) * 60 +
+ longAttribute("seconds", stepTag))));
+ } else if (stepTag.getTagName().equals("parallel")) {
+ List<DeclaredZone> zones = new ArrayList<>();
+ for (Element regionTag : XML.getChildren(stepTag)) {
+ zones.add(readDeclaredZone(environment, regionTag));
+ }
+ steps.add(new ParallelZones(zones));
+ } else { // a region: deploy step
+ steps.add(readDeclaredZone(environment, stepTag));
+ }
}
- }
- else {
+ } else {
steps.add(new DeclaredZone(environment));
}
@@ -207,6 +241,11 @@ public class DeploymentSpec {
return tagName.equals("test") || tagName.equals("staging") || tagName.equals("prod");
}
+ private static DeclaredZone readDeclaredZone(Environment environment, Element regionTag) {
+ return new DeclaredZone(environment, Optional.of(RegionName.from(XML.getValue(regionTag).trim())),
+ readActive(regionTag));
+ }
+
private static Optional<String> readGlobalServiceId(Element environmentTag) {
String globalServiceId = environmentTag.getAttribute("global-service-id");
if (globalServiceId == null || globalServiceId.isEmpty()) {
@@ -363,6 +402,37 @@ public class DeploymentSpec {
}
+ /** A deployment step which is to run deployment to multiple zones in parallel */
+ public static class ParallelZones extends Step {
+
+ private final List<DeclaredZone> zones;
+
+ public ParallelZones(List<DeclaredZone> zones) {
+ this.zones = ImmutableList.copyOf(zones);
+ }
+
+ /** The list of zones to deploy in */
+ public List<DeclaredZone> zones() { return this.zones; }
+
+ @Override
+ public boolean deploysTo(Environment environment, Optional<RegionName> region) {
+ return zones.stream().anyMatch(zone -> zone.deploysTo(environment, region));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ParallelZones)) return false;
+ ParallelZones that = (ParallelZones) o;
+ return Objects.equals(zones, that.zones);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(zones);
+ }
+ }
+
/** Controls when this application will be upgraded to new Vespa versions */
public enum UpgradePolicy {
/** Canary: Applications with this policy will upgrade before any other */
diff --git a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
index 0458e9d4c15..2b503125e33 100644
--- a/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
+++ b/config-model-api/src/test/java/com/yahoo/config/application/api/DeploymentSpecTest.java
@@ -4,14 +4,15 @@ package com.yahoo.config.application.api;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
import java.io.StringReader;
import java.util.Optional;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
/**
* @author bratseth
*/
@@ -233,4 +234,51 @@ public class DeploymentSpecTest {
assertEquals("<deployment version='1.0'/>", DeploymentSpec.empty.xmlForm());
}
+ @Test
+ public void productionSpecWithParallelDeployments() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <prod> \n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " <parallel>\n" +
+ " <region active='true'>us-central-1</region>\n" +
+ " <region active='true'>us-east-3</region>\n" +
+ " </parallel>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ DeploymentSpec spec = DeploymentSpec.fromXml(r);
+ DeploymentSpec.ParallelZones parallelZones = ((DeploymentSpec.ParallelZones) spec.steps().get(3));
+ assertEquals(2, parallelZones.zones().size());
+ assertEquals(RegionName.from("us-central-1"), parallelZones.zones().get(0).region().get());
+ assertEquals(RegionName.from("us-east-3"), parallelZones.zones().get(1).region().get());
+ }
+
+ @Test
+ public void productionSpecWithDuplicateRegions() {
+ StringReader r = new StringReader(
+ "<deployment>\n" +
+ " <prod>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " <parallel>\n" +
+ " <region active='true'>us-west-1</region>\n" +
+ " <region active='true'>us-central-1</region>\n" +
+ " <region active='true'>us-east-3</region>\n" +
+ " </parallel>\n" +
+ " <parallel>\n" +
+ " <region active='true'>eu-west-1</region>\n" +
+ " <region active='true'>us-central-1</region>\n" +
+ " </parallel>\n" +
+ " </prod>\n" +
+ "</deployment>"
+ );
+ try {
+ DeploymentSpec.fromXml(r);
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ assertEquals("All declared regions must be unique, but found these duplicated regions: " +
+ "[us-west-1, us-central-1]", e.getMessage());
+ }
+ }
+
}
diff --git a/config-model/src/main/resources/schema/deployment.rnc b/config-model/src/main/resources/schema/deployment.rnc
index 3ce5e002e53..36897643964 100644
--- a/config-model/src/main/resources/schema/deployment.rnc
+++ b/config-model/src/main/resources/schema/deployment.rnc
@@ -25,7 +25,8 @@ Staging = element staging {
Prod = element prod {
attribute global-service-id { text }? &
Region* &
- Delay*
+ Delay* &
+ Parallel*
}
Region = element region {
@@ -38,3 +39,7 @@ Delay = element delay {
attribute minutes { xsd:long }? &
attribute seconds { xsd:long }?
}
+
+Parallel = element parallel {
+ Region*
+}
diff --git a/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml
new file mode 100644
index 00000000000..0d3b74b8119
--- /dev/null
+++ b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/deployment.xml
@@ -0,0 +1,11 @@
+<deployment version="1.0">
+ <test/>
+ <staging/>
+ <prod global-service-id="query">
+ <parallel>
+ <region active="true">us-east-3</region>
+ <delay hours="1"/>
+ <region active="true">us-west-1</region>
+ </parallel>
+ </prod>
+</deployment>
diff --git a/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/hosts.xml b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/hosts.xml
new file mode 100644
index 00000000000..115efd488d0
--- /dev/null
+++ b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/hosts.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<hosts>
+ <host name="localhost">
+ <alias>node1</alias>
+ </host>
+ <host name="schmocalhost">
+ <alias>node2</alias>
+ </host>
+</hosts>
diff --git a/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/services.xml b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/services.xml
new file mode 100644
index 00000000000..03d8fc012ac
--- /dev/null
+++ b/config-model/src/test/cfg/application/invalid_parallel_deployment_xml/services.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<services version="1.0">
+
+ <admin version="2.0">
+ <adminserver hostalias="node1"/>
+ </admin>
+
+ <container version="1.0">
+ <nodes>
+ <node hostalias="node1" />
+ </nodes>
+ <search/>
+ </container>
+
+</services>
diff --git a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
index 1e1b8cd2ac8..3d5c9b1c187 100644
--- a/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
+++ b/config-model/src/test/java/com/yahoo/config/model/ApplicationDeployTest.java
@@ -196,7 +196,7 @@ public class ApplicationDeployTest {
@Test
public void testThatModelIsRebuiltWhenSearchDefinitionIsAdded() throws IOException {
- File tmpDir = Files.createTempDir();
+ File tmpDir = tmpFolder.getRoot();
IOUtils.copyDirectory(new File(TESTDIR, "app1"), tmpDir);
FilesApplicationPackage app = createAppPkg(tmpDir.getAbsolutePath());
assertThat(getSearchDefinitions(app).size(), is(5));
@@ -208,25 +208,37 @@ public class ApplicationDeployTest {
@Test
public void testThatAppWithDeploymentXmlIsValid() throws IOException {
- File tmpDir = Files.createTempDir();
+ File tmpDir = tmpFolder.getRoot();
IOUtils.copyDirectory(new File(TESTDIR, "app1"), tmpDir);
createAppPkg(tmpDir.getAbsolutePath());
}
@Test(expected = IllegalArgumentException.class)
public void testThatAppWithIllegalDeploymentXmlIsNotValid() throws IOException {
- File tmpDir = Files.createTempDir();
+ File tmpDir = tmpFolder.getRoot();
IOUtils.copyDirectory(new File(TESTDIR, "app_invalid_deployment_xml"), tmpDir);
createAppPkg(tmpDir.getAbsolutePath());
}
@Test
public void testThatAppWithIllegalEmptyProdRegion() throws IOException {
- File tmpDir = Files.createTempDir();
+ File tmpDir = tmpFolder.getRoot();
IOUtils.copyDirectory(new File(TESTDIR, "empty_prod_region_in_deployment_xml"), tmpDir);
createAppPkg(tmpDir.getAbsolutePath());
}
+ @Test
+ public void testThatAppWithInvalidParallelDeploymentFails() throws IOException {
+ File tmpDir = tmpFolder.getRoot();
+ IOUtils.copyDirectory(new File(TESTDIR, "invalid_parallel_deployment_xml"), tmpDir);
+ try {
+ createAppPkg(tmpDir.getAbsolutePath());
+ fail("Expected exception");
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("element \"delay\" not allowed here"));
+ }
+ }
+
private List<SearchDefinition> getSearchDefinitions(FilesApplicationPackage app) {
return new DeployState.Builder().applicationPackage(app).build().getSearchDefinitions();
}
diff --git a/config-model/src/test/schema-test-files/deployment.xml b/config-model/src/test/schema-test-files/deployment.xml
index 89b52bc44ca..99b1dc1be69 100644
--- a/config-model/src/test/schema-test-files/deployment.xml
+++ b/config-model/src/test/schema-test-files/deployment.xml
@@ -9,5 +9,13 @@
<region active='true'>us-central-1</region>
<delay hours='3' minutes='7' seconds='13'/>
<region active='true'>us-east-3</region>
+ <parallel>
+ <region active='true'>us-north-1</region>
+ <region active='true'>us-south-1</region>
+ </parallel>
+ <parallel>
+ <region active='true'>us-north-2</region>
+ <region active='true'>us-south-2</region>
+ </parallel>
</prod>
</deployment>
diff --git a/jdisc_http_service/pom.xml b/jdisc_http_service/pom.xml
index 0c19f2c3d15..2dfcb0bd166 100644
--- a/jdisc_http_service/pom.xml
+++ b/jdisc_http_service/pom.xml
@@ -22,6 +22,10 @@
<classifier>no_aop</classifier>
</dependency>
<dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java
index a43310aff51..8f04a870dc9 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/Cookie.java
@@ -1,21 +1,18 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http;
-import org.eclipse.jetty.server.CookieCutter;
-import org.eclipse.jetty.server.Response;
+import org.jboss.netty.handler.codec.http.cookie.ClientCookieDecoder;
+import org.jboss.netty.handler.codec.http.cookie.ClientCookieEncoder;
+import org.jboss.netty.handler.codec.http.cookie.DefaultCookie;
+import org.jboss.netty.handler.codec.http.cookie.ServerCookieDecoder;
+import org.jboss.netty.handler.codec.http.cookie.ServerCookieEncoder;
-import java.net.HttpCookie;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -29,8 +26,6 @@ import java.util.stream.StreamSupport;
*/
public class Cookie {
- private final static Logger log = Logger.getLogger(Cookie.class.getName());
-
private final Set<Integer> ports = new HashSet<>();
private String name;
private String value;
@@ -38,7 +33,7 @@ public class Cookie {
private String path;
private String comment;
private String commentUrl;
- private long maxAgeSeconds = Integer.MIN_VALUE;
+ private long maxAgeMillis = TimeUnit.SECONDS.toMillis(Integer.MIN_VALUE);
private int version;
private boolean secure;
private boolean httpOnly;
@@ -55,7 +50,7 @@ public class Cookie {
path = cookie.path;
comment = cookie.comment;
commentUrl = cookie.commentUrl;
- maxAgeSeconds = cookie.maxAgeSeconds;
+ maxAgeMillis = cookie.maxAgeMillis;
version = cookie.version;
secure = cookie.secure;
httpOnly = cookie.httpOnly;
@@ -136,11 +131,11 @@ public class Cookie {
}
public int getMaxAge(TimeUnit unit) {
- return (int)unit.convert(maxAgeSeconds, TimeUnit.SECONDS);
+ return (int)unit.convert(maxAgeMillis, TimeUnit.MILLISECONDS);
}
public Cookie setMaxAge(int maxAge, TimeUnit unit) {
- this.maxAgeSeconds = maxAge >= 0 ? unit.toSeconds(maxAge) : Integer.MIN_VALUE;
+ this.maxAgeMillis = unit.toMillis(maxAge);
return this;
}
@@ -194,7 +189,7 @@ public class Cookie {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cookie cookie = (Cookie) o;
- return maxAgeSeconds == cookie.maxAgeSeconds &&
+ return maxAgeMillis == cookie.maxAgeMillis &&
version == cookie.version &&
secure == cookie.secure &&
httpOnly == cookie.httpOnly &&
@@ -210,7 +205,7 @@ public class Cookie {
@Override
public int hashCode() {
- return Objects.hash(ports, name, value, domain, path, comment, commentUrl, maxAgeSeconds, version, secure, httpOnly, discard);
+ return Objects.hash(ports, name, value, domain, path, comment, commentUrl, maxAgeMillis, version, secure, httpOnly, discard);
}
@Override
@@ -219,51 +214,25 @@ public class Cookie {
ret.append(name).append("=").append(value);
return ret.toString();
}
- // NOTE cookie encoding and decoding:
- // The implementation uses Jetty for server-side (encoding of Set-Cookie and decoding of Cookie header),
- // and java.net.HttpCookie for client-side (encoding of Cookie and decoding of Set-Cookie header).
- //
- // Implementation is RFC-6265 compliant.
public static String toCookieHeader(Iterable<? extends Cookie> cookies) {
- return StreamSupport.stream(cookies.spliterator(), false)
- .map(cookie -> {
- HttpCookie httpCookie = new HttpCookie(cookie.getName(), cookie.getValue());
- httpCookie.setComment(cookie.getComment());
- httpCookie.setCommentURL(cookie.getCommentURL());
- httpCookie.setDiscard(cookie.isDiscard());
- httpCookie.setDomain(cookie.getDomain());
- httpCookie.setHttpOnly(cookie.isHttpOnly());
- httpCookie.setMaxAge(cookie.getMaxAge(TimeUnit.SECONDS));
- httpCookie.setPath(cookie.getPath());
- httpCookie.setSecure(cookie.isSecure());
- httpCookie.setVersion(cookie.getVersion());
- String portList = cookie.ports().stream()
- .map(Number::toString)
- .collect(Collectors.joining(","));
- httpCookie.setPortlist(portList);
- return httpCookie.toString();
- })
- .collect(Collectors.joining(";"));
+ ClientCookieEncoder encoder = ClientCookieEncoder.STRICT;
+ List<org.jboss.netty.handler.codec.http.cookie.Cookie> nettyCookies =
+ StreamSupport.stream(cookies.spliterator(), false)
+ // NOTE: Only name and value is included in Cookie header as of RFC-6265
+ .map(cookie -> new DefaultCookie(cookie.getName(), cookie.getValue()))
+ .collect(Collectors.toList());
+ return encoder.encode(nettyCookies);
}
public static List<Cookie> fromCookieHeader(String headerVal) {
- CookieCutter cookieCutter = new CookieCutter();
- cookieCutter.addCookieField(headerVal);
- return Arrays.stream(cookieCutter.getCookies())
- .map(servletCookie -> {
- Cookie cookie = new Cookie();
- cookie.setName(servletCookie.getName());
- cookie.setValue(servletCookie.getValue());
- cookie.setComment(servletCookie.getComment());
- cookie.setPath(servletCookie.getPath());
- cookie.setDomain(servletCookie.getDomain());
- cookie.setMaxAge(servletCookie.getMaxAge(), TimeUnit.SECONDS);
- cookie.setSecure(servletCookie.getSecure());
- cookie.setVersion(servletCookie.getVersion());
- cookie.setHttpOnly(servletCookie.isHttpOnly());
- return cookie;
- })
+ if (headerVal == null) return Collections.emptyList();
+
+ ServerCookieDecoder decoder = ServerCookieDecoder.STRICT;
+ Set<org.jboss.netty.handler.codec.http.cookie.Cookie> nettyCookies = decoder.decode(headerVal);
+ return nettyCookies.stream()
+ // NOTE: Only name and value is included in Cookie header as of RFC-6265
+ .map(nettyCookie -> new Cookie(nettyCookie.name(), nettyCookie.value()))
.collect(Collectors.toList());
}
@@ -278,60 +247,34 @@ public class Cookie {
// TODO Rename to toSetCookieHeader for Vespa 7
public static List<String> toSetCookieHeaderAll(Iterable<? extends Cookie> cookies) {
- // Ugly, bot Jetty does not provide a dedicated cookie parser (will be included in Jetty 10)
- Response response = new Response(null, null);
- for (Cookie cookie : cookies) {
- response.addSetRFC6265Cookie(
- cookie.getName(),
- cookie.getValue(),
- cookie.getDomain(),
- cookie.getPath(),
- cookie.getMaxAge(TimeUnit.SECONDS),
- cookie.isSecure(),
- cookie.isHttpOnly());
- }
- return new ArrayList<>(response.getHeaders("Set-Cookie"));
+ ServerCookieEncoder encoder = ServerCookieEncoder.STRICT;
+ List<org.jboss.netty.handler.codec.http.cookie.Cookie> nettyCookies =
+ StreamSupport.stream(cookies.spliterator(), false)
+ .map(cookie -> {
+ org.jboss.netty.handler.codec.http.cookie.Cookie nettyCookie
+ = new DefaultCookie(cookie.getName(), cookie.getValue());
+ nettyCookie.setPath(cookie.getPath());
+ nettyCookie.setMaxAge(cookie.getMaxAge(TimeUnit.SECONDS));
+ nettyCookie.setSecure(cookie.isSecure());
+ nettyCookie.setHttpOnly(cookie.isHttpOnly());
+ nettyCookie.setDomain(cookie.getDomain());
+ return nettyCookie;
+ })
+ .collect(Collectors.toList());
+ return encoder.encode(nettyCookies);
}
// TODO Change return type to Cookie for Vespa 7
public static List<Cookie> fromSetCookieHeader(String headerVal) {
- return HttpCookie.parse(headerVal).stream()
- .map(httpCookie -> {
- Cookie cookie = new Cookie();
- cookie.setName(httpCookie.getName());
- cookie.setValue(httpCookie.getValue());
- cookie.setComment(httpCookie.getComment());
- cookie.setCommentUrl(httpCookie.getCommentURL());
- cookie.setDiscard(httpCookie.getDiscard());
- cookie.setDomain(httpCookie.getDomain());
- cookie.setHttpOnly(httpCookie.isHttpOnly());
- cookie.setMaxAge((int)httpCookie.getMaxAge(), TimeUnit.SECONDS);
- cookie.setPath(httpCookie.getPath());
- cookie.setSecure(httpCookie.getSecure());
- cookie.setVersion(httpCookie.getVersion());
- cookie.ports().addAll(parsePortList(httpCookie.getPortlist()));
- return cookie;
- })
- .collect(Collectors.toList());
- }
+ if (headerVal == null) return Collections.emptyList();
-
- private static List<Integer> parsePortList(String rawPortList) {
- if (rawPortList == null) return Collections.emptyList();
-
- List<Integer> ports = new ArrayList<>();
- StringTokenizer tokenizer = new StringTokenizer(rawPortList, ",");
- while (tokenizer.hasMoreTokens()) {
- String rawPort = tokenizer.nextToken().trim();
- if (!rawPort.isEmpty()) {
- try {
- ports.add(Integer.parseInt(rawPort));
- } catch (NumberFormatException e) {
- log.log(Level.FINE, "Unable to parse port: " + rawPort, e);
- }
- }
- }
- return ports;
+ ClientCookieDecoder encoder = ClientCookieDecoder.STRICT;
+ org.jboss.netty.handler.codec.http.cookie.Cookie nettyCookie = encoder.decode(headerVal);
+ return Collections.singletonList(new Cookie(nettyCookie.name(), nettyCookie.value())
+ .setHttpOnly(nettyCookie.isHttpOnly())
+ .setSecure(nettyCookie.isSecure())
+ .setMaxAge(nettyCookie.maxAge(), TimeUnit.SECONDS)
+ .setPath(nettyCookie.path())
+ .setDomain(nettyCookie.domain()));
}
-
}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java
index ca12de72ec2..0c98f294c82 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/CookieTestCase.java
@@ -106,14 +106,14 @@ public class CookieTestCase {
"foo.name=foo.value",
Collections.singletonList(newCookie("foo")));
assertEncodeCookie(
- "foo.name=foo.value;bar.name=bar.value",
+ "foo.name=foo.value; bar.name=bar.value",
Arrays.asList(newCookie("foo"), newCookie("bar")));
}
@Test
public void requireThatSetCookieCanBeEncoded() {
assertEncodeSetCookie(
- Collections.singletonList("foo.name=foo.value;Path=path;Domain=domain;Secure;HttpOnly"),
+ Collections.singletonList("foo.name=foo.value; Path=path; Domain=domain; Secure; HTTPOnly"),
Collections.singletonList(newSetCookie("foo")));
}
@@ -131,7 +131,6 @@ public class CookieTestCase {
}
@Test
- @SuppressWarnings("deprecation")
public void requireThatSetCookieCanBeDecoded() {
final Cookie foo = new Cookie();
foo.setName("foo.name");
@@ -141,7 +140,6 @@ public class CookieTestCase {
foo.setMaxAge(0, TimeUnit.SECONDS);
foo.setSecure(true);
foo.setHttpOnly(true);
- foo.setVersion(1);
assertDecodeSetCookie(foo, "foo.name=foo.value;Max-Age=0;Path=path;Domain=domain;Secure;HTTPOnly;");
final Cookie bar = new Cookie();
@@ -150,7 +148,6 @@ public class CookieTestCase {
bar.setPath("path");
bar.setDomain("domain");
bar.setMaxAge(0, TimeUnit.SECONDS);
- bar.setVersion(1);
assertDecodeSetCookie(bar, "bar.name=bar.value;Max-Age=0;Path=path;Domain=domain;");
}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
index 7ed13decbf6..221a1adc1fe 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
@@ -356,7 +356,7 @@ public class HttpServerTest {
driver.client().get("/status.html")
.expectStatusCode(is(OK))
.expectHeader("Set-Cookie",
- is("foo=bar;Path=/foopath;Domain=.localhost;Secure;HttpOnly"));
+ is("foo=bar; Path=/foopath; Domain=.localhost; Secure; HTTPOnly"));
assertThat(driver.close(), is(true));
}
diff --git a/pom.xml b/pom.xml
index 3eef0c69709..d345d5eb8d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -557,6 +557,11 @@
<version>${curator.version}</version>
</dependency>
<dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty</artifactId>
+ <version>3.10.6.Final</version>
+ </dependency>
+ <dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
diff --git a/zkfacade/pom.xml b/zkfacade/pom.xml
index 45ba8c32372..b6dbbe63f71 100644
--- a/zkfacade/pom.xml
+++ b/zkfacade/pom.xml
@@ -65,6 +65,11 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <!-- Needed to get org.jboss.netty.bootstrap, which zookeeper requires -->
+ <dependency>
+ <groupId>io.netty</groupId>
+ <artifactId>netty</artifactId>
+ </dependency>
</dependencies>
<build>
<plugins>