Programming‎ > ‎Golang‎ > ‎

REST Microservices with Gin











Introduction


This post gives an overview with example to write RESTful API services in Golang.


It used Gin, Cassandra.


Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster.


The setup contains 1 node hosting both Cassandra and Golang service 




Services in Go


Schema


Routes


Let us first define the routes (REST APIs)

[Create]  POST : http://127.0.0.1:8080/api/v1/policies

[Read]    GET : http://127.0.0.1:8080/api/v1/policies

[Read]    GET : http://127.0.0.1:8080/api/v1/policies/1

[Update] PUT : http://127.0.0.1:8080/api/v1/policies/1

[Delete]  DELETE : http://127.0.0.1:8080/api/v1/policies/1


Table


Let's also define the table structure to store the policies blob

Table 'policies'

id — big_int(20) & auto-increment

policy — varchar(255)


Gin for Routes


We’re gonna use Gin who is a micro framework routing. It’s friendly easy and fast.

go get github.com/gin-gonic/gin


Code


Ok, let’s start coding ! Firstly in a new “main.go” file, we call our libraries.


package main

import

(

   "github.com/gin-gonic/gin"

   "strconv"

   "github.com/hashicorp/consul/api"

)


Secondly, we declare the structure “Policy” :


type User struct {

    Id int64 `db:"id" json:"id"`

    Policy string `db:"policy" json:"policy"`

}

The “db” param will be used later, with the database connection…


Thirdly, in the main() function, we regoup our routes in a single group :


func main() {

  r := gin.Default()

  v1 := r.Group("api/v1")

  {

  v1.GET("/policies", GetPolicies)

  v1.GET("/policies/:id", GetPolicies)

  v1.POST("/policies", PostPolicy)

  v1.PUT("/policies/:id", UpdatePolicy)

  v1.DELETE("/policies/:id", DeletePolicy)

  }

  r.Run(":8080")

}


Then we declare the five functions calling in the routes





Deployment 


Create a VMa with Redhat flavor CentOS 7.1


Cassandra Node Setup



Install Java


$ sudo yum -y install java-1.8.0-openjdk


$ java -version

openjdk version "1.8.0_65"

OpenJDK Runtime Environment (build 1.8.0_65-b17)

OpenJDK 64-Bit Server VM (build 25.65-b01, mixed mode)


Install Cassandra


$ wget http://www.carfab.com/apachesoftware/cassandra/2.2.4/apache-cassandra-2.2.4-bin.tar.gz 

$ tar -xzvf apache-cassandra-2.2.4-bin.tar.gz

$ cd apache-cassandra-2.2.4


Configure Cassandra


$ sudo mkdir /opt/lib

$ sudo mkdir /opt/lib/cassandra/

$ sudo mkdir /opt/lib/cassandra/data

$ sudo chmod 777 /opt/lib/cassandra/ -R


$ cd conf/

$ cp cassandra.yaml cassandra.yaml.orig


$vi cassandra.yaml


Old

# The name of the cluster. This is mainly used to prevent machines in

# one logical cluster from joining another.

cluster_name: 'Test Cluster'


New

cluster_name: 'CRUD-Cluster'


Old\New - Leave this as it is

# If you already have a cluster with 1 token per node, and wish to migrate to

# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations

num_tokens: 256


Old

# - AllowAllAuthenticator performs no checks - set it to disable authentication.

# - PasswordAuthenticator relies on username/password pairs to authenticate

#   users. It keeps usernames and hashed passwords in system_auth.credentials table.

#   Please increase system_auth keyspace replication factor if you use this authenticator.

#   If using PasswordAuthenticator, CassandraRoleManager must also be used (see below)

authenticator: AllowAllAuthenticator


New

authenticator: PasswordAuthenticator (Recommended)

authenticator: AllowAllAuthenticator



Old

# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.

# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please

#   increase system_auth keyspace replication factor if you use this authorizer.

authorizer: AllowAllAuthorizer


New

authorizer: CassandraAuthorizer (Recommended)

authorizer: AllowAllAuthorizer


Old\New - Use existing only

# Besides Murmur3Partitioner, partitioners included for backwards

