From 5821c01a7b04d2e58e4b0c65f317467cb26f37bf Mon Sep 17 00:00:00 2001 From: Mayuresh Gaitonde Date: Thu, 13 May 2021 20:33:05 -0700 Subject: [PATCH] Add ability to specify vip parameters --- config.yaml | 3 +++ config/config.go | 15 +++++++++++---- controller/app.go | 19 +++++++++++-------- controller/app_test.go | 16 ++++++++++------ controller/bgp.go | 19 ++++++++++++------- controller/bgp_test.go | 3 ++- controller/consul.go | 6 +++++- controller/consul_test.go | 7 +++++-- controller/monitor.go | 18 +++++++++--------- server/server.go | 13 ++++++++++--- 10 files changed, 78 insertions(+), 41 deletions(-) diff --git a/config.yaml b/config.yaml index 3d7e4ff..f5972cc 100644 --- a/config.yaml +++ b/config.yaml @@ -24,4 +24,7 @@ bgp: apps: - name: app1 vip: 1.1.1.1/32 + vip_config: + # additional per VIP BGP communities + bgp_communities: [ aaaa:bbbb ] monitor: port:tcp:5000 diff --git a/config/config.go b/config/config.go index 6db970b..35df0f7 100644 --- a/config/config.go +++ b/config/config.go @@ -26,11 +26,18 @@ type BgpConfig struct { 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 { - Name string - Vip string - Monitors []string - Nats []string + Name string + Vip string + VipConfig VipConfig `yaml:"vip_config"` + Monitors []string + Nats []string } type Config struct { diff --git a/controller/app.go b/controller/app.go index 98a5c19..af676e3 100644 --- a/controller/app.go +++ b/controller/app.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/golang/glog" + "github.com/mayuresh82/gocast/config" ) type MonitorType int @@ -50,11 +51,12 @@ func (m Monitors) Contains(elem *Monitor) bool { } type App struct { - Name string - Vip *net.IPNet - Monitors Monitors - Nats []string - Source string + Name string + Vip *Route + VipConfig config.VipConfig + Monitors Monitors + Nats []string + Source string } func (a *App) Equal(other *App) bool { @@ -66,10 +68,10 @@ func (a *App) Equal(other *App) bool { 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 NewApp(appName, vip string, vipConfig config.VipConfig, monitors []string, nats []string, source string) (*App, error) { if appName == "" { return nil, fmt.Errorf("Invalid app name") } @@ -78,7 +80,8 @@ func NewApp(appName, vip string, monitors []string, nats []string, source string if err != nil { 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 { // valid monitor formats: // "port:tcp:123" , "exec:/local/check.sh", "consul" diff --git a/controller/app_test.go b/controller/app_test.go index 54c9662..77ffca9 100644 --- a/controller/app_test.go +++ b/controller/app_test.go @@ -3,32 +3,36 @@ package controller import ( "testing" + "github.com/mayuresh82/gocast/config" "github.com/stretchr/testify/assert" ) func TestAppParsing(t *testing.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) - 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) - 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.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("123", app1.Monitors[0].Port) a.Equal("tcp", app1.Monitors[0].Protocol) + a.Equal(config.VipConfig{}, app1.VipConfig) a.Equal(true, app1.Equal(app2)) + a.Equal("111:222", app2.Vip.Communities[0]) + a.Equal(Monitor_EXEC, app3.Monitors[0].Type) a.Equal("/bin/testme", app3.Monitors[0].Cmd) // 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) - _, 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) } diff --git a/controller/bgp.go b/controller/bgp.go index ad8a9e6..96da20d 100644 --- a/controller/bgp.go +++ b/controller/bgp.go @@ -14,6 +14,11 @@ import ( gobgp "github.com/osrg/gobgp/pkg/server" ) +type Route struct { + Net *net.IPNet + Communities []string +} + type Controller struct { peerAS int 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}) } -func (c *Controller) getApiPath(route *net.IPNet) *api.Path { +func (c *Controller) getApiPath(route *Route) *api.Path { afi := api.Family_AFI_IP - if route.IP.To4() == nil { + if route.Net.IP.To4() == nil { afi = api.Family_AFI_IP6 } - prefixlen, _ := route.Mask.Size() + prefixlen, _ := route.Net.Mask.Size() nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{ - Prefix: route.IP.String(), + Prefix: route.Net.IP.String(), PrefixLen: uint32(prefixlen), }) a1, _ := ptypes.MarshalAny(&api.OriginAttribute{ @@ -107,7 +112,7 @@ func (c *Controller) getApiPath(route *net.IPNet) *api.Path { NextHop: c.localIP.String(), }) var communities []uint32 - for _, comm := range c.communities { + for _, comm := range append(c.communities, route.Communities...) { communities = append(communities, convertCommunity(comm)) } 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 err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{}, func(p *api.Peer) { if p.Conf.NeighborAddress == c.peerIP.String() { @@ -140,7 +145,7 @@ func (c *Controller) Announce(route *net.IPNet) error { 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)}) } diff --git a/controller/bgp_test.go b/controller/bgp_test.go index de6fbb5..b9ec48b 100644 --- a/controller/bgp_test.go +++ b/controller/bgp_test.go @@ -93,7 +93,8 @@ func TestBgpNew(t *testing.T) { a.FailNow(err.Error()) } _, 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()) } diff --git a/controller/consul.go b/controller/consul.go index 7f9a95f..43fdbb7 100644 --- a/controller/consul.go +++ b/controller/consul.go @@ -10,6 +10,7 @@ import ( "time" "github.com/golang/glog" + "github.com/mayuresh82/gocast/config" ) const ( @@ -85,6 +86,7 @@ func (c *ConsulMon) queryServices() ([]*App, error) { monitors []string nats []string ) + var vipConf config.VipConfig for _, tag := range service.Tags { // try to find the requires tags. Only vip is mandatory parts := strings.Split(tag, "=") @@ -94,6 +96,8 @@ func (c *ConsulMon) queryServices() ([]*App, error) { switch parts[0] { case "gocast_vip": vip = parts[1] + case "gocast_vip_communities": + vipConf.BgpCommunities = strings.Split(parts[1], ",") case "gocast_monitor": monitors = append(monitors, parts[1]) 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) continue } - app, err := NewApp(service.Service, vip, monitors, nats, "consul") + app, err := NewApp(service.Service, vip, vipConf, monitors, nats, "consul") if err != nil { glog.Errorf("Unable to add consul app: %v", err) continue diff --git a/controller/consul_test.go b/controller/consul_test.go index e648db6..8ddc115 100644 --- a/controller/consul_test.go +++ b/controller/consul_test.go @@ -6,6 +6,7 @@ import ( "net/http" "testing" + "github.com/mayuresh82/gocast/config" "github.com/stretchr/testify/assert" ) @@ -15,7 +16,7 @@ var mockConsulData = map[string]string{ "ID": "test-app-1", "Service": "test-service", "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.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])) // test no match diff --git a/controller/monitor.go b/controller/monitor.go index fc9282a..d5fa333 100644 --- a/controller/monitor.go +++ b/controller/monitor.go @@ -100,7 +100,7 @@ func NewMonitor(config *c.Config) *MonitorMgr { mon.config = config // add apps defined in config 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 { glog.Errorf("Failed to add configured app %s: %v", a.Name, err) continue @@ -159,8 +159,8 @@ func (m *MonitorMgr) Add(app *App) { glog.V(2).Infof("App %s already exists", app.Name) 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) + 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.Net.String(), appMon.app.Name) return } } @@ -183,7 +183,7 @@ func (m *MonitorMgr) Remove(appName string) { 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) } for _, nat := range a.app.Nats { @@ -191,7 +191,7 @@ func (m *MonitorMgr) Remove(appName string) { if len(parts) != 2 { 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) } } @@ -228,7 +228,7 @@ func (m *MonitorMgr) checkCond(am *appMon) error { if m.runMonitors(app) { glog.V(2).Infof("All Monitors for app: %s succeeded", app.Name) if !am.announced { - if err := addLoopback(app.Name, app.Vip); err != nil { + if err := addLoopback(app.Name, app.Vip.Net); err != nil { return err } for _, nat := range app.Nats { @@ -236,7 +236,7 @@ func (m *MonitorMgr) checkCond(am *appMon) error { if len(parts) != 2 { 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 } } @@ -294,13 +294,13 @@ func (m *MonitorMgr) CloseAll() { if am.checkOn { am.done <- true } - deleteLoopback(am.app.Vip) + deleteLoopback(am.app.Vip.Net) for _, nat := range am.app.Nats { parts := strings.Split(nat, ":") if len(parts) != 2 { 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]) } } } diff --git a/server/server.go b/server/server.go index 7556ae6..75edb6c 100644 --- a/server/server.go +++ b/server/server.go @@ -4,9 +4,12 @@ import ( "context" "encoding/json" "fmt" - "github.com/golang/glog" - "github.com/mayuresh82/gocast/controller" "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 @@ -46,7 +49,11 @@ func (s *Server) Serve(ctx context.Context) { func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) { 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 { http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest) return