2 Commits

Author SHA1 Message Date
Mayuresh Gaitonde
82152d030d Fix dockerfile 2021-05-13 23:54:52 -07:00
Mayuresh Gaitonde
5821c01a7b Add ability to specify vip parameters 2021-05-13 20:33:05 -07:00
11 changed files with 91 additions and 48 deletions

View File

@@ -3,18 +3,19 @@ RUN apk update && \
apk upgrade && \ apk upgrade && \
apk add --no-cache git && \ apk add --no-cache git && \
apk add make apk add make
RUN mkdir -p /opt/gocast
RUN mkdir -p /go/src/github.com/mayuresh82 RUN mkdir -p /go/src/github.com/mayuresh82/gocast
RUN cd /go/src/github.com/mayuresh82 && \
git clone https://github.com/mayuresh82/gocast COPY . /go/src/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/
FROM alpine:latest FROM alpine:latest
RUN apk --no-cache add ca-certificates bash iptables netcat-openbsd sudo 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 /go/src/github.com/mayuresh82/gocast .
EXPOSE 8080/tcp EXPOSE 8080/tcp

View File

@@ -24,4 +24,7 @@ bgp:
apps: apps:
- name: app1 - name: app1
vip: 1.1.1.1/32 vip: 1.1.1.1/32
vip_config:
# additional per VIP BGP communities
bgp_communities: [ aaaa:bbbb ]
monitor: port:tcp:5000 monitor: port:tcp:5000

View File

