Merge pull request #44 from giolekva/server_handling

Multiple server support
diff --git a/core/kg/cmd/commands/root.go b/core/kg/cmd/commands/root.go
index 812cdf1..70a4d23 100644
--- a/core/kg/cmd/commands/root.go
+++ b/core/kg/cmd/commands/root.go
@@ -2,6 +2,7 @@
 
 import (
 	"github.com/giolekva/pcloud/core/kg/log"
+	"github.com/giolekva/pcloud/core/kg/model"
 	"github.com/giolekva/pcloud/core/kg/server"
 	"github.com/spf13/cobra"
 )
@@ -23,7 +24,7 @@
 }
 
 func serverCmdF(command *cobra.Command, args []string) error {
-	config := &log.LoggerConfiguration{
+	logger := log.NewLogger(&log.LoggerConfiguration{
 		EnableConsole: true,
 		ConsoleJSON:   true,
 		ConsoleLevel:  "debug",
@@ -31,19 +32,16 @@
 		FileJSON:      true,
 		FileLevel:     "debug",
 		FileLocation:  "server.log",
-	}
-	logger := log.NewLogger(config)
-	srv, err := server.NewServer(logger)
-	if err != nil {
-		logger.Error(err.Error())
-		return err
-	}
-	defer srv.Shutdown()
+	})
+	config := model.NewConfig()
 
-	serverErr := srv.Start()
-	if serverErr != nil {
-		logger.Error(err.Error())
-		return serverErr
-	}
+	grpcServer := server.NewGRPCServer(logger, config, nil)
+	httpServer := server.NewHTTPServer(logger, config, nil)
+
+	servers := server.New(logger)
+	servers.AddServers(grpcServer)
+	servers.AddServers(httpServer)
+	servers.Run()
+
 	return nil
 }
