ADd consul support , multiple monitors, config file
This commit is contained in:
@@ -6,13 +6,13 @@ RUN apk update && \
|
|||||||
RUN mkdir -p /opt/gocast
|
RUN mkdir -p /opt/gocast
|
||||||
RUN mkdir -p /go/src/github.com/mayuresh82
|
RUN mkdir -p /go/src/github.com/mayuresh82
|
||||||
RUN cd /go/src/github.com/mayuresh82 && \
|
RUN cd /go/src/github.com/mayuresh82 && \
|
||||||
git clone --branch dev https://github.com/mayuresh82/gocast
|
git clone https://github.com/mayuresh82/gocast
|
||||||
WORKDIR /go/src/github.com/mayuresh82/gocast
|
WORKDIR /go/src/github.com/mayuresh82/gocast
|
||||||
RUN make
|
RUN make
|
||||||
RUN cp gocast /opt/gocast/
|
RUN cp gocast /opt/gocast/
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
RUN apk --no-cache add ca-certificates
|
RUN apk --no-cache add ca-certificates bash iptables netcat-openbsd sudo
|
||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
COPY --from=builder /opt/gocast/gocast .
|
COPY --from=builder /opt/gocast/gocast .
|
||||||
|
|
||||||
|
|||||||
1
Gopkg.lock
generated
1
Gopkg.lock
generated
@@ -350,6 +350,7 @@
|
|||||||
"github.com/golang/protobuf/ptypes/any",
|
"github.com/golang/protobuf/ptypes/any",
|
||||||
"github.com/osrg/gobgp/api",
|
"github.com/osrg/gobgp/api",
|
||||||
"github.com/osrg/gobgp/pkg/server",
|
"github.com/osrg/gobgp/pkg/server",
|
||||||
|
"gopkg.in/yaml.v2",
|
||||||
]
|
]
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|||||||
22
config.yaml
Normal file
22
config.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
agent:
|
||||||
|
# http server listen addr
|
||||||
|
listen_addr: :8080
|
||||||
|
# Interval for health check
|
||||||
|
monitor_interval: 10s
|
||||||
|
# Time to flush out inactive apps
|
||||||
|
cleanup_timer: 15m
|
||||||
|
# Consul api addr for dynamic discovery
|
||||||
|
consul_addr: https://consul
|
||||||
|
# interval to query consul for app discovery
|
||||||
|
consul_query_interval: 5m
|
||||||
|
|
||||||
|
bgp:
|
||||||
|
local_as: 12345
|
||||||
|
remote_as: 6789
|
||||||
|
# override the peer IP to use instead of auto discovering
|
||||||
|
peer_ip: 10.10.10.1
|
||||||
|
communities:
|
||||||
|
- asn:nnnn
|
||||||
|
- asn:nnnn
|
||||||
|
origin: igp
|
||||||
|
|
||||||
39
config/config.go
Normal file
39
config/config.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Agent struct {
|
||||||
|
ListenAddr string `yaml:"listen_addr"`
|
||||||
|
MonitorInterval time.Duration `yaml:"monitor_interval"`
|
||||||
|
CleanupTimer time.Duration `yaml:"cleanup_timer"`
|
||||||
|
ConsulAddr string `yaml:"consul_addr"`
|
||||||
|
ConsulQueryInterval time.Duration `yaml:"consul_query_interval"`
|
||||||
|
}
|
||||||
|
Bgp struct {
|
||||||
|
LocalAS int `yaml:"local_as"`
|
||||||
|
PeerAS int `yaml:"peer_as"`
|
||||||
|
PeerIP string `yaml:"peer_ip"`
|
||||||
|
Communities []string
|
||||||
|
Origin string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfig(file string) *Config {
|
||||||
|
absPath, _ := filepath.Abs(file)
|
||||||
|
data, err := ioutil.ReadFile(absPath)
|
||||||
|
if err != nil {
|
||||||
|
glog.Exitf("FATAL: Unable to read config file: %v", err)
|
||||||
|
}
|
||||||
|
config := &Config{}
|
||||||
|
if err := yaml.Unmarshal(data, config); err != nil {
|
||||||
|
glog.Exitf("FATAL: Unable to decode yaml: %v", err)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
@@ -12,12 +12,13 @@ type MonitorType int
|
|||||||
const (
|
const (
|
||||||
Monitor_PORT MonitorType = 1
|
Monitor_PORT MonitorType = 1
|
||||||
Monitor_EXEC MonitorType = 2
|
Monitor_EXEC MonitorType = 2
|
||||||
|
Monitor_CONSUL MonitorType = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
var Monitors = map[string]MonitorType{"port": Monitor_PORT, "exec": Monitor_EXEC}
|
var MonitorMap = map[string]MonitorType{"port": Monitor_PORT, "exec": Monitor_EXEC, "consul": Monitor_CONSUL}
|
||||||
|
|
||||||
func (m MonitorType) String() string {
|
func (m MonitorType) String() string {
|
||||||
for str, mtr := range Monitors {
|
for str, mtr := range MonitorMap {
|
||||||
if m == mtr {
|
if m == mtr {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
@@ -32,13 +33,49 @@ type Monitor struct {
|
|||||||
Cmd string
|
Cmd string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Equal(other *Monitor) bool {
|
||||||
|
return m.Type == other.Type && m.Port == other.Port && m.Protocol == other.Protocol && m.Cmd == other.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
type Monitors []*Monitor
|
||||||
|
|
||||||
|
func (m Monitors) Contains(elem *Monitor) bool {
|
||||||
|
for _, mon := range m {
|
||||||
|
if mon.Equal(elem) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Name string
|
Name string
|
||||||
Vip *net.IPNet
|
Vip *net.IPNet
|
||||||
Monitor Monitor
|
Monitors Monitors
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(appName, vip, monitor, monitorType string) (*App, error) {
|
func (a *App) Equal(other *App) bool {
|
||||||
|
if len(a.Monitors) != len(other.Monitors) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, m := range other.Monitors {
|
||||||
|
if !a.Monitors.Contains(m) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.Name == other.Name && a.Vip.String() == other.Vip.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) needsNatRule() (bool, *Monitor) {
|
||||||
|
for _, m := range a.Monitors {
|
||||||
|
if m.Type == Monitor_CONSUL && m.Port != "" {
|
||||||
|
return true, m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApp(appName, vip string, monitors []string) (*App, error) {
|
||||||
if appName == "" {
|
if appName == "" {
|
||||||
return nil, fmt.Errorf("Invalid app name")
|
return nil, fmt.Errorf("Invalid app name")
|
||||||
}
|
}
|
||||||
@@ -48,17 +85,25 @@ func NewApp(appName, vip, monitor, monitorType string) (*App, error) {
|
|||||||
return nil, fmt.Errorf("Invalid VIP specified, need ip/mask")
|
return nil, fmt.Errorf("Invalid VIP specified, need ip/mask")
|
||||||
}
|
}
|
||||||
app.Vip = ipnet
|
app.Vip = ipnet
|
||||||
m := Monitor{Type: Monitors[monitorType]}
|
for _, m := range monitors {
|
||||||
switch monitorType {
|
parts := strings.Split(m, ":")
|
||||||
|
if len(parts) != 2 && len(parts) != 3 {
|
||||||
|
glog.Errorf("Invalid monitor specified, ignoring")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mon := &Monitor{Type: MonitorMap[parts[0]]}
|
||||||
|
switch mon.Type.String() {
|
||||||
case "port":
|
case "port":
|
||||||
parts := strings.Split(monitor, ":")
|
mon.Protocol = parts[1]
|
||||||
m.Protocol = parts[0]
|
mon.Port = parts[2]
|
||||||
m.Port = parts[1]
|
|
||||||
case "exec":
|
case "exec":
|
||||||
m.Cmd = monitor
|
mon.Cmd = parts[1]
|
||||||
|
case "consul":
|
||||||
|
glog.V(2).Infof("Using consul health monitor")
|
||||||
default:
|
default:
|
||||||
glog.V(2).Infof("No monitor specified")
|
glog.V(2).Infof("No monitor specified")
|
||||||
}
|
}
|
||||||
app.Monitor = m
|
app.Monitors = append(app.Monitors, mon)
|
||||||
|
}
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,31 +5,45 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/protobuf/ptypes"
|
"github.com/golang/protobuf/ptypes"
|
||||||
"github.com/golang/protobuf/ptypes/any"
|
"github.com/golang/protobuf/ptypes/any"
|
||||||
|
c "github.com/mayuresh82/gocast/config"
|
||||||
api "github.com/osrg/gobgp/api"
|
api "github.com/osrg/gobgp/api"
|
||||||
gobgp "github.com/osrg/gobgp/pkg/server"
|
gobgp "github.com/osrg/gobgp/pkg/server"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
peerAS int
|
peerAS int
|
||||||
localIP, peerIP net.IP
|
localIP, peerIP net.IP
|
||||||
|
communities []string
|
||||||
|
origin uint32
|
||||||
s *gobgp.BgpServer
|
s *gobgp.BgpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(localAS, peerAS int, peerIP string) (*Controller, error) {
|
func NewController(config *c.Config) (*Controller, error) {
|
||||||
c := &Controller{}
|
c := &Controller{}
|
||||||
if peerIP == "" {
|
if config.Bgp.PeerIP == "" {
|
||||||
gw, err := gateway()
|
gw, err := gateway()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.peerIP = gw
|
c.peerIP = gw
|
||||||
} else {
|
} else {
|
||||||
c.peerIP = net.ParseIP(peerIP)
|
c.peerIP = net.ParseIP(config.Bgp.PeerIP)
|
||||||
}
|
}
|
||||||
if c.peerIP == nil {
|
if c.peerIP == nil {
|
||||||
return nil, fmt.Errorf("Unable to get peer IP")
|
return nil, fmt.Errorf("Unable to get peer IP")
|
||||||
}
|
}
|
||||||
|
c.communities = config.Bgp.Communities
|
||||||
|
switch config.Bgp.Origin {
|
||||||
|
case "igp":
|
||||||
|
c.origin = 0
|
||||||
|
case "egp":
|
||||||
|
c.origin = 1
|
||||||
|
case "unknown":
|
||||||
|
c.origin = 2
|
||||||
|
}
|
||||||
s := gobgp.NewBgpServer()
|
s := gobgp.NewBgpServer()
|
||||||
go s.Serve()
|
go s.Serve()
|
||||||
localAddr, err := localAddress(c.peerIP)
|
localAddr, err := localAddress(c.peerIP)
|
||||||
@@ -39,7 +53,7 @@ func NewController(localAS, peerAS int, peerIP string) (*Controller, error) {
|
|||||||
c.localIP = localAddr
|
c.localIP = localAddr
|
||||||
if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
|
if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||||
Global: &api.Global{
|
Global: &api.Global{
|
||||||
As: uint32(localAS),
|
As: uint32(config.Bgp.LocalAS),
|
||||||
RouterId: localAddr.String(),
|
RouterId: localAddr.String(),
|
||||||
ListenPort: -1, // gobgp won't listen on tcp:179
|
ListenPort: -1, // gobgp won't listen on tcp:179
|
||||||
},
|
},
|
||||||
@@ -47,7 +61,7 @@ func NewController(localAS, peerAS int, peerIP string) (*Controller, error) {
|
|||||||
return nil, fmt.Errorf("Unable to start bgp: %v", err)
|
return nil, fmt.Errorf("Unable to start bgp: %v", err)
|
||||||
}
|
}
|
||||||
c.s = s
|
c.s = s
|
||||||
c.peerAS = peerAS
|
c.peerAS = config.Bgp.PeerAS
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +86,19 @@ func (c *Controller) getApiPath(route *net.IPNet) *api.Path {
|
|||||||
PrefixLen: uint32(prefixlen),
|
PrefixLen: uint32(prefixlen),
|
||||||
})
|
})
|
||||||
a1, _ := ptypes.MarshalAny(&api.OriginAttribute{
|
a1, _ := ptypes.MarshalAny(&api.OriginAttribute{
|
||||||
Origin: 0,
|
Origin: c.origin,
|
||||||
})
|
})
|
||||||
a2, _ := ptypes.MarshalAny(&api.NextHopAttribute{
|
a2, _ := ptypes.MarshalAny(&api.NextHopAttribute{
|
||||||
NextHop: c.localIP.String(),
|
NextHop: c.localIP.String(),
|
||||||
})
|
})
|
||||||
attrs := []*any.Any{a1, a2}
|
var communities []uint32
|
||||||
|
for _, comm := range c.communities {
|
||||||
|
communities = append(communities, convertCommunity(comm))
|
||||||
|
}
|
||||||
|
a3, _ := ptypes.MarshalAny(&api.CommunitiesAttribute{
|
||||||
|
Communities: communities,
|
||||||
|
})
|
||||||
|
attrs := []*any.Any{a1, a2, a3}
|
||||||
return &api.Path{
|
return &api.Path{
|
||||||
Family: &api.Family{Afi: afi, Safi: api.Family_SAFI_UNICAST},
|
Family: &api.Family{Afi: afi, Safi: api.Family_SAFI_UNICAST},
|
||||||
AnyNlri: nlri,
|
AnyNlri: nlri,
|
||||||
@@ -134,3 +155,10 @@ func (c *Controller) Shutdown() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertCommunity(comm string) uint32 {
|
||||||
|
parts := strings.Split(comm, ":")
|
||||||
|
first, _ := strconv.ParseUint(parts[0], 10, 32)
|
||||||
|
second, _ := strconv.ParseUint(parts[1], 10, 32)
|
||||||
|
return uint32(first)<<16 | uint32(second)
|
||||||
|
}
|
||||||
|
|||||||
115
controller/consul.go
Normal file
115
controller/consul.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
consulNodeEnv = "CONSUL_NODE"
|
||||||
|
matchTag = "enable_gocast"
|
||||||
|
nodeUrl = "/catalog/node"
|
||||||
|
healthCheckurl = "/health/checks"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConsulMon struct {
|
||||||
|
addr string
|
||||||
|
node string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConsulServiceData struct {
|
||||||
|
Services map[string]struct {
|
||||||
|
ID string
|
||||||
|
Service string
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(inp []string, elem string) bool {
|
||||||
|
for _, a := range inp {
|
||||||
|
if a == elem {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConsulMon(addr string) (*ConsulMon, error) {
|
||||||
|
node := os.Getenv(consulNodeEnv)
|
||||||
|
if node == "" {
|
||||||
|
return nil, fmt.Errorf("%s env variable not set", consulNodeEnv)
|
||||||
|
}
|
||||||
|
return &ConsulMon{addr: addr, node: node}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConsulMon) queryServices() ([]*App, error) {
|
||||||
|
var apps []*App
|
||||||
|
addr := c.addr + fmt.Sprintf("%s/%s", nodeUrl, c.node)
|
||||||
|
resp, err := http.Get(addr)
|
||||||
|
if err != nil {
|
||||||
|
return apps, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var consulData ConsulServiceData
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&consulData); err != nil {
|
||||||
|
return apps, fmt.Errorf("Unable to decode consul data: %v", err)
|
||||||
|
}
|
||||||
|
for _, service := range consulData.Services {
|
||||||
|
if !contains(service.Tags, matchTag) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
vip string
|
||||||
|
monitors []string
|
||||||
|
)
|
||||||
|
for _, tag := range service.Tags {
|
||||||
|
// try to find the requires tags. Only vip is mandatory
|
||||||
|
parts := strings.Split(tag, "=")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch parts[0] {
|
||||||
|
case "vip":
|
||||||
|
vip = parts[1]
|
||||||
|
case "monitor":
|
||||||
|
monitors = append(monitors, parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vip == "" {
|
||||||
|
glog.Errorf("No vip Tag found in matched service :%s", service.Service)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
app, err := NewApp(service.Service, vip, monitors)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Unable to add consul app: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
apps = append(apps, app)
|
||||||
|
|
||||||
|
}
|
||||||
|
return apps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConsulMon) healthCheck(service string) (bool, error) {
|
||||||
|
addr := c.addr + fmt.Sprintf("%s/%s", healthCheckurl, service)
|
||||||
|
resp, err := http.Get(addr)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var data []interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, nodeInfo := range data {
|
||||||
|
n := nodeInfo.(map[string]interface{})
|
||||||
|
if n["Node"] == c.node && n["Status"].(string) == "passing" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("No healcheck info found for node %s in consul", c.node)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
c "github.com/mayuresh82/gocast/config"
|
||||||
api "github.com/osrg/gobgp/api"
|
api "github.com/osrg/gobgp/api"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -10,6 +11,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultMonitorInterval = 10 * time.Second
|
||||||
|
defaultCleanupTimer = 15 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
func portMonitor(protocol, port string) bool {
|
func portMonitor(protocol, port string) bool {
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
@@ -18,14 +24,14 @@ func portMonitor(protocol, port string) bool {
|
|||||||
glog.V(4).Infof("Monitor tcp port up")
|
glog.V(4).Infof("Monitor tcp port up")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
conn.Close()
|
||||||
case "udp":
|
case "udp":
|
||||||
conn, err := net.ListenPacket(protocol, ":"+port)
|
conn, err := net.ListenPacket(protocol, ":"+port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(4).Infof("Monitor udp port up")
|
glog.V(4).Infof("Monitor udp port up")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -55,72 +61,152 @@ type appMon struct {
|
|||||||
type MonitorMgr struct {
|
type MonitorMgr struct {
|
||||||
monitors map[string]*appMon
|
monitors map[string]*appMon
|
||||||
cleanups map[string]chan bool
|
cleanups map[string]chan bool
|
||||||
c *Controller
|
config *c.Config
|
||||||
monitorInterval time.Duration
|
ctrl *Controller
|
||||||
cleanupTimer time.Duration
|
consul *ConsulMon
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonitor(localAS, peerAS int, monitorInterval time.Duration, peerIP string, cleanup time.Duration) *MonitorMgr {
|
func NewMonitor(config *c.Config) *MonitorMgr {
|
||||||
c, err := NewController(localAS, peerAS, peerIP)
|
ctrl, err := NewController(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Exitf("Failed to start BGP controller: %v", err)
|
glog.Exitf("Failed to start BGP controller: %v", err)
|
||||||
}
|
}
|
||||||
return &MonitorMgr{
|
mon := &MonitorMgr{
|
||||||
c: c,
|
ctrl: ctrl,
|
||||||
monitors: make(map[string]*appMon),
|
monitors: make(map[string]*appMon),
|
||||||
cleanups: make(map[string]chan bool),
|
cleanups: make(map[string]chan bool),
|
||||||
monitorInterval: monitorInterval,
|
}
|
||||||
cleanupTimer: cleanup,
|
if config.Agent.ConsulAddr != "" {
|
||||||
|
cmon, err := NewConsulMon(config.Agent.ConsulAddr)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to start consul monitor: %v", err)
|
||||||
|
} else {
|
||||||
|
mon.consul = cmon
|
||||||
|
go mon.consulMon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.Agent.MonitorInterval == 0 {
|
||||||
|
config.Agent.MonitorInterval = defaultMonitorInterval
|
||||||
|
}
|
||||||
|
if config.Agent.CleanupTimer == 0 {
|
||||||
|
config.Agent.CleanupTimer = defaultCleanupTimer
|
||||||
|
}
|
||||||
|
mon.config = config
|
||||||
|
return mon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MonitorMgr) consulMon() {
|
||||||
|
for {
|
||||||
|
apps, err := m.consul.queryServices()
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to query consul: %v", err)
|
||||||
|
} else {
|
||||||
|
for _, app := range apps {
|
||||||
|
m.Add(app)
|
||||||
|
}
|
||||||
|
// remove currently running apps that are not discovered in this pass
|
||||||
|
var toRemove []string
|
||||||
|
m.Lock()
|
||||||
|
for name := range m.monitors {
|
||||||
|
var found bool
|
||||||
|
for _, app := range apps {
|
||||||
|
if name == app.Name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
glog.V(2).Infof("Removing app: %s as it was not found in consul", name)
|
||||||
|
toRemove = append(toRemove, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tr := range toRemove {
|
||||||
|
m.Remove(tr)
|
||||||
|
}
|
||||||
|
m.Unlock()
|
||||||
|
}
|
||||||
|
<-time.After(m.config.Agent.ConsulQueryInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorMgr) Add(app *App) {
|
func (m *MonitorMgr) Add(app *App) {
|
||||||
// stop and start a new one if one already running
|
// check if already running
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
for _, appMon := range m.monitors {
|
||||||
|
if appMon.app.Equal(app) && appMon.checkOn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if appMon.app.Vip.String() == app.Vip.String() && appMon.app.Name != app.Name {
|
||||||
|
glog.Errorf("Error: Vip %s is already being announced by app: %s", app.Vip.String(), appMon.app.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
m.Remove(app.Name)
|
m.Remove(app.Name)
|
||||||
appMon := &appMon{app: app, done: make(chan bool)}
|
appMon := &appMon{app: app, done: make(chan bool)}
|
||||||
m.Lock()
|
|
||||||
m.monitors[app.Name] = appMon
|
m.monitors[app.Name] = appMon
|
||||||
m.Unlock()
|
|
||||||
go m.runLoop(appMon)
|
go m.runLoop(appMon)
|
||||||
glog.Infof("Registered a new app: %v", app)
|
glog.Infof("Registered a new app: %v", app)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorMgr) Remove(appName string) {
|
func (m *MonitorMgr) Remove(appName string) {
|
||||||
m.Lock()
|
|
||||||
defer m.Unlock()
|
|
||||||
if a, ok := m.monitors[appName]; ok {
|
if a, ok := m.monitors[appName]; ok {
|
||||||
if a.checkOn {
|
if a.checkOn {
|
||||||
a.done <- true
|
a.done <- true
|
||||||
}
|
}
|
||||||
if a.announced {
|
if a.announced {
|
||||||
if err := m.c.Withdraw(a.app.Vip); err != nil {
|
if err := m.ctrl.Withdraw(a.app.Vip); err != nil {
|
||||||
glog.Errorf("Failed to withdraw route: %v", err)
|
glog.Errorf("Failed to withdraw route: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deleteLoopback(appName)
|
deleteLoopback(a.app.Vip)
|
||||||
|
if ok, mon := a.app.needsNatRule(); ok {
|
||||||
|
natRule("D", a.app.Vip.IP, m.ctrl.localIP, mon.Port, mon.Protocol)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete(m.monitors, appName)
|
delete(m.monitors, appName)
|
||||||
}
|
}
|
||||||
|
func (m *MonitorMgr) runMonitors(app *App) bool {
|
||||||
|
var check bool
|
||||||
|
for _, mon := range app.Monitors {
|
||||||
|
switch mon.Type {
|
||||||
|
case Monitor_PORT:
|
||||||
|
check = portMonitor(mon.Protocol, mon.Port)
|
||||||
|
case Monitor_EXEC:
|
||||||
|
check = execMonitor(mon.Cmd)
|
||||||
|
case Monitor_CONSUL:
|
||||||
|
c, err := m.consul.healthCheck(app.Name)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to perform consul healthcheck for %s: %v", app.Name, err)
|
||||||
|
}
|
||||||
|
check = c
|
||||||
|
}
|
||||||
|
if !check {
|
||||||
|
glog.V(2).Infof("%s Monitor for app: %s Failed", mon.Type.String(), app.Name)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MonitorMgr) checkCond(am *appMon) error {
|
func (m *MonitorMgr) checkCond(am *appMon) error {
|
||||||
var cond bool
|
|
||||||
app := am.app
|
app := am.app
|
||||||
switch app.Monitor.Type {
|
|
||||||
case Monitor_PORT:
|
|
||||||
cond = portMonitor(app.Monitor.Protocol, app.Monitor.Port)
|
|
||||||
case Monitor_EXEC:
|
|
||||||
cond = execMonitor(app.Monitor.Cmd)
|
|
||||||
}
|
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
if cond {
|
if m.runMonitors(app) {
|
||||||
glog.V(2).Infof("%s Monitor for app: %s succeeded", app.Monitor.Type.String(), app.Name)
|
glog.V(2).Infof("All Monitors for app: %s succeeded", app.Name)
|
||||||
if !am.announced {
|
if !am.announced {
|
||||||
if err := addLoopback(app.Name, app.Vip); err != nil {
|
if err := addLoopback(app.Name, app.Vip); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := m.c.Announce(app.Vip); err != nil {
|
if ok, mon := app.needsNatRule(); ok {
|
||||||
|
if err := natRule("A", app.Vip.IP, m.ctrl.localIP, mon.Port, mon.Protocol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := m.ctrl.Announce(app.Vip); err != nil {
|
||||||
return fmt.Errorf("Failed to announce route: %v", err)
|
return fmt.Errorf("Failed to announce route: %v", err)
|
||||||
}
|
}
|
||||||
am.announced = true
|
am.announced = true
|
||||||
@@ -129,9 +215,8 @@ func (m *MonitorMgr) checkCond(am *appMon) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
glog.V(2).Infof("%s Monitor for app: %s Failed", app.Monitor.Type.String(), app.Name)
|
|
||||||
if am.announced {
|
if am.announced {
|
||||||
if err := m.c.Withdraw(app.Vip); err != nil {
|
if err := m.ctrl.Withdraw(app.Vip); err != nil {
|
||||||
return fmt.Errorf("Failed to withdraw route: %v", err)
|
return fmt.Errorf("Failed to withdraw route: %v", err)
|
||||||
}
|
}
|
||||||
am.announced = false
|
am.announced = false
|
||||||
@@ -148,7 +233,7 @@ func (m *MonitorMgr) runLoop(am *appMon) {
|
|||||||
if err := m.checkCond(am); err != nil {
|
if err := m.checkCond(am); err != nil {
|
||||||
glog.Errorln(err)
|
glog.Errorln(err)
|
||||||
}
|
}
|
||||||
t := time.NewTicker(m.monitorInterval)
|
t := time.NewTicker(m.config.Agent.MonitorInterval)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -165,25 +250,30 @@ func (m *MonitorMgr) runLoop(am *appMon) {
|
|||||||
|
|
||||||
func (m *MonitorMgr) CloseAll() {
|
func (m *MonitorMgr) CloseAll() {
|
||||||
glog.Infof("Shutting down all open bgp sessions")
|
glog.Infof("Shutting down all open bgp sessions")
|
||||||
if err := m.c.Shutdown(); err != nil {
|
if err := m.ctrl.Shutdown(); err != nil {
|
||||||
glog.Errorf("Failed to shut-down BGP: %v", err)
|
glog.Errorf("Failed to shut-down BGP: %v", err)
|
||||||
}
|
}
|
||||||
for name, am := range m.monitors {
|
for _, am := range m.monitors {
|
||||||
if am.checkOn {
|
if am.checkOn {
|
||||||
am.done <- true
|
am.done <- true
|
||||||
}
|
}
|
||||||
deleteLoopback(name)
|
deleteLoopback(am.app.Vip)
|
||||||
|
if ok, mon := am.app.needsNatRule(); ok {
|
||||||
|
natRule("D", am.app.Vip.IP, m.ctrl.localIP, mon.Port, mon.Protocol)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorMgr) Cleanup(app string, exit chan bool) {
|
func (m *MonitorMgr) Cleanup(app string, exit chan bool) {
|
||||||
t := time.NewTimer(m.cleanupTimer)
|
t := time.NewTimer(m.config.Agent.CleanupTimer)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
glog.Infof("Cleaning up app %s", app)
|
glog.Infof("Cleaning up app %s", app)
|
||||||
|
m.Lock()
|
||||||
m.Remove(app)
|
m.Remove(app)
|
||||||
|
m.Unlock()
|
||||||
case <-exit:
|
case <-exit:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -191,5 +281,5 @@ func (m *MonitorMgr) Cleanup(app string, exit chan bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MonitorMgr) GetInfo() (*api.Peer, error) {
|
func (m *MonitorMgr) GetInfo() (*api.Peer, error) {
|
||||||
return m.c.PeerInfo()
|
return m.ctrl.PeerInfo()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,20 +33,38 @@ func localAddress(gw net.IP) (net.IP, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addLoopback(name string, addr *net.IPNet) error {
|
func addLoopback(name string, addr *net.IPNet) error {
|
||||||
mask := fmt.Sprintf("%d.%d.%d.%d", addr.Mask[0], addr.Mask[1], addr.Mask[2], addr.Mask[3])
|
prefixLen, _ := addr.Mask.Size()
|
||||||
cmd := fmt.Sprintf("ifconfig lo:%s %s netmask %s up", name, addr.IP.String(), mask)
|
label := fmt.Sprintf("lo:%s", name)
|
||||||
|
// linux kernel limits labels to 15 chars
|
||||||
|
if len(label) > 15 {
|
||||||
|
label = label[:15]
|
||||||
|
}
|
||||||
|
cmd := fmt.Sprintf("ip address add %s/%d dev lo label %s", addr.IP.String(), prefixLen, label)
|
||||||
_, err := exec.Command("bash", "-c", cmd).Output()
|
_, err := exec.Command("bash", "-c", cmd).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to Add loopback command: %v", err)
|
return fmt.Errorf("Failed to Add loopback command: %s: %v", cmd, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteLoopback(name string) error {
|
func deleteLoopback(addr *net.IPNet) error {
|
||||||
cmd := fmt.Sprintf("ifconfig lo:%s down", name)
|
prefixLen, _ := addr.Mask.Size()
|
||||||
|
cmd := fmt.Sprintf("ip address delete %s/%d dev lo", addr.IP.String(), prefixLen)
|
||||||
_, err := exec.Command("bash", "-c", cmd).Output()
|
_, err := exec.Command("bash", "-c", cmd).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to delete loopback command: %v", err)
|
return fmt.Errorf("Failed to delete loopback command: %s: %v", cmd, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func natRule(op string, vip, localAddr net.IP, port, protocol string) error {
|
||||||
|
cmd := fmt.Sprintf(
|
||||||
|
"iptables -t nat -%s PREROUTING -p %s -d %s --dport %s -j DNAT --to-destination %s:%s",
|
||||||
|
op, protocol, vip.String(), port, localAddr.String(), port,
|
||||||
|
)
|
||||||
|
_, err := exec.Command("bash", "-c", cmd).Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to %s nat rule: %s: %v", op, cmd, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
14
main.go
14
main.go
@@ -3,27 +3,23 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
|
c "github.com/mayuresh82/gocast/config"
|
||||||
"github.com/mayuresh82/gocast/controller"
|
"github.com/mayuresh82/gocast/controller"
|
||||||
"github.com/mayuresh82/gocast/server"
|
"github.com/mayuresh82/gocast/server"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
serverAddr = flag.String("serverAddr", ":8080", "Addr for http service")
|
config = flag.String("config", "", "Path to config file")
|
||||||
localAS = flag.Int("localAS", 65000, "Local ASN of the gocast host")
|
|
||||||
peerAS = flag.Int("peerAS", 65254, "AS to peer with")
|
|
||||||
monitorInterval = flag.Duration("monitorInterval", 5*time.Second, "Interval for health check")
|
|
||||||
peerIP = flag.String("peerIP", "", "Override the IP to peer with. Default: gateway ip")
|
|
||||||
cleanupTimer = flag.Duration("cleanup", 15*time.Minute, "Time to flush out inactive apps")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
mon := controller.NewMonitor(*localAS, *peerAS, *monitorInterval, *peerIP, *cleanupTimer)
|
conf := c.GetConfig(*config)
|
||||||
srv := server.NewServer(*serverAddr, mon)
|
mon := controller.NewMonitor(conf)
|
||||||
|
srv := server.NewServer(conf.Agent.ListenAddr, mon)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
// catch interrupt
|
// catch interrupt
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (s *Server) Serve(ctx context.Context) {
|
|||||||
|
|
||||||
func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
queries := r.URL.Query()
|
queries := r.URL.Query()
|
||||||
app, err := controller.NewApp(queries["name"][0], queries["vip"][0], queries["monitor"][0], queries["type"][0])
|
app, err := controller.NewApp(queries["name"][0], queries["vip"][0], queries["monitor"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -61,7 +61,9 @@ func (s *Server) unregisterHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Invalid request, need app name specified", http.StatusBadRequest)
|
http.Error(w, "Invalid request, need app name specified", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
s.mon.Lock()
|
||||||
s.mon.Remove(appName[0])
|
s.mon.Remove(appName[0])
|
||||||
|
s.mon.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) infoHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) infoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user