Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v2 #124

Closed
wants to merge 6 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add etcd, rabbitmq, channel, client modules & channel and client endp…
…oints
Clivern committed Jan 3, 2021
commit 3930f03078eb07a6c00b0eeeec334a4513bf4583
9 changes: 9 additions & 0 deletions .github/auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# https://github.com/bobvanderlinden/probot-auto-merge
blockingLabels:
- blocking
rules:
- minApprovals:
OWNER: 1
MEMBER: 2
- requiredLabels:
- merge
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -21,10 +21,16 @@ jobs:
export PATH=${PATH}:`go env GOPATH`/bin
make install_revive

- name: Install etcd server
run: |
bash ./bin/local_etcd_ubuntu.sh

- name: Run make ci
run: |
export PATH=${PATH}:`go env GOPATH`/bin
make ci
make integration
make integration
git status
git diff > diff.log
cat diff.log
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -11,16 +11,14 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Config file
config.dist.yml

# App build
beaver

# Ignore vendor and run dep ensure
vendor

config.test.yml
config.prod.yml
config.dev.yml
coverage.html
cover.out

64 changes: 0 additions & 64 deletions .travis.yml

This file was deleted.

12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -47,14 +47,24 @@ check_license:
.PHONY: test_short
test_short:
@echo ">> ============= Running Short Tests ============= <<"
$(GO) clean -testcache
$(GO) test -short $(pkgs)


## test: Run test cases.
.PHONY: test
test:
@echo ">> ============= Running All Tests ============= <<"
$(GO) test -v -cover $(pkgs)
$(GO) clean -testcache
$(GO) test -tags=unit -v -cover $(pkgs)


## integration: Run integration test cases (Requires etcd, RabbitMQ)
.PHONY: integration
integration:
@echo ">> ============= Running All Tests ============= <<"
$(GO) clean -testcache
$(GO) test -tags=integration -v -cover $(pkgs)


## lint: Lint the code.
2 changes: 1 addition & 1 deletion beaver.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Clivern. All rights reserved.
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

34 changes: 34 additions & 0 deletions beaver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build integration

package main