@@ -26,9 +26,16 @@ type BgpConfig struct {
Origin string Origin string
} }
type VipConfig struct {
// per VIP BGP communities to announce. This is in addition to the
// global config
BgpCommunities []string `yaml:"bgp_communities"`
}
type AppConfig struct { type AppConfig struct {
Name string Name string
Vip string Vip string
VipConfig VipConfig `yaml:"vip_config"`
Monitors []string Monitors []string
Nats []string Nats []string
} }

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/mayuresh82/gocast/config"
) )
type MonitorType int type MonitorType int
@@ -51,7 +52,8 @@ func (m Monitors) Contains(elem *Monitor) bool {
type App struct { type App struct {
Name string Name string
Vip *net.IPNet Vip *Route
VipConfig config.VipConfig
Monitors Monitors Monitors Monitors
Nats []string Nats []string
Source string Source string
@@ -66,10 +68,15 @@ func (a *App) Equal(other *App) bool {
return false return false
} }
} }
return a.Name == other.Name && a.Vip.String() == other.Vip.String() return a.Name == other.Name && a.Vip.Net.String() == other.Vip.Net.String()
} }
func NewApp(appName, vip string, monitors []string, nats []string, source string) (*App, error) { func (a *App) String() string {
return fmt.Sprintf("Name: %s, Vip: %s, VipConf: %v, Monitors: %v, Nats: %v, Source: %s",
a.Name, a.Vip.Net.String(), a.VipConfig, a.Monitors, a.Nats, a.Source)
}
func NewApp(appName, vip string, vipConfig config.VipConfig, monitors []string, nats []string, source string) (*App, error) {
if appName == "" { if appName == "" {
return nil, fmt.Errorf("Invalid app name") return nil, fmt.Errorf("Invalid app name")
} }
@@ -78,7 +85,8 @@ func NewApp(appName, vip string, monitors []string, nats []string, source string
if err != nil { if err != nil {
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 = &Route{Net: ipnet, Communities: vipConfig.BgpCommunities}
app.VipConfig = vipConfig
for _, m := range monitors { for _, m := range monitors {
// valid monitor formats: // valid monitor formats:
// "port:tcp:123" , "exec:/local/check.sh", "consul" // "port:tcp:123" , "exec:/local/check.sh", "consul"

View File

@@ -3,32 +3,36 @@ package controller
import ( import (
"testing" "testing"
"github.com/mayuresh82/gocast/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestAppParsing(t *testing.T) { func TestAppParsing(t *testing.T) {
a := assert.New(t) a := assert.New(t)
app1, err := NewApp("app1", "1.1.1.1/32", []string{"port:tcp:123"}, []string{}, "") app1, err := NewApp("app1", "1.1.1.1/32", config.VipConfig{}, []string{"port:tcp:123"}, []string{}, "")
a.Nil(err) a.Nil(err)
app2, err := NewApp("app1", "1.1.1.1/32", []string{"port:tcp:123"}, []string{}, "") app2, err := NewApp("app1", "1.1.1.1/32", config.VipConfig{BgpCommunities: []string{"111:222"}}, []string{"port:tcp:123"}, []string{}, "")
a.Nil(err) a.Nil(err)
app3, err := NewApp("app3", "2.2.2.2/32", []string{"exec:/bin/testme"}, []string{}, "") app3, err := NewApp("app3", "2.2.2.2/32", config.VipConfig{}, []string{"exec:/bin/testme"}, []string{}, "")
a.Nil(err) a.Nil(err)
a.Equal("1.1.1.1/32", app1.Vip.String()) a.Equal("1.1.1.1/32", app1.Vip.Net.String())
a.Equal(Monitor_PORT, app1.Monitors[0].Type) a.Equal(Monitor_PORT, app1.Monitors[0].Type)
a.Equal("123", app1.Monitors[0].Port) a.Equal("123", app1.Monitors[0].Port)
a.Equal("tcp", app1.Monitors[0].Protocol) a.Equal("tcp", app1.Monitors[0].Protocol)
a.Equal(config.VipConfig{}, app1.VipConfig)
a.Equal(true, app1.Equal(app2)) a.Equal(true, app1.Equal(app2))
a.Equal("111:222", app2.Vip.Communities[0])
a.Equal(Monitor_EXEC, app3.Monitors[0].Type) a.Equal(Monitor_EXEC, app3.Monitors[0].Type)
a.Equal("/bin/testme", app3.Monitors[0].Cmd) a.Equal("/bin/testme", app3.Monitors[0].Cmd)
// test errors // test errors
_, err = NewApp("app4", "4.4.4.4", []string{}, []string{}, "") _, err = NewApp("app4", "4.4.4.4", config.VipConfig{}, []string{}, []string{}, "")
a.NotNil(err) a.NotNil(err)
_, err = NewApp("app4", "4.4.4.4/32", []string{"port:abcd::1023"}, []string{}, "") _, err = NewApp("app4", "4.4.4.4/32", config.VipConfig{}, []string{"port:abcd::1023"}, []string{}, "")
a.NotNil(err) a.NotNil(err)
} }

View File

@@ -14,6 +14,11 @@ import (
gobgp "github.com/osrg/gobgp/pkg/server" gobgp "github.com/osrg/gobgp/pkg/server"
) )
type Route struct {
Net *net.IPNet
Communities []string
}
type Controller struct { type Controller struct {
peerAS int peerAS int
localIP, peerIP net.IP localIP, peerIP net.IP
@@ -90,14 +95,14 @@ func (c *Controller) AddPeer(peer string) error {
return c.s.AddPeer(context.Background(), &api.AddPeerRequest{Peer: n}) return c.s.AddPeer(context.Background(), &api.AddPeerRequest{Peer: n})
} }
func (c *Controller) getApiPath(route *net.IPNet) *api.Path { func (c *Controller) getApiPath(route *Route) *api.Path {
afi := api.Family_AFI_IP afi := api.Family_AFI_IP
if route.IP.To4() == nil { if route.Net.IP.To4() == nil {
afi = api.Family_AFI_IP6 afi = api.Family_AFI_IP6
} }
prefixlen, _ := route.Mask.Size() prefixlen, _ := route.Net.Mask.Size()
nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{ nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{
Prefix: route.IP.String(), Prefix: route.Net.IP.String(),
PrefixLen: uint32(prefixlen), PrefixLen: uint32(prefixlen),
}) })
a1, _ := ptypes.MarshalAny(&api.OriginAttribute{ a1, _ := ptypes.MarshalAny(&api.OriginAttribute{
@@ -107,7 +112,7 @@ func (c *Controller) getApiPath(route *net.IPNet) *api.Path {
NextHop: c.localIP.String(), NextHop: c.localIP.String(),
}) })
var communities []uint32 var communities []uint32
for _, comm := range c.communities { for _, comm := range append(c.communities, route.Communities...) {
communities = append(communities, convertCommunity(comm)) communities = append(communities, convertCommunity(comm))
} }
a3, _ := ptypes.MarshalAny(&api.CommunitiesAttribute{ a3, _ := ptypes.MarshalAny(&api.CommunitiesAttribute{
@@ -121,7 +126,7 @@ func (c *Controller) getApiPath(route *net.IPNet) *api.Path {
} }
} }
func (c *Controller) Announce(route *net.IPNet) error { func (c *Controller) Announce(route *Route) error {
var found bool var found bool
err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) { err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) {
if p.Conf.NeighborAddress == c.peerIP.String() { if p.Conf.NeighborAddress == c.peerIP.String() {
@@ -140,7 +145,7 @@ func (c *Controller) Announce(route *net.IPNet) error {
return err return err
} }
func (c *Controller) Withdraw(route *net.IPNet) error { func (c *Controller) Withdraw(route *Route) error {
return c.s.DeletePath(context.Background(), &api.DeletePathRequest{Path: c.getApiPath(route)}) return c.s.DeletePath(context.Background(), &api.DeletePathRequest{Path: c.getApiPath(route)})
} }

View File

@@ -93,7 +93,8 @@ func TestBgpNew(t *testing.T) {
a.FailNow(err.Error()) a.FailNow(err.Error())
} }
_, ipnet, _ := net.ParseCIDR("20.30.40.0/24") _, ipnet, _ := net.ParseCIDR("20.30.40.0/24")
if err := ctrl.Announce(ipnet); err != nil { r := &Route{Net: ipnet}
if err := ctrl.Announce(r); err != nil {
a.FailNow(err.Error()) a.FailNow(err.Error())
} }

View File

@@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/mayuresh82/gocast/config"
) )
const ( const (
@@ -85,6 +86,7 @@ func (c *ConsulMon) queryServices() ([]*App, error) {
monitors []string monitors []string
nats []string nats []string
) )
var vipConf config.VipConfig
for _, tag := range service.Tags { for _, tag := range service.Tags {
// try to find the requires tags. Only vip is mandatory // try to find the requires tags. Only vip is mandatory
parts := strings.Split(tag, "=") parts := strings.Split(tag, "=")
@@ -94,6 +96,8 @@ func (c *ConsulMon) queryServices() ([]*App, error) {
switch parts[0] { switch parts[0] {
case "gocast_vip": case "gocast_vip":
vip = parts[1] vip = parts[1]
case "gocast_vip_communities":
vipConf.BgpCommunities = strings.Split(parts[1], ",")
case "gocast_monitor": case "gocast_monitor":
monitors = append(monitors, parts[1]) monitors = append(monitors, parts[1])
case "gocast_nat": case "gocast_nat":
@@ -104,7 +108,7 @@ func (c *ConsulMon) queryServices() ([]*App, error) {
glog.Errorf("No vip Tag found in matched service :%s", service.Service) glog.Errorf("No vip Tag found in matched service :%s", service.Service)
continue continue
} }
app, err := NewApp(service.Service, vip, monitors, nats, "consul") app, err := NewApp(service.Service, vip, vipConf, monitors, nats, "consul")
if err != nil { if err != nil {
glog.Errorf("Unable to add consul app: %v", err) glog.Errorf("Unable to add consul app: %v", err)
continue continue

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/mayuresh82/gocast/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -15,7 +16,7 @@ var mockConsulData = map[string]string{
"ID": "test-app-1", "ID": "test-app-1",
"Service": "test-service", "Service": "test-service",
"Tags": [ "Tags": [
"enable_gocast", "gocast_vip=1.1.1.1/32", "gocast_monitor=consul" "enable_gocast", "gocast_vip=1.1.1.1/32", "gocast_monitor=consul", "gocast_vip_communities=111:222,333:444"
] ]
} }
}}`, }}`,
@@ -103,7 +104,9 @@ func TestQueryServices(t *testing.T) {
a.FailNow(err.Error()) a.FailNow(err.Error())
} }
a.Equal(1, len(apps)) a.Equal(1, len(apps))
app, _ := NewApp("test-service", "1.1.1.1/32", []string{"consul"}, []string{}, "consul") a.Equal([]string{"111:222", "333:444"}, apps[0].Vip.Communities)
app, _ := NewApp("test-service", "1.1.1.1/32", config.VipConfig{}, []string{"consul"}, []string{}, "consul")
a.True(app.Equal(apps[0])) a.True(app.Equal(apps[0]))
// test no match // test no match

View File

@@ -100,7 +100,7 @@ func NewMonitor(config *c.Config) *MonitorMgr {
mon.config = config mon.config = config
// add apps defined in config // add apps defined in config
for _, a := range config.Apps { for _, a := range config.Apps {
app, err := NewApp(a.Name, a.Vip, a.Monitors, a.Nats, "config") app, err := NewApp(a.Name, a.Vip, a.VipConfig, a.Monitors, a.Nats, "config")
if err != nil { if err != nil {
glog.Errorf("Failed to add configured app %s: %v", a.Name, err) glog.Errorf("Failed to add configured app %s: %v", a.Name, err)
continue continue
@@ -159,8 +159,8 @@ func (m *MonitorMgr) Add(app *App) {
glog.V(2).Infof("App %s already exists", app.Name) glog.V(2).Infof("App %s already exists", app.Name)
return return
} }
if appMon.app.Vip.String() == app.Vip.String() && appMon.app.Name != app.Name { if appMon.app.Vip.Net.String() == app.Vip.Net.String() && appMon.app.Name != app.Name {
glog.Errorf("Error: Vip %s is already being announced by app: %s", app.Vip.String(), appMon.app.Name) glog.Errorf("Error: Vip %s is already being announced by app: %s", app.Vip.Net.String(), appMon.app.Name)
return return
} }
} }
@@ -168,7 +168,7 @@ func (m *MonitorMgr) Add(app *App) {
appMon := &appMon{app: app, done: make(chan bool)} appMon := &appMon{app: app, done: make(chan bool)}
m.monitors[app.Name] = appMon m.monitors[app.Name] = appMon
go m.runLoop(appMon) go m.runLoop(appMon)
glog.Infof("Registered a new app: %v", app) glog.Infof("Registered a new app: %v", app.String())
} }
// Remove removes an app from monitor manager, stops BGP // Remove removes an app from monitor manager, stops BGP
@@ -183,7 +183,7 @@ func (m *MonitorMgr) Remove(appName string) {
glog.Errorf("Failed to withdraw route: %v", err) glog.Errorf("Failed to withdraw route: %v", err)
} }
} }
if err := deleteLoopback(a.app.Vip); err != nil { if err := deleteLoopback(a.app.Vip.Net); err != nil {
glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err) glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err)
} }
for _, nat := range a.app.Nats { for _, nat := range a.app.Nats {
@@ -191,7 +191,7 @@ func (m *MonitorMgr) Remove(appName string) {
if len(parts) != 2 { if len(parts) != 2 {
continue continue
} }
if err := natRule("D", a.app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil { if err := natRule("D", a.app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil {
glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err) glog.Errorf("Failed to remove app: %s: %v", a.app.Name, err)
} }
} }
@@ -228,7 +228,7 @@ func (m *MonitorMgr) checkCond(am *appMon) error {
if m.runMonitors(app) { if m.runMonitors(app) {
glog.V(2).Infof("All Monitors for app: %s succeeded", 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.Net); err != nil {
return err return err
} }
for _, nat := range app.Nats { for _, nat := range app.Nats {
@@ -236,7 +236,7 @@ func (m *MonitorMgr) checkCond(am *appMon) error {
if len(parts) != 2 { if len(parts) != 2 {
continue continue
} }
if err := natRule("A", app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil { if err := natRule("A", app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1]); err != nil {
return err return err
} }
} }
@@ -294,13 +294,13 @@ func (m *MonitorMgr) CloseAll() {
if am.checkOn { if am.checkOn {
am.done <- true am.done <- true
} }
deleteLoopback(am.app.Vip) deleteLoopback(am.app.Vip.Net)
for _, nat := range am.app.Nats { for _, nat := range am.app.Nats {
parts := strings.Split(nat, ":") parts := strings.Split(nat, ":")
if len(parts) != 2 { if len(parts) != 2 {
continue continue
} }
natRule("D", am.app.Vip.IP, m.ctrl.localIP, parts[0], parts[1]) natRule("D", am.app.Vip.Net.IP, m.ctrl.localIP, parts[0], parts[1])
} }
} }
} }

View File

@@ -4,9 +4,12 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/golang/glog"
"github.com/mayuresh82/gocast/controller"
"net/http" "net/http"
"strings"
"github.com/golang/glog"
"github.com/mayuresh82/gocast/config"
"github.com/mayuresh82/gocast/controller"
) )
// Server is the main entrypoint into the app and serves app requests // Server is the main entrypoint into the app and serves app requests
@@ -46,7 +49,11 @@ 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"], queries["nat"], "http") var vipConf config.VipConfig
if vipComm, ok := queries["vip_communities"]; ok {
vipConf.BgpCommunities = strings.Split(vipComm[0], ",")
}
app, err := controller.NewApp(queries["name"][0], queries["vip"][0], vipConf, queries["monitor"], queries["nat"], "http")
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