ActiveBlog

Implementing a CouchDB Service on Stackato: Technical Overview
by Rudy Wahono

Rudy Wahono, December 10, 2013
ActiveState has been a long time supporter of colleges and universities by providing development tools as part of the ActiveState Education Partner Program. In line with this initiative, we have recently provided four students from the British Columbia Institute of Technology (BCIT) access to Stackato for a class project. In the second of a two part series, Rudy Wahono provides a technical overview of their project: Developing a CouchDB Big Data Service on Stackato. 
 
Here, we provide an overview of the service our team implemented as part of COMP3900 at the British Columbia Institute of Technology (BCIT). The service implemented is a custom Stackato service that enables a CouchDB database to be exposed to Stackato applications. Specifically, we discuss the implementation of the four major functionalities of the service: provision, unprovision, bind and unbind.
 
Background
Stackato is a private Platform-as-a-Service (PaaS) that allows developers to deploy and manage applications easily. Applications are pushed onto Stackato and can be bound to available services. There are several services already included in Stackato, but users are not limited to these options as they can create their own custom Stackato service.
 
The Stackato service is an application that provides an API endpoint between the Cloud Controller, which communicates with the client, and the background services. In our example, we use Ruby to write the application.  It consists of two parts: the gateway and the node. The Cloud Controller communicates with the service through the gateway. Both the Cloud Controller and the gateway follow a protocol that uses RESTful HTTP to communicate.  
 
Stackato users who want to expose their application to their own services may do so by implementing a custom Stackato service. The custom service enables the user to provision a service and bind it to the application.  To provision a service in our case means to create a new CouchDB database on a CouchDB process that's already running.To bind a service is to bind the application to a service instance that has been provisioned. Binding also creates environment variables for the application to use.
 
The four functionalities of the service are implemented in the file lib/.../couchdb_node.rb which implements the VCAP::Services::Couchdb::Node class. The following sections describe the process behind implementing the provision, unprovision, bind and unbind functionality of our stackato-couchdb service.
 
Preliminary Setup
For our implementation, we used:
 
Stackato v.2.10.6
CouchDB 1.5.0.
 
A gem called rest-client is used to communicate with the CouchDB daemon.

 

require "rest_client"

In the constructor, the following local variables are initialized:

 

@couchdb_admin = options[:couchdb_admin]

@couchdb_password = options[:couchdb_password]

@couchdb_hostname = options[:couchdb_hostname]

@port = options[:port]

 

These variables represent configuration items that were inserted to the config/ couchdb_node.yml configuration file during bootstrap. The config items were parsed to the options hash in  bin/couchdb_node.  Here, they are extracted to the specified local variables.  The couchdb_admin, and couchdb_password refers to the admin name and password for operating couchdb.
 
Provision
For our service, to provision means to create a new database inside CouchDB. The process involves creating a database, storing the name of the new database in a hash, and returning it.  The procedure for provisioning the database is as follows:
 
In the provision method, the name of the database is randomly generated using UUIDTools:
 
instance.name = "db-" + UUIDTools::UUID.random_create.to_s
 
Then, a create_database method is called, passing in the instance as a parameter.  In create_database, the RestClient.put method is called. This sends a PUT request to CouchDB to create a new database.
 
RestClient.put("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/#{db_name}",'')
 
Afterwards, we insert a dummy content to the CouchDB authorization file _security so that we are able to insert users during binding instead of making a new user list each time.
 
initial_authorization = {'admins' => {'names' => [], 'roles' => []}, 'members' => {'names' => [], 'roles' => []}}

RestClient.put("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/#{db_name}/_security", 
initial_authorization.to_json, :content_type => :json)
 
Un-Provision
Unprovision is the opposite of provision.  It simply deletes a specified database and deletes the service instance.  Unprovision takes the name of a node instance as a parameter. From this, we find the node using get_instance(name)
 
Next, we call a delete_database method that sends a DELETE request to CouchDB to delete the database.
 