import (
"fmt"
"testing"

"github.com/clivern/beaver/core/driver"
"github.com/clivern/beaver/pkg"

"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)

// TestMain
func TestMain(m *testing.M) {
fmt.Println("====> Setup")
baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

db := driver.NewEtcdDriver()
db.Connect()
defer db.Close()

gin.SetMode(gin.ReleaseMode)

// Cleanup
db.Delete(viper.GetString("app.database.etcd.databaseName"))
}
54 changes: 54 additions & 0 deletions bin/install_etcd_ubuntu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash

ETCD_VER=v3.4.14

# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}

rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test

curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz

/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version

sudo cp /tmp/etcd-download-test/etcd /usr/local/bin/
sudo cp /tmp/etcd-download-test/etcdctl /usr/local/bin/

sudo mkdir -p /var/lib/etcd/
sudo mkdir /etc/etcd

sudo groupadd --system etcd
sudo useradd -s /sbin/nologin --system -g etcd etcd

sudo chown -R etcd:etcd /var/lib/etcd/

echo "[Unit]
Description=etcd key-value store
Documentation=https://github.com/etcd-io/etcd
After=network.target

[Service]
User=etcd
Type=notify
Environment=ETCD_DATA_DIR=/var/lib/etcd
Environment=ETCD_NAME=%m
ExecStart=/usr/local/bin/etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379
Restart=always
RestartSec=10s
LimitNOFILE=40000

[Install]
WantedBy=multi-user.target" > /etc/systemd/system/etcd.service

sudo systemctl daemon-reload
sudo systemctl start etcd.service

# Then enable authentication on etcd server
# etcdctl user add root
# etcdctl auth enable
20 changes: 20 additions & 0 deletions bin/install_rabbitmq_ubuntu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

apt-get update
echo "deb http://www.rabbitmq.com/debian/ testing main" >> /etc/apt/sources.list
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc | sudo apt-key add -

apt-get update
sudo apt-get install -y rabbitmq-server

sudo systemctl enable rabbitmq-server
sudo systemctl start rabbitmq-server

sudo rabbitmqctl add_user admin password
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

sudo rabbitmq-plugins enable rabbitmq_management

# Login into http://127.0.0.1:15672/
# admin/password
22 changes: 22 additions & 0 deletions bin/local_etcd_mac.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

cd cache

# Cleanup
rm etcd-v3.4.14-darwin-amd64.zip
rm -rf etcd-v3.4.14-darwin-amd64
rm etcd.log
rm -rf default.etcd

curl -OL https://github.com/etcd-io/etcd/releases/download/v3.4.14/etcd-v3.4.14-darwin-amd64.zip
unzip etcd-v3.4.14-darwin-amd64.zip

./etcd-v3.4.14-darwin-amd64/etcd > etcd.log 2>&1 &

echo "===> etcd PID:" $!

sleep 10

curl -L http://127.0.0.1:2379/version

cd ..
21 changes: 21 additions & 0 deletions bin/local_etcd_ubuntu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/bash

cd cache

# Cleanup
rm etcd-v3.4.14-linux-amd64.tar.gz
rm -rf etcd-v3.4.14-linux-amd64
rm etcd.log
rm -rf default.etcd

curl -sL https://github.com/etcd-io/etcd/releases/download/v3.4.14/etcd-v3.4.14-linux-amd64.tar.gz | tar xz

./etcd-v3.4.14-linux-amd64/etcd > etcd.log 2>&1 &

echo "===> etcd PID:" $!

sleep 10

curl -L http://127.0.0.1:2379/version

cd ..
File renamed without changes.
4 changes: 2 additions & 2 deletions cmd/license.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Clivern. All rights reserved.
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

@@ -16,7 +16,7 @@ var licenseCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(`MIT License

Copyright (c) 2020 Clivern
Copyright (c) 2018 Clivern

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Clivern. All rights reserved.
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

90 changes: 28 additions & 62 deletions cmd/serve.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Clivern. All rights reserved.
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

@@ -21,7 +21,6 @@ import (

"github.com/drone/envsubst"
"github.com/gin-gonic/gin"
"github.com/markbates/pkger"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -63,11 +62,10 @@ var serveCmd = &cobra.Command{
}

if viper.GetString("app.log.output") != "stdout" {
fs := util.FileSystem{}
dir, _ := filepath.Split(viper.GetString("app.log.output"))

if !fs.DirExists(dir) {
if _, err := fs.EnsureDir(dir, 777); err != nil {
if !util.DirExists(dir) {
if _, err := util.EnsureDir(dir, 755); err != nil {
panic(fmt.Sprintf(
"Directory [%s] creation failed with error: %s",
dir,
@@ -76,15 +74,17 @@ var serveCmd = &cobra.Command{
}
}

if !fs.FileExists(viper.GetString("app.log.output")) {
if !util.FileExists(viper.GetString("app.log.output")) {
f, err := os.Create(viper.GetString("app.log.output"))

if err != nil {
panic(fmt.Sprintf(
"Error while creating log file [%s]: %s",
viper.GetString(fmt.Sprintf("%s.log.output", viper.GetString("role"))),
viper.GetString("app.log.output"),
err.Error(),
))
}

defer f.Close()
}
}
@@ -119,6 +119,8 @@ var serveCmd = &cobra.Command{
log.SetFormatter(&log.TextFormatter{})
}

viper.SetDefault("app.name", util.GenerateUUID4())

r := gin.Default()

r.Use(middleware.Correlation())
@@ -133,66 +135,30 @@ var serveCmd = &cobra.Command{

r.GET("/", controller.Index)
r.GET("/_health", controller.HealthCheck)
r.GET("/_node", controller.GetNodeInfo)
r.GET("/_metrics", controller.GetMetrics)

api := r.Group("/api")
apiv1 := r.Group("/api/v2")
{
api.GET("/channel/:name", controller.GetChannelByName)
api.POST("/channel", controller.CreateChannel)
api.DELETE("/channel/:name", controller.DeleteChannelByName)
api.PUT("/channel/:name", controller.UpdateChannelByName)

api.GET("/client/:id", controller.GetClientByID)
api.POST("/client", controller.CreateClient)
api.DELETE("/client/:id", controller.DeleteClientByID)
api.PUT("/client/:id/unsubscribe", controller.Unsubscribe)
api.PUT("/client/:id/subscribe", controller.Subscribe)

api.GET("/config/:key", controller.GetConfigByKey)
api.POST("/config", controller.CreateConfig)
api.DELETE("/config/:key", controller.DeleteConfigByKey)
api.PUT("/config/:key", controller.UpdateConfigByKey)
apiv1.GET("/client/:id", controller.GetClientByID)
apiv1.POST("/client", controller.CreateClient)
apiv1.DELETE("/client/:id", controller.DeleteClientByID)
apiv1.PUT("/client/:id/unsubscribe", controller.Unsubscribe)
apiv1.PUT("/client/:id/subscribe", controller.Subscribe)

apiv1.GET("/channel/:name", controller.GetChannelByName)
apiv1.POST("/channel", controller.CreateChannel)
apiv1.DELETE("/channel/:name", controller.DeleteChannelByName)
apiv1.PUT("/channel/:name", controller.UpdateChannelByName)
apiv1.GET("/channel/:name/subscribers", controller.GetChannelSubscribersByName)
apiv1.GET("/channel/:name/listeners", controller.GetChannelListenersByName)
}

socket := &controller.Websocket{}
socket.Init()

r.GET("/ws/:id/:token", func(c *gin.Context) {
socket.HandleConnections(
c.Writer,
c.Request,
c.Param("id"),
c.Param("token"),
c.Request.Header.Get("X-Correlation-ID"),
)
})

r.POST("/api/broadcast", func(c *gin.Context) {
rawBody, err := c.GetRawData()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"error": "Invalid request",
})
return
}
socket.BroadcastAction(c, rawBody)
})

r.POST("/api/publish", func(c *gin.Context) {
rawBody, err := c.GetRawData()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": "error",
"error": "Invalid request",
})
return
}
socket.PublishAction(c, rawBody)
})
r.GET(
viper.GetString("app.metrics.prometheus.endpoint"),
gin.WrapH(controller.Metrics()),
)

go socket.HandleMessages()
// Goroutine for node heartbeat
go controller.Heartbeat()

var runerr error

4 changes: 2 additions & 2 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Clivern. All rights reserved.
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

@@ -27,7 +27,7 @@ var versionCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(
fmt.Sprintf(
`Current Walrus Version %v Commit %v, Built @%v By %v.`,
`Current Beaver Version %v Commit %v, Built @%v By %v.`,
Version,
Commit,
Date,
68 changes: 68 additions & 0 deletions config.dist.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# App configs
app:
# Env mode (dev or prod)
mode: ${BEAVER_APP_MODE:-dev}
# HTTP port
port: ${BEAVER_API_PORT:-8080}
# App URL
url: ${BEAVER_APP_DOMAIN:-http://127.0.0.1:8080}
# TLS configs
tls:
status: ${BEAVER_API_TLS_STATUS:-off}
pemPath: ${BEAVER_API_TLS_PEMPATH:-cert/server.pem}
keyPath: ${BEAVER_API_TLS_KEYPATH:-cert/server.key}

# API Configs
api:
key: ${BEAVER_API_KEY:- }

# Runtime, Requests/Response and Beetle Metrics
metrics:
prometheus:
# Route for the metrics endpoint
endpoint: ${BEAVER_METRICS_PROM_ENDPOINT:-/metrics}

# Log configs
log:
# Log level, it can be debug, info, warn, error, panic, fatal
level: ${BEAVER_LOG_LEVEL:-info}
# output can be stdout or abs path to log file /var/logs/beetle.log
output: ${BEAVER_LOG_OUTPUT:-stdout}
# Format can be json
format: ${BEAVER_LOG_FORMAT:-json}

database:
# database driver
driver: ${BEAVER_DB_DRIVER:-etcd}

etcd:
# etcd database name or prefix
databaseName: ${BEAVER_DB_ETCD_DB:-beaver_v2}
# etcd username
username: ${BEAVER_DB_ETCD_USERNAME:- }
# etcd password
password: ${BEAVER_DB_ETCD_PASSWORD:- }
# etcd endpoints
endpoints: ${BEAVER_DB_ETCD_ENDPOINTS:-http://127.0.0.1:2379}
# Timeout in seconds
timeout: 30

# Backend HTTP Webhook
webhook:
url: ${BEAVER_WEBHOOK_URL:- }
retry: ${BEAVER_WEBHOOK_RETRY:-3}
apiKey: ${BEAVER_WEBHOOK_API_KEY:- }

# Cluster Configs
cluster:
# Whether to enable or disable the cluster
enable: ${BEAVER_CLUSTER_STATUS:-true}

broker:
# Broker driver, supported drivers are rabbitmq
driver: ${BEAVER_CLUSTER_BROKER_DRIVER:-rabbitmq}

# RabbitMQ Driver Configs
rabbitmq:
# Connection string
connection: ${BEAVER_BROKER_RABBITMQ_CONNECTION:-amqp://username:password@127.0.0.1:5672/}
21 changes: 0 additions & 21 deletions config.travis.yml

This file was deleted.

21 changes: 0 additions & 21 deletions config.yml

This file was deleted.

23 changes: 23 additions & 0 deletions core/cluster/broadcast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package cluster

// This module used to broadcast messages to all cluster nodes
// By default if the node is part of a cluster, it will join
// RabbitMQ

type Broadcast struct {
}

type Message struct {
}

func (b *Broadcast) Publish(m *Message) error {
return nil
}

func (b *Broadcast) Listen() {

}
33 changes: 33 additions & 0 deletions core/cluster/broadcast_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build unit

package cluster

import (
"testing"

"github.com/franela/goblin"
)

// TestBroadcast test cases
func TestBroadcast(t *testing.T) {
g := goblin.Goblin(t)

g.Describe("#Func", func() {
g.It("It should satisfy all provided test cases", func() {
var tests = []struct {
input int
wantOutput int
}{
{1, 1},
}

for _, tt := range tests {
g.Assert(tt.input).Equal(tt.wantOutput)
}
})
})
}
87 changes: 87 additions & 0 deletions core/cluster/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package cluster

import (
"fmt"
"os"
"strings"

"github.com/clivern/beaver/core/driver"

"github.com/spf13/viper"
)

// Node type
type Node struct {
db driver.Database
}

// NewNode creates a node instance
func NewNode(db driver.Database) *Node {
result := new(Node)
result.db = db

return result
}

// Alive report the node as live to etcd
func (n *Node) Alive(seconds int64) error {
hostname, err := n.GetHostname()

if err != nil {
return err
}

key := fmt.Sprintf(
"%s/node/%s__%s",
viper.GetString("app.database.etcd.databaseName"),
hostname,
viper.GetString("app.name"),
)

leaseID, err := n.db.CreateLease(seconds)

if err != nil {
return err
}

err = n.db.PutWithLease(fmt.Sprintf("%s/state", key), "alive", leaseID)

if err != nil {
return err
}

err = n.db.PutWithLease(fmt.Sprintf("%s/url", key), viper.GetString("app.url"), leaseID)

if err != nil {
return err
}

err = n.db.PutWithLease(fmt.Sprintf("%s/load", key), "0", leaseID)

if err != nil {
return err
}

err = n.db.RenewLease(leaseID)

if err != nil {
return err
}

return nil
}

// GetHostname gets the hostname
func (n *Node) GetHostname() (string, error) {
hostname, err := os.Hostname()

if err != nil {
return "", err
}

return strings.ToLower(hostname), nil
}
72 changes: 72 additions & 0 deletions core/cluster/node_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build integration

package cluster

import (
"fmt"
"testing"
"time"

"github.com/clivern/beaver/core/driver"
"github.com/clivern/beaver/pkg"

"github.com/franela/goblin"
"github.com/spf13/viper"
)

// TestIntegrationNodeAlive
func TestIntegrationNodeAlive(t *testing.T) {
// Skip if -short flag exist
if testing.Short() {
t.Skip("skipping test in short mode.")
}

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

db := driver.NewEtcdDriver()
db.Connect()
defer db.Close()

// Cleanup
db.Delete(viper.GetString("app.database.etcd.databaseName"))

node := NewNode(db)
stats := NewStats(db)

g := goblin.Goblin(t)

g.Describe("#NodeAlive", func() {
g.It("It should return zero count", func() {
result, err := stats.GetTotalNodes()

g.Assert(result).Equal(0)
g.Assert(err).Equal(nil)
})

g.It("It should return nil", func() {
g.Assert(node.Alive(1)).Equal(nil)
})

g.It("It should return 1", func() {
result, err := stats.GetTotalNodes()

g.Assert(result).Equal(1)
g.Assert(err).Equal(nil)
})

g.It("It should return zero count", func() {
// Wait till lease expire
time.Sleep(3 * time.Second)

result, err := stats.GetTotalNodes()

g.Assert(result).Equal(0)
g.Assert(err).Equal(nil)
})
})
}
79 changes: 79 additions & 0 deletions core/cluster/node_unit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build unit

package cluster

import (
"fmt"
"testing"

"github.com/clivern/beaver/core/driver"
"github.com/clivern/beaver/pkg"

"github.com/franela/goblin"
"go.etcd.io/etcd/clientv3"
)

// TestUnitNodeAlive test cases
func TestUnitNodeAlive(t *testing.T) {

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

g := goblin.Goblin(t)

g.Describe("#Node.Alive", func() {
g.It("It should satisfy all provided test cases", func() {
var tests = []struct {
input int64
mockReturn error
wantError bool
}{
{5, nil, false},
{6, nil, false},
{7, nil, false},
{5, fmt.Errorf("error1"), true},
{6, fmt.Errorf("error2"), true},
{7, fmt.Errorf("error3"), true},
}

for _, tt := range tests {
e := new(driver.EtcdMock)
n := NewNode(e)

result, _ := n.GetHostname()

e.On("CreateLease", int64(tt.input)).Return(clientv3.LeaseID(1234567), tt.mockReturn)
e.On("RenewLease", clientv3.LeaseID(1234567)).Return(tt.mockReturn)
e.On("PutWithLease", fmt.Sprintf("beaver_v2/node/%s__x-x-x-x/state", result), "alive", clientv3.LeaseID(1234567)).Return(tt.mockReturn)
e.On("PutWithLease", fmt.Sprintf("beaver_v2/node/%s__x-x-x-x/url", result), "http://127.0.0.1:8080", clientv3.LeaseID(1234567)).Return(tt.mockReturn)
e.On("PutWithLease", fmt.Sprintf("beaver_v2/node/%s__x-x-x-x/load", result), "0", clientv3.LeaseID(1234567)).Return(tt.mockReturn)

g.Assert(n.Alive(tt.input) != nil).Equal(tt.wantError)
}
})
})
}

// TestUnitNodeGetHostname test cases
func TestUnitNodeGetHostname(t *testing.T) {

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

g := goblin.Goblin(t)

g.Describe("#Node.GetHostname", func() {
g.It("It should satisfy all provided test cases", func() {
e := new(driver.EtcdMock)
n := NewNode(e)

result, err := n.GetHostname()
g.Assert(err).Equal(nil)
g.Assert(result != "").Equal(true)
})
})
}
77 changes: 77 additions & 0 deletions core/cluster/stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package cluster

import (
"fmt"

"github.com/clivern/beaver/core/driver"

"github.com/spf13/viper"
)

// Stats type
type Stats struct {
db driver.Database
}

// NewStats creates a stats instance
func NewStats(db driver.Database) *Stats {
result := new(Stats)
result.db = db

return result
}

// GetTotalNodes gets total nodes count
func (s *Stats) GetTotalNodes() (int, error) {

key := fmt.Sprintf(
"%s/node",
viper.GetString("app.database.etcd.databaseName"),
)

keys, err := s.db.GetKeys(key)

if err != nil {
return 0, err
}

return len(keys), nil
}

// GetTotalChannels gets total channels count
func (s *Stats) GetTotalChannels() (int, error) {

key := fmt.Sprintf(
"%s/channel",
viper.GetString("app.database.etcd.databaseName"),
)

keys, err := s.db.GetKeys(key)

if err != nil {
return 0, err
}

return len(keys), nil
}

// GetTotalClients gets total clients count
func (s *Stats) GetTotalClients() (int, error) {

key := fmt.Sprintf(
"%s/client",
viper.GetString("app.database.etcd.databaseName"),
)

keys, err := s.db.GetKeys(key)

if err != nil {
return 0, err
}

return len(keys), nil
}
148 changes: 148 additions & 0 deletions core/cluster/stats_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build integration

package cluster

import (
"fmt"
"testing"

"github.com/clivern/beaver/core/driver"
"github.com/clivern/beaver/pkg"

"github.com/franela/goblin"
"github.com/spf13/viper"
)

// TestIntegrationNodeStats
func TestIntegrationNodeStats(t *testing.T) {
// Skip if -short flag exist
if testing.Short() {
t.Skip("skipping test in short mode.")
}

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

db := driver.NewEtcdDriver()
db.Connect()
defer db.Close()

// Cleanup
db.Delete(viper.GetString("app.database.etcd.databaseName"))

stats := NewStats(db)

g := goblin.Goblin(t)

g.Describe("#GetTotalNodes", func() {
g.It("It should return zero count", func() {
result, err := stats.GetTotalNodes()

g.Assert(result).Equal(0)
g.Assert(err).Equal(nil)
})

g.It("It should return 2", func() {
db.Put(fmt.Sprintf(
"%s/node/node1/item1",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/node/node1/item2",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/node/node2/item1",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/node/node2/item2",
viper.GetString("app.database.etcd.databaseName"),
), "#")

result, err := stats.GetTotalNodes()

g.Assert(result).Equal(2)
g.Assert(err).Equal(nil)
})
})

g.Describe("#GetTotalChannels", func() {
g.It("It should return zero count", func() {
result, err := stats.GetTotalChannels()

g.Assert(result).Equal(0)
g.Assert(err).Equal(nil)
})

g.It("It should return 2", func() {
db.Put(fmt.Sprintf(
"%s/channel/channel1/item1",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/channel/channel1/item2",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/channel/channel2/item1",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/channel/channel2/item2",
viper.GetString("app.database.etcd.databaseName"),
), "#")

result, err := stats.GetTotalChannels()

g.Assert(result).Equal(2)
g.Assert(err).Equal(nil)
})
})

g.Describe("#GetTotalClients", func() {
g.It("It should return zero count", func() {
result, err := stats.GetTotalClients()

g.Assert(result).Equal(0)
g.Assert(err).Equal(nil)
})

g.It("It should return 2", func() {
db.Put(fmt.Sprintf(
"%s/client/client1/item1",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/client/client1/item2",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/client/client2/item1",
viper.GetString("app.database.etcd.databaseName"),
), "#")

db.Put(fmt.Sprintf(
"%s/client/client2/item2",
viper.GetString("app.database.etcd.databaseName"),
), "#")

result, err := stats.GetTotalClients()

g.Assert(result).Equal(2)
g.Assert(err).Equal(nil)
})
})
}
131 changes: 131 additions & 0 deletions core/cluster/stats_unit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build unit

package cluster

import (
"fmt"
"testing"

"github.com/clivern/beaver/core/driver"
"github.com/clivern/beaver/pkg"

"github.com/franela/goblin"
)

// TestUnitStatsGetTotalNodes test cases
func TestUnitStatsGetTotalNodes(t *testing.T) {

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

g := goblin.Goblin(t)

g.Describe("#Stats.GetTotalNodes", func() {
g.It("It should satisfy all provided test cases", func() {
var tests = []struct {
inputKey string
inputKeys []string
inputError error
wantCount int
wantError bool
}{
{"beaver_v2/node", []string{"a", "b", "c"}, nil, 3, false},
{"beaver_v2/node", []string{"a", "b", "c", "d"}, nil, 4, false},
{"beaver_v2/node", []string{}, nil, 0, false},
{"beaver_v2/node", []string{"a", "b", "c", "d"}, fmt.Errorf("Error1"), 0, true},
{"beaver_v2/node", []string{}, fmt.Errorf("Error1"), 0, true},
}

for _, tt := range tests {
e := new(driver.EtcdMock)
s := NewStats(e)

e.On("GetKeys", tt.inputKey).Return(tt.inputKeys, tt.inputError)
count, err := s.GetTotalNodes()

g.Assert(count).Equal(tt.wantCount)
g.Assert(err != nil).Equal(tt.wantError)
}
})
})
}

// TestUnitStatsGetTotalChannels test cases
func TestUnitStatsGetTotalChannels(t *testing.T) {

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

g := goblin.Goblin(t)

g.Describe("#Stats.GetTotalChannels", func() {
g.It("It should satisfy all provided test cases", func() {
var tests = []struct {
inputKey string
inputKeys []string
inputError error
wantCount int
wantError bool
}{
{"beaver_v2/channel", []string{"a", "b", "c"}, nil, 3, false},
{"beaver_v2/channel", []string{"a", "b", "c", "d"}, nil, 4, false},
{"beaver_v2/channel", []string{}, nil, 0, false},
{"beaver_v2/channel", []string{"a", "b", "c", "d"}, fmt.Errorf("Error1"), 0, true},
{"beaver_v2/channel", []string{}, fmt.Errorf("Error1"), 0, true},
}

for _, tt := range tests {
e := new(driver.EtcdMock)
s := NewStats(e)

e.On("GetKeys", tt.inputKey).Return(tt.inputKeys, tt.inputError)
count, err := s.GetTotalChannels()

g.Assert(count).Equal(tt.wantCount)
g.Assert(err != nil).Equal(tt.wantError)
}
})
})
}

// TestUnitStatsGetTotalClients test cases
func TestUnitStatsGetTotalClients(t *testing.T) {

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

g := goblin.Goblin(t)

g.Describe("#Stats.GetTotalClients", func() {
g.It("It should satisfy all provided test cases", func() {
var tests = []struct {
inputKey string
inputKeys []string
inputError error
wantCount int
wantError bool
}{
{"beaver_v2/client", []string{"a", "b", "c"}, nil, 3, false},
{"beaver_v2/client", []string{"a", "b", "c", "d"}, nil, 4, false},
{"beaver_v2/client", []string{}, nil, 0, false},
{"beaver_v2/client", []string{"a", "b", "c", "d"}, fmt.Errorf("Error1"), 0, true},
{"beaver_v2/client", []string{}, fmt.Errorf("Error1"), 0, true},
}

for _, tt := range tests {
e := new(driver.EtcdMock)
s := NewStats(e)

e.On("GetKeys", tt.inputKey).Return(tt.inputKeys, tt.inputError)
count, err := s.GetTotalClients()

g.Assert(count).Equal(tt.wantCount)
g.Assert(err != nil).Equal(tt.wantError)
}
})
})
}
367 changes: 274 additions & 93 deletions core/controller/channel.go

Large diffs are not rendered by default.

207 changes: 207 additions & 0 deletions core/controller/channel_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build integration

package controller

import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/clivern/beaver/core/driver"
"github.com/clivern/beaver/pkg"

"github.com/franela/goblin"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)

// TestIntegrationChannelController
func TestIntegrationChannelController(t *testing.T) {
// Skip if -short flag exist
if testing.Short() {
t.Skip("skipping test in short mode.")
}

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

db := driver.NewEtcdDriver()
db.Connect()
defer db.Close()

gin.SetMode(gin.ReleaseMode)

// Cleanup
db.Delete(viper.GetString("app.database.etcd.databaseName"))

g := goblin.Goblin(t)

g.Describe("#CreateChannel", func() {
g.It("It should create a channel", func() {
r := gin.Default()
r.POST("/api/v2/channel", CreateChannel)

w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v2/channel", strings.NewReader(`{"name": "cc_test_001", "type": "private"}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(201)
g.Assert(strings.Contains(w.Body.String(), "cc_test_001")).Equal(true)
})
})

