Skip to content

Commit

Permalink
Added Voldemort Web Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
rsumbaly committed Mar 29, 2011
1 parent 3d26d92 commit ba1512e
Show file tree
Hide file tree
Showing 15 changed files with 690 additions and 0 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS
Expand Up @@ -17,6 +17,7 @@ Jay Kreps
Jonathan Traupman
Joshua Tuberville
Kirk True
Matthew Hayes
Michael R. Head
Mike Frost
Neha Narkhede
Expand Down
31 changes: 31 additions & 0 deletions contrib/web-manager/README.md
@@ -0,0 +1,31 @@
# Voldemort Web Manager #

The web manager use the Voldemort admin client to expose a web interface to Voldemort cluster management. It supports:

* Creation of new stores using JSON serialization
* Listing stores
* Displaying JSON schema for a store
* Listing subset of entries from a store
* Changing bootstrap URL through UI

## Requirements ##

First install JRuby. Then run the following command to install the required gems.

sudo jruby -S gem install sinatra emk-sinatra-url-for haml warbler sinatra-reloader

## Building ##

Simply build from the Voldemort root directory using "ant". The web manager is written in Ruby but it needs the Voldemort JARs.

## Running ##

Run the following command in the web-manager directory and point your browser to localhost:4567.

jruby init.rb

## Deploying ##

The web manager can be packaged as a WAR using the following command from the web-manager directory:

jruby -S warble
95 changes: 95 additions & 0 deletions contrib/web-manager/app/controllers/viewer.rb
@@ -0,0 +1,95 @@
require 'rubygems'
require 'sinatra'
require 'haml'

include Java

get '/' do
redirect url_for '/stores'
end

get '/stores' do
begin
proxy = VoldemortAdmin::AdminProxy.new(@bootstrap_host, @bootstrap_port)
@stores = proxy.stores
rescue
end
unless @stores.nil?
@stores.sort!
haml :index
else
haml :bad_url
end
end

get '/store/:name' do |name|
@name = name
proxy = VoldemortAdmin::AdminProxy.new(@bootstrap_host, @bootstrap_port)
@store = proxy.store(name)
halt 404 unless @store
@limit = 25
fetch_count = @limit + 1
@entries = proxy.entries(name, fetch_count)
@has_more = @entries.size >= fetch_count
@entries = @entries.take(@limit)
haml :store
end

include_class Java::voldemort.client.SocketStoreClientFactory
include_class Java::voldemort.client.ClientConfig

get '/store/:name/:key' do |name, key|
config = ClientConfig.new
config.setBootstrapUrls("tcp://" + @bootstrap_url)
factory = SocketStoreClientFactory.new(config)
client = factory.getStoreClient(name)

proxy = VoldemortAdmin::AdminProxy.new(@bootstrap_host, @bootstrap_port)
@store = proxy.store(name)
key_schema = @store.key_info.schema

# TODO: This only supports keys which are int32 or strings. Figure out how a string
# can be passed from the browser and converted to the appropriate type before calling
# client.getValue.
if (key_schema =~ /int32/)
client.getValue(java.lang.Integer.new(key.to_i)).to_s
else
client.getValue(key).to_s
end
end

get '/stores/new' do
haml :store_new
end

require 'app/helpers/VoldemortAdmin'

post '/stores/new' do
key_info = VoldemortAdmin::SerializerInfo.new
key_info.name = params[:store_key_name]
key_info.schema = params[:store_key_schema]

value_info = VoldemortAdmin::SerializerInfo.new
value_info.name = params[:store_value_name]
value_info.schema = params[:store_value_schema]

store_info = VoldemortAdmin::StoreInfo.new
store_info.name = params[:store_name]
store_info.key_info = key_info
store_info.value_info = value_info

proxy = VoldemortAdmin::AdminProxy.new(@bootstrap_host, @bootstrap_port)
proxy.create_store(store_info)

redirect url_for '/stores'
end

get '/config' do
haml :config
end

post '/config' do
session["bootstrap_host"] = params["bootstrap_host"]
session["bootstrap_port"] = params["bootstrap_port"]
redirect url_for '/stores'
end
140 changes: 140 additions & 0 deletions contrib/web-manager/app/helpers/VoldemortAdmin.rb
@@ -0,0 +1,140 @@
require 'rubygems'

include Java

include_class Java::voldemort.serialization.SerializerDefinition
include_class Java::voldemort.store.StoreDefinition
include_class Java::voldemort.client.RoutingTier
include_class Java::voldemort.client.protocol.admin.AdminClient
include_class Java::voldemort.client.protocol.admin.AdminClientConfig
include_class Java::voldemort.client.protocol.admin.filter.DefaultVoldemortFilter
include_class Java::voldemort.serialization.DefaultSerializerFactory