# compatibility include RandomPartitioner, ByteOrderedPartitioner, and

# OrderPreservingPartitioner.

#

partitioner: org.apache.cassandra.dht.Murmur3Partitioner


Old

# Directories where Cassandra should store data on disk.  Cassandra

# will spread data evenly across them, subject to the granularity of

# the configured compaction strategy.

# If not set, the default directory is $CASSANDRA_HOME/data/data.

# data_file_directories:

#     - /var/lib/cassandra/data


New - Ensure no-space before data_file_directories

# If not set, the default directory is $CASSANDRA_HOME/data/data.

data_file_directories:

      - /opt/lib/cassandra/data


Old

# commit log.  when running on magnetic HDD, this should be a

# separate spindle than the data directories.

# If not set, the default directory is $CASSANDRA_HOME/data/commitlog.

# commitlog_directory: /var/lib/cassandra/commitlog


New - Ensure no-space before commlilog

# If not set, the default directory is $CASSANDRA_HOME/data/commitlog.

# commitlog_directory: /var/lib/cassandra/commitlog

commitlog_directory: /opt/lib/cassandra/commitlog


Old

saved caches

# If not set, the default directory is $CASSANDRA_HOME/data/saved_caches.

# saved_caches_directory: /var/lib/cassandra/saved_caches


New

# saved_caches_directory: /var/lib/cassandra/saved_caches

saved_caches_directory: /opt/lib/cassandra/saved_caches


Old\New - No change for now, need to change for cluster

# any class that implements the SeedProvider interface and has a

# constructor that takes a Map<String, String> of parameters will do.

seed_provider:

    # Addresses of hosts that are deemed contact points.

    # Cassandra nodes use this list of hosts to find each other and learn

    # the topology of the ring.  You must change this if you are running

    # multiple nodes!

    - class_name: org.apache.cassandra.locator.SimpleSeedProvider

      parameters:

          # seeds is actually a comma-delimited list of addresses.

          # Ex: "<ip1>,<ip2>,<ip3>"

          - seeds: "127.0.0.1"


# If IP based cqlsh access           - seeds: "127.0.0.1,192.168.0.164"


Old

# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.

listen_address: localhost


New

# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.

listen_address: 192.168.0.164


#If IP based cqlsh access  listen_address: 192.168.0.164



Old

# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address

# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4

# address will be used. If true the first ipv6 address will be used. Defaults to false preferring

# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.

rpc_address: localhost


New

# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.

rpc_address: 192.168.0.164


# If IP based cqlsh access rpc_address: 192.168.0.164



Note that unlike listen_address, you can specify 0.0.0.0, but you must also

# set broadcast_rpc_address to a value other than 0.0.0.0.

# Also set broadcast_rpc_address: 1.2.3.4, if rpc_address is set to 0.0.0.0


Old\New - Same, In production use GossipingPropertyFileSnitch, single node use simplesnitch

# You can use a custom Snitch by setting this to the full class name

# of the snitch, which will be assumed to be on your classpath.

endpoint_snitch: SimpleSnitch


if use GossipingPropertyFileSnitch, chance as per your topology

# vi cassandra-rackdc.properties

# These properties are used with GossipingPropertyFileSnitch and will

# indicate the rack and dc for this node

dc=DC1

rack=RAC1


Disable firewall

sudo systemctl disable firewalld

sudo systemctl stop firewalld

sudo systemctl status firewalld


Start Cassandra

$ cd /home/centos/bin/cassandra/apache-cassandra-2.2.3/bin/

$ ./cassandra &


Check cassandra

ps auwx | grep cassandra



Cassandra Tools

$ cd /home/centos/bin/cassandra/apache-cassandra-2.2.3/bin/

$ ./nodetool help

$ ./cqlsh --help


$ ./nodetool version

ReleaseVersion: 2.2.3


$ ./nodetool status

Datacenter: datacenter1

=======================

Status=Up/Down

|/ State=Normal/Leaving/Joining/Moving

--  Address        Load       Tokens       Owns    Host ID                               Rack

UN  127.0.0.1  131.31 KB  256          ?       39fc1d89-ea3d-45aa-ab1c-68139c76945d  rack1