g.Describe("#CreateChannel", func() {
g.It("It should create a channel", func() {
r := gin.Default()
r.POST("/api/v2/channel", CreateChannel)

w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v2/channel", strings.NewReader(`{"name": "cc_test_003", "type": "public"}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(201)
g.Assert(strings.Contains(w.Body.String(), "cc_test_003")).Equal(true)
})
})

g.Describe("#GetChannelByName", func() {
g.It("It should return 404", func() {
r := gin.Default()
r.GET("/api/v2/channel/:name", GetChannelByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/channel/cc_test_002", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(404)
g.Assert(strings.Contains(w.Body.String(), "not found")).Equal(true)
})

g.It("It should return 200", func() {
r := gin.Default()
r.GET("/api/v2/channel/:name", GetChannelByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/channel/cc_test_001", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
g.Assert(strings.Contains(w.Body.String(), "cc_test_001")).Equal(true)
})
})

g.Describe("#UpdateChannelByName", func() {
g.It("It should return 404", func() {
r := gin.Default()
r.PUT("/api/v2/channel/:name", UpdateChannelByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/api/v2/channel/cc_test_002", strings.NewReader(`{"type": "public"}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(404)
g.Assert(strings.Contains(w.Body.String(), "not found")).Equal(true)
})

g.It("It should return 200", func() {
r := gin.Default()
r.PUT("/api/v2/channel/:name", UpdateChannelByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/api/v2/channel/cc_test_001", strings.NewReader(`{"type": "public"}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
g.Assert(strings.Contains(w.Body.String(), "public")).Equal(true)
})
})

g.Describe("#DeleteChannelByName", func() {
g.It("It should return 404", func() {
r := gin.Default()
r.DELETE("/api/v2/channel/:name", DeleteChannelByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v2/channel/cc_test_002", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(404)
g.Assert(strings.Contains(w.Body.String(), "not found")).Equal(true)
})

g.It("It should return 204", func() {
r := gin.Default()
r.DELETE("/api/v2/channel/:name", DeleteChannelByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v2/channel/cc_test_001", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(204)
})
})

g.Describe("#GetChannelSubscribersByName", func() {
g.It("It should return 404", func() {
r := gin.Default()
r.GET("/api/v2/channel/:name/subscribers", GetChannelSubscribersByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/channel/cc_test_002/subscribers", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(404)
g.Assert(strings.Contains(w.Body.String(), "not found")).Equal(true)
})

g.It("It should return 200", func() {
r := gin.Default()
r.GET("/api/v2/channel/:name/subscribers", GetChannelSubscribersByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/channel/cc_test_003/subscribers", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
g.Assert(strings.Contains(w.Body.String(), "0")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "count")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "subscribers")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "[]")).Equal(true)
})
})

g.Describe("#GetChannelListenersByName", func() {
g.It("It should return 404", func() {
r := gin.Default()
r.GET("/api/v2/channel/:name/listeners", GetChannelListenersByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/channel/cc_test_002/listeners", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(404)
g.Assert(strings.Contains(w.Body.String(), "not found")).Equal(true)
})

g.It("It should return 200", func() {
r := gin.Default()
r.GET("/api/v2/channel/:name/listeners", GetChannelListenersByName)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/channel/cc_test_003/listeners", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
g.Assert(strings.Contains(w.Body.String(), "0")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "count")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "listeners")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "[]")).Equal(true)
})
})
}
359 changes: 0 additions & 359 deletions core/controller/channel_test.go

This file was deleted.

296 changes: 171 additions & 125 deletions core/controller/client.go

Large diffs are not rendered by default.

194 changes: 194 additions & 0 deletions core/controller/client_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build integration

package controller

import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/clivern/beaver/core/driver"
"github.com/clivern/beaver/core/module"
"github.com/clivern/beaver/pkg"

"github.com/franela/goblin"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)

// TestIntegrationClientController
func TestIntegrationClientController(t *testing.T) {
// Skip if -short flag exist
if testing.Short() {
t.Skip("skipping test in short mode.")
}

baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

db := driver.NewEtcdDriver()
db.Connect()
defer db.Close()

gin.SetMode(gin.ReleaseMode)

// Cleanup
db.Delete(viper.GetString("app.database.etcd.databaseName"))

g := goblin.Goblin(t)

g.Describe("#CreateChannel", func() {
g.It("It should create a channel", func() {
r := gin.Default()
r.POST("/api/v2/channel", CreateChannel)

w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v2/channel", strings.NewReader(`{"name": "client_test_01", "type": "private"}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(201)
g.Assert(strings.Contains(w.Body.String(), "client_test_01")).Equal(true)
})
})

g.Describe("#CreateClient", func() {
g.It("It should create a client", func() {
r := gin.Default()
r.POST("/api/v2/client", CreateClient)

w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v2/client", strings.NewReader(`{"channels": []}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(201)
g.Assert(strings.Contains(w.Body.String(), "id")).Equal(true)
})
})

g.Describe("#GetClientByID", func() {
g.It("It should return 400", func() {
r := gin.Default()
r.GET("/api/v2/client/:id", GetClientByID)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/client/x-x-x-x", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(400)
})

g.It("It should return 404", func() {
r := gin.Default()
r.GET("/api/v2/client/:id", GetClientByID)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v2/client/ce70de31-cb08-48f6-b849-7fdf0b02b722", nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(404)
})
})

client := module.NewClient(db)
newClient := module.GenerateClient([]string{})
client.CreateClient(*newClient)

g.Describe("#GetClientByID", func() {
g.It("It should return 200", func() {
r := gin.Default()
r.GET("/api/v2/client/:id", GetClientByID)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v2/client/%s", newClient.ID), nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
g.Assert(strings.Contains(w.Body.String(), "id")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), newClient.ID)).Equal(true)
})
})

