Merge branch 'jetty-9.3.x' into gcloud-session-manager
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 71f25a4..c472bd4 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -779,6 +779,11 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.eclipse.jetty.gcloud</groupId>
+      <artifactId>gcloud-session-manager</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-nosql</artifactId>
       <version>${project.version}</version>
diff --git a/jetty-gcloud/gcloud-session-manager/pom.xml b/jetty-gcloud/gcloud-session-manager/pom.xml
new file mode 100644
index 0000000..f83e49d
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.eclipse.jetty.gcloud</groupId>
+    <artifactId>gcloud-parent</artifactId>
+    <version>9.3.4-SNAPSHOT</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>gcloud-session-manager</artifactId>
+  <name>Jetty :: GCloud :: Session Manager</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.gcloud</groupId>
+      <artifactId>gcloud-java-datastore</artifactId>
+      <version>${gcloud.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-servlet</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.websocket</groupId>
+      <artifactId>websocket-server</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+ <properties>
+   <bundle-symbolic-name>${project.groupId}.session</bundle-symbolic-name>
+ </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <extensions>true</extensions>
+          <executions>
+              <execution>
+                  <goals>
+                      <goal>manifest</goal>
+                  </goals>
+                  <configuration>
+                      <instructions>
+                          <Export-Package>org.eclipse.jetty.gcloud.session.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}";</Export-Package>
+                      </instructions>
+                    </configuration>
+                 </execution>
+            </executions>
+      </plugin>
+   </plugins>
+  </build>
+
+</project>
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml b/jetty-gcloud/gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml
new file mode 100644
index 0000000..75caeb1
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/config/etc/jetty-gcloud-sessions.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- ============================================================================================== -->
+  <!-- GCloud configuration  from property file                                                       -->
+  <!-- Note: passwords stored in the property file can use jetty obfuscation (see                     -->
+  <!-- https://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html -->
+  <!-- ============================================================================================== -->
+   <Call id="gconf" class="org.eclipse.jetty.gcloud.session.GCloudConfiguration" name="fromFile">
+      <Arg><Property name="jetty.base" default="."/>/<Property name="jetty.gcloudSession.configFile" default="etc/gcloud.props"/></Arg>
+   </Call>
+
+  <!-- ============================================================================================== -->
+  <!-- Alternate GCloud configuration from properties                                                 -->
+  <!-- Note: passwords can use jetty obfuscation (see                                                 -->
+  <!-- https://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html -->
+  <!-- ============================================================================================== -->
+<!--
+  <New id="gconf" class="org.eclipse.jetty.gcloud.session.GCloudConfiguration">
+    <Set name="projectId"><Property name="jetty.gcloudSession.projectId"/></Set>
+    <Set name="p12File"><Property name="jetty.gcloudSession.p12File"/></Set>
+    <Set name="serviceAccount"><Property name="jetty.gcloudSession.serviceAccount"/></Set>
+    <Set name="password"><Property name="jetty.gcloudSession.password"/></Set>
+  </New>
+-->
+
+
+  <!-- ===================================================================== -->
+  <!-- Configure a GCloudSessionIdManager                                    -->
+  <!-- ===================================================================== -->
+  <Set name="sessionIdManager">
+    <New id="idMgr" class="org.eclipse.jetty.gcloud.session.GCloudSessionIdManager">
+      <Arg>
+        <Ref id="Server"/>
+      </Arg>
+      <Set name="workerName"><Property name="jetty.gcloudSession.workerName" default="node1"/></Set>
+      <Set name="config"><Ref id="gconf"/></Set>
+    </New>
+  </Set>
+
+</Configure>
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/config/modules/gcloud-sessions.mod b/jetty-gcloud/gcloud-session-manager/src/main/config/modules/gcloud-sessions.mod
new file mode 100644
index 0000000..df1a13e
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/config/modules/gcloud-sessions.mod
@@ -0,0 +1,75 @@
+#
+# Jetty GCloudDatastore Session Manager module
+#
+
+[depend]
+annotations
+webapp
+
+[files]
+
+maven://com.google.gcloud/gcloud-java-datastore/0.0.7|lib/gcloud/gcloud-java-datastore-0.0.7.jar
+maven://com.google.gcloud/gcloud-java-core/0.0.7|lib/gcloud/gcloud-java-core-0.0.7.jar
+maven://com.google.auth/google-auth-library-credentials/0.1.0|lib/gcloud/google-auth-library-credentials-0.1.0.jar
+maven://com.google.auth/google-auth-library-oauth2-http/0.1.0|lib/gcloud/google-auth-library-oauth2-http-0.1.0.jar
+maven://com.google.http-client/google-http-client-jackson2/1.19.0|lib/gcloud/google-http-client-jackson2-1.19.0.jar
+maven://com.fasterxml.jackson.core/jackson-core/2.1.3|lib/gcloud/jackson-core-2.1.3.jar
+maven://com.google.http-client/google-http-client/1.20.0|lib/gcloud/google-http-client-1.20.0.jar
+maven://com.google.code.findbugs/jsr305/1.3.9|lib/gcloud/jsr305-1.3.9.jar
+maven://org.apache.httpcomponents/httpclient/4.0.1|lib/gcloud/httpclient-4.0.1.jar
+maven://org.apache.httpcomponents/httpcore/4.0.1|lib/gcloud/httpcore-4.0.1.jar
+maven://commons-logging/commons-logging/1.1.1|lib/gcloud/commons-logging-1.1.1.jar
+maven://commons-codec/commons-codec/1.3|lib/gcloud/commons-codec-1.3.jar
+maven://com.google.oauth-client/google-oauth-client/1.20.0|lib/gcloud//google-oauth-client-1.20.0.jar
+maven://com.google.guava/guava/18.0|lib/gcloud/guava-18.0.jar
+maven://com.google.api-client/google-api-client-appengine/1.20.0|lib/gcloud/google-api-client-appengine-1.20.0.jar
+maven://com.google.oauth-client/google-oauth-client-appengine/1.20.0|lib/gcloud/google-oauth-client-appengine-1.20.0.jar
+maven://com.google.oauth-client/google-oauth-client-servlet/1.20.0|lib/gcloud/google-oauth-client-servlet-1.20.0.jar
+maven://com.google.http-client/google-http-client-jdo/1.20.0|lib/gcloud/google-http-client-jdo-1.20.0.jar
+maven://com.google.api-client/google-api-client-servlet/1.20.0|lib/gcloud/google-api-client-servlet-1.20.0.jar
+maven://javax.jdo/jdo2-api/2.3-eb|lib/gcloud/jdo2-api-2.3-eb.jar
+maven://javax.transaction/transaction-api/1.1|lib/gcloud/transaction-api-1.1.jar
+maven://com.google.http-client/google-http-client-appengine/1.20.0|lib/gcloud/google-http-client-appengine-1.20.0.jar
+maven://com.google.http-client/google-http-client-jackson/1.20.0|lib/gcloud/google-http-client-jackson-1.20.0.jar
+maven://org.codehaus.jackson/jackson-core-asl/1.9.11|lib/gcloud/jackson-core-asl-1.9.11.jar
+maven://joda-time/joda-time/2.8.2|lib/gcloud/joda-time-2.8.2.jar
+maven://org.json/json/20090211|lib/gcloud/json-20090211.jar
+maven://com.google.apis/google-api-services-datastore-protobuf/v1beta2-rev1-2.1.2|lib/gcloud/google-api-services-datastore-protobuf-v1beta2-rev1-2.1.2.jar
+maven://com.google.protobuf/protobuf-java/2.5.0|lib/gcloud/protobuf-java-2.5.0.jar
+maven://com.google.http-client/google-http-client-protobuf/1.15.0-rc|lib/gcloud/google-http-client-protobuf-1.15.0-rc.jar
+maven://com.google.api-client/google-api-client/1.15.0-rc|lib/gcloud/google-api-client-1.15.0-rc.jar
+maven://com.google.apis/google-api-services-datastore/v1beta2-rev23-1.19.0|lib/gcloud/google-api-services-datastore-v1beta2-rev23-1.19.0.jar
+
+[lib]
+lib/gcloud-session-manager-${jetty.version}.jar
+lib/gcloud/*.jar
+
+[xml]
+etc/jetty-gcloud-sessions.xml
+
+[license]
+GCloudDatastore is an open source project hosted on Github and released under the Apache 2.0 license.
+https://github.com/GoogleCloudPlatform/gcloud-java
+http://www.apache.org/licenses/LICENSE-2.0.html
+
+[ini-template]
+## GCloudDatastore Session config
+
+## Unique identifier for this node in the cluster
+# jetty.gcloudSession.workerName=node1
+
+## Name of properties files containing gcloud config
+#jetty.gcloudSession.configFilet=etc/gcloud.props
+
+##Alternative to properties file, individual properties
+##  the gcloud projectId
+#jetty.gcloudSession.projectId=
+
+##  the p12 file associated with the project
+#jetty.gcloudSession.p12File=
+
+##  the serviceAccount for the Datastore
+#jetty.gcloudSession.serviceAccount=
+
+##  the password (can be obfuscated)
+#jetty.gcloudSession.password=
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudConfiguration.java b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudConfiguration.java
new file mode 100644
index 0000000..c1a9596
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudConfiguration.java
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.security.Password;
+
+import com.google.gcloud.AuthCredentials;
+import com.google.gcloud.datastore.DatastoreOptions;
+
+
+
+/**
+ * GCloudConfiguration
+ *
+ *
+ */
+public class GCloudConfiguration
+{
+    public static final String PROJECT_ID = "projectId";
+    public static final String P12 = "p12";
+    public static final String PASSWORD = "password";
+    public static final String SERVICE_ACCOUNT = "serviceAccount";
+    
+    private String _projectId;
+    private File _p12File;
+    private String _serviceAccount;
+    private String _password;
+    private AuthCredentials _authCredentials;
+    private DatastoreOptions _options;
+    
+    /**
+     * Generate a configuration from a properties file
+     * 
+     * @param propsFile
+     * @return
+     * @throws IOException
+     */
+    public static GCloudConfiguration fromFile(String propsFile)
+    throws IOException
+    {
+        if (propsFile == null)
+            throw new IllegalArgumentException ("Null properties file");
+        
+        File f = new File(propsFile);
+        if (!f.exists())
+            throw new IllegalArgumentException("No such file "+f.getAbsolutePath());
+        Properties props = new Properties();
+        try (FileInputStream is=new FileInputStream(f))
+        {
+            props.load(is);
+        }
+        
+        GCloudConfiguration config = new GCloudConfiguration();
+        config.setProjectId(props.getProperty(PROJECT_ID));
+        config.setP12File(props.getProperty(P12));
+        config.setPassword(props.getProperty(PASSWORD));
+        config.setServiceAccount(props.getProperty(SERVICE_ACCOUNT));
+        return config;
+    }
+    
+    
+    
+    public String getProjectId()
+    {
+        return _projectId;
+    }
+
+    public File getP12File()
+    {
+        return _p12File;
+    }
+
+    public String getServiceAccount()
+    {
+        return _serviceAccount;
+    }
+
+
+    public void setProjectId(String projectId)
+    {
+        checkForModification();
+        _projectId = projectId;
+    }
+
+    public void setP12File (String file)
+    {
+        checkForModification();
+        _p12File = new File(file);
+    }
+    
+    
+    public void setServiceAccount (String serviceAccount)
+    {
+        checkForModification();
+        _serviceAccount = serviceAccount;
+    }
+    
+    
+    public void setPassword (String pwd)
+    {
+        checkForModification();
+        Password p = new Password(pwd);
+        _password = p.toString();
+    }
+
+
+    public DatastoreOptions getDatastoreOptions ()
+            throws Exception
+    {
+        if (_options == null)
+        {
+            _options = DatastoreOptions.builder()
+                    .projectId(_projectId)
+                    .authCredentials(getAuthCredentials())
+                    .build();
+        }
+        return _options;
+    }
+
+    /**
+     * @return
+     * @throws Exception
+     */
+    public AuthCredentials getAuthCredentials()
+    throws Exception
+    {
+        if (_authCredentials == null)
+        {
+            if (_password == null)
+                throw new IllegalStateException("No password");
+            if (_projectId == null)
+                throw new IllegalStateException("No project id");
+
+            if (_projectId == null)
+                throw new IllegalStateException("No project id");
+
+            if (_p12File == null || !_p12File.exists())
+                throw new IllegalStateException("No p12 file: "+(_p12File==null?"null":_p12File.getAbsolutePath()));
+
+            if (_serviceAccount == null)
+                throw new IllegalStateException("No service account");
+
+            char[] pwdChars = _password.toCharArray();
+            KeyStore keystore = KeyStore.getInstance("PKCS12");
+            keystore.load(new FileInputStream(getP12File()), pwdChars);
+            PrivateKey privateKey = (PrivateKey) keystore.getKey("privatekey", pwdChars);
+            _authCredentials = AuthCredentials.createFor(getServiceAccount(), privateKey);
+        }
+        return _authCredentials;
+    }
+    
+    /**
+     * @throws IllegalStateException
+     */
+    protected void checkForModification () throws IllegalStateException
+    {
+        if (_authCredentials != null || _options != null)
+            throw new IllegalStateException("Cannot modify auth configuration after datastore initialized");     
+    }
+}
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionIdManager.java b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionIdManager.java
new file mode 100644
index 0000000..e8a32a5
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionIdManager.java
@@ -0,0 +1,323 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.server.session.AbstractSessionIdManager;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+import com.google.gcloud.datastore.Entity;
+import com.google.gcloud.datastore.Key;
+import com.google.gcloud.datastore.KeyFactory;
+
+
+
+/**
+ * GCloudSessionIdManager
+ *
+ * 
+ * 
+ */
+public class GCloudSessionIdManager extends AbstractSessionIdManager
+{
+    private  final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+    public static final int DEFAULT_IDLE_EXPIRY_MULTIPLE = 2;
+    public static final String KIND = "GCloudSessionId";
+    private Server _server;
+    private Datastore _datastore;
+    private KeyFactory _keyFactory;
+    private GCloudConfiguration _config;
+    
+    
+    
+ 
+    /**
+     * @param server
+     */
+    public GCloudSessionIdManager(Server server)
+    {
+        super();
+        _server = server;
+    }
+
+    /**
+     * @param server
+     * @param random
+     */
+    public GCloudSessionIdManager(Server server, Random random)
+    {
+       super(random);
+       _server = server;
+    }
+
+
+
+    /** 
+     * Start the id manager.
+     * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_config == null)
+            throw new IllegalStateException("No gcloud configuration specified");       
+        
+
+        _datastore = DatastoreFactory.instance().get(_config.getDatastoreOptions());
+        _keyFactory = _datastore.newKeyFactory().kind(KIND);
+  
+        super.doStart();
+    }
+
+
+    
+    /** 
+     * Stop the id manager
+     * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+    }
+
+    
+   
+
+    
+    /** 
+     * Check to see if the given session id is being
+     * used by a session in any context.
+     * 
+     * This method will consult the cluster.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#idInUse(java.lang.String)
+     */
+    @Override
+    public boolean idInUse(String id)
+    {
+        if (id == null)
+            return false;
+        
+        String clusterId = getClusterId(id);
+        
+        //ask the cluster - this should also tickle the idle expiration timer on the sessionid entry
+        //keeping it valid
+        try
+        {
+            return exists(clusterId);
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem checking inUse for id="+clusterId, e);
+            return false;
+        }
+        
+    }
+
+    /** 
+     * Remember a new in-use session id.
+     * 
+     * This will save the in-use session id to the cluster.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#addSession(javax.servlet.http.HttpSession)
+     */
+    @Override
+    public void addSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+
+        //insert into the store
+        insert (((AbstractSession)session).getClusterId());
+    }
+
+  
+
+    
+    public GCloudConfiguration getConfig()
+    {
+        return _config;
+    }
+
+    public void setConfig(GCloudConfiguration config)
+    {
+        _config = config;
+    }
+
+    
+    
+    /** 
+     * Remove a session id from the list of in-use ids.
+     * 
+     * This will remvove the corresponding session id from the cluster.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#removeSession(javax.servlet.http.HttpSession)
+     */
+    @Override
+    public void removeSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+
+        //delete from the cache
+        delete (((AbstractSession)session).getClusterId());
+    }
+
+    /** 
+     * Remove a session id. This compels all other contexts who have a session
+     * with the same id to also remove it.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+     */
+    @Override
+    public void invalidateAll(String id)
+    {
+        //delete the session id from list of in-use sessions
+        delete (id);
+
+
+        //tell all contexts that may have a session object with this id to
+        //get rid of them
+        Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+        for (int i=0; contexts!=null && i<contexts.length; i++)
+        {
+            SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+            if (sessionHandler != null)
+            {
+                SessionManager manager = sessionHandler.getSessionManager();
+
+                if (manager != null && manager instanceof GCloudSessionManager)
+                {
+                    ((GCloudSessionManager)manager).invalidateSession(id);
+                }
+            }
+        }
+
+    }
+
+    /** 
+     * Change a session id. 
+     * 
+     * Typically this occurs when a previously existing session has passed through authentication.
+     * 
+     * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#renewSessionId(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
+     */
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request)
+    {
+        //generate a new id
+        String newClusterId = newSessionId(request.hashCode());
+
+        delete(oldClusterId);
+        insert(newClusterId);
+
+
+        //tell all contexts to update the id 
+        Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+        for (int i=0; contexts!=null && i<contexts.length; i++)
+        {
+            SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+            if (sessionHandler != null) 
+            {
+                SessionManager manager = sessionHandler.getSessionManager();
+
+                if (manager != null && manager instanceof GCloudSessionManager)
+                {
+                    ((GCloudSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
+                }
+            }
+        }
+
+    }
+
+    
+    
+    /**
+     * Ask the datastore if a particular id exists.
+     * 
+     * @param id
+     * @return
+     */
+    protected boolean exists (String id)
+    {
+        if (_datastore == null)
+            throw new IllegalStateException ("No DataStore");
+        Key key = _keyFactory.newKey(id);
+        return _datastore.get(key) != null;
+    }
+    
+
+    /**
+     * Put a session id into the cluster.
+     * 
+     * @param id
+     */
+    protected void insert (String id)
+    {        
+        if (_datastore == null)
+            throw new IllegalStateException ("No DataStore");
+
+        Entity entity = Entity.builder(makeKey(id))
+                        .set("id", id).build();
+        _datastore.put(entity);
+    }
+
+   
+   
+    
+    /**
+     * Remove a session id from the cluster.
+     * 
+     * @param id
+     */
+    protected void delete (String id)
+    {
+        if (_datastore == null)
+            throw new IllegalStateException ("No DataStore");
+        
+        _datastore.delete(makeKey(id));
+    }
+    
+    
+
+    /**
+     * Generate a unique key from the session id.
+     * 
+     * @param id
+     * @return
+     */
+    protected Key makeKey (String id)
+    {
+        return _keyFactory.newKey(id);
+    }
+}
diff --git a/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionManager.java b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionManager.java
new file mode 100644
index 0000000..b6b39a9
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/main/java/org/eclipse/jetty/gcloud/session/GCloudSessionManager.java
@@ -0,0 +1,1298 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.session.AbstractSession;
+import org.eclipse.jetty.server.session.AbstractSessionManager;
+import org.eclipse.jetty.server.session.MemSession;
+import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+import com.google.gcloud.datastore.Blob;
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+import com.google.gcloud.datastore.Entity;
+import com.google.gcloud.datastore.GqlQuery;
+import com.google.gcloud.datastore.Key;
+import com.google.gcloud.datastore.KeyFactory;
+import com.google.gcloud.datastore.Query;
+import com.google.gcloud.datastore.Query.ResultType;
+import com.google.gcloud.datastore.QueryResults;
+
+
+
+/**
+ * GCloudSessionManager
+ * 
+ * 
+ */
+public class GCloudSessionManager extends AbstractSessionManager
+{
+    private  final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+    
+    
+    public static final String KIND = "GCloudSession";
+    public static final int DEFAULT_MAX_QUERY_RESULTS = 100;
+    public static final long DEFAULT_SCAVENGE_SEC = 600; 
+    
+    /**
+     * Sessions known to this node held in memory
+     */
+    private ConcurrentHashMap<String, GCloudSessionManager.Session> _sessions;
+
+    
+    /**
+     * The length of time a session can be in memory without being checked against
+     * the cluster. A value of 0 indicates that the session is never checked against
+     * the cluster - the current node is considered to be the master for the session.
+     *
+     */
+    private long _staleIntervalSec = 0;
+    
+    protected Scheduler.Task _task; //scavenge task
+    protected Scheduler _scheduler;
+    protected Scavenger _scavenger;
+    protected long _scavengeIntervalMs = 1000L * DEFAULT_SCAVENGE_SEC; //10mins
+    protected boolean _ownScheduler;
+    
+    private Datastore _datastore;
+    private KeyFactory _keyFactory;
+
+
+    private SessionEntityConverter _converter;
+
+
+    private int _maxResults = DEFAULT_MAX_QUERY_RESULTS;
+
+
+    /**
+     * Scavenger
+     *
+     */
+    protected class Scavenger implements Runnable
+    {
+
+        @Override
+        public void run()
+        {
+           try
+           {
+               scavenge();
+           }
+           finally
+           {
+               if (_scheduler != null && _scheduler.isRunning())
+                   _task = _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
+           }
+        }
+    }
+
+    /**
+     * SessionEntityConverter
+     *
+     *
+     */
+    public class SessionEntityConverter
+    {
+        public  final String CLUSTERID = "clusterId";
+        public  final String CONTEXTPATH = "contextPath";
+        public  final String VHOST = "vhost";
+        public  final String ACCESSED = "accessed";
+        public  final String LASTACCESSED = "lastAccessed";
+        public  final String CREATETIME = "createTime";
+        public  final  String COOKIESETTIME = "cookieSetTime";
+        public  final String LASTNODE = "lastNode";
+        public  final String EXPIRY = "expiry";
+        public  final  String MAXINACTIVE = "maxInactive";
+        public  final  String ATTRIBUTES = "attributes";
+
+      
+        
+        public Entity entityFromSession (Session session, Key key) throws Exception
+        {
+            if (session == null)
+                return null;
+            
+            Entity entity = null;
+            
+            //serialize the attribute map
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(session.getAttributeMap());
+            oos.flush();
+            
+            //turn a session into an entity
+            entity = Entity.builder(key)
+                    .set(CLUSTERID, session.getId())
+                    .set(CONTEXTPATH, session.getContextPath())
+                    .set(VHOST, session.getVHost())
+                    .set(ACCESSED, session.getAccessed())
+                    .set(LASTACCESSED, session.getLastAccessedTime())
+                    .set(CREATETIME, session.getCreationTime())
+                    .set(COOKIESETTIME, session.getCookieSetTime())
+                    .set(LASTNODE,session.getLastNode())
+                    .set(EXPIRY, session.getExpiry())
+                    .set(MAXINACTIVE, session.getMaxInactiveInterval())
+                    .set(ATTRIBUTES, Blob.copyFrom(baos.toByteArray())).build();
+                     
+            return entity;
+        }
+        
+        public Session sessionFromEntity (Entity entity) throws Exception
+        {
+            if (entity == null)
+                return null;
+
+            final AtomicReference<Session> reference = new AtomicReference<Session>();
+            final AtomicReference<Exception> exception = new AtomicReference<Exception>();
+            Runnable load = new Runnable()
+            {
+                public void run ()
+                {
+                    try
+                    {
+                        //turn an entity into a Session
+                        String clusterId = entity.getString(CLUSTERID);
+                        String contextPath = entity.getString(CONTEXTPATH);
+                        String vhost = entity.getString(VHOST);
+                        long accessed = entity.getLong(ACCESSED);
+                        long lastAccessed = entity.getLong(LASTACCESSED);
+                        long createTime = entity.getLong(CREATETIME);
+                        long cookieSetTime = entity.getLong(COOKIESETTIME);
+                        String lastNode = entity.getString(LASTNODE);
+                        long expiry = entity.getLong(EXPIRY);
+                        long maxInactive = entity.getLong(MAXINACTIVE);
+                        Blob blob = (Blob) entity.getBlob(ATTRIBUTES);
+
+                        Session session = new Session (clusterId, createTime, accessed, maxInactive);
+                        session.setLastNode(lastNode);
+                        session.setContextPath(contextPath);
+                        session.setVHost(vhost);
+                        session.setCookieSetTime(cookieSetTime);
+                        session.setLastAccessedTime(lastAccessed);
+                        session.setLastNode(lastNode);
+                        session.setExpiry(expiry);
+                        try (ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(blob.asInputStream()))
+                        {
+                            Object o = ois.readObject();
+                            session.addAttributes((Map<String,Object>)o);
+                        }
+                        reference.set(session);
+                    }
+                    catch (Exception e)
+                    {
+                        exception.set(e);
+                    }
+                }
+            };
+            
+            if (_context==null)
+                load.run();
+            else
+                _context.getContextHandler().handle(null,load);
+   
+           
+            if (exception.get() != null)
+            {
+                exception.get().printStackTrace();
+                throw exception.get();
+            }
+            
+            return reference.get();
+        }
+    }
+    
+    /*
+     * Every time a Session is put into the cache one of these objects
+     * is created to copy the data out of the in-memory session, and 
+     * every time an object is read from the cache one of these objects
+     * a fresh Session object is created based on the data held by this
+     * object.
+     */
+    public class SerializableSessionData implements Serializable
+    {
+        /**
+         * 
+         */
+        private static final long serialVersionUID = -7779120106058533486L;
+        String clusterId;
+        String contextPath;
+        String vhost;
+        long accessed;
+        long lastAccessed;
+        long createTime;
+        long cookieSetTime;
+        String lastNode;
+        long expiry;
+        long maxInactive;
+        Map<String, Object> attributes;
+
+        public SerializableSessionData()
+        {
+
+        }
+
+       
+       public SerializableSessionData(Session s)
+       {
+           clusterId = s.getClusterId();
+           contextPath = s.getContextPath();
+           vhost = s.getVHost();
+           accessed = s.getAccessed();
+           lastAccessed = s.getLastAccessedTime();
+           createTime = s.getCreationTime();
+           cookieSetTime = s.getCookieSetTime();
+           lastNode = s.getLastNode();
+           expiry = s.getExpiry();
+           maxInactive = s.getMaxInactiveInterval();
+           attributes = s.getAttributeMap(); // TODO pointer, not a copy
+       }
+        
+        private void writeObject(java.io.ObjectOutputStream out) throws IOException
+        {  
+            out.writeUTF(clusterId); //session id
+            out.writeUTF(contextPath); //context path
+            out.writeUTF(vhost); //first vhost
+
+            out.writeLong(accessed);//accessTime
+            out.writeLong(lastAccessed); //lastAccessTime
+            out.writeLong(createTime); //time created
+            out.writeLong(cookieSetTime);//time cookie was set
+            out.writeUTF(lastNode); //name of last node managing
+      
+            out.writeLong(expiry); 
+            out.writeLong(maxInactive);
+            out.writeObject(attributes);
+        }
+        
+        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
+        {
+            clusterId = in.readUTF();
+            contextPath = in.readUTF();
+            vhost = in.readUTF();
+            
+            accessed = in.readLong();//accessTime
+            lastAccessed = in.readLong(); //lastAccessTime
+            createTime = in.readLong(); //time created
+            cookieSetTime = in.readLong();//time cookie was set
+            lastNode = in.readUTF(); //last managing node
+            expiry = in.readLong(); 
+            maxInactive = in.readLong();
+            attributes = (HashMap<String,Object>)in.readObject();
+        }
+        
+    }
+    
+
+    
+    /**
+     * Session
+     *
+     * Representation of a session in local memory.
+     */
+    public class Session extends MemSession
+    {
+        
+        private ReentrantLock _lock = new ReentrantLock();
+        
+        /**
+         * The (canonical) context path for with which this session is associated
+         */
+        private String _contextPath;
+        
+        
+        
+        /**
+         * The time in msec since the epoch at which this session should expire
+         */
+        private long _expiryTime; 
+        
+        
+        /**
+         * Time in msec since the epoch at which this session was last read from cluster
+         */
+        private long _lastSyncTime;
+        
+        
+        /**
+         * The workername of last node known to be managing the session
+         */
+        private String _lastNode;
+        
+        
+        /**
+         * If dirty, session needs to be (re)sent to cluster
+         */
+        protected boolean _dirty=false;
+        
+        
+     
+
+        /**
+         * Any virtual hosts for the context with which this session is associated
+         */
+        private String _vhost;
+
+        
+        /**
+         * Count of how many threads are active in this session
+         */
+        private AtomicInteger _activeThreads = new AtomicInteger(0);
+        
+        
+        
+        
+        /**
+         * A new session.
+         * 
+         * @param request
+         */
+        protected Session (HttpServletRequest request)
+        {
+            super(GCloudSessionManager.this,request);
+            long maxInterval = getMaxInactiveInterval();
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+            _lastNode = getSessionIdManager().getWorkerName();
+           setVHost(GCloudSessionManager.getVirtualHost(_context));
+           setContextPath(GCloudSessionManager.getContextPath(_context));
+           _activeThreads.incrementAndGet(); //access will not be called on a freshly created session so increment here
+        }
+        
+        
+    
+        
+        /**
+         * A restored session.
+         * 
+         * @param sessionId
+         * @param created
+         * @param accessed
+         * @param maxInterval
+         */
+        protected Session (String sessionId, long created, long accessed, long maxInterval)
+        {
+            super(GCloudSessionManager.this, created, accessed, sessionId);
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+        }
+        
+        /** 
+         * Called on entry to the session.
+         * 
+         * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
+         */
+        @Override
+        protected boolean access(long time)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Access session({}) for context {} on worker {}", getId(), getContextPath(), getSessionIdManager().getWorkerName());
+            try
+            {
+
+                long now = System.currentTimeMillis();
+                //lock so that no other thread can call access or complete until the first one has refreshed the session object if necessary
+                _lock.lock();
+                //a request thread is entering
+                if (_activeThreads.incrementAndGet() == 1)
+                {
+                    //if the first thread, check that the session in memory is not stale, if we're checking for stale sessions
+                    if (getStaleIntervalSec() > 0  && (now - getLastSyncTime()) >= (getStaleIntervalSec() * 1000L))
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("Acess session({}) for context {} on worker {} stale session. Reloading.", getId(), getContextPath(), getSessionIdManager().getWorkerName());
+                        refresh();
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {            
+                _lock.unlock();
+            }
+
+            if (super.access(time))
+            {
+                int maxInterval=getMaxInactiveInterval();
+                _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
+                return true;
+            }
+            return false;
+        }
+
+
+        /**
+         * Exit from session
+         * @see org.eclipse.jetty.server.session.AbstractSession#complete()
+         */
+        @Override
+        protected void complete()
+        {
+            super.complete();
+
+            //lock so that no other thread that might be calling access can proceed until this complete is done
+            _lock.lock();
+
+            try
+            {
+                //if this is the last request thread to be in the session
+                if (_activeThreads.decrementAndGet() == 0)
+                {
+                    try
+                    {
+                        //an invalid session will already have been removed from the
+                        //local session map and deleted from the cluster. If its valid save
+                        //it to the cluster.
+                        //TODO consider doing only periodic saves if only the last access
+                        //time to the session changes
+                        if (isValid())
+                        {
+                            //if session still valid && its dirty or stale or never been synced, write it to the cluster
+                            //otherwise, we just keep the updated last access time in memory
+                            if (_dirty || getLastSyncTime() == 0 || isStale(System.currentTimeMillis()))
+                            {
+                                willPassivate();
+                                save(this);
+                                didActivate();
+                            }
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.warn("Problem saving session({})",getId(), e);
+                    } 
+                    finally
+                    {
+                        _dirty = false;
+                    }
+                }
+            }
+            finally
+            {
+                _lock.unlock();
+            }
+        }
+        
+        /** Test if the session is stale
+         * @param atTime
+         * @return
+         */
+        protected boolean isStale (long atTime)
+        {
+            return (getStaleIntervalSec() > 0) && (atTime - getLastSyncTime() >= (getStaleIntervalSec()*1000L));
+        }
+        
+        
+        /** Test if the session is dirty
+         * @return
+         */
+        protected boolean isDirty ()
+        {
+            return _dirty;
+        }
+
+        /** 
+         * Expire the session.
+         * 
+         * @see org.eclipse.jetty.server.session.AbstractSession#timeout()
+         */
+        @Override
+        protected void timeout()
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Timing out session {}", getId());
+            super.timeout();
+        }
+        
+      
+        
+        /**
+         * Reload the session from the cluster. If the node that
+         * last managed the session from the cluster is ourself,
+         * then the session does not need refreshing.
+         * NOTE: this method MUST be called with sufficient locks
+         * in place to prevent 2 or more concurrent threads from
+         * simultaneously updating the session.
+         */
+        private void refresh () 
+        throws Exception
+        {
+            //get fresh copy from the cluster
+            Session fresh = load(makeKey(getClusterId(), _context));
+
+            //if the session no longer exists, invalidate
+            if (fresh == null)
+            {
+                invalidate();
+                return;
+            }
+
+            //cluster copy assumed to be the same as we were the last
+            //node to manage it
+            if (fresh.getLastNode().equals(getLastNode()))
+                return;
+
+            setLastNode(getSessionIdManager().getWorkerName());
+            
+            //prepare for refresh
+            willPassivate();
+
+            //if fresh has no attributes, remove them
+            if (fresh.getAttributes() == 0)
+                this.clearAttributes();
+            else
+            {
+                //reconcile attributes
+                for (String key:fresh.getAttributeMap().keySet())
+                {
+                    Object freshvalue = fresh.getAttribute(key);
+
+                    //session does not already contain this attribute, so bind it
+                    if (getAttribute(key) == null)
+                    { 
+                        doPutOrRemove(key,freshvalue);
+                        bindValue(key,freshvalue);
+                    }
+                    else //session already contains this attribute, update its value
+                    {
+                        doPutOrRemove(key,freshvalue);
+                    }
+
+                }
+                // cleanup, remove values from session, that don't exist in data anymore:
+                for (String key : getNames())
+                {
+                    if (fresh.getAttribute(key) == null)
+                    {
+                        Object oldvalue = getAttribute(key);
+                        doPutOrRemove(key,null);
+                        unbindValue(key,oldvalue);
+                    }
+                }
+            }
+            //finish refresh
+            didActivate();
+        }
+
+
+        public void setExpiry (long expiry)
+        {
+            _expiryTime = expiry;
+        }
+        
+
+        public long getExpiry ()
+        {
+            return _expiryTime;
+        }
+        
+        public boolean isExpiredAt (long time)
+        {
+            if (_expiryTime <= 0)
+                return false; //never expires
+            
+            return  (_expiryTime <= time);
+        }
+        
+        public void swapId (String newId, String newNodeId)
+        {
+            //TODO probably synchronize rather than use the access/complete lock?
+            _lock.lock();
+            setClusterId(newId);
+            setNodeId(newNodeId);
+            _lock.unlock();
+        }
+        
+        @Override
+        public void setAttribute (String name, Object value)
+        {
+            Object old = changeAttribute(name, value);
+            if (value == null && old == null)
+                return; //if same as remove attribute but attribute was already removed, no change
+            
+           _dirty = true;
+        }
+        
+        
+        public String getContextPath()
+        {
+            return _contextPath;
+        }
+
+
+        public void setContextPath(String contextPath)
+        {
+            this._contextPath = contextPath;
+        }
+
+
+        public String getVHost()
+        {
+            return _vhost;
+        }
+
+
+        public void setVHost(String vhost)
+        {
+            this._vhost = vhost;
+        }
+        
+        public String getLastNode()
+        {
+            return _lastNode;
+        }
+
+
+        public void setLastNode(String lastNode)
+        {
+            _lastNode = lastNode;
+        }
+
+
+        public long getLastSyncTime()
+        {
+            return _lastSyncTime;
+        }
+
+
+        public void setLastSyncTime(long lastSyncTime)
+        {
+            _lastSyncTime = lastSyncTime;
+        }
+
+    }
+
+
+
+    
+    /**
+     * Start the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
+     */
+    @Override
+    public void doStart() throws Exception
+    {
+        if (_sessionIdManager == null)
+            throw new IllegalStateException("No session id manager defined");
+        
+        GCloudConfiguration config = ((GCloudSessionIdManager)_sessionIdManager).getConfig();
+        if (config == null)
+            throw new IllegalStateException("No gcloud configuration");
+        
+        
+        _datastore = DatastoreFactory.instance().get(config.getDatastoreOptions());
+        _keyFactory = _datastore.newKeyFactory().kind(KIND);
+        _converter = new SessionEntityConverter();       
+        _sessions = new ConcurrentHashMap<String, Session>();
+
+        //try and use a common scheduler, fallback to own
+        _scheduler = getSessionHandler().getServer().getBean(Scheduler.class);
+        if (_scheduler == null)
+        {
+            _scheduler = new ScheduledExecutorScheduler();
+            _ownScheduler = true;
+            _scheduler.start();
+        }
+        else if (!_scheduler.isStarted())
+            throw new IllegalStateException("Shared scheduler not started");
+ 
+        setScavengeIntervalSec(getScavengeIntervalSec());
+        
+        super.doStart();
+    }
+
+
+    /**
+     * Stop the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
+     */
+    @Override
+    public void doStop() throws Exception
+    {
+        super.doStop();
+
+        if (_task!=null)
+            _task.cancel();
+        _task=null;
+        if (_ownScheduler && _scheduler !=null)
+            _scheduler.stop();
+        _scheduler = null;
+
+        _sessions.clear();
+        _sessions = null;
+    }
+
+
+
+    /**
+     * Look for sessions in local memory that have expired.
+     */
+    public void scavenge ()
+    {
+        try
+        {
+            //scavenge in the database every so often
+            scavengeGCloudDataStore();
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem scavenging", e);
+        }
+    }
+
+ 
+    
+    protected void scavengeGCloudDataStore()
+    throws Exception
+    {
+       
+        //query the datastore for sessions that have expired
+        long now = System.currentTimeMillis();
+        
+        //give a bit of leeway so we don't immediately something that has only just expired a nanosecond ago
+        now = now - (_scavengeIntervalMs/2);
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("Scavenging for sessions expired before "+now);
+
+
+        GqlQuery.Builder builder = Query.gqlQueryBuilder(ResultType.ENTITY, "select * from "+KIND+" where expiry < @1 limit "+_maxResults);
+        builder.allowLiteral(true);
+        builder.addBinding(now);
+        Query<Entity> query = builder.build();
+        QueryResults<Entity> results = _datastore.run(query);
+        
+        while (results.hasNext())
+        {          
+            Entity sessionEntity = results.next();
+            scavengeSession(sessionEntity);        
+        }
+
+    }
+
+    /**
+     * Scavenge a session that has expired
+     * @param e
+     * @throws Exception
+     */
+    protected void scavengeSession (Entity e)
+            throws Exception
+    {
+        long now = System.currentTimeMillis();
+        Session session = _converter.sessionFromEntity(e);
+        if (session == null)
+            return;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Scavenging session: {}",session.getId());
+        //if the session isn't in memory already, put it there so we can do a normal timeout call
+         Session memSession =  _sessions.putIfAbsent(session.getId(), session);
+         if (memSession == null)
+         {
+             memSession = session;
+         }
+
+        //final check
+        if (memSession.isExpiredAt(now))
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Session {} is definitely expired", memSession.getId());
+            memSession.timeout();   
+        }
+    }
+
+    public long getScavengeIntervalSec ()
+    {
+        return _scavengeIntervalMs/1000;
+    }
+
+    
+    
+    /**
+     * Set the interval between runs of the scavenger. It should not be run too
+     * often.
+     * 
+     * 
+     * @param sec
+     */
+    public void setScavengeIntervalSec (long sec)
+    {
+
+        long old_period=_scavengeIntervalMs;
+        long period=sec*1000L;
+
+        _scavengeIntervalMs=period;
+
+        if (_scavengeIntervalMs > 0)
+        {
+            //add a bit of variability into the scavenge time so that not all
+            //nodes with the same scavenge time sync up
+            long tenPercent = _scavengeIntervalMs/10;
+            if ((System.currentTimeMillis()%2) == 0)
+                _scavengeIntervalMs += tenPercent;
+            if (LOG.isDebugEnabled())
+                LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Scavenging disabled"); 
+        }
+
+ 
+        
+        synchronized (this)
+        {
+            if (_scheduler != null && (period!=old_period || _task==null))
+            {
+                //clean up any previously scheduled scavenger
+                if (_task!=null)
+                    _task.cancel();
+
+                //start a new one
+                if (_scavengeIntervalMs > 0)
+                {
+                    if (_scavenger == null)
+                        _scavenger = new Scavenger();
+
+                    _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
+                }
+            }
+        }
+    }
+    
+    
+    public long getStaleIntervalSec()
+    {
+        return _staleIntervalSec;
+    }
+
+
+    public void setStaleIntervalSec(long staleIntervalSec)
+    {
+        _staleIntervalSec = staleIntervalSec;
+    }
+    
+    
+    public int getMaxResults()
+    {
+        return _maxResults;
+    }
+
+
+    public void setMaxResults(int maxResults)
+    {
+        if (_maxResults <= 0)
+            _maxResults = DEFAULT_MAX_QUERY_RESULTS;
+        else
+            _maxResults = maxResults;
+    }
+
+
+    /** 
+     * Add a new session for the context related to this session manager
+     * 
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
+     */
+    @Override
+    protected void addSession(AbstractSession session)
+    {
+        if (session==null)
+            return;
+        
+        if (LOG.isDebugEnabled()) LOG.debug("Adding session({}) to session manager for context {} on worker {}",session.getClusterId(), getContextPath(getContext()),getSessionIdManager().getWorkerName() + " with lastnode="+((Session)session).getLastNode());
+        _sessions.put(session.getClusterId(), (Session)session);
+        
+        try
+        {     
+                session.willPassivate();
+                save(((GCloudSessionManager.Session)session));
+                session.didActivate();
+            
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to store new session id="+session.getId() , e);
+        }
+    }
+
+    /** 
+     * Ask the cluster for the session.
+     * 
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
+     */
+    @Override
+    public AbstractSession getSession(String idInCluster)
+    {
+        Session session = null;
+
+        //try and find the session in this node's memory
+        Session memSession = (Session)_sessions.get(idInCluster);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("getSession({}) {} in session map",idInCluster,(memSession==null?"not":""));
+
+        long now = System.currentTimeMillis();
+        try
+        {
+            //if the session is not in this node's memory, then load it from the datastore
+            if (memSession == null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("getSession({}): loading session data from cluster", idInCluster);
+
+                session = load(makeKey(idInCluster, _context));
+                if (session != null)
+                {
+                    //Check that it wasn't expired
+                    if (session.getExpiry() > 0 && session.getExpiry() <= now)
+                    {
+                        if (LOG.isDebugEnabled()) LOG.debug("getSession ({}): Session expired", idInCluster);
+                        //ensure that the session id for the expired session is deleted so that a new session with the 
+                        //same id cannot be created (because the idInUse() test would succeed)
+                        ((GCloudSessionIdManager)getSessionIdManager()).removeSession(session);
+                        return null;  
+                    }
+
+                    //Update the last worker node to me
+                    session.setLastNode(getSessionIdManager().getWorkerName());                            
+                    //TODO consider saving session here if lastNode was not this node
+
+                    //Check that another thread hasn't loaded the same session
+                    Session existingSession = _sessions.putIfAbsent(idInCluster, session);
+                    if (existingSession != null)
+                    {
+                        //use the one that the other thread inserted
+                        session = existingSession;
+                        LOG.debug("getSession({}): using session loaded by another request thread ", idInCluster);
+                    }
+                    else
+                    {
+                        //indicate that the session was reinflated
+                        session.didActivate();
+                        LOG.debug("getSession({}): loaded session from cluster", idInCluster);
+                    }
+                    return session;
+                }
+                else
+                {
+                    //The requested session does not exist anywhere in the cluster
+                    LOG.debug("getSession({}): No session in cluster matching",idInCluster);
+                    return null;
+                }
+            }
+            else
+            {
+               //The session exists in this node's memory
+               LOG.debug("getSession({}): returning session from local memory ", memSession.getClusterId());
+                return memSession;
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to load session="+idInCluster, e);
+            return null;
+        }
+    }
+    
+    
+
+    /** 
+     * The session manager is stopping.
+     * 
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#shutdownSessions()
+     */
+    @Override
+    protected void shutdownSessions() throws Exception
+    {
+        Set<String> keys = new HashSet<String>(_sessions.keySet());
+        for (String key:keys)
+        {
+            Session session = _sessions.remove(key); //take the session out of the session list
+            //If the session is dirty, then write it to the cluster.
+            //If the session is simply stale do NOT write it to the cluster, as some other node
+            //may have started managing that session - this means that the last accessed/expiry time
+            //will not be updated, meaning it may look like it can expire sooner than it should.
+            try
+            {
+                if (session.isDirty())
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Saving dirty session {} before exiting ", session.getId());
+                    save(session);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+    }
+
+
+    @Override
+    protected AbstractSession newSession(HttpServletRequest request)
+    {
+        return new Session(request);
+    }
+
+    /** 
+     * Remove a session from local memory, and delete it from
+     * the cluster cache.
+     * 
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+     */
+    @Override
+    protected boolean removeSession(String idInCluster)
+    {
+        Session session = (Session)_sessions.remove(idInCluster);
+        try
+        {
+            if (session != null)
+            {
+                delete(session);
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem deleting session id="+idInCluster, e);
+        }
+        return session!=null;
+    }
+    
+    
+    
+    
+    @Override
+    public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
+    {
+        Session session = null;
+        try
+        {
+            //take the session with that id out of our managed list
+            session = (Session)_sessions.remove(oldClusterId);
+            if (session != null)
+            {
+                //TODO consider transactionality and ramifications if the session is live on another node
+                delete(session); //delete the old session from the cluster  
+                session.swapId(newClusterId, newNodeId); //update the session
+                _sessions.put(newClusterId, session); //put it into managed list under new key
+                save(session); //put the session under the new id into the cluster
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+
+        super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
+    }
+
+
+    /**
+     * Load a session from the clustered cache.
+     * 
+     * @param key
+     * @return
+     */
+    protected Session load (Key key)
+    throws Exception
+    {
+        if (_datastore == null)
+            throw new IllegalStateException("No DataStore");
+        
+        if (LOG.isDebugEnabled()) LOG.debug("Loading session {} from DataStore", key);
+
+        Entity entity = _datastore.get(key);
+        if (entity == null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("No session {} in DataStore ",key);
+            return null;
+        }
+        else
+        {
+            Session session = _converter.sessionFromEntity(entity);
+            session.setLastSyncTime(System.currentTimeMillis());
+            return session;
+        }
+    }
+    
+    
+    
+    /**
+     * Save or update the session to the cluster cache
+     * 
+     * @param session
+     * @throws Exception
+     */
+    protected void save (GCloudSessionManager.Session session)
+    throws Exception
+    {
+        if (_datastore == null)
+            throw new IllegalStateException("No DataStore");
+        
+        if (LOG.isDebugEnabled()) LOG.debug("Writing session {} to DataStore", session.getId());
+    
+        Entity entity = _converter.entityFromSession(session, makeKey(session, _context));
+        _datastore.put(entity);
+        session.setLastSyncTime(System.currentTimeMillis());
+    }
+    
+    
+    
+    /**
+     * Remove the session from the cluster cache.
+     * 
+     * @param session
+     */
+    protected void delete (GCloudSessionManager.Session session)
+    {  
+        if (_datastore == null)
+            throw new IllegalStateException("No DataStore");
+        if (LOG.isDebugEnabled()) LOG.debug("Removing session {} from DataStore", session.getId());
+        _datastore.delete(makeKey(session, _context));
+    }
+
+    
+    /**
+     * Invalidate a session for this context with the given id
+     * 
+     * @param idInCluster
+     */
+    public void invalidateSession (String idInCluster)
+    {
+        Session session = (Session)_sessions.get(idInCluster);
+
+        if (session != null)
+        {
+            session.invalidate();
+        }
+    }
+
+    
+    /**
+     * Make a unique key for this session.
+     * As the same session id can be used across multiple contexts, to
+     * make it unique, the key must be composed of:
+     * <ol>
+     * <li>the id</li>
+     * <li>the context path</li>
+     * <li>the virtual hosts</li>
+     * </ol>
+     * 
+     *TODO consider the difference between getClusterId and getId
+     * @param session
+     * @return
+     */
+    private Key makeKey (Session session, Context context)
+    {
+       return makeKey(session.getId(), context);
+    }
+    
+    /**
+     * Make a unique key for this session.
+     * As the same session id can be used across multiple contexts, to
+     * make it unique, the key must be composed of:
+     * <ol>
+     * <li>the id</li>
+     * <li>the context path</li>
+     * <li>the virtual hosts</li>
+     * </ol>
+     * 
+     *TODO consider the difference between getClusterId and getId
+     * @param session
+     * @return
+     */
+    private Key makeKey (String id, Context context)
+    {
+        String key = getContextPath(context);
+        key = key + "_" + getVirtualHost(context);
+        key = key+"_"+id;
+        return _keyFactory.newKey(key);
+    }
+    
+    /**
+     * Turn the context path into an acceptable string
+     * 
+     * @param context
+     * @return
+     */
+    private static String getContextPath (ContextHandler.Context context)
+    {
+        return canonicalize (context.getContextPath());
+    }
+
+    /**
+     * Get the first virtual host for the context.
+     *
+     * Used to help identify the exact session/contextPath.
+     *
+     * @return 0.0.0.0 if no virtual host is defined
+     */
+    private static String getVirtualHost (ContextHandler.Context context)
+    {
+        String vhost = "0.0.0.0";
+
+        if (context==null)
+            return vhost;
+
+        String [] vhosts = context.getContextHandler().getVirtualHosts();
+        if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
+            return vhost;
+
+        return vhosts[0];
+    }
+
+    /**
+     * Make an acceptable name from a context path.
+     *
+     * @param path
+     * @return
+     */
+    private static String canonicalize (String path)
+    {
+        if (path==null)
+            return "";
+
+        return path.replace('/', '_').replace('.','_').replace('\\','_');
+    }
+
+}
diff --git a/jetty-gcloud/gcloud-session-manager/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTester.java b/jetty-gcloud/gcloud-session-manager/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTester.java
new file mode 100644
index 0000000..fe596e2
--- /dev/null
+++ b/jetty-gcloud/gcloud-session-manager/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTester.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.gcloud.session;
+
+
+
+
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+public class GCloudSessionTester
+{
+    public static void main( String[] args ) throws Exception
+    {
+        if (args.length < 4)
+            System.err.println("Usage: GCloudSessionTester projectid p12file password serviceaccount");
+        
+        System.setProperty("org.eclipse.jetty.server.session.LEVEL", "DEBUG");
+        
+        Server server = new Server(8080);
+        HashLoginService loginService = new HashLoginService();
+        loginService.setName( "Test Realm" );
+        loginService.setConfig( "../../jetty-distribution/target/distribution/demo-base/resources/realm.properties" );
+        server.addBean( loginService );
+
+        GCloudConfiguration config = new GCloudConfiguration();
+        config.setProjectId(args[0]);
+        config.setP12File(args[1]);
+        config.setPassword(args[2]);
+        config.setServiceAccount(args[3]);
+
+        GCloudSessionIdManager idmgr = new GCloudSessionIdManager(server);
+        idmgr.setConfig(config);
+        idmgr.setWorkerName("w1");
+        server.setSessionIdManager(idmgr);
+
+ 
+        WebAppContext webapp = new WebAppContext();
+        webapp.setContextPath("/");
+        webapp.setWar("../../jetty-distribution/target/distribution/demo-base/webapps/test.war");
+        webapp.addAliasCheck(new AllowSymLinkAliasChecker());
+        GCloudSessionManager mgr = new GCloudSessionManager();
+        mgr.setSessionIdManager(idmgr);
+        webapp.setSessionHandler(new SessionHandler(mgr));
+
+        // A WebAppContext is a ContextHandler as well so it needs to be set to
+        // the server so it is aware of where to send the appropriate requests.
+        server.setHandler(webapp);
+
+        // Start things up! 
+        server.start();
+
+    
+        server.join();
+    }
+}
diff --git a/jetty-gcloud/pom.xml b/jetty-gcloud/pom.xml
new file mode 100644
index 0000000..af97322
--- /dev/null
+++ b/jetty-gcloud/pom.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <artifactId>jetty-project</artifactId>
+    <groupId>org.eclipse.jetty</groupId>
+    <version>9.3.4-SNAPSHOT</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty.gcloud</groupId>
+  <artifactId>gcloud-parent</artifactId>
+  <packaging>pom</packaging>
+  <name>Jetty :: GCloud</name>
+
+  <properties>
+    <gcloud.version>0.0.8</gcloud.version>
+  </properties>
+
+  <modules>
+    <module>gcloud-session-manager</module>
+  </modules>
+
+</project>
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
index 42b7086..1ae25d8 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -73,6 +73,7 @@
     private static final Logger LOG = Log.getLogger(ServletHolder.class);
     private int _initOrder = -1;
     private boolean _initOnStartup=false;
+    private boolean _initialized = false;
     private Map<String, String> _roleMap;
     private String _forcedPath;
     private String _runAsRole;
@@ -81,7 +82,6 @@
     private ServletRegistration.Dynamic _registration;
     private JspContainer _jspContainer;
 
-
     private transient Servlet _servlet;
     private transient Config _config;
     private transient long _unavailable;
@@ -396,21 +396,24 @@
     public void initialize ()
     throws Exception
     {
-        super.initialize();
-        if (_extInstance || _initOnStartup)
-        {
-            try
+        if(!_initialized){
+            super.initialize();
+            if (_extInstance || _initOnStartup)
             {
-                initServlet();
-            }
-            catch(Exception e)
-            {
-                if (_servletHandler.isStartWithUnavailable())
-                    LOG.ignore(e);
-                else
-                    throw e;
+                try
+                {
+                    initServlet();
+                }
+                catch(Exception e)
+                {
+                    if (_servletHandler.isStartWithUnavailable())
+                        LOG.ignore(e);
+                    else
+                        throw e;
+                }
             }
         }
+        _initialized = true;
     }
 
 
@@ -443,6 +446,7 @@
             _servlet=null;
 
         _config=null;
+        _initialized = false;
     }
 
     /* ------------------------------------------------------------ */
diff --git a/pom.xml b/pom.xml
index 6fbae2c..6ca00ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -530,6 +530,7 @@
     <module>jetty-rewrite</module>
     <module>jetty-nosql</module>
     <module>jetty-infinispan</module>
+    <module>jetty-gcloud</module>
     <module>tests</module>
     <module>examples</module>
     <module>jetty-quickstart</module>
diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml
index b2e8e22..23fed9a 100644
--- a/tests/test-sessions/pom.xml
+++ b/tests/test-sessions/pom.xml
@@ -35,5 +35,6 @@
     <module>test-jdbc-sessions</module>
     <module>test-mongodb-sessions</module>
     <module>test-infinispan-sessions</module>
+    <module>test-gcloud-sessions</module>
   </modules>
 </project>
diff --git a/tests/test-sessions/test-gcloud-sessions/pom.xml b/tests/test-sessions/test-gcloud-sessions/pom.xml
new file mode 100644
index 0000000..1c487b7
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/pom.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+// ========================================================================
+// Copyright (c) Webtide LLC
+// 
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.eclipse.jetty.tests</groupId>
+    <artifactId>test-sessions-parent</artifactId>
+    <version>9.3.4-SNAPSHOT</version>
+  </parent>
+  <artifactId>test-gcloud-sessions</artifactId>
+  <name>Jetty Tests :: Sessions :: GCloud</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <properties>
+    <bundle-symbolic-name>${project.groupId}.sessions.gcloud</bundle-symbolic-name>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <!-- DO NOT DEPLOY (or Release) -->
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skipTests>true</skipTests>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+       <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-webapp</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.tests</groupId>
+            <artifactId>test-sessions-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.gcloud</groupId>
+            <artifactId>gcloud-session-manager</artifactId>
+            <version>${project.version}</version>
+         </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty.toolchain</groupId>
+      <artifactId>jetty-test-helper</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <profiles>
+      <profile>
+        <id>gcloud</id>
+        <activation>
+          <property>
+            <name>gcloud.enabled</name>
+            <value>true</value>
+          </property>
+        </activation>
+        <build>
+          <plugins>
+            <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-surefire-plugin</artifactId>
+              <configuration>
+                <skipTests>false</skipTests>
+                <systemPropertyVariables>
+                  <test.projectId>jetty9-work</test.projectId>
+                  <test.port>8088</test.port>
+                </systemPropertyVariables>
+              </configuration>
+            </plugin>
+          </plugins>
+        </build>
+      </profile>
+  </profiles>
+
+</project>
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ClientCrossContextSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ClientCrossContextSessionTest.java
new file mode 100644
index 0000000..5d05a3f
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ClientCrossContextSessionTest.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractClientCrossContextSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ClientCrossContextSessionTest
+ *
+ *
+ */
+public class ClientCrossContextSessionTest extends AbstractClientCrossContextSessionTest
+{
+    static GCloudSessionTestSupport _testSupport;
+    
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractClientCrossContextSessionTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return new GCloudTestServer(port, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testCrossContextDispatch() throws Exception
+    {
+        super.testCrossContextDispatch();
+    }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ForwardedSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ForwardedSessionTest.java
new file mode 100644
index 0000000..20109ae
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ForwardedSessionTest.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractForwardedSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * ForwardedSessionTest
+ *
+ *
+ */
+public class ForwardedSessionTest extends AbstractForwardedSessionTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractForwardedSessionTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+       return new GCloudTestServer(port, _testSupport.getConfiguration());
+    }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTestSupport.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTestSupport.java
new file mode 100644
index 0000000..adfdf9b
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudSessionTestSupport.java
@@ -0,0 +1,372 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.JarResource;
+import org.eclipse.jetty.util.resource.Resource;
+
+import com.google.api.client.util.Strings;
+import com.google.gcloud.datastore.Key;
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+import com.google.gcloud.datastore.DatastoreOptions;
+import com.google.gcloud.datastore.Entity;
+import com.google.gcloud.datastore.GqlQuery;
+import com.google.gcloud.datastore.ProjectionEntity;
+import com.google.gcloud.datastore.Query;
+import com.google.gcloud.datastore.Query.ResultType;
+import com.google.gcloud.datastore.QueryResults;
+import com.google.gcloud.datastore.StructuredQuery;
+import com.google.gcloud.datastore.StructuredQuery.Projection;
+
+/**
+ * GCloudSessionTestSupport
+ *
+ *
+ */
+public class GCloudSessionTestSupport
+{
+    
+    /**
+     * GCloudTestConfiguration
+     * 
+     * Specialization of GCloudConfiguration for gcd test environment
+     *
+     */
+    public class GCloudTestConfiguration extends GCloudConfiguration
+    {
+        int _port;
+
+        public GCloudTestConfiguration(String projectId, int port)
+        {
+            setProjectId(projectId);
+            _port = port;
+        }
+
+
+        @Override
+        public DatastoreOptions getDatastoreOptions() throws Exception
+        { 
+          return DatastoreOptions.builder()
+                    .projectId(_projectId)
+                    .host("http://localhost:" + _port)
+                    .build();
+        }
+    }
+    
+    
+    private static class ProcessOutputReader implements Runnable
+    {
+        private InputStream _is;
+        private String _startupSentinel;
+        private BufferedReader _reader;
+
+        public ProcessOutputReader (InputStream is, String startupSentinel)
+        throws Exception
+        {
+            _is = is;
+            _startupSentinel = startupSentinel;
+            _reader = new BufferedReader(new InputStreamReader(_is));
+            if (!Strings.isNullOrEmpty(_startupSentinel))
+            {
+                String line;
+                while ((line = _reader.readLine()) != (null) && !line.contains(_startupSentinel))
+                {
+                    //System.err.println(line);
+                }
+            }
+        }
+
+
+        public void run()
+        {
+            String line;
+            try
+            {
+                while ((line = _reader.readLine()) != (null))
+                {
+                }
+            }
+            catch (IOException ignore)
+            {
+                /* ignore */
+            }
+            finally
+            {
+                IO.close(_reader);
+            }
+        }
+    }
+    
+
+    public static String DEFAULT_PROJECTID = "jetty9-work";
+    public static int DEFAULT_PORT = 8088;
+    public static String DEFAULT_GCD_ZIP = "gcd-v1beta2-rev1-2.1.2b.zip";
+    public static String DEFAULT_GCD_UNPACKED = "gcd-v1beta2-rev1-2.1.2b";
+    public static String DEFAULT_DOWNLOAD_URL = "http://storage.googleapis.com/gcd/tools/";
+    
+    String _projectId;
+    int _port;
+    File _datastoreDir;
+    File _gcdInstallDir;
+    File _gcdUnpackedDir;
+    Datastore _ds;
+    
+    public GCloudSessionTestSupport (String projectId, int port, File gcdInstallDir)
+    {
+        _projectId = projectId;
+        if (_projectId == null)
+            _projectId = DEFAULT_PROJECTID;
+        _port = port;
+        if (_port <= 0)
+            _port = DEFAULT_PORT;
+        
+        _gcdInstallDir = gcdInstallDir;
+        if (_gcdInstallDir == null)
+            _gcdInstallDir = new File (System.getProperty("java.io.tmpdir"));
+    }
+    
+    public GCloudSessionTestSupport ()
+    {
+        this(null,0, null);
+    }
+
+    public GCloudConfiguration getConfiguration ()
+    {
+        return new GCloudTestConfiguration(_projectId, _port);
+    }
+    
+    
+    public void setUp()
+    throws Exception
+    {
+       downloadGCD();
+       createDatastore();
+       startDatastore();
+    }
+    
+    
+    public void downloadGCD()
+    throws Exception
+    {
+        File zipFile = new File (_gcdInstallDir, DEFAULT_GCD_ZIP);       
+        _gcdUnpackedDir = new File (_gcdInstallDir, DEFAULT_GCD_UNPACKED);
+        File gcdSh = new File (_gcdUnpackedDir, "gcd.sh");
+        if (gcdSh.exists())
+            return;
+        
+        
+        if (_gcdInstallDir.exists() && !zipFile.exists())
+        {
+           //download it
+            ReadableByteChannel rbc = Channels.newChannel(new URL(DEFAULT_DOWNLOAD_URL+DEFAULT_GCD_ZIP).openStream());
+            try (FileOutputStream fos = new FileOutputStream(zipFile)) 
+            {
+              fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+            }
+        }
+        
+        if (zipFile.exists())
+        {
+            //unpack it
+            Resource zipResource = JarResource.newJarResource(Resource.newResource(zipFile));
+            zipResource.copyTo(_gcdInstallDir);
+        }
+        
+        System.err.println("GCD downloaded and unpacked");
+    }
+    
+    
+    
+    public void createDatastore ()
+    throws Exception
+    {
+    
+        _datastoreDir = Files.createTempDirectory("gcloud-sessions").toFile();
+        _datastoreDir.deleteOnExit();
+        
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
+        processBuilder.directory(_datastoreDir);
+        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")) 
+        {
+          processBuilder.command("cmd", "/C", new File(_gcdUnpackedDir, "gcd.cmd").getAbsolutePath(), "create", "-p", _projectId, _projectId);
+          processBuilder.redirectOutput(new File("NULL:"));
+        } 
+        else 
+        {
+          processBuilder.redirectOutput(new File("/tmp/run.out"));
+          processBuilder.command("bash", new File(_gcdUnpackedDir, "gcd.sh").getAbsolutePath(), "create", "-p",_projectId, _projectId);
+        }
+
+        Process temp = processBuilder.start();
+        System.err.println("Create outcome: "+temp.waitFor());
+    }
+    
+    
+    public void startDatastore()
+    throws Exception
+    {
+        //start the datastore for the test
+        ProcessBuilder processBuilder = new ProcessBuilder();
+        processBuilder.directory(_datastoreDir);
+        processBuilder.redirectErrorStream(true);
+        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows")) 
+        {
+          processBuilder.command("cmd", "/C", new File(_gcdUnpackedDir, "gcd.cmd").getAbsolutePath(), "start", "--testing", "--allow_remote_shutdown","--port="+String.valueOf(_port), _projectId);
+        } 
+        else 
+        {
+          processBuilder.command("bash", new File(_gcdUnpackedDir, "gcd.sh").getAbsolutePath(), "start", "--testing", "--allow_remote_shutdown", "--port="+String.valueOf(_port), _projectId);
+        }
+        
+        System.err.println("Starting datastore");
+        Process temp = processBuilder.start();
+        ProcessOutputReader reader = new ProcessOutputReader(temp.getInputStream(), "Dev App Server is now running");
+        Thread readerThread = new Thread(reader, "GCD reader");
+        readerThread.setDaemon(true);
+        readerThread.start();
+    }
+    
+    public void stopDatastore()
+    throws Exception
+    {
+        //Send request to terminate test datastore
+        URL url = new URL("http", "localhost", _port, "/_ah/admin/quit");
+        HttpURLConnection con = (HttpURLConnection) url.openConnection();
+        con.setRequestMethod("POST");
+        con.setDoOutput(true);
+        con.setDoInput(true);
+        OutputStream out = con.getOutputStream();
+        out.write("".getBytes());
+        out.flush();
+        InputStream in = con.getInputStream();
+        while (in.read() != -1)
+        {
+            // consume input
+          
+        }
+
+        System.err.println("Stop issued");
+    }
+ 
+    
+    public void clearDatastore()
+    {
+        org.eclipse.jetty.util.IO.delete(_datastoreDir);
+    }
+    
+    public void tearDown()
+    throws Exception
+    {
+        stopDatastore();
+        clearDatastore();
+    }
+    
+    public void ensureDatastore()
+    throws Exception
+    {
+        if (_ds == null)
+            _ds = DatastoreFactory.instance().get(getConfiguration().getDatastoreOptions());
+    }
+    public void listSessions () throws Exception
+    {
+        ensureDatastore();
+        GqlQuery.Builder builder = Query.gqlQueryBuilder(ResultType.ENTITY, "select * from "+GCloudSessionManager.KIND);
+       
+        Query<Entity> query = builder.build();
+    
+        QueryResults<Entity> results = _ds.run(query);
+        assertNotNull(results);
+        System.err.println("SESSIONS::::::::");
+        while (results.hasNext())
+        {
+            
+            Entity e = results.next();
+            System.err.println(e.getString("clusterId")+" expires at "+e.getLong("expiry"));
+        }
+        System.err.println("END OF SESSIONS::::::::");
+    }
+    
+    public void assertSessions(int count) throws Exception
+    {
+        ensureDatastore();
+        StructuredQuery<ProjectionEntity> keyOnlyProjectionQuery = Query.projectionEntityQueryBuilder()
+                .kind(GCloudSessionManager.KIND)
+                .projection(Projection.property("__key__"))
+                .limit(100)
+                .build();  
+        QueryResults<ProjectionEntity> results =   _ds.run(keyOnlyProjectionQuery);
+        assertNotNull(results);
+        int actual = 0;
+        while (results.hasNext())
+        { 
+            results.next();
+            ++actual;
+        }       
+        assertEquals(count, actual);
+    }
+    
+    public void deleteSessions () throws Exception
+    {
+       ensureDatastore();
+        StructuredQuery<ProjectionEntity> keyOnlyProjectionQuery = Query.projectionEntityQueryBuilder()
+                .kind(GCloudSessionManager.KIND)
+                .projection(Projection.property("__key__"))
+                .limit(100)
+                .build();  
+        QueryResults<ProjectionEntity> results =   _ds.run(keyOnlyProjectionQuery);
+        if (results != null)
+        {
+            List<Key> keys = new ArrayList<Key>();
+            
+            while (results.hasNext())
+            { 
+                ProjectionEntity pe = results.next();
+                keys.add(pe.key());
+            }
+            
+            _ds.delete(keys.toArray(new Key[keys.size()]));
+        }
+        
+        assertSessions(0);
+    }
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudTestServer.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudTestServer.java
new file mode 100644
index 0000000..11f2125
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/GCloudTestServer.java
@@ -0,0 +1,97 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.eclipse.jetty.server.session.SessionHandler;
+
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreFactory;
+
+/**
+ * GCloudTestServer
+ *
+ *
+ */
+public class GCloudTestServer extends AbstractTestServer
+{
+    static int __workers=0;
+    public static int STALE_INTERVAL_SEC = 1;
+
+ 
+
+    /**
+     * @param port
+     * @param maxInactivePeriod
+     * @param scavengePeriod
+     * @param sessionIdMgrConfig
+     */
+    public GCloudTestServer(int port, int maxInactivePeriod, int scavengePeriod, GCloudConfiguration config)
+    {
+        super(port, maxInactivePeriod, scavengePeriod, config);
+    }
+
+    /**
+     * @param port
+     * @param configuration
+     */
+    public GCloudTestServer(int port, GCloudConfiguration configuration)
+    {
+        super(port, 30,10, configuration);
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionIdManager(java.lang.Object)
+     */
+    @Override
+    public SessionIdManager newSessionIdManager(Object config)
+    {
+        GCloudSessionIdManager idManager = new GCloudSessionIdManager(getServer());
+        idManager.setWorkerName("w"+(__workers++));
+        idManager.setConfig((GCloudConfiguration)config);
+        return idManager;
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionManager()
+     */
+    @Override
+    public SessionManager newSessionManager()
+    {
+        GCloudSessionManager sessionManager = new GCloudSessionManager();
+        sessionManager.setSessionIdManager((GCloudSessionIdManager)_sessionIdManager);
+        sessionManager.setStaleIntervalSec(STALE_INTERVAL_SEC);
+        sessionManager.setScavengeIntervalSec(_scavengePeriod);
+        return sessionManager;
+        
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractTestServer#newSessionHandler(org.eclipse.jetty.server.SessionManager)
+     */
+    @Override
+    public SessionHandler newSessionHandler(SessionManager sessionManager)
+    {
+        return new SessionHandler(sessionManager);
+    }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ImmortalSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ImmortalSessionTest.java
new file mode 100644
index 0000000..84c3840
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ImmortalSessionTest.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractImmortalSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ImmortalSessionTest
+ *
+ *
+ */
+public class ImmortalSessionTest extends AbstractImmortalSessionTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractImmortalSessionTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int maxInactiveMs, int scavengeMs)
+    {
+       return new GCloudTestServer(port, port, scavengeMs, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testImmortalSession() throws Exception
+    {
+        super.testImmortalSession();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/InvalidationSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/InvalidationSessionTest.java
new file mode 100644
index 0000000..6cc875c
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/InvalidationSessionTest.java
@@ -0,0 +1,82 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractInvalidationSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * InvalidationSessionTest
+ *
+ *
+ */
+public class InvalidationSessionTest extends AbstractInvalidationSessionTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractInvalidationSessionTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return new GCloudTestServer(port, _testSupport.getConfiguration());
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractInvalidationSessionTest#pause()
+     */
+    @Override
+    public void pause()
+    {
+        //This test moves around a session between 2 nodes. After it is invalidated on the 1st node,
+        //it will still be in the memory of the 2nd node. We need to wait until after the stale time
+        //has expired on node2 for it to reload the session and discover it has been deleted.
+        try
+        {
+            Thread.currentThread().sleep((2*GCloudTestServer.STALE_INTERVAL_SEC)*1000);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+        
+    }
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LastAccessTimeTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LastAccessTimeTest.java
new file mode 100644
index 0000000..d4f42fe
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LastAccessTimeTest.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractLastAccessTimeTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * LastAccessTimeTest
+ *
+ *
+ */
+public class LastAccessTimeTest extends AbstractLastAccessTimeTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractLastAccessTimeTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return  new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testLastAccessTime() throws Exception
+    {
+        super.testLastAccessTime();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LocalSessionScavengingTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LocalSessionScavengingTest.java
new file mode 100644
index 0000000..ca39661
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/LocalSessionScavengingTest.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractLocalSessionScavengingTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * LocalSessionScavengingTest
+ *
+ *
+ */
+public class LocalSessionScavengingTest extends AbstractLocalSessionScavengingTest
+{
+  static GCloudSessionTestSupport _testSupport;
+    
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractLocalSessionScavengingTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return  new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testLocalSessionsScavenging() throws Exception
+    {
+        super.testLocalSessionsScavenging();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/NewSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/NewSessionTest.java
new file mode 100644
index 0000000..1dc256b
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/NewSessionTest.java
@@ -0,0 +1,74 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import java.io.File;
+
+import org.eclipse.jetty.server.session.AbstractNewSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * NewSessionTest
+ *
+ *
+ */
+public class NewSessionTest extends AbstractNewSessionTest
+{
+    GCloudSessionTestSupport _testSupport;
+    
+    @Before
+    public void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @After
+    public void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractNewSessionTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+       return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    public void testNewSession() throws Exception
+    {
+        super.testNewSession();
+    }
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/OrphanedSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/OrphanedSessionTest.java
new file mode 100644
index 0000000..ffaaaec
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/OrphanedSessionTest.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractOrphanedSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * OrphanedSessionTest
+ *
+ *
+ */
+public class OrphanedSessionTest extends AbstractOrphanedSessionTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractOrphanedSessionTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return  new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testOrphanedSession() throws Exception
+    {
+        super.testOrphanedSession();
+        _testSupport.assertSessions(0);
+    }
+    
+    
+    
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ReentrantRequestSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ReentrantRequestSessionTest.java
new file mode 100644
index 0000000..e821618
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ReentrantRequestSessionTest.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractReentrantRequestSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ReentrantRequestSessionTest
+ *
+ *
+ */
+public class ReentrantRequestSessionTest extends AbstractReentrantRequestSessionTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractReentrantRequestSessionTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return  new GCloudTestServer(port, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testReentrantRequestSession() throws Exception
+    {
+        super.testReentrantRequestSession();
+    }
+    
+    
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/RemoveSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/RemoveSessionTest.java
new file mode 100644
index 0000000..5b7f99b
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/RemoveSessionTest.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractRemoveSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * RemoveSessionTest
+ *
+ *
+ */
+public class RemoveSessionTest extends AbstractRemoveSessionTest
+{
+   static GCloudSessionTestSupport _testSupport;
+    
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractRemoveSessionTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    { 
+        return new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testRemoveSession() throws Exception
+    {
+        super.testRemoveSession();
+    }
+    
+    
+
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SameNodeLoadTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SameNodeLoadTest.java
new file mode 100644
index 0000000..e230f43
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SameNodeLoadTest.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSameNodeLoadTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SameNodeLoadTest
+ *
+ *
+ */
+public class SameNodeLoadTest extends AbstractSameNodeLoadTest
+{
+  static GCloudSessionTestSupport _testSupport;
+    
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractSameNodeLoadTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return  new GCloudTestServer(port, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testLoad() throws Exception
+    {
+        super.testLoad();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ServerCrossContextSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ServerCrossContextSessionTest.java
new file mode 100644
index 0000000..59f366b
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/ServerCrossContextSessionTest.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractServerCrossContextSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * ServerCrossContextSessionTest
+ *
+ *
+ */
+public class ServerCrossContextSessionTest extends AbstractServerCrossContextSessionTest
+{
+
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractServerCrossContextSessionTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return  new GCloudTestServer(port, _testSupport.getConfiguration()); 
+    }
+
+    @Test
+    @Override
+    public void testCrossContextDispatch() throws Exception
+    {
+        super.testCrossContextDispatch();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionExpiryTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionExpiryTest.java
new file mode 100644
index 0000000..785a2a1
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionExpiryTest.java
@@ -0,0 +1,102 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionExpiryTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionExpiryTest
+ *
+ *
+ */
+public class SessionExpiryTest extends AbstractSessionExpiryTest
+{
+
+    static GCloudSessionTestSupport _testSupport;
+    
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                   Integer.parseInt(port),
+                                                   null);
+        _testSupport.setUp();
+    }
+    
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+    
+    
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractSessionExpiryTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return  new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testSessionNotExpired() throws Exception
+    {
+        super.testSessionNotExpired();
+        _testSupport.deleteSessions();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractSessionExpiryTest#testSessionExpiry()
+     */
+    @Test
+    @Override
+    public void testSessionExpiry() throws Exception
+    {
+        super.testSessionExpiry();
+        _testSupport.assertSessions(0);
+    }
+
+    @Override
+    public void verifySessionCreated(TestHttpSessionListener listener, String sessionId)
+    {
+        super.verifySessionCreated(listener, sessionId);
+        try{ _testSupport.listSessions(); _testSupport.assertSessions(1);}catch(Exception e) {e.printStackTrace();} 
+    }
+
+    @Override
+    public void verifySessionDestroyed(TestHttpSessionListener listener, String sessionId)
+    {
+        super.verifySessionDestroyed(listener, sessionId);
+        try{ _testSupport.listSessions(); _testSupport.assertSessions(0);}catch(Exception e) {e.printStackTrace();}
+    }
+
+    
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionInvalidateAndCreateTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionInvalidateAndCreateTest.java
new file mode 100644
index 0000000..b56a13e
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionInvalidateAndCreateTest.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionInvalidateAndCreateTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionInvalidateAndCreateTest
+ *
+ *
+ */
+public class SessionInvalidateAndCreateTest extends AbstractSessionInvalidateAndCreateTest
+{
+
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractSessionInvalidateAndCreateTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return  new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testSessionScavenge() throws Exception
+    {
+        super.testSessionScavenge();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionMigrationTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionMigrationTest.java
new file mode 100644
index 0000000..5ed5740
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionMigrationTest.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionMigrationTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionMigrationTest
+ *
+ *
+ */
+public class SessionMigrationTest extends AbstractSessionMigrationTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractSessionMigrationTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return  new GCloudTestServer(port, _testSupport.getConfiguration());
+    }
+
+    
+    @Test
+    @Override
+    public void testSessionMigration() throws Exception
+    {
+        super.testSessionMigration();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionRenewTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionRenewTest.java
new file mode 100644
index 0000000..c7b0c87
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionRenewTest.java
@@ -0,0 +1,71 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionRenewTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionRenewTest
+ *
+ *
+ */
+public class SessionRenewTest extends AbstractSessionRenewTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractSessionRenewTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return  new GCloudTestServer(port,max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testSessionRenewal() throws Exception
+    {
+        super.testSessionRenewal();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionValueSavingTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionValueSavingTest.java
new file mode 100644
index 0000000..54bf8ff
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/SessionValueSavingTest.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import org.eclipse.jetty.server.session.AbstractSessionValueSavingTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * SessionValueSavingTest
+ *
+ *
+ */
+public class SessionValueSavingTest extends AbstractSessionValueSavingTest
+{
+
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractSessionValueSavingTest#createServer(int, int, int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port, int max, int scavenge)
+    {
+        return  new GCloudTestServer(port, max, scavenge, _testSupport.getConfiguration());
+    }
+
+    @Test
+    @Override
+    public void testSessionValueSaving() throws Exception
+    {
+        super.testSessionValueSaving();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/StopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/StopSessionManagerPreserveSessionTest.java
new file mode 100644
index 0000000..97b656f
--- /dev/null
+++ b/tests/test-sessions/test-gcloud-sessions/src/test/java/org/eclipse/jetty/gcloud/session/StopSessionManagerPreserveSessionTest.java
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.gcloud.session;
+
+import static org.junit.Assert.fail;
+import org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest;
+import org.eclipse.jetty.server.session.AbstractTestServer;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * StopSessionManagerPreserveSessionTest
+ *
+ *
+ */
+public class StopSessionManagerPreserveSessionTest extends AbstractStopSessionManagerPreserveSessionTest
+{
+    static GCloudSessionTestSupport _testSupport;
+
+    @BeforeClass
+    public static void setup () throws Exception
+    {
+        String projectId = System.getProperty("test.projectId", null);
+        String port = System.getProperty("test.port","0");
+        _testSupport = new GCloudSessionTestSupport(projectId,
+                                                    Integer.parseInt(port),
+                                                    null);
+        _testSupport.setUp();
+    }
+
+    @AfterClass
+    public static void teardown () throws Exception
+    {
+        _testSupport.tearDown();
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest#checkSessionPersisted(boolean)
+     */
+    @Override
+    public void checkSessionPersisted(boolean expected)
+    {
+        try
+        {
+            _testSupport.assertSessions(1);
+        }
+        catch (Exception e)
+        {
+            fail(e.getMessage());
+        }
+
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest#createServer(int)
+     */
+    @Override
+    public AbstractTestServer createServer(int port)
+    {
+        return  new GCloudTestServer(port, _testSupport.getConfiguration());
+    }
+
+    /** 
+     * @see org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest#configureSessionManagement(org.eclipse.jetty.servlet.ServletContextHandler)
+     */
+    @Override
+    public void configureSessionManagement(ServletContextHandler context)
+    {
+        
+    }
+
+    @Test
+    @Override
+    public void testStopSessionManagerPreserveSession() throws Exception
+    {
+        super.testStopSessionManagerPreserveSession();
+    }
+
+    
+}
diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
index 0d1ac5b..8d2d660 100644
--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
+++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
@@ -83,19 +83,22 @@
                     assertTrue(sessionCookie != null);
                     // Mangle the cookie, replacing Path with $Path, etc.
                     sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
-
+                    
+                    
                     // Be sure the session is also present in node2
 
                     Request request2 = client.newRequest(urls[1] + "?action=increment");
                     request2.header("Cookie", sessionCookie);
                     ContentResponse response2 = request2.send();
                     assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
+ 
 
                     // Invalidate on node1
                     Request request1 = client.newRequest(urls[0] + "?action=invalidate");
                     request1.header("Cookie", sessionCookie);
                     response1 = request1.send();
                     assertEquals(HttpServletResponse.SC_OK, response1.getStatus());
+           
 
                     pause();