diff options
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)); } @@ -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> |