Initial commit for gocast
This commit is contained in:
64
controller/app.go
Normal file
64
controller/app.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MonitorType int
|
||||
|
||||
const (
|
||||
Monitor_PORT MonitorType = 1
|
||||
Monitor_EXEC MonitorType = 2
|
||||
)
|
||||
|
||||
var Monitors = map[string]MonitorType{"port": Monitor_PORT, "exec": Monitor_EXEC}
|
||||
|
||||
func (m MonitorType) String() string {
|
||||
for str, mtr := range Monitors {
|
||||
if m == mtr {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
Type MonitorType
|
||||
Port string
|
||||
Protocol string
|
||||
Cmd string
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Name string
|
||||
Vip *net.IPNet
|
||||
Monitor Monitor
|
||||
}
|
||||
|
||||
func NewApp(appName, vip, monitor, monitorType string) (*App, error) {
|
||||
if appName == "" {
|
||||
return nil, fmt.Errorf("Invalid app name")
|
||||
}
|
||||
app := &App{Name: appName}
|
||||
_, ipnet, err := net.ParseCIDR(vip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid VIP specified, need ip/mask")
|
||||
}
|
||||
app.Vip = ipnet
|
||||
m := Monitor{Type: Monitors[monitorType]}
|
||||
switch monitorType {
|
||||
case "port":
|
||||
parts := strings.Split(monitor, ":")
|
||||
m.Protocol = parts[0]
|
||||
m.Port = parts[1]
|
||||
case "exec":
|
||||
m.Cmd = monitor
|
||||
default:
|
||||
glog.V(2).Infof("No monitor specified")
|
||||
}
|
||||
app.Monitor = m
|
||||
return app, nil
|
||||
}
|
||||
136
controller/bgp.go
Normal file
136
controller/bgp.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/any"
|
||||
api "github.com/osrg/gobgp/api"
|
||||
gobgp "github.com/osrg/gobgp/pkg/server"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
peerAS int
|
||||
localIP, peerIP net.IP
|
||||
s *gobgp.BgpServer
|
||||
}
|
||||
|
||||
func NewController(localAS, peerAS int, peerIP string) (*Controller, error) {
|
||||
c := &Controller{}
|
||||
if peerIP == "" {
|
||||
gw, err := gateway()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.peerIP = gw
|
||||
} else {
|
||||
c.peerIP = net.ParseIP(peerIP)
|
||||
}
|
||||
if c.peerIP == nil {
|
||||
return nil, fmt.Errorf("Unable to get peer IP")
|
||||
}
|
||||
s := gobgp.NewBgpServer()
|
||||
go s.Serve()
|
||||
localAddr, err := localAddress(c.peerIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.localIP = localAddr
|
||||
if err := s.StartBgp(context.Background(), &api.StartBgpRequest{
|
||||
Global: &api.Global{
|
||||
As: uint32(localAS),
|
||||
RouterId: localAddr.String(),
|
||||
ListenPort: -1, // gobgp won't listen on tcp:179
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Unable to start bgp: %v", err)
|
||||
}
|
||||
c.s = s
|
||||
c.peerAS = peerAS
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Controller) AddPeer(peer string) error {
|
||||
n := &api.Peer{
|
||||
Conf: &api.PeerConf{
|
||||
NeighborAddress: peer,
|
||||
PeerAs: uint32(c.peerAS),
|
||||
},
|
||||
}
|
||||
return c.s.AddPeer(context.Background(), &api.AddPeerRequest{Peer: n})
|
||||
}
|
||||
|
||||
func (c *Controller) getApiPath(route *net.IPNet) *api.Path {
|
||||
afi := api.Family_AFI_IP
|
||||
if route.IP.To4() == nil {
|
||||
afi = api.Family_AFI_IP6
|
||||
}
|
||||
prefixlen, _ := route.Mask.Size()
|
||||
nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{
|
||||
Prefix: route.IP.String(),
|
||||
PrefixLen: uint32(prefixlen),
|
||||
})
|
||||
a1, _ := ptypes.MarshalAny(&api.OriginAttribute{
|
||||
Origin: 0,
|
||||
})
|
||||
a2, _ := ptypes.MarshalAny(&api.NextHopAttribute{
|
||||
NextHop: c.localIP.String(),
|
||||
})
|
||||
attrs := []*any.Any{a1, a2}
|
||||
return &api.Path{
|
||||
Family: &api.Family{Afi: afi, Safi: api.Family_SAFI_UNICAST},
|
||||
AnyNlri: nlri,
|
||||
AnyPattrs: attrs,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) Announce(route *net.IPNet) error {
|
||||
peers, err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var found bool
|
||||
for _, p := range peers {
|
||||
if p.Conf.NeighborAddress == c.peerIP.String() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if err := c.AddPeer(c.peerIP.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = c.s.AddPath(context.Background(), &api.AddPathRequest{Path: c.getApiPath(route)})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Controller) Withdraw(route *net.IPNet) error {
|
||||
return c.s.DeletePath(context.Background(), &api.DeletePathRequest{Path: c.getApiPath(route)})
|
||||
}
|
||||
|
||||
func (c *Controller) PeerInfo() (*api.Peer, error) {
|
||||
peers, err := c.s.ListPeer(context.Background(), &api.ListPeerRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range peers {
|
||||
if p.Conf.NeighborAddress == c.peerIP.String() {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Controller) Shutdown() error {
|
||||
if err := c.s.ShutdownPeer(context.Background(), &api.ShutdownPeerRequest{
|
||||
Address: c.peerIP.String(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.s.StopBgp(context.Background(), &api.StopBgpRequest{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
195
controller/monitor.go
Normal file
195
controller/monitor.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/glog"
|
||||
api "github.com/osrg/gobgp/api"
|
||||
"net"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func portMonitor(protocol, port string) bool {
|
||||
switch protocol {
|
||||
case "tcp":
|
||||
conn, err := net.Listen(protocol, ":"+port)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("Monitor tcp port up")
|
||||
return true
|
||||
}
|
||||
defer conn.Close()
|
||||
case "udp":
|
||||
conn, err := net.ListenPacket(protocol, ":"+port)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("Monitor udp port up")
|
||||
return true
|
||||
}
|
||||
defer conn.Close()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func execMonitor(cmd string) bool {
|
||||
out := exec.Command("bash", "-c", cmd)
|
||||
if err := out.Start(); err != nil {
|
||||
glog.V(2).Infof("Cannot exec cmd: %s : %v", cmd, err)
|
||||
return false
|
||||
}
|
||||
if err := out.Wait(); err != nil {
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
glog.V(4).Infof("Monitor cmd failed")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type appMon struct {
|
||||
app *App
|
||||
done chan bool
|
||||
announced bool
|
||||
checkOn bool
|
||||
}
|
||||
|
||||
type MonitorMgr struct {
|
||||
monitors map[string]*appMon
|
||||
cleanups map[string]chan bool
|
||||
c *Controller
|
||||
monitorInterval time.Duration
|
||||
cleanupTimer time.Duration
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewMonitor(localAS, peerAS int, monitorInterval time.Duration, peerIP string, cleanup time.Duration) *MonitorMgr {
|
||||
c, err := NewController(localAS, peerAS, peerIP)
|
||||
if err != nil {
|
||||
glog.Exitf("Failed to start BGP controller: %v", err)
|
||||
}
|
||||
return &MonitorMgr{
|
||||
c: c,
|
||||
monitors: make(map[string]*appMon),
|
||||
cleanups: make(map[string]chan bool),
|
||||
monitorInterval: monitorInterval,
|
||||
cleanupTimer: cleanup,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitorMgr) Add(app *App) {
|
||||
// stop and start a new one if one already running
|
||||
m.Remove(app.Name)
|
||||
appMon := &appMon{app: app, done: make(chan bool)}
|
||||
m.Lock()
|
||||
m.monitors[app.Name] = appMon
|
||||
m.Unlock()
|
||||
go m.runLoop(appMon)
|
||||
glog.Infof("Registered a new app: %v", app)
|
||||
}
|
||||
|
||||
func (m *MonitorMgr) Remove(appName string) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
if a, ok := m.monitors[appName]; ok {
|
||||
if a.checkOn {
|
||||
a.done <- true
|
||||
}
|
||||
if a.announced {
|
||||
if err := m.c.Withdraw(a.app.Vip); err != nil {
|
||||
glog.Errorf("Failed to withdraw route: %v", err)
|
||||
}
|
||||
}
|
||||
deleteLoopback(appName)
|
||||
}
|
||||
delete(m.monitors, appName)
|
||||
}
|
||||
|
||||
func (m *MonitorMgr) checkCond(am *appMon) error {
|
||||
var cond bool
|
||||
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()
|
||||
defer m.Unlock()
|
||||
if cond {
|
||||
glog.V(2).Infof("%s Monitor for app: %s succeeded", app.Monitor.Type.String(), app.Name)
|
||||
if !am.announced {
|
||||
if err := addLoopback(app.Name, app.Vip); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.c.Announce(app.Vip); err != nil {
|
||||
return fmt.Errorf("Failed to announce route: %v", err)
|
||||
}
|
||||
am.announced = true
|
||||
if exit, ok := m.cleanups[app.Name]; ok {
|
||||
exit <- true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glog.V(2).Infof("%s Monitor for app: %s Failed", app.Monitor.Type.String(), app.Name)
|
||||
if am.announced {
|
||||
if err := m.c.Withdraw(app.Vip); err != nil {
|
||||
return fmt.Errorf("Failed to withdraw route: %v", err)
|
||||
}
|
||||
am.announced = false
|
||||
exit := make(chan bool)
|
||||
go m.Cleanup(app.Name, exit)
|
||||
m.cleanups[app.Name] = exit
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MonitorMgr) runLoop(am *appMon) {
|
||||
am.checkOn = true
|
||||
if err := m.checkCond(am); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
t := time.NewTicker(m.monitorInterval)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
if err := m.checkCond(am); err != nil {
|
||||
glog.Errorln(err)
|
||||
}
|
||||
case <-am.done:
|
||||
glog.V(2).Infof("Exit run-loop for app: %s", am.app.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitorMgr) CloseAll() {
|
||||
glog.Infof("Shutting down all open bgp sessions")
|
||||
if err := m.c.Shutdown(); err != nil {
|
||||
glog.Errorf("Failed to shut-down BGP: %v", err)
|
||||
}
|
||||
for name, am := range m.monitors {
|
||||
if am.checkOn {
|
||||
am.done <- true
|
||||
}
|
||||
deleteLoopback(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitorMgr) Cleanup(app string, exit chan bool) {
|
||||
t := time.NewTimer(m.cleanupTimer)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
glog.Infof("Cleaning up app %s", app)
|
||||
m.Remove(app)
|
||||
case <-exit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MonitorMgr) GetInfo() (*api.Peer, error) {
|
||||
return m.c.PeerInfo()
|
||||
}
|
||||
52
controller/system.go
Normal file
52
controller/system.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func gateway() (net.IP, error) {
|
||||
cmd := `ip route | grep "^default" | cut -d" " -f3`
|
||||
out, err := exec.Command("bash", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to execute command: %s", cmd)
|
||||
}
|
||||
return net.ParseIP(strings.TrimSpace(string(out))), nil
|
||||
}
|
||||
|
||||
func localAddress(gw net.IP) (net.IP, error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
if v.Contains(gw) {
|
||||
return v.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Unable to find local address")
|
||||
}
|
||||
|
||||
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])
|
||||
cmd := fmt.Sprintf("ifconfig lo:%s %s netmask %s up", name, addr.IP.String(), mask)
|
||||
_, err := exec.Command("bash", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Add loopback command: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteLoopback(name string) error {
|
||||
cmd := fmt.Sprintf("ifconfig lo:%s down", name)
|
||||
_, err := exec.Command("bash", "-c", cmd).Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to delete loopback command: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user