diff --git a/core/kg/go.mod b/core/kg/go.mod
index 5efcf10..ebc81e8 100644
--- a/core/kg/go.mod
+++ b/core/kg/go.mod
@@ -4,9 +4,11 @@
 
 require (
 	github.com/Masterminds/squirrel v1.5.0
+	github.com/gorilla/mux v1.8.0
 	github.com/jmoiron/sqlx v1.3.1
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.1.3
+	github.com/vardius/shutdown v1.0.2
 	go.uber.org/zap v1.16.0
 	golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 // indirect
 	golang.org/x/tools v0.0.0-20200313205530-4303120df7d8 // indirect
diff --git a/core/kg/go.sum b/core/kg/go.sum
index 114f18a..60b5893 100644
--- a/core/kg/go.sum
+++ b/core/kg/go.sum
@@ -94,6 +94,8 @@
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -229,6 +231,8 @@
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/vardius/shutdown v1.0.2 h1:wNHRpdYc+KvJ3Q9KmkwKiqsnaW5LfkcP9ElXXv0OpC4=
+github.com/vardius/shutdown v1.0.2/go.mod h1:rvvQd32kv5NJycU1l4VJtqrJKY0Gg0IoQpVxZJeL44M=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
diff --git a/core/kg/model/config.go b/core/kg/model/config.go
index 021fb74..fd8c034 100644
--- a/core/kg/model/config.go
+++ b/core/kg/model/config.go
@@ -3,10 +3,20 @@
 const (
 	databaseDriverPostgres = "postgres"
 	defaultDataSource      = "postgres://user:test@localhost/pcloud_test?sslmode=disable&connect_timeout=10"
+
+	defaultHTTPHost         = "0.0.0.0"
+	defaultHTTPPort         = 9086
+	defaultHTTPReadTimeout  = 5
+	defaultHTTPWriteTimeout = 10
+	defaultHTTPIdleTimeout  = 120
+
+	defaultGRPCPort = 9087
 )
 
 type Config struct {
-	SqlSettings SqlSettings
+	SQL  SQLConfig
+	HTTP HTTPConfig
+	GRPC GRPCConfig
 }
 
 func NewConfig() *Config {
@@ -16,20 +26,57 @@
 }
 
 func (c *Config) SetDefaults() {
-	c.SqlSettings.SetDefaults()
+	c.SQL.SetDefaults()
+	c.HTTP.SetDefaults()
+	c.GRPC.SetDefaults()
 }
 
-type SqlSettings struct {
-	DriverName string `access:"environment,write_restrictable,cloud_restrictable"`
-	DataSource string `access:"environment,write_restrictable,cloud_restrictable"`
+type SQLConfig struct {
+	DriverName string
+	DataSource string
 }
 
-func (s *SqlSettings) SetDefaults() {
+func (s *SQLConfig) SetDefaults() {
 	if s.DriverName == "" {
 		s.DriverName = databaseDriverPostgres
 	}
-
 	if s.DataSource == "" {
 		s.DataSource = defaultDataSource
 	}
 }
+
+type HTTPConfig struct {
+	Host         string
+	Port         int
+	ReadTimeout  int
+	WriteTimeout int
+	IdleTimeout  int
+}
+
+func (s *HTTPConfig) SetDefaults() {
+	if s.Host == "" {
+		s.Host = defaultHTTPHost
+	}
+	if s.Port == 0 {
+		s.Port = defaultHTTPPort
+	}
+	if s.ReadTimeout == 0 {
+		s.ReadTimeout = defaultHTTPReadTimeout
+	}
+	if s.WriteTimeout == 0 {
+		s.WriteTimeout = defaultHTTPWriteTimeout
+	}
+	if s.IdleTimeout == 0 {
+		s.IdleTimeout = defaultHTTPIdleTimeout
+	}
+}
+
+type GRPCConfig struct {
+	Port int
+}
+
+func (s *GRPCConfig) SetDefaults() {
+	if s.Port == 0 {
+		s.Port = defaultGRPCPort
+	}
+}
diff --git a/core/kg/server/grpc_server.go b/core/kg/server/grpc_server.go
new file mode 100644
index 0000000..565ce77
--- /dev/null
+++ b/core/kg/server/grpc_server.go
@@ -0,0 +1,63 @@
+package server
+
+import (
+	"fmt"
+	"net"
+	"os"
+
+	"github.com/giolekva/pcloud/core/kg/log"
+	"github.com/giolekva/pcloud/core/kg/model"
+	"github.com/giolekva/pcloud/core/kg/store"
+	"google.golang.org/grpc"
+)
+
+// GRPCServerImpl grpc server implementation
+type GRPCServerImpl struct {
+	Log    *log.Logger
+	srv    *grpc.Server
+	config *model.Config
+	store  store.Store
+}
+
+var _ Server = &GRPCServerImpl{}
+
+// NewGRPCServer creates new GRPC Server
+func NewGRPCServer(logger *log.Logger, config *model.Config, store store.Store) Server {
+	a := &GRPCServerImpl{
+		Log:    logger,
+		config: config,
+		store:  store,
+	}
+
+	pwd, _ := os.Getwd()
+	a.Log.Info("GRPC server current working", log.String("directory", pwd))
+	return a
+}
+
+// Start method starts a grpc server
+func (a *GRPCServerImpl) Start() error {
+	a.Log.Info("Starting GRPC Server...")
+
+	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", a.config.GRPC.Port))
+	if err != nil {
+		a.Log.Error("Failed to listen: %v", log.Err(err))
+		return err
+	}
+
+	a.srv = grpc.NewServer()
+
+	a.Log.Info("GRPC Server is listening on", log.Int("port", a.config.GRPC.Port))
+	if err := a.srv.Serve(lis); err != nil {
+		a.Log.Error("Failed to serve rpc: %v", log.Err(err))
+		return err
+	}
+	return nil
+}
+
+// Shutdown method shuts grpc server down
+func (a *GRPCServerImpl) Shutdown() error {
+	a.Log.Info("Stopping GRPC Server...")
+	a.srv.GracefulStop()
+	a.Log.Info("GRPC Server stopped")
+	return nil
+}
diff --git a/core/kg/server/http_server.go b/core/kg/server/http_server.go
new file mode 100644
index 0000000..6802332
--- /dev/null
+++ b/core/kg/server/http_server.go
@@ -0,0 +1,79 @@
+package server
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/giolekva/pcloud/core/kg/log"
+	"github.com/giolekva/pcloud/core/kg/model"
+	"github.com/giolekva/pcloud/core/kg/store"
+	"github.com/gorilla/mux"
+)
+
+// HTTPServerImpl http server implementation
+type HTTPServerImpl struct {
+	Log    *log.Logger
+	srv    *http.Server
+	root   *mux.Router
+	config *model.Config
+	store  store.Store
+}
+
+var _ Server = &HTTPServerImpl{}
+
+// NewHTTPServer creates new HTTP Server
+func NewHTTPServer(logger *log.Logger, config *model.Config, store store.Store) Server {
+	a := &HTTPServerImpl{
+		Log:    logger,
+		root:   mux.NewRouter(),
+		config: config,
+		store:  store,
+	}
+
+	pwd, _ := os.Getwd()
+	a.Log.Info("HTTP server current working", log.String("directory", pwd))
+	return a
+}
+
+// Start method starts a http server
+func (a *HTTPServerImpl) Start() error {
+	a.Log.Info("Starting HTTP Server...")
+
+	a.srv = &http.Server{
+		Addr:         fmt.Sprintf("%s:%d", a.config.HTTP.Host, a.config.HTTP.Port),
+		Handler:      a.root,
+		ReadTimeout:  time.Duration(a.config.HTTP.ReadTimeout) * time.Second,
+		WriteTimeout: time.Duration(a.config.HTTP.WriteTimeout) * time.Second,
+		IdleTimeout:  time.Duration(a.config.HTTP.IdleTimeout) * time.Second,
+	}
+
+	a.Log.Info("HTTP Server is listening on", log.Int("port", a.config.HTTP.Port))
+	if err := a.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+		a.Log.Error("Failed to listen and serve: %v", log.Err(err))
+		return err
+	}
+	return nil
+}
+
+// Shutdown method shuts http server down
+func (a *HTTPServerImpl) Shutdown() error {
+	a.Log.Info("Stopping HTTP Server...")
+	if a.srv == nil {
+		return errors.New("No http server present")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	if err := a.srv.Shutdown(ctx); err != nil {
+		a.Log.Error("Unable to shutdown server", log.Err(err))
+	}
+
+	// a.srv.Close()
+	// a.srv = nil
+	a.Log.Info("HTTP Server stopped")
+	return nil
+}
diff --git a/core/kg/server/server.go b/core/kg/server/server.go
deleted file mode 100644
index 12c2905..0000000
--- a/core/kg/server/server.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package server
-
-import (
-	"net"
-	"os"
-
-	"github.com/giolekva/pcloud/core/kg/log"
-	"github.com/giolekva/pcloud/core/kg/store"
-	"google.golang.org/grpc"
-)
-
-const listenerPort = ":9081"
-
-// Server type defines application global state
-type Server struct {
-	Log   *log.Logger
-	srv   *grpc.Server
-	store store.Store
-}
-
-// NewServer creates new Server
-func NewServer(logger *log.Logger) (*Server, error) {
-	a := &Server{
-		Log: logger,
-	}
-
-	pwd, _ := os.Getwd()
-	a.Log.Info("Current working", log.String("directory", pwd))
-	return a, nil
-}
-
-// Start method starts an app
-func (a *Server) Start() error {
-	// settings := model.NewConfig().SqlSettings
-	// a.store = sqlstore.New(settings)
-
-	lis, err := net.Listen("tcp", listenerPort)
-	if err != nil {
-		a.Log.Error("failed to listen: %v", log.Err(err))
-		return err
-	}
-
-	a.srv = grpc.NewServer()
-
-	a.Log.Info("Server is listening on", log.String("port", listenerPort))
-	if err := a.srv.Serve(lis); err != nil {
-		a.Log.Error("failed to serve: %v", log.Err(err))
-		return err
-	}
-	a.Log.Info("Server stopped")
-	return nil
-}
-
-// Shutdown method shuts server down
-func (a *Server) Shutdown() {
-	a.Log.Info("Stopping Server...")
-	a.srv.GracefulStop()
-}
diff --git a/core/kg/server/servers.go b/core/kg/server/servers.go
new file mode 100644
index 0000000..7363a0e
--- /dev/null
+++ b/core/kg/server/servers.go
@@ -0,0 +1,76 @@
+package server
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+
+	"github.com/giolekva/pcloud/core/kg/log"
+)
+
+// Server interface
+type Server interface {
+	Start() error
+	Shutdown() error
+}
+
+// Servers represents different server services
+type Servers struct {
+	servers []Server
+	logger  *log.Logger
+}
+
+// New provides new service application
+func New(logger *log.Logger) *Servers {
+	return &Servers{
+		logger: logger,
+	}
+}
+
+// AddServers adds servers to service
+func (ss *Servers) AddServers(servers ...Server) {
+	ss.servers = append(ss.servers, servers...)
+}
+
+// Run runs the service application
+func (ss *Servers) Run() {
+	for _, server := range ss.servers {
+		go func(server Server) {
+			if err := server.Start(); err != nil {
+				ss.logger.Error("can't start server", log.Err(err))
+				os.Exit(1)
+			}
+		}(server)
+	}
+	// wait for kill signal before attempting to gracefully shutdown
+	// the running service
+	interruptChan := make(chan os.Signal, 1)
+	signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
+	<-interruptChan
+	ss.logger.Info("os.Interrupt...")
+	ss.shutdown()
+}
+
+func (ss *Servers) shutdown() {
+	ss.logger.Info("Shutting down...")
+
+	errCh := make(chan error, len(ss.servers))
+
+	for _, server := range ss.servers {
+		go func(server Server) {
+			errCh <- server.Shutdown()
+		}(server)
+	}
+
+	for i := 0; i < len(ss.servers); i++ {
+		if err := <-errCh; err != nil {
+			go func(err error) {
+				ss.logger.Error("Shutdown error", log.Err(err))
+				os.Exit(1)
+			}(err)
+			return
+		}
+	}
+
+	ss.logger.Info("Gracefully stopped")
+}
diff --git a/core/kg/store/sqlstore/store.go b/core/kg/store/sqlstore/store.go
index f5577f0..fd53f1e 100644
--- a/core/kg/store/sqlstore/store.go
+++ b/core/kg/store/sqlstore/store.go
@@ -8,9 +8,9 @@
 )
 
 type SqlStore struct {
-	db       *sqlx.DB
-	stores   SqlStoreStores
-	settings *model.SqlSettings
+	db     *sqlx.DB
+	stores SqlStoreStores
+	config *model.SQLConfig
 }
 
 var _ store.Store = &SqlStore{}
@@ -19,9 +19,9 @@
 	user store.UserStore
 }
 
-func New(settings model.SqlSettings) *SqlStore {
+func New(config model.SQLConfig) *SqlStore {
 	store := &SqlStore{
-		settings: &settings,
+		config: &config,
 	}
 
 	store.initConnection()
@@ -32,7 +32,7 @@
 }
 
 func (ss *SqlStore) initConnection() {
-	ss.db = sqlx.MustConnect(ss.settings.DriverName, ss.settings.DataSource)
+	ss.db = sqlx.MustConnect(ss.config.DriverName, ss.config.DataSource)
 }
 
 func (ss *SqlStore) getQueryBuilder() sq.StatementBuilderType {