Cassandra Create Table


$ ./cqlsh [192.168.0.164 -u cassandra -p cassandra] # Default user\password not required if authenticator and authorizor is Allow All


$ ./cqlsh

Connected to CRUD-Cluster at 127.0.0.1:9042.

[cqlsh 5.0.1 | Cassandra 2.2.4 | CQL spec 3.3.1 | Native protocol v4]

Use HELP for help.

cqlsh> 



Before you execute the program, Launch `cqlsh` and execute:

> create keyspace demo with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };

> use demo;

> create table demo.policy(class text, name text, blob text, PRIMARY KEY(class, name));


This is not required as class, name is already part of primary key.

> create index on demo.policy(class);

> create index on demo.policy(name);


cqlsh> use demo;

cqlsh:demo> desc policy;


CREATE TABLE demo.policy (

    class text,

    name text,

    blob text,

    PRIMARY KEY (class, name)

) WITH CLUSTERING ORDER BY (name ASC)

    AND bloom_filter_fp_chance = 0.01

    AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}'

    AND comment = ''

    AND compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}

    AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}

    AND dclocal_read_repair_chance = 0.1

    AND default_time_to_live = 0

    AND gc_grace_seconds = 864000

    AND max_index_interval = 2048

    AND memtable_flush_period_in_ms = 0

    AND min_index_interval = 128

    AND read_repair_chance = 0.0

    AND speculative_retry = '99.0PERCENTILE';


cqlsh:demo> select * from policy;


 class  | name       | blob

--------+------------+-------

 common | email-sign | rule5

 common |  email-web | rule6


(2 rows)



Start Consul Server [Optional for Consul Service Discovery]


The consul is used for service discovery of cassandra by CRUD service.


Download consul

$ cd /home/centos

$ wget https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip

$ sudo yum -y install unzip

$ unzip 0.5.2_linux_amd64.zip


$ mkdir /tmp/consul

$ sudo mkdir /etc/consul.d

$ ./consul agent -server -bootstrap-expect=1 -data-dir /tmp/consul -config-dir /etc/consul.d -node=consul-server-db --bind=192.168.0.158 &    


$ ./consul members

    2015/12/18 19:16:52 [INFO] agent.rpc: Accepted client: 127.0.0.1:58388

Node              Address             Status  Type    Build  Protocol  DC

consul-server-db  192.168.0.158:8301  alive   server  0.5.2  2         dc1



Register Cassandra Service in Consul [Optional for Consul Service Discovery]


curl -X PUT http://localhost:8500/v1/agent/service/register -d '{"ID":"cassandra", "name":"cassandra","tags": ["rails"], "port": 9160, "Address":"192.168.0.158"}'

$ curl http://localhost:8500/v1/catalog/service/cassandra | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   181  100   181    0     0   1180      0 --:--:-- --:--:-- --:--:--  1183
[
    {
        "Address": "192.168.0.158",
        "Node": "consul-server-db",
        "ServiceAddress": "192.168.0.158",
        "ServiceID": "cassandra",
        "ServiceName": "cassandra",
        "ServicePort": 9160,
        "ServiceTags": [
            "rails"
        ]
    }
]



CRUD-Server Node Setup


Install Golang

$ sudo yum -y update

$ sudo yum -y install golang


$ go version

go version go1.4.2 linux/amd64



Install gin\gocql\api for Routes

$ go help gopath

$ mkdir /home/centos/repos

$ export GOPATH=/home/centos/repos/

$ go get github.com/gin-gonic/gin

$ go get github.com/gocql/gocql

$ go get github.com/hashicorp/consul/api



Create directory structure for Go service


$ cd /home/centos/repos

$ mkdir crud-service

$ mkdir crud-service/crud-server

$ mkdir crud-service/crud-client

$ mkdir crud-service/crud-test

$ mkdir crud-service/crud-server/bin

$ mkdir crud-service/crud-server/pkg

$ mkdir crud-service/crud-server/src

$ mkdir crud-service/crud-client/bin

$ mkdir crud-service/crud-client/pkg

$ mkdir crud-service/crud-client/src

$ mkdir crud-service/crud-test/bin

$ mkdir crud-service/crud-test/pkg

$ mkdir crud-service/crud-test/src


Create the service main.go


/home/centos/repos/crud-service/crud-server/src/main.go


main.go



 cat src/main.go


package main


import

(

   "fmt"

   "log"

   "github.com/gin-gonic/gin"

//   "strconv"

   "github.com/gocql/gocql"

//   "github.com/hashicorp/consul/api"

)



type Policy struct {

    Class string `db:"class" json:"class"`

    Name string `db:"name" json:"name"`

    Blob string `db:"blob" json:"blob"`

}


/* Before you execute the program, Launch `cqlsh` and execute:

create keyspace demo with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };

create table demo.policy(class text, name text, blob text, PRIMARY KEY(class, name));


// Being primary key, below won;t work

create index on demo.policy(class);

create index on demo.policy(name);


curl -X PUT http://localhost:8500/v1/agent/service/register -d '{"ID":"cassandra", "name":"cassandra","tags": ["rails"], "port": 9160, "Address":"192.168.0.168"}'

curl http://localhost:8500/v1/catalog/service/cassandra | python -m json.tool

*/



func checkErr(err error, msg string) {

    if err != nil {

        log.Fatalln(msg, err)

    }

}



func main() {

  r := gin.Default()

  v1 := r.Group("api/v1")


  {

  v1.GET("/policies/:class", GetPolicies)

  v1.GET("/policies/:class/:name", GetPolicy)

  v1.POST("/policies", PostPolicy)

  v1.PUT("/policies/:class/:name", UpdatePolicy)

  v1.DELETE("/policies/:class/:name", DeletePolicy)

  }


  r.Run(":8080")

}



func GetCassandraIP() (IP string) {


   //var IP string

/*

   //Get a new client

   client, err := api.NewClient(api.DefaultConfig())

   if err != nil {

      panic(err)

   }


    agent := client.Agent()

    services, err := agent.Services()


    fmt.Println("services : ", services)

        if err != nil {

                log.Fatal("err: %v", err)

        }


        if _, ok := services["cassandra"]; !ok {

                log.Fatal("missing service: %v", services)

        }


    IP = services["cassandra"].Address

    fmt.Println(" Service cassandra IP : ", services["cassandra"].Address)

*/

    IP = "127.0.0.1"

    fmt.Println(" Service cassandra IP : ", IP)

    return


}


// CRUD Operations





// READ all policies


func GetPolicies(c *gin.Context) {


    // connect to the cluster

    cluster := gocql.NewCluster(GetCassandraIP())


    // A keyspace in Cassandra is a namespace that defines data replication on nodes. A cluster contains one keyspace per node.

    cluster.Keyspace = "demo"


    // May use gocql.Quorum

    cluster.Consistency = gocql.LocalOne

    session, _ := cluster.CreateSession()


    // Make sure that the connection can close once you are done.

    defer session.Close()


//    ####################################### Query Logic ##########################################


    class := c.Params.ByName("class")


    fmt.Println("class : ", class)


    var policies []Policy

    var policy Policy


    iter := session.Query("SELECT class, name, blob FROM policy WHERE class=?", class).Iter()

    for iter.Scan(&policy.Class, &policy.Name, &policy.Blob) {


        // fmt.Println("1 : ", policy.Class, policy.Name, policy.Blob)

        policies = append(policies, policy)

    }


    if len(policies) > 0 {

        c.JSON(200, policies)

    } else {

        c.JSON(404, gin.H{"error": "no policy(s) into the table"})

    }


    if err := iter.Close(); err != nil {

        log.Fatal(err)

    }



    // curl -i http://localhost:8080/api/v1/policies/common

}




// Read a user



func GetPolicy(c *gin.Context) {


    // connect to the cluster

    cluster := gocql.NewCluster(GetCassandraIP())


    // A keyspace in Cassandra is a namespace that defines data replication on nodes. A cluster contains one keyspace per node.

    cluster.Keyspace = "demo"


    // May use gocql.Quorum

    cluster.Consistency = gocql.LocalOne

    session, _ := cluster.CreateSession()


    // Make sure that the connection can close once you are done.

    defer session.Close()


//    ####################################### Query Logic ##########################################


    class := c.Params.ByName("class")

    name := c.Params.ByName("name")

    var policy Policy


    err := session.Query("SELECT class, name, blob FROM policy WHERE class=? AND name=?", class, name).Scan(&policy.Class, &policy.Name, &policy.Blob);



    if err == nil {

        c.JSON(200, policy)

    } else {

        c.JSON(404, gin.H{"error": "no policy(s) found"})

    }


    // curl -i http://localhost:8080/api/v1/policies/common/email

}





// Create a user


func PostPolicy(c *gin.Context) {


    // connect to the cluster

    cluster := gocql.NewCluster(GetCassandraIP())


    // A keyspace in Cassandra is a namespace that defines data replication on nodes. A cluster contains one keyspace per node.

    cluster.Keyspace = "demo"


    // May use gocql.Quorum

    cluster.Consistency = gocql.LocalOne

    session, _ := cluster.CreateSession()


    // Make sure that the connection can close once you are done.

    defer session.Close()


//    ####################################### Query Logic ##########################################


    var policy Policy

    if c.BindJSON(&policy) == nil {

       fmt.Println("Bind success", c.Params.ByName("Class"))

    } else {

       fmt.Println("Bind failure")

    }



    fmt.Println("Policy: ", policy.Class, policy.Name, policy.Blob)


    if policy.Class != "" && policy.Name != "" && policy.Blob != "" {


    err := session.Query("INSERT INTO policy (class, name, blob) VALUES (?, ?, ?)", policy.Class, policy.Name, policy.Blob).Exec()



            if err == nil {

              c.JSON(201, policy)


/* // Commenting consul KV store

              // Get a new client

                client, err := api.NewClient(api.DefaultConfig())

                if err != nil {

                    panic(err)

                }


                // Get a handle to the KV API

                kv := client.KV()


                key := "notify" + "/" + "policy" + "/" + policy.Class + "/" + policy.Name

                value := []byte("POST")


                fmt.Println(key, " : ", value)




                // PUT a new KV pair

                p := &api.KVPair{Key: key, Value: value}

                _, err = kv.Put(p, nil)

                if err != nil {

                    panic(err)

                } else {

                        fmt.Println("kv updated")

                }

*/


            } else {

                checkErr(err, "Insert failed")

            }

    } else {

        c.JSON(422, gin.H{"error": "fields are empty"})

    }



    // curl -i -X POST -H "Content-Type: application/json" -d "{ \"class\": \"common\", \"name\": \"email\", \"blob\": \"{rule-2}\" }" http://localhost:8080/api/v1/policies

}



// Update a user


func UpdatePolicy(c *gin.Context) {



    // connect to the cluster

    cluster := gocql.NewCluster(GetCassandraIP())


    // A keyspace in Cassandra is a namespace that defines data replication on nodes. A cluster contains one keyspace per node.

    cluster.Keyspace = "demo"


    // May use gocql.Quorum

    cluster.Consistency = gocql.LocalOne

    session, _ := cluster.CreateSession()


    // Make sure that the connection can close once you are done.

    defer session.Close()


//    ####################################### Query Logic ##########################################


    fmt.Println("In Update")



    var json Policy

    c.BindJSON(&json)


    json.Class = c.Params.ByName("class")

    json.Name = c.Params.ByName("name")



    fmt.Println("json : ", json.Class, json.Name, json.Blob)

    var policy Policy


    err := session.Query("SELECT class, name, blob FROM policy WHERE class=? AND name=?", json.Class, json.Name).Scan(&policy.Class, &policy.Name, &policy.Blob);



    if err == nil {

        fmt.Println("select query success ")

        if json.Blob != "" {


        err := session.Query("UPDATE policy SET blob=? WHERE class=? AND name=?", json.Blob, json.Class, json.Name).Exec();


            if err == nil {

              c.JSON(200, json)


/* // Commenting consul KV


             // Get a new client

                client, err := api.NewClient(api.DefaultConfig())

                if err != nil {

                    panic(err)

                }


                // Get a handle to the KV API

                kv := client.KV()


                key := "notify" + "/" + "policy" + "/" + json.Class + "/" + json.Name

                value := []byte("PUT")


                fmt.Println(key, " : ", value)


                // PUT a new KV pair

                p := &api.KVPair{Key: key, Value: value}

                _, err = kv.Put(p, nil)

                if err != nil {

                    panic(err)

                } else {

                        fmt.Println("kv updated")

                }

*/


            } else {

                checkErr(err, "Update failed")

            }

        } else {

            c.JSON(422, gin.H{"error": "fields are empty"})

        }


    } else {

        fmt.Println("select query failed")

        c.JSON(404, gin.H{"error": "policy not found"})

    }




    // curl -i -X PUT -H "Content-Type: application/json" -d "{ \"blob\": \"{rule-2}\" }" http://localhost:8080/api/v1/policies/common/email

}



// Delete a user


func DeletePolicy(c *gin.Context) {



    // connect to the cluster

    cluster := gocql.NewCluster(GetCassandraIP())


    // A keyspace in Cassandra is a namespace that defines data replication on nodes. A cluster contains one keyspace per node.

    cluster.Keyspace = "demo"


    // May use gocql.Quorum

    cluster.Consistency = gocql.LocalOne

    session, _ := cluster.CreateSession()


    // Make sure that the connection can close once you are done.

    defer session.Close()


//    ####################################### Query Logic ##########################################



    class := c.Params.ByName("class")

    name := c.Params.ByName("name")

    var policy Policy


    err := session.Query("SELECT class, name, blob FROM policy WHERE class=? AND name=?", class, name).Scan(&policy.Class, &policy.Name, &policy.Blob);


    if err == nil {

        if err := session.Query("DELETE FROM policy WHERE class=? AND name=?", class, name).Exec(); err != nil {

                        log.Fatal(err)

            }


        if err == nil {

          c.JSON(200, policy)


/* // Commented consul KV


             // Get a new client

                client, err := api.NewClient(api.DefaultConfig())

                if err != nil {

                    panic(err)

                }


                // Get a handle to the KV API

                kv := client.KV()


                key := "notify" + "/" + "policy" + "/" + policy.Class + "/" + policy.Name

                value := []byte("DELETE")


                fmt.Println(key, " : ", value)


                // PUT a new KV pair

                p := &api.KVPair{Key: key, Value: value}

                _, err = kv.Put(p, nil)

                if err != nil {

                    panic(err)

                } else {

                        fmt.Println("kv updated")

                }

*/



        } else {

            checkErr(err, "Delete failed")

        }


    } else {

        c.JSON(404, gin.H{"error": "policy not found"})

    }


    // curl -i -X DELETE http://localhost:8080/api/v1/policies/common/email

}






Disable firewall

sudo systemctl disable firewalld

sudo systemctl stop firewalld

sudo systemctl status firewalld



Start Consul Client [Optional for Consul Service Discovery]


The consul is used for service discovery of cassandra by CRUD service.


Download consul

$ cd /home/centos

$ wget https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip

$ sudo yum -y install unzip

$ unzip 0.5.2_linux_amd64.zip


$ mkdir /tmp/consul

$ sudo mkdir /etc/consul.d

$ ./consul agent -data-dir /tmp/consul -config-dir /etc/consul.d -node=consul-server-db --bind=192.168.0.164 &    


$ ./consul join 192.168.0.164 (IP of Cassandra Node)

$ ./consul members

    2015/12/18 19:22:27 [INFO] agent.rpc: Accepted client: 127.0.0.1:53227

Node                Address             Status  Type    Build  Protocol  DC

consul-server-crud  192.168.0.164:8301  alive   client  0.5.2  2         dc1

consul-server-db    192.168.0.158:8301  alive   server  0.5.2  2         dc1


Start the CRUD service


$ cd /home/centos/repos/crud-service/crud-server/src/

$ go run src/main.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET   /api/v1/policies/:class   --> main.GetPolicies (3 handlers)
[GIN-debug] GET   /api/v1/policies/:class/:name --> main.GetPolicy (3 handlers)
[GIN-debug] POST  /api/v1/policies          --> main.PostPolicy (3 handlers)
[GIN-debug] PUT   /api/v1/policies/:class/:name --> main.UpdatePolicy (3 handlers)
[GIN-debug] DELETE /api/v1/policies/:class/:name --> main.DeletePolicy (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
 


CRUD-Client Node Setup


Create a VM say 192.168.0.168


Access the CRUD service REST interface and validate.


Read Operation (DB empty)


$ curl -i http://192.168.0.164:8080/api/v1/policies/common

HTTP/1.1 404 Not Found

Content-Type: application/json; charset=utf-8

Date: Fri, 18 Dec 2015 15:30:00 GMT

Content-Length: 40


{"error":"no policy(s) into the table"}


Server Log:
[GIN] 2015/12/18 - 21:00:00 | 404 |  126.750701ms | 192.168.0.168:50952 |   GET     /api/v1/policies/common
                                                                                                           


$ curl -i http://192.168.0.164:8080/api/v1/policies/common/email

HTTP/1.1 404 Not Found

Content-Type: application/json; charset=utf-8

Date: Fri, 18 Dec 2015 15:30:48 GMT

Content-Length: 31


{"error":"no policy(s) found"}


Server Log:

[GIN] 2015/12/18 - 21:00:48 | 404 |   91.628026ms | 192.168.0.168:51389 |   GET     /api/v1/policies/common/email

                                                                                                                 


Create Operation


$ curl -i -X POST -H "Content-Type: application/json" -d "{ \"class\": \"common\", \"name\": \"email-sign\", \"blob\": \"rule5\" }" http://192.168.0.164:8080/api/v1/policies

HTTP/1.1 201 Created

Content-Type: application/json; charset=utf-8

Date: Fri, 18 Dec 2015 15:33:03 GMT

Content-Length: 54


{"class":"common","name":"email-sign","blob":"rule5"}


Server Log: 

[GIN] 2015/12/18 - 21:08:51 | 201 |   72.241961ms | 192.168.0.168:55797 |   POST    /api/v1/policies


$ curl -i http://192.168.0.164:8080/api/v1/policies/common

HTTP/1.1 200 OK

Content-Type: application/json; charset=utf-8

Date: Fri, 18 Dec 2015 15:33:11 GMT

Content-Length: 56


[{"class":"common","name":"email-sign","blob":"rule5"}]



$ curl -i -X POST -H "Content-Type: application/json" -d "{ \"class\": \"common\", \"name\": \"email-web\", \"blob\": \"rule6\" }" http://192.168.0.164:8080/api/v1/policies

$ curl -i http://192.168.0.164:8080/api/v1/policies/common
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 18 Dec 2015 15:39:45 GMT
Content-Length: 109

[{"class":"common","name":"email-sign","blob":"rule5"},{"class":"common","name":"email-web","blob":"rule6"}]


Update Operation


$ curl -i -X PUT -H "Content-Type: application/json" -d "{ \"blob\": \"{rule-2}\" }" http://192.168.0.164:8080/api/v1/policies/common/email-sign
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 18 Dec 2015 15:41:19 GMT
Content-Length: 57

{"class":"common","name":"email-sign","blob":"{rule-2}"}

Server Log:
[GIN] 2015/12/18 - 21:11:19 | 200 |   63.486158ms | 192.168.0.168:57144 |   PUT     /api/v1/policies/common/email-sign

$ curl -i http://192.168.0.164:8080/api/v1/policies/common
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 18 Dec 2015 15:42:21 GMT
Content-Length: 112

[{"class":"common","name":"email-sign","blob":"{rule-2}"},{"class":"common","name":"email-web","blob":"rule6"}]

$ curl -i http://192.168.0.164:8080/api/v1/policies/common/email-sign
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Fri, 18 Dec 2015 15:42:34 GMT
Content-Length: 57

{"class":"common","name":"email-sign","blob":"{rule-2}"}


Delete Operation


$ curl -i -X DELETE http://192.168.0.164:8080/api/v1/policies/common/email-sign

HTTP/1.1 200 OK

Content-Type: application/json; charset=utf-8

Date: Fri, 18 Dec 2015 15:43:38 GMT

Content-Length: 57


Server Log:

[GIN] 2015/12/18 - 21:13:38 | 200 |   81.548881ms | 192.168.0.168:58409 |   DELETE  /api/v1/policies/common/email-sign


{"class":"common","name":"email-sign","blob":"{rule-2}"}


$ curl -i http://192.168.0.164:8080/api/v1/policies/common

HTTP/1.1 200 OK

Content-Type: application/json; charset=utf-8

Date: Fri, 18 Dec 2015 15:43:44 GMT

Content-Length: 55


[{"class":"common","name":"email-web","blob":"rule6"}]




Push the CRUD service into Git Repository


Create a repo

https://github.com/deepagargit/crud-service

Clone the repo

git clone https://github.com/deepagargit/crud-service

Create new git branch

$ git checkout -b develop
Switched to a new branch 'develop'

Copy the file in repo


$ ls -R
.:
commands.sh  common  crud-client  crud-server  crud-test  run.sh

./common:
commands.cql

./crud-client:
bin  pkg  src

./crud-client/bin:

./crud-client/pkg:

./crud-client/src:

./crud-server:
bin  pkg  src

./crud-server/bin:

./crud-server/pkg:

./crud-server/src:
main.go

./crud-test:
bin  pkg  src

./crud-test/bin:

./crud-test/pkg:

./crud-test/src:


$ cat run.sh
#!/usr/bin/env bash
cd $(dirname $0)
go run crud-server/src/main.go

$ cat commands.sh
#!/usr/bin/env bash
cd $(dirname $0)
sleep 10s
cqlsh -f common/commands.cql

$ cat common/commands.cql
create keyspace demo with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };
create table demo.policy(class text, name text, blob text, PRIMARY KEY(class, name));