module VoldemortAdmin

class StoreInfo
attr_accessor :name, :key_info, :value_info

def self.from_def(store_def)
info = StoreInfo.new
info.name = store_def.name
info.key_info = SerializerInfo.from_def(store_def.key_serializer)
info.value_info = SerializerInfo.from_def(store_def.value_serializer)
info
end

def <=>(other)
self.name <=> other.name
end
end

class SerializerInfo
attr_accessor :name, :schema

def self.from_def(serializer_def)
info = SerializerInfo.new
info.name = serializer_def.name
info.schema = serializer_def.current_schema_info if serializer_def.has_schema_info
info
end
end

class AdminProxy
attr_reader :host, :port

def initialize(host, port)
@host = host
@port = port
end

def url
@host + ":" + @port
end

def create_store(store_info)
key_def = SerializerDefinition.new(store_info.key_info.name, store_info.key_info.schema)
value_def = SerializerDefinition.new(store_info.value_info.name, store_info.value_info.schema)

store_def = StoreDefinition.new(
store_info.name,
"bdb", #type
key_def,
value_def,
RoutingTier::CLIENT,
"consistent-routing", #routingStrategyType
1, #replicationFactor
1, #preferredReads
1, #requiredReads
1, #preferredWrites
1, #requiredWrites
nil, #viewOfStore
nil, #valTrans
nil, #zoneReplicationFactor
nil, #zoneCountReads
nil, #zoneCountWrites
nil, #retentionDays
nil #retentionThrottleRate
)

client.add_store(store_def)
end

def store(name)
store = stores.find { |store| store.name == name }
store
end

def entries(name, max=nil)
defs = client.remote_store_def_list(0).value
partitions = cluster.node_by_id(0).partition_ids
store_def = defs.find { |d| d.name == name }
result = []
if store_def
key_serializer = store_def.key_serializer
value_serializer = store_def.value_serializer

entries = client.fetch_entries(0, name, partitions, DefaultVoldemortFilter.new, false)

begin
has_any = entries.has_next?
rescue
end

if !has_any.nil? && has_any
while entries.has_next?
break if !max.nil? and result.size >= max

entry = entries.next
key_bytes = entry.first.get
value_bytes = entry.second.value

factory = DefaultSerializerFactory.new

key = factory.get_serializer(key_serializer).to_object(key_bytes)
value = factory.get_serializer(value_serializer).to_object(value_bytes)

result << [key, value]
end
end
end
result
end

def stores
defs = client.remote_store_def_list(0).value
defs.map do |store_def|
StoreInfo.from_def(store_def)
end
end

private

def cluster
@cluster ||= client.admin_client_cluster
end

def client
@client ||= AdminClient.new("tcp://" + url, AdminClientConfig.new)
end
end
end
4 changes: 4 additions & 0 deletions contrib/web-manager/app/views/bad_url.haml
@@ -0,0 +1,4 @@
%p
There seems to be a problem accessing Voldemort. Try
%a{:href => (url_for "/config")} switching
to a different instance.
15 changes: 15 additions & 0 deletions contrib/web-manager/app/views/config.haml
@@ -0,0 +1,15 @@
%form{:id=>"config_form", :method=>"post", :action => (url_for "/config")}
%h4 Change the Voldemort instance to connect to

%p
%label Host:
%br
%input{:id=>"bootstrap_host", :name=>"bootstrap_host", :type=>"text", :value=>"#{@bootstrap_host}"}

%p
%label Port:
%br
%input{:id=>"bootstrap_port", :name=>"bootstrap_port", :type=>"text", :value=>"#{@bootstrap_port}"}

%p
%input{:type=>"submit", :value=>"Update"}
9 changes: 9 additions & 0 deletions contrib/web-manager/app/views/index.haml
@@ -0,0 +1,9 @@
%div
%a{:href => (url_for "/stores/new")} Create new store

%h3 List of stores

%div
- @stores.each do |store|
%a{:href => (url_for "/store/#{store.name}")}= store.name
%br
18 changes: 18 additions & 0 deletions contrib/web-manager/app/views/layout.haml
@@ -0,0 +1,18 @@
%html
%head
%title Voldemort Cluster Management
%link{:rel => "stylesheet", :href => (url_for "/css/common.css"), :type => "text/css"}
%script{:src => "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"}
%script{:src => (url_for "/script/common.js")}
%body
#header
#title
%h1
%a{:href => (url_for "/stores")} Voldemort Cluster Management
%p
Bootstrap URL:
= @bootstrap_url
%a{:href => (url_for "/config")} Change
#outer
#main
= yield

0 comments on commit ba1512e

Please sign in to comment.