github.com/solo-io/unik@v0.0.0-20190717152701-a58d3e8e33b7/instance-listener/main.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"net"
    11  	"net/http"
    12  	"os"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  )
    17  
    18  const statefile = "/data/statefile.json"
    19  
    20  type state struct {
    21  	MacIpMap  map[string]string            `json:"Ips"`
    22  	MacEnvMap map[string]map[string]string `json:"Envs"`
    23  }
    24  
    25  func main() {
    26  	args := os.Args
    27  	for i, arg := range args {
    28  		log.Printf("arg %v: %s", i, arg)
    29  	}
    30  	dataPrefix := flag.String("prefix", "unik_", "prefix for data sent via udp (for identification purposes")
    31  	enablePersistence := flag.Bool("enablePersistence", true, "assume a persistent volume is mounted to /data")
    32  	flag.Parse()
    33  	for i, arg := range flag.Args() {
    34  
    35  		log.Printf("flagarg %v: %s", i, arg)
    36  	}
    37  	if *dataPrefix == "unik_" {
    38  		log.Printf("ERROR: must provide -prefix")
    39  		return
    40  	}
    41  	if *dataPrefix == "" {
    42  		log.Printf("ERROR: -prefix cannot be \"\"")
    43  		return
    44  	}
    45  	if !*enablePersistence {
    46  		os.MkdirAll("/data", 0755)
    47  	}
    48  	ipMapLock := sync.RWMutex{}
    49  	envMapLock := sync.RWMutex{}
    50  	saveLock := sync.Mutex{}
    51  	var s state
    52  	s.MacIpMap = make(map[string]string)
    53  	s.MacEnvMap = make(map[string]map[string]string)
    54  
    55  	data, err := ioutil.ReadFile(statefile)
    56  	if err != nil {
    57  		log.Printf("could not read statefile, maybe this is first boot: " + err.Error())
    58  	} else {
    59  		if err := json.Unmarshal(data, &s); err != nil {
    60  			log.Printf("failed to parse state json: " + err.Error())
    61  		}
    62  	}
    63  
    64  	listenerIp, listenerIpMask, err := getLocalIp()
    65  	if err != nil {
    66  		log.Printf("ERROR: failed to get local ip: %v", err)
    67  		return
    68  	}
    69  
    70  	log.Printf("Starting unik discovery (udp heartbeat broadcast) with ip %s", listenerIp.String())
    71  	info := []byte(*dataPrefix + ":" + listenerIp.String())
    72  	BROADCAST_IPv4 := reverseMask(listenerIp, listenerIpMask)
    73  	if listenerIpMask == nil {
    74  		log.Printf("ERROR: listener-ip: %v; listener-ip-mask: %v; could not calculate broadcast address", listenerIp, listenerIpMask)
    75  		return
    76  	}
    77  	socket, err := net.DialUDP("udp4", nil, &net.UDPAddr{
    78  		IP:   BROADCAST_IPv4,
    79  		Port: 9967,
    80  	})
    81  	if err != nil {
    82  		log.Printf(fmt.Sprintf("ERROR: broadcast-ip: %v; failed to dial udp broadcast connection", BROADCAST_IPv4))
    83  		return
    84  	}
    85  	go func() {
    86  		log.Printf("broadcasting...")
    87  		for {
    88  			_, err = socket.Write(info)
    89  			if err != nil {
    90  				log.Printf("ERROR: broadcast-ip: %v; failed writing to broadcast udp socket: "+err.Error(), BROADCAST_IPv4)
    91  				return
    92  			}
    93  			time.Sleep(5000 * time.Millisecond)
    94  		}
    95  	}()
    96  	m := http.NewServeMux()
    97  	m.HandleFunc("/register", func(res http.ResponseWriter, req *http.Request) {
    98  		if req.Method != "POST" {
    99  			res.WriteHeader(http.StatusNotFound)
   100  			return
   101  		}
   102  		splitAddr := strings.Split(req.RemoteAddr, ":")
   103  		if len(splitAddr) < 1 {
   104  			log.Printf("req.RemoteAddr: %v, could not parse remote addr into ip/port combination", req.RemoteAddr)
   105  			return
   106  		}
   107  		instanceIp := splitAddr[0]
   108  		macAddress := req.URL.Query().Get("mac_address")
   109  		log.Printf("Instance registered")
   110  		log.Printf("ip: %v", instanceIp)
   111  		log.Printf("ip: %v", macAddress)
   112  		//mac address = the instance id in vsphere/vbox
   113  		go func() {
   114  			ipMapLock.Lock()
   115  			defer ipMapLock.Unlock()
   116  			s.MacIpMap[macAddress] = instanceIp
   117  			go save(s, saveLock)
   118  		}()
   119  		envMapLock.RLock()
   120  		defer envMapLock.RUnlock()
   121  		env, ok := s.MacEnvMap[macAddress]
   122  		if !ok {
   123  			env = make(map[string]string)
   124  			log.Printf("mac: %v", macAddress)
   125  			log.Printf("env: %v", s.MacEnvMap)
   126  			log.Printf("no env set for instance, replying with empty map")
   127  		}
   128  		data, err := json.Marshal(env)
   129  		if err != nil {
   130  			log.Printf("could not marshal env to json: " + err.Error())
   131  			return
   132  		}
   133  		log.Printf("responding with data: %s", data)
   134  		fmt.Fprintf(res, "%s", data)
   135  	})
   136  	m.HandleFunc("/set_instance_env", func(res http.ResponseWriter, req *http.Request) {
   137  		if req.Method != "POST" {
   138  			res.WriteHeader(http.StatusNotFound)
   139  			return
   140  		}
   141  		macAddress := req.URL.Query().Get("mac_address")
   142  		data, err := ioutil.ReadAll(req.Body)
   143  		if err != nil {
   144  			res.WriteHeader(http.StatusInternalServerError)
   145  			res.Write([]byte(err.Error()))
   146  			return
   147  		}
   148  		defer req.Body.Close()
   149  		var env map[string]string
   150  		if err := json.Unmarshal(data, &env); err != nil {
   151  			res.WriteHeader(http.StatusInternalServerError)
   152  			res.Write([]byte(err.Error()))
   153  			return
   154  		}
   155  		log.Printf("Env set for instance")
   156  		log.Printf("mac: %v", macAddress)
   157  		log.Printf("env: %v", env)
   158  		envMapLock.Lock()
   159  		defer envMapLock.Unlock()
   160  		s.MacEnvMap[macAddress] = env
   161  		go save(s, saveLock)
   162  		res.WriteHeader(http.StatusAccepted)
   163  	})
   164  	m.HandleFunc("/instances", func(res http.ResponseWriter, req *http.Request) {
   165  		if req.Method != "GET" {
   166  			res.WriteHeader(http.StatusNotFound)
   167  			return
   168  		}
   169  		ipMapLock.RLock()
   170  		defer ipMapLock.RUnlock()
   171  		data, err := json.Marshal(s.MacIpMap)
   172  		if err != nil {
   173  			res.WriteHeader(http.StatusInternalServerError)
   174  			res.Write([]byte(err.Error()))
   175  		}
   176  		res.Write(data)
   177  	})
   178  	log.Printf("listening on port 3000")
   179  	http.ListenAndServe(":3000", m)
   180  }
   181  
   182  func getLocalIp() (net.IP, net.IPMask, error) {
   183  	ifaces, err := net.Interfaces()
   184  	if err != nil {
   185  		return net.IP{}, net.IPMask{}, errors.New("retrieving network interfaces" + err.Error())
   186  	}
   187  	for _, iface := range ifaces {
   188  		log.Printf("found an interface: %v\n", iface)
   189  		addrs, err := iface.Addrs()
   190  		if err != nil {
   191  			continue
   192  		}
   193  		for _, addr := range addrs {
   194  			log.Printf("inspecting address: %v", addr)
   195  			switch v := addr.(type) {
   196  			case *net.IPNet:
   197  				if !v.IP.IsLoopback() && v.IP.IsGlobalUnicast() && v.IP.To4() != nil && v.Mask != nil {
   198  					return v.IP.To4(),v.Mask,  nil
   199  				}
   200  			}
   201  		}
   202  	}
   203  	return net.IP{}, net.IPMask{}, errors.New("failed to find ip on ifaces: " + fmt.Sprintf("%v", ifaces))
   204  }
   205  
   206  // ReverseMask returns the result of masking the IP address ip with mask.
   207  func reverseMask(ip net.IP, mask net.IPMask) net.IP {
   208  	n := len(ip)
   209  	if n != len(mask) {
   210  		return nil
   211  	}
   212  	out := make(net.IP, n)
   213  	for i := 0; i < n; i++ {
   214  		out[i] = ip[i] | (^mask[i])
   215  	}
   216  	return out
   217  }
   218  
   219  func save(s state, l sync.Mutex) {
   220  	if err := func() error {
   221  		l.Lock()
   222  		defer l.Unlock()
   223  		data, err := json.Marshal(s)
   224  		if err != nil {
   225  			return err
   226  		}
   227  		if err := ioutil.WriteFile(statefile, data, 0644); err != nil {
   228  			return err
   229  		}
   230  		return nil
   231  	}(); err != nil {
   232  		log.Printf("failed to save state file %s", statefile)
   233  	}
   234  }