Skip to content

Commit

Permalink
#37: added support for etags for /model/live and /model/static
Browse files Browse the repository at this point in the history
* deprecated /system/model and /system/live in favor of /model/static and /model/live
  • Loading branch information
ypujante committed May 17, 2011
1 parent 4e62f3b commit 005a2d2
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 34 deletions.
Expand Up @@ -240,14 +240,14 @@ def loadModel(self, modelUrl=None, modelFile=None):
body = {
'modelUrl': modelUrl
}
response = self._doRequest("system/model", "POST", body)
response = self._doRequest("model/static", "POST", body)
status = response.status_int
else:
with open(modelFile, 'r') as mf:
headers = {
'Content-Type': 'text/json'
}
response = self._doRequest("system/model", "POST", mf, headers)
response = self._doRequest("model/static", "POST", mf, headers)
status = response.status_int

# XXX - Should exceptions be thrown here?
Expand All @@ -274,9 +274,9 @@ def status(self, live=True, beautify=False, filter=None):
"""

if live:
uri = "system/live"
uri = "model/live"
else:
uri = "system/model"
uri = "model/static"

params = {}

Expand Down
Expand Up @@ -182,11 +182,21 @@ class UrlMappings
name restExecution: "/rest/v1/$fabric/plan/$planId/execution/$id"(controller: 'plan') {
action = [GET: 'rest_view_execution', HEAD: 'rest_execution_status']
}
name restStaticModel: "/rest/v1/$fabric/model/static"(controller: 'model') {
action = [POST: 'rest_upload_model', GET: 'rest_get_static_model']
}
name restLiveModel: "/rest/v1/$fabric/model/live"(controller: 'model') {
action = [GET: 'rest_get_live_model']
}

/**
* DEPRECATED: kept for backward compatibility only
*/
"/rest/v1/$fabric/system/model"(controller: 'model') {
action = [POST: 'rest_upload_model', GET: 'rest_get_current_model']
action = [POST: 'rest_upload_model', GET: 'rest_get_static_model']
}
"/rest/v1/$fabric/system/live"(controller: 'system') {
action = [GET: 'rest_get_live_system']
"/rest/v1/$fabric/system/live"(controller: 'model') {
action = [GET: 'rest_get_live_model']
}

"500"(view: '/error')
Expand Down
Expand Up @@ -25,12 +25,16 @@ import org.linkedin.glu.provisioner.core.model.JSONSystemModelSerializer
import org.linkedin.groovy.util.io.GroovyIOUtils
import org.linkedin.glu.orchestration.engine.system.SystemService
import org.linkedin.glu.console.provisioner.services.storage.SystemStorageException
import org.linkedin.glu.grails.utils.ConsoleHelper
import org.linkedin.glu.provisioner.core.model.SystemModel
import org.linkedin.glu.orchestration.engine.agents.AgentsService

/**
* @author: ypujante@linkedin.com
*/
public class ModelController extends ControllerBase
{
AgentsService agentsService
SystemService systemService
ConsoleConfig consoleConfig

Expand Down Expand Up @@ -147,15 +151,52 @@ public class ModelController extends ControllerBase
}
}

def rest_get_current_model = {
/**
* Handle GET /model/static
*/
def rest_get_static_model = {
renderModelWithETag(request.system)
}

/**
* Handle GET /model/live
*/
def rest_get_live_model = {
def model = agentsService.getCurrentSystemModel(request.fabric)
model = model.filterBy(request.system.filters)
renderModelWithETag(model)
}

/**
* Handle the rendering of the model taking into account etag
*/
private void renderModelWithETag(SystemModel model)
{
// the etag is a combination of the model content + request uri (path + query string)
String etag = """
${model.computeContentSha1()}
${request['javax.servlet.forward.servlet_path']}
${request['javax.servlet.forward.query_string']}
"""
etag = ConsoleHelper.computeChecksum(etag)

// handling ETag
if(request.getHeader('If-None-Match') == etag)
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED)
render ''
return
}

String modelString

def model
if(params.prettyPrint)
model = request.system.toString()
modelString = model.toString()
else
model = JSONSystemModelSerializer.INSTANCE.serialize(request.system)
modelString = JSONSystemModelSerializer.INSTANCE.serialize(model)

response.setHeader('Etag', etag)
response.setContentType('text/json')
render model
render modelString
}
}
Expand Up @@ -21,7 +21,6 @@ import org.linkedin.glu.orchestration.engine.agents.AgentsService
import org.linkedin.glu.orchestration.engine.deployment.DeploymentService
import org.linkedin.glu.provisioner.plan.api.Plan
import org.linkedin.glu.console.domain.DbSystemModel
import org.linkedin.glu.provisioner.core.model.JSONSystemModelSerializer
import org.linkedin.glu.provisioner.core.model.SystemEntry
import org.linkedin.glu.orchestration.engine.system.SystemService

Expand Down Expand Up @@ -279,22 +278,4 @@ class SystemController extends ControllerBase
]
}
}


/**
* Handle GET /system/live (live system as opposed to model)
*/
def rest_get_live_system = {
def model = agentsService.getCurrentSystemModel(request.fabric)
model = model.filterBy(request.system.filters)

if(params.prettyPrint)
model = model.toString()
else
model = JSONSystemModelSerializer.INSTANCE.serialize(model)

response.setContentType('text/json')
render model

}
}
Expand Up @@ -29,6 +29,9 @@ import org.linkedin.util.clock.Timespan
import org.linkedin.util.io.PathUtils
import org.linkedin.groovy.util.net.GroovyNetUtils
import org.linkedin.util.io.resource.Resource
import org.linkedin.util.codec.HexaCodec
import org.linkedin.util.codec.OneWayMessageDigestCodec
import org.linkedin.util.codec.OneWayCodec

/**
* @author ypujante@linkedin.com */
Expand All @@ -37,6 +40,9 @@ class ConsoleHelper
public static final String MODULE = ConsoleHelper.class.getName();
public static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MODULE);

public static final OneWayCodec SHA1 =
OneWayMessageDigestCodec.createSHA1Instance('', HexaCodec.INSTANCE)

// we use a codec because somehow the decoding of the cookies when json format is not working :(
public final static Codec COOKIE_CODEC = new Base64Codec('console')

Expand Down Expand Up @@ -279,4 +285,18 @@ class ConsoleHelper
else
return null
}

/**
* Computes the checksum of a string
*
* @param s <code>null</code> is ok
* @return (sha1 of the string as a hexadecimal string)
*/
static String computeChecksum(String s)
{
if(s == null)
return null

CodecUtils.encodeString(SHA1, s)
}
}
@@ -0,0 +1,177 @@
/*
* Copyright (c) 2011 Yan Pujante
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/

package org.linkedin.glu.console.controllers

import grails.test.ControllerUnitTestCase
import org.linkedin.glu.provisioner.core.model.SystemModel
import javax.servlet.http.HttpServletResponse
import groovy.mock.interceptor.MockFor
import org.linkedin.glu.orchestration.engine.agents.AgentsService


/**
* @author yan@pongasoft.com */
class ModelControllerTests extends ControllerUnitTestCase
{
void testGetStaticModelWithETag1()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'

// test with no etag => returns the model
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true"
controller.params.prettyPrint = true
controller.rest_get_static_model()

assertEquals(systemModel.toString(), controller.response.contentAsString)
assertEquals("f8a5658c3e695fa2ea4d14bb08ed48959b35ea08", controller.response.getHeader('ETag'))
}

void testGetStaticModelWithETag2()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'

// when providing If-None-Match => should get 304
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true"
controller.params.prettyPrint = true
controller.request.addHeader('If-None-Match', "f8a5658c3e695fa2ea4d14bb08ed48959b35ea08")
controller.rest_get_static_model()

assertEquals(HttpServletResponse.SC_NOT_MODIFIED, controller.response.status)
}

void testGetStaticModelWithETag3()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'
systemModel.metadata.p2 = 'v2'

// model is different => should return new version with new etag
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true"
controller.params.prettyPrint = true
controller.request.addHeader('If-None-Match', "f8a5658c3e695fa2ea4d14bb08ed48959b35ea08")
controller.rest_get_static_model()

assertEquals(systemModel.toString(), controller.response.contentAsString)
assertEquals("45b35ce1364c5a66d4b3a696fc002f923b288974", controller.response.getHeader('ETag'))
}

void testGetStaticModelWithETag4()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'

// query string is different => should return new version with new etag
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true&legacy=true"
controller.params.prettyPrint = true
controller.params.legacy = true
controller.request.addHeader('If-None-Match', "f8a5658c3e695fa2ea4d14bb08ed48959b35ea08")
controller.rest_get_static_model()

assertEquals(systemModel.toString(), controller.response.contentAsString)
assertEquals("f603f374b32d2a460aed128be819340a86e21d2e", controller.response.getHeader('ETag'))
}

void testGetLiveModelWithETag1()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'

withAgentsService(systemModel) {
// test with no etag => returns the model
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true"
controller.params.prettyPrint = true
controller.rest_get_live_model()

assertEquals(systemModel.toString(), controller.response.contentAsString)
assertEquals("f8a5658c3e695fa2ea4d14bb08ed48959b35ea08", controller.response.getHeader('ETag'))
}
}

void testGetLiveModelWithETag2()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'

withAgentsService(systemModel) {
// when providing If-None-Match => should get 304
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true"
controller.params.prettyPrint = true
controller.request.addHeader('If-None-Match', "f8a5658c3e695fa2ea4d14bb08ed48959b35ea08")
controller.rest_get_live_model()

assertEquals(HttpServletResponse.SC_NOT_MODIFIED, controller.response.status)
}
}

void testGetLiveModelWithETag3()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'
systemModel.metadata.p2 = 'v2'

withAgentsService(systemModel) {
// model is different => should return new version with new etag
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true"
controller.params.prettyPrint = true
controller.request.addHeader('If-None-Match', "f8a5658c3e695fa2ea4d14bb08ed48959b35ea08")
controller.rest_get_live_model()

assertEquals(systemModel.toString(), controller.response.contentAsString)
assertEquals("45b35ce1364c5a66d4b3a696fc002f923b288974", controller.response.getHeader('ETag'))
}
}

void testGetLiveModelWithETag4()
{
SystemModel systemModel = new SystemModel()
systemModel.metadata.p1 = 'v1'

withAgentsService(systemModel) {
// query string is different => should return new version with new etag
controller.request.system = systemModel
controller.request['javax.servlet.forward.query_string'] = "prettyPrint=true&legacy=true"
controller.params.prettyPrint = true
controller.params.legacy = true
controller.request.addHeader('If-None-Match', "f8a5658c3e695fa2ea4d14bb08ed48959b35ea08")
controller.rest_get_live_model()

assertEquals(systemModel.toString(), controller.response.contentAsString)
assertEquals("f603f374b32d2a460aed128be819340a86e21d2e", controller.response.getHeader('ETag'))
}
}

private void withAgentsService(SystemModel systemModel, Closure closure)
{
def agentsServiceMock = new MockFor(AgentsService)
agentsServiceMock.demand.getCurrentSystemModel { return systemModel }
def agentsService = agentsServiceMock.proxyInstance()
controller.agentsService = agentsService

closure()

agentsServiceMock.verify(agentsService)
}
}

0 comments on commit 005a2d2

Please sign in to comment.