db_name = instance.name RestClient.delete("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/#{db_name}")
 
The service instance is then deleted in unprovision by the destroy_instance(instance) call.
 
Bind
Binding exposes the application to a database by creating a new user for that database and by returning the user credentials to the application container.  
 
The first step in binding is to create authorization and authentication specifications for the database.  Specifically, user authentication is inserted to _users document. Then, the username is inserted to the database "admins" list in <db_name>/_security document.
 
The credentials are returned by the bind method and stored in the VCAP_SERVICES environment variable in the application container.  
 
The procedure for binding the database is as follows.
 
A username and password are generated using SecureRandom:
 
user = "user-" + SecureRandom.hex(8)
password = SecureRandom.hex(8)
Then a create_database_user method is called, in which the new user is inserted to CouchDB.  
 
The authentication process involves inserting a user authentication to /_users.
 
user_authentication = {'_id' => "org.couchdb.user:#{user}", 'type' => 'user', 'name' => "#{user}", 'roles' => [], 
'password' => "#{password}" }

RestClient.put("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/_users/org.couchdb.user:#{user}",

user_authentication.to_json, :content_type => :json)
 
The authorization process involves getting the content of <db_name>/_security, parsing the JSON content to a hash (using JSON gem), inserting the username to the admins section of the hash, and then updating the _security document by sending a PUT request.
 
securityContent = RestClient.get("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/#{name}/_security", 
{:accept => :json})
  
securityContent = JSON.parse(securityContent)

securityContent["admins"]["names"].push(user)

RestClient.put("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/#{name}/_security", 
securityContent.to_json, :content_type => :json)
 
A new credential that includes the username and password is then created and returned.
 
Un-Bind
 
Unbind is the opposite of bind. Unbind revokes an application's access to the specified database.  Specifically, the user is deleted from the  _users document and the database's_security document.  
 
The procedure for unbind is similar to bind, except that the user will be deleted instead.  However, in CouchDB, there is one important caveat when deleting a user from the_users document, which is that a _rev id is required in the request to delete the user.  The _rev id specifies the revision version of the document.  CouchDB requires this to be sent with the delete request to prevent conflicts.  Thus, _rev is extracted from the _users document before a delete request is sent.
 
The procedure for deleting the database is as follows.  The user is first deleted from the database's _security document.
 
securityContent = RestClient.get("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/#{name}/_security", 
{:accept => :json})

securityContent = JSON.parse(securityContent)

securityContent["admins"]["names"].delete("#{user}")

RestClient.put("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/#{name}/_security", 
securityContent.to_json, :content_type => :json)
 
The user authentication is deleted from _users.  To do this, the  _rev id needs to be extracted from the _users document.  This is achieved by sending a GET request for the _users document, parsing the content, and extracting _rev to a variable.  A delete request is then sent with the user and _rev as parameters.
 
# get the content of _users
response =  
RestClient.get("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}:#{@port}/_users/org.couchdb.user:#{user}", 
{:accept => :json})

# parsing the response
response = JSON.parse(response)

# extracting _rev
rev = response["_rev"]

# deleting the user from _users by passing in the username and _rev as parameters   
RestClient.delete("http://#{@couchdb_admin}:#{@couchdb_password}@#{@couchdb_hostname}: #{@port}/_users/org.couchdb.user:#{user}?rev=#{rev}")
 

Special Thanks to Matthew Fisher, Jeff Hobbs, and Bruce Link.

 
 
 
Click here to read the first blog in the series: Implementing a CouchDB Service on Stackato: Project Overview.
 
 

Subscribe to ActiveState Blogs by Email

Share this post:

Category: stackato
About the Author: RSS

Rudy Wahono is a second-year Computer Systems Technology student at the British Columbia Institute of Technology. He has a bachelor's degree in Chemistry from University of British Columbia. Rudy loves to read, is a fan of soccer, and never shies away from a nice batch of iced cappuccinos.