g.Describe("#Subscribe", func() {
g.It("It should return 200", func() {
r := gin.Default()
r.PUT("/api/v2/client/:id/subscribe", Subscribe)

w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/api/v2/client/%s/subscribe", newClient.ID), strings.NewReader(`{"channels": ["client_test_01"]}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
})

g.It("It should return 200", func() {
r := gin.Default()
r.GET("/api/v2/client/:id", GetClientByID)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v2/client/%s", newClient.ID), nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
g.Assert(strings.Contains(w.Body.String(), "id")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), newClient.ID)).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "client_test_01")).Equal(true)
})
})

g.Describe("#Unsubscribe", func() {
g.It("It should return 200", func() {
r := gin.Default()
r.PUT("/api/v2/client/:id/unsubscribe", Unsubscribe)

w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/api/v2/client/%s/unsubscribe", newClient.ID), strings.NewReader(`{"channels": ["client_test_01"]}`))
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
})

g.It("It should return 200", func() {
r := gin.Default()
r.GET("/api/v2/client/:id", GetClientByID)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v2/client/%s", newClient.ID), nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(200)
g.Assert(strings.Contains(w.Body.String(), "id")).Equal(true)
g.Assert(strings.Contains(w.Body.String(), newClient.ID)).Equal(true)
g.Assert(strings.Contains(w.Body.String(), "client_test_01")).Equal(false)
})
})