Add the files to Git


Touch the gitkeep file in empty folder.
touch .gitkeep

git add .

$ git status
# On branch develop
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   commands.sh
#       new file:   common/commands.cql
#       new file:   crud-client/.gitkeep
#       new file:   crud-client/bin/.gitkeep
#       new file:   crud-client/pkg/.gitkeep
#       new file:   crud-client/src/.gitkeep
#       new file:   crud-server/bin/.gitkeep
#       new file:   crud-server/pkg/.gitkeep
#       new file:   crud-server/src/main.go
#       new file:   crud-test/.gitkeep
#       new file:   crud-test/bin/.gitkeep
#       new file:   crud-test/pkg/.gitkeep
#       new file:   crud-test/src/.gitkeep
#       new file:   run.sh
#

Add User Config

git config --global user.email "deepak.aagarwal@gmail.com"

git config --global user.name "Deepak Agarwal"

Commit the files

$ git commit -m "Added CRUD service with Golang"

[develop (root-commit) 7611268] Added CRUD service with Golang

 14 files changed, 432 insertions(+)

 create mode 100755 commands.sh

 create mode 100644 common/commands.cql

 create mode 100644 crud-client/.gitkeep

 create mode 100644 crud-client/bin/.gitkeep

 create mode 100644 crud-client/pkg/.gitkeep

 create mode 100644 crud-client/src/.gitkeep

 create mode 100644 crud-server/bin/.gitkeep

 create mode 100644 crud-server/pkg/.gitkeep

 create mode 100644 crud-server/src/main.go

 create mode 100644 crud-test/.gitkeep

 create mode 100644 crud-test/bin/.gitkeep

 create mode 100644 crud-test/pkg/.gitkeep

 create mode 100644 crud-test/src/.gitkeep

 create mode 100755 run.sh


Push to the develop branch

$ git push origin develop

Username for 'https://github.com': deepagargit

Password for 'https://deepagargit@github.com':

Counting objects: 12, done.

Delta compression using up to 16 threads.

Compressing objects: 100% (8/8), done.

Writing objects: 100% (12/12), 3.21 KiB | 0 bytes/s, done.

Total 12 (delta 0), reused 0 (delta 0)

To https://github.com/deepagargit/crud-service

 * [new branch]      develop -> develop






References





Comments