g.Describe("#DeleteClientByID", func() {
g.It("It should return 204", func() {
r := gin.Default()
r.DELETE("/api/v2/client/:id", DeleteClientByID)

w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", fmt.Sprintf("/api/v2/client/%s", newClient.ID), nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(204)
})

g.It("It should return 404", func() {
r := gin.Default()
r.DELETE("/api/v2/client/:id", DeleteClientByID)

w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", fmt.Sprintf("/api/v2/client/%s", newClient.ID), nil)
r.ServeHTTP(w, req)

g.Assert(w.Code).Equal(404)
})
})
}
182 changes: 0 additions & 182 deletions core/controller/client_test.go

This file was deleted.

218 changes: 0 additions & 218 deletions core/controller/config.go

This file was deleted.

296 changes: 0 additions & 296 deletions core/controller/config_test.go

This file was deleted.

3 changes: 2 additions & 1 deletion core/controller/health_check.go
Original file line number Diff line number Diff line change
@@ -5,8 +5,9 @@
package controller

import (
"github.com/gin-gonic/gin"
"net/http"

"github.com/gin-gonic/gin"
)

// HealthCheck controller
54 changes: 23 additions & 31 deletions core/controller/health_check_test.go
Original file line number Diff line number Diff line change
@@ -2,51 +2,43 @@
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build unit

package controller

import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/nbio/st"
"github.com/spf13/viper"
"net/http"
"net/http/httptest"
"os"
"strconv"
"testing"
)

// init setup stuff
func init() {
basePath := fmt.Sprintf("%s/src/github.com/clivern/beaver", os.Getenv("GOPATH"))
configFile := fmt.Sprintf("%s/%s", basePath, "config.test.yml")
"github.com/clivern/beaver/pkg"

viper.SetConfigFile(configFile)
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)

err := viper.ReadInConfig()
// TestHealthCheck test cases
func TestHealthCheck(t *testing.T) {

if err != nil {
panic(fmt.Sprintf(
"Error while loading config file [%s]: %s",
configFile,
err.Error(),
))
}
baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

os.Setenv("BeaverBasePath", fmt.Sprintf("%s/", basePath))
os.Setenv("PORT", strconv.Itoa(viper.GetInt("app.port")))
}
gin.SetMode(gin.ReleaseMode)

// TestHealthCheckController test case
func TestHealthCheckController(t *testing.T) {
g := goblin.Goblin(t)

router := gin.Default()
router.GET("/_healthcheck", HealthCheck)
g.Describe("#HealthCheck", func() {
g.It("It should return the expected response and status code", func() {
r := gin.Default()
r.GET("/_health", HealthCheck)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/_healthcheck", nil)
router.ServeHTTP(w, req)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/_health", nil)
r.ServeHTTP(w, req)

st.Expect(t, 200, w.Code)
st.Expect(t, `{"status":"ok"}`, w.Body.String())
g.Assert(w.Code).Equal(200)
g.Assert(w.Body.String()).Equal(`{"status":"ok"}`)
})
})
}
93 changes: 93 additions & 0 deletions core/controller/heartbeat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package controller

import (
"fmt"
"time"

"github.com/clivern/beaver/core/cluster"
"github.com/clivern/beaver/core/driver"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
)

var (
totalNodes = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "beaver",
Name: "cluster_total_nodes",
Help: "Total nodes in the cluster",
})

totalClients = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "beaver",
Name: "cluster_total_clients",
Help: "Total clients in the cluster",
})

totalChannels = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "beaver",
Name: "cluster_total_channels",
Help: "Total channels in the cluster",
})
)

func init() {
prometheus.MustRegister(totalNodes)
prometheus.MustRegister(totalClients)
prometheus.MustRegister(totalChannels)
}

// Heartbeat node heartbeat
func Heartbeat() {
db := driver.NewEtcdDriver()

err := db.Connect()

if err != nil {
panic(fmt.Sprintf(
"Error while connecting to etcd: %s",
err.Error(),
))
}

defer db.Close()

node := cluster.NewNode(db)
stats := cluster.NewStats(db)

log.Info(`Start heartbeat daemon`)

count := 0

for {
err := node.Alive(5)

if err != nil {
log.WithFields(log.Fields{
"error": err.Error(),
}).Error(`Error while connecting to etcd`)
} else {
log.Debug(`Node heartbeat done`)
}

time.Sleep(1 * time.Second)

count, _ = stats.GetTotalNodes()
totalNodes.Set(float64(count))

count, _ = stats.GetTotalClients()
totalClients.Set(float64(count))

count, _ = stats.GetTotalChannels()
totalChannels.Set(float64(count))

time.Sleep(2 * time.Second)
}
}
68 changes: 65 additions & 3 deletions core/controller/home.go
Original file line number Diff line number Diff line change
@@ -5,14 +5,76 @@
package controller

import (
"github.com/gin-gonic/gin"
"net/http"

"github.com/gin-gonic/gin"
)

// Index controller
func Index(c *gin.Context) {
homeTpl := `<!doctype html><html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Beaver</title> <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css"> <style> html, body { background-color: #fff; color: #636b6f; font-family: 'Nunito', sans-serif; font-weight: 200; height: 100vh; margin: 0; } .full-height { height: 100vh; } .flex-center { align-items: center; display: flex; justify-content: center; } .position-ref { position: relative; } .top-right { position: absolute; right: 10px; top: 18px; } .content { text-align: center; } .title { font-size: 30px; } .links > a { color: #636b6f; padding: 0 25px; font-size: 12px; font-weight: 600; letter-spacing: .1rem; text-decoration: none; text-transform: uppercase; } .m-b-md { margin-bottom: 30px; } </style> </head> <body> <div class="flex-center position-ref full-height"> <div class="content"> <div class="title"> Beaver </div> <p>A Real Time Messaging Server.</p> </div> </div> </body></html>`
tpl := `<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Beaver</title>
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">
<style>
html,
body {
background-color: #fff;
color: #636b6f;
font-family: 'Nunito', sans-serif;
font-weight: 200;
height: 100vh;
margin: 0;
}
.full-height {
height: 100vh;
}
.flex-center {
align-items: center;
display: flex;
justify-content: center;
}
.position-ref {
position: relative;
}
.top-right {
position: absolute;
right: 10px;
top: 18px;
}
.content {
text-align: center;
}
.title {
font-size: 30px;
}
.links>a {
color: #636b6f;
padding: 0 25px;
font-size: 12px;
font-weight: 600;
letter-spacing: .1rem;
text-decoration: none;
text-transform: uppercase;
}
.m-b-md {
margin-bottom: 30px;
}
</style>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="content">
<div class="title">Beaver</div>
<p>A Real Time Messaging Server.</p>
</div>
</div>
</body>
</html>`

c.Writer.WriteHeader(http.StatusOK)
c.Writer.Write([]byte(homeTpl))
c.Writer.Write([]byte(tpl))
return
}
53 changes: 22 additions & 31 deletions core/controller/home_test.go
Original file line number Diff line number Diff line change
@@ -2,51 +2,42 @@
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

// +build unit

package controller

import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/nbio/st"
"github.com/spf13/viper"
"net/http"
"net/http/httptest"
"os"
"strconv"
"testing"
)

// init setup stuff
func init() {
basePath := fmt.Sprintf("%s/src/github.com/clivern/beaver", os.Getenv("GOPATH"))
configFile := fmt.Sprintf("%s/%s", basePath, "config.test.yml")
"github.com/clivern/beaver/pkg"

viper.SetConfigFile(configFile)
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)

err := viper.ReadInConfig()
// TestIndex test cases
func TestIndex(t *testing.T) {

if err != nil {
panic(fmt.Sprintf(
"Error while loading config file [%s]: %s",
configFile,
err.Error(),
))
}
baseDir := pkg.GetBaseDir("cache")
pkg.LoadConfigs(fmt.Sprintf("%s/config.dist.yml", baseDir))

os.Setenv("BeaverBasePath", fmt.Sprintf("%s/", basePath))
os.Setenv("PORT", strconv.Itoa(viper.GetInt("app.port")))
}
gin.SetMode(gin.ReleaseMode)

// TestHomeController test case
func TestHomeController(t *testing.T) {
g := goblin.Goblin(t)

router := gin.Default()
router.GET("/", HealthCheck)
g.Describe("#Index", func() {
g.It("It should return the expected response and status code", func() {
r := gin.Default()
r.GET("/", Index)

w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
router.ServeHTTP(w, req)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
r.ServeHTTP(w, req)

st.Expect(t, 200, w.Code)
st.Expect(t, `{"status":"ok"}`, w.Body.String())
g.Assert(w.Code).Equal(200)
})
})
}
2 changes: 1 addition & 1 deletion core/controller/metrics.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Clivern. All rights reserved.
// Copyright 2018 Clivern. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

52 changes: 0 additions & 52 deletions core/controller/metrics_test.go

This file was deleted.

52 changes: 0 additions & 52 deletions core/controller/node_test.go

This file was deleted.

Loading