├── libreg ├── README.md ├── libreg.go └── registry │ ├── mock │ └── mock.go │ ├── registry.go │ └── consul │ └── consul.go ├── rackhd ├── Dockerfile-rackhd ├── Dockerfile-endpoint ├── models │ ├── models_suite_test.go │ ├── db.go │ ├── nodes.go │ ├── rackhd.go │ └── rackhd_test.go ├── watcher │ ├── watcher_suite_test.go │ ├── watcher.go │ └── watcher_test.go ├── proxy │ ├── helper.go │ └── controller.go ├── cmd │ ├── utils │ │ ├── main.go │ │ └── api │ │ │ └── api.go │ └── rackhd │ │ └── main.go ├── docker-compose-rackhd.yaml └── README.md ├── registry ├── Dockerfile-rackhd ├── Dockerfile-registry ├── Dockerfile-ssdp ├── registry_suite_test.go ├── cmd │ ├── rackHDSpoofer │ │ └── main.go │ ├── ssdpspoofer │ │ ├── ssdpSpoof.json │ │ └── main.go │ └── registry │ │ └── main.go ├── registry.json ├── utils │ ├── ssdpSpoofer │ │ ├── main.go.bak │ │ └── ssdpSpoof.json │ └── ClearConsul │ │ └── main.go.bak ├── docker-compose-registry.yaml ├── registry.go ├── README.md ├── registry_test.go └── LICENSE ├── .gitignore ├── glide.yaml ├── CONTRIBUTING.md ├── .travis.yml ├── README.md ├── glide.lock ├── Makefile └── LICENSE /libreg/README.md: -------------------------------------------------------------------------------- 1 | # libreg 2 | Microservice Registry Abstraction Library 3 | -------------------------------------------------------------------------------- /rackhd/Dockerfile-rackhd: -------------------------------------------------------------------------------- 1 | FROM rackhd/golang:1.7.0-wheezy 2 | 3 | ADD ./bin/rackhd /go/bin/rackhd 4 | 5 | ENTRYPOINT ["/go/bin/rackhd"] 6 | -------------------------------------------------------------------------------- /rackhd/Dockerfile-endpoint: -------------------------------------------------------------------------------- 1 | FROM rackhd/golang:1.7.0-wheezy 2 | 3 | ADD ./bin/endpoint /go/bin/endpoint 4 | 5 | ENTRYPOINT ["/go/bin/endpoint"] 6 | -------------------------------------------------------------------------------- /registry/Dockerfile-rackhd: -------------------------------------------------------------------------------- 1 | FROM debian:wheezy 2 | 3 | ADD ./bin/rackhdspoofer /bin/rackhdspoofer 4 | 5 | ENTRYPOINT ["/bin/rackhdspoofer"] 6 | -------------------------------------------------------------------------------- /registry/Dockerfile-registry: -------------------------------------------------------------------------------- 1 | FROM rackhd/golang:1.7.0-wheezy 2 | 3 | ADD ./bin/registry /go/bin/registry 4 | ADD ./registry.json /go/bin/registry.json 5 | 6 | ENTRYPOINT ["/go/bin/registry"] 7 | -------------------------------------------------------------------------------- /registry/Dockerfile-ssdp: -------------------------------------------------------------------------------- 1 | FROM debian:wheezy 2 | 3 | ADD ./bin/ssdpspoofer /bin/ssdpspoofer 4 | ADD ./cmd/ssdpspoofer/ssdpSpoof.json /bin/ssdpSpoof.json 5 | 6 | ENTRYPOINT ["/bin/ssdpspoofer"] 7 | -------------------------------------------------------------------------------- /rackhd/models/models_suite_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestModels(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Models Suite") 13 | } 14 | -------------------------------------------------------------------------------- /rackhd/watcher/watcher_suite_test.go: -------------------------------------------------------------------------------- 1 | package watcher_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestWatcher(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Watcher Suite") 13 | } 14 | -------------------------------------------------------------------------------- /registry/registry_suite_test.go: -------------------------------------------------------------------------------- 1 | package registry_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestRegistry(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Registry Suite") 13 | } 14 | -------------------------------------------------------------------------------- /registry/cmd/rackHDSpoofer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "gopkg.in/gin-gonic/gin.v1" 8 | ) 9 | 10 | func main() { 11 | r := gin.Default() 12 | r.GET("/api/2.0/nodes", func(c *gin.Context) { 13 | c.String(http.StatusOK, "") 14 | }) 15 | if err := r.Run("0.0.0.0:8080"); err != nil { 16 | log.Fatalln(err) 17 | } // listen and serve on addr:port 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # OSX 7 | *.DS_Store 8 | 9 | # Folders 10 | _obj 11 | _test 12 | 13 | # Architecture specific extensions/prefixes 14 | *.[568vq] 15 | [568vq].out 16 | 17 | *.cgo1.go 18 | *.cgo2.c 19 | _cgo_defun.c 20 | _cgo_gotypes.go 21 | _cgo_export.* 22 | _testmain.go 23 | 24 | *.exe 25 | *.test 26 | *.prof 27 | *.coverprofile 28 | *.html 29 | 30 | bin 31 | vendor 32 | .glide 33 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/RackHD/neighborhood-manager 2 | import: 3 | - package: github.com/docker/libkv 4 | version: ~0.2.1 5 | subpackages: 6 | - store 7 | - store/consul 8 | - package: github.com/hashicorp/consul 9 | version: ~0.7.0 10 | subpackages: 11 | - api 12 | - package: github.com/hashicorp/go-cleanhttp 13 | - package: github.com/king-jam/gossdp 14 | - package: github.com/spf13/viper 15 | - package: github.com/streadway/amqp 16 | testImport: 17 | - package: github.com/onsi/ginkgo 18 | version: ~1.2.0 19 | - package: github.com/onsi/gomega 20 | version: ~1.0.0 21 | -------------------------------------------------------------------------------- /rackhd/models/db.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docker/libkv" 6 | "github.com/docker/libkv/store" 7 | "github.com/docker/libkv/store/consul" 8 | "time" 9 | ) 10 | 11 | var db store.Store 12 | 13 | func init() { 14 | consul.Register() 15 | } 16 | 17 | // InitBackend creates the default backend capability 18 | func InitBackend() { 19 | client := "localhost:8500" 20 | 21 | var err error 22 | db, err = libkv.NewStore( 23 | store.CONSUL, 24 | []string{client}, 25 | &store.Config{ 26 | ConnectionTimeout: 10 * time.Second, 27 | }, 28 | ) 29 | if err != nil { 30 | fmt.Printf("Failed to init DB!\n") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /registry/cmd/ssdpspoofer/ssdpSpoof.json: -------------------------------------------------------------------------------- 1 | { 2 | "ssdp":{ 3 | "Source1":{ 4 | "broadcastIP":[ 5 | "rackhdspoofer:8080" 6 | ], 7 | "urnList":[ 8 | "urn:schemas-upnp-org:service:agent:0.1", 9 | "urn:schemas-upnp-org:service:lldp:0.1", 10 | "urn:schemas-upnp-org:service:catalog-compute:0.1", 11 | "urn:dmtf-org:service:redfish-rest:1.0", 12 | "urn:schemas-upnp-org:service:api:1.1", 13 | "urn:schemas-upnp-org:service:api:2.0", 14 | "urn:schemas-upnp-org:device:on-http:1" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /registry/registry.json: -------------------------------------------------------------------------------- 1 | { 2 | "ssdp":{ 3 | "tags":{ 4 | "Inservice":[ 5 | "urn:schemas-upnp-org:service:agent:0.1", 6 | "urn:schemas-upnp-org:service:lldp:0.1", 7 | "urn:schemas-upnp-org:service:catalog-compute:0.1" 8 | ], 9 | "Inservice-agent":[ 10 | "urn:skunkworxs:inservice:agent:0" 11 | ], 12 | "RackHD":[ 13 | "urn:dmtf-org:service:redfish-rest:1.0", 14 | "urn:schemas-upnp-org:service:api:1.1", 15 | "urn:schemas-upnp-org:service:api:2.0", 16 | "urn:schemas-upnp-org:device:on-http:1" 17 | ] 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Neighborhood Manager Contribution Guidelines 2 | ============================================ 3 | 4 | ## Dependency Management 5 | ### Adding a library dependency 6 | When a new library is used for the first time, it must be added to Glide. Open the file `glide.yaml` and add a new line under the `import` heading: 7 | ``` 8 | import: 9 | - package: github.com/king-jam/gossdp 10 | ``` 11 | replacing the example GitHub link with the path that `go get` needs to grab the library. 12 | 13 | When this is done, save `glide.yaml` and run `glide update` to copy the dependent source code to the `vendor/` folder. 14 | 15 | After this, the steps in the next section should be done to lock in the specific version of the dependent library. 16 | 17 | ### Updating a library dependency 18 | When a dependent library has an updated commit that is desired (such as a big fix, added feature, etc), the library's entry in `glide.lock` should be updated. Open the file and find its entry, such as 19 | ``` 20 | - name: github.com/hashicorp/consul 21 | version: 36dc9201f2e006d4b5db1f0446b17357811297bf 22 | ``` 23 | Replace the existing commit hash with the hash of the new desired commit. 24 | Save and close the file, then run `glide install` 25 | -------------------------------------------------------------------------------- /libreg/libreg.go: -------------------------------------------------------------------------------- 1 | package libreg 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "github.com/RackHD/neighborhood-manager/libreg/registry" 9 | ) 10 | 11 | // Initialize creates a new Store object, initializing the client 12 | type Initialize func(addrs []string, options *registry.Config) (registry.Registry, error) 13 | 14 | var ( 15 | // Backend initializers 16 | initializers = make(map[registry.Backend]Initialize) 17 | 18 | supportedBackend = func() string { 19 | keys := make([]string, 0, len(initializers)) 20 | for k := range initializers { 21 | keys = append(keys, string(k)) 22 | } 23 | sort.Strings(keys) 24 | return strings.Join(keys, ", ") 25 | }() 26 | ) 27 | 28 | // NewRegistry creates an instance of registry 29 | func NewRegistry(backend registry.Backend, addrs []string, options *registry.Config) (registry.Registry, error) { 30 | if init, exists := initializers[backend]; exists { 31 | return init(addrs, options) 32 | } 33 | 34 | return nil, fmt.Errorf("%s %s", registry.ErrBackendNotSupported.Error(), supportedBackend) 35 | } 36 | 37 | // AddRegistry adds a new registry backend to libreg 38 | func AddRegistry(backend registry.Backend, init Initialize) { 39 | initializers[backend] = init 40 | } 41 | -------------------------------------------------------------------------------- /registry/cmd/ssdpspoofer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/king-jam/gossdp" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | func main() { 17 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 18 | if err != nil { 19 | log.Fatalf("%s\n", err) 20 | } 21 | viper.SetConfigName("ssdpSpoof") 22 | viper.SetConfigType("json") 23 | viper.AddConfigPath(dir) 24 | viper.AddConfigPath("$HOME") 25 | viper.AddConfigPath(".") 26 | 27 | err = viper.ReadInConfig() 28 | if err != nil { 29 | log.Fatalf("SSDP Spoofer Configuration Error: %s\n", err) 30 | } 31 | 32 | log.Printf("SSDP Spoofer Configuration: %s\n", viper.ConfigFileUsed()) 33 | 34 | ssdp, err := gossdp.NewSsdp(nil) 35 | if err != nil { 36 | log.Fatalf("Error creating SSDP Server: %s\n", err) 37 | } 38 | 39 | rGen := rand.New(rand.NewSource(time.Now().UnixNano())) 40 | nodes := viper.GetStringMapString("ssdp") 41 | for node := range nodes { 42 | IPs := viper.GetStringSlice("ssdp." + node + ".broadcastIP") 43 | urns := viper.GetStringSlice("ssdp." + node + ".urnList") 44 | ip := IPs[0] 45 | uuid := strconv.Itoa(rGen.Int()) 46 | log.Println(ip) 47 | if node == "NOUUID" { 48 | uuid = "" 49 | } 50 | 51 | for _, urn := range urns { 52 | server := gossdp.AdvertisableServer{ 53 | ServiceType: urn, 54 | DeviceUuid: uuid, 55 | Location: fmt.Sprintf("%s%s%s", "http://", ip, "/fakepath"), 56 | MaxAge: 30, 57 | } 58 | ssdp.AdvertiseServer(server) 59 | 60 | } 61 | } 62 | 63 | defer ssdp.Stop() 64 | ssdp.Start() 65 | } 66 | -------------------------------------------------------------------------------- /rackhd/proxy/helper.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | // Response is the internal proxy response object 11 | type Response struct { 12 | Header http.Header 13 | StatusCode int 14 | Body []byte 15 | RequestURL string 16 | Error error 17 | } 18 | 19 | // NewResponse copies a http.Response into a proxy Response 20 | func NewResponse(resp *http.Response) (*Response, error) { 21 | body, err := ioutil.ReadAll(resp.Body) 22 | if err != nil { 23 | log.Printf("Error reading Response.Body %s\n", err) 24 | return nil, err 25 | } 26 | proxyResponse := &Response{ 27 | Header: resp.Header, 28 | StatusCode: resp.StatusCode, 29 | Body: body, 30 | RequestURL: resp.Request.URL.String(), 31 | Error: nil, 32 | } 33 | return proxyResponse, err 34 | } 35 | 36 | // NewResponseFromError sets errors 37 | func NewResponseFromError(err error) *Response { 38 | proxyRespnse := &Response{ 39 | StatusCode: 500, 40 | Error: err, 41 | } 42 | return proxyRespnse 43 | } 44 | 45 | // NewRequest copies a http.Request & Header and sets the new host 46 | func NewRequest(r *http.Request, host string) (*http.Request, error) { 47 | buff, err := ioutil.ReadAll(r.Body) 48 | if err != nil { 49 | log.Printf("Error reading Request.Body %s\n", err) 50 | return nil, err 51 | } 52 | reader := bytes.NewReader(buff) 53 | req, err := http.NewRequest(r.Method, "http://"+host+r.URL.Path, reader) 54 | if err != nil { 55 | return nil, err 56 | } 57 | for k, v := range r.Header { 58 | for _, value := range v { 59 | req.Header.Set(k, value) 60 | } 61 | } 62 | return req, nil 63 | } 64 | -------------------------------------------------------------------------------- /registry/utils/ssdpSpoofer/main.go.bak: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/king-jam/gossdp" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | func main() { 17 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 18 | viper.SetConfigName("ssdpSpoof") 19 | viper.SetConfigType("json") 20 | viper.AddConfigPath(dir) 21 | viper.AddConfigPath("$GOPATH/bin") 22 | viper.AddConfigPath("$HOME") 23 | viper.AddConfigPath(".") 24 | 25 | err = viper.ReadInConfig() 26 | if err != nil { 27 | log.Fatalf("SSDP Spoofer Configuration Error: %s\n", err) 28 | } 29 | 30 | log.Printf("SSDP Spoofer Configuration: %s\n", viper.ConfigFileUsed()) 31 | 32 | ssdp, err := gossdp.NewSsdp(nil) 33 | if err != nil { 34 | log.Fatalf("Error creating SSDP Server: %s\n", err) 35 | } 36 | 37 | rGen := rand.New(rand.NewSource(time.Now().UnixNano())) 38 | nodes := viper.GetStringMapString("ssdp") 39 | go ssdp.Start() 40 | for node := range nodes { 41 | IPs := viper.GetStringSlice("ssdp." + node + ".broadcastIP") 42 | urns := viper.GetStringSlice("ssdp." + node + ".urnList") 43 | ip := IPs[0] 44 | uuid := strconv.Itoa(rGen.Int()) 45 | log.Println(ip) 46 | if node == "NOUUID" { 47 | uuid = "" 48 | } 49 | 50 | for _, urn := range urns { 51 | //Host SSDP Server for advertising Agent/Plugin capabilities. 52 | server := gossdp.AdvertisableServer{ 53 | ServiceType: urn, 54 | DeviceUuid: uuid, 55 | Location: fmt.Sprintf("%s%s%s", "http://", ip, "/fakepath"), 56 | MaxAge: 30, 57 | } 58 | ssdp.AdvertiseServer(server) 59 | 60 | } 61 | } 62 | 63 | time.Sleep(100 * time.Second) 64 | ssdp.Stop() 65 | } 66 | -------------------------------------------------------------------------------- /registry/utils/ssdpSpoofer/ssdpSpoof.json: -------------------------------------------------------------------------------- 1 | { 2 | "ssdp":{ 3 | "Source1":{ 4 | "broadcastIP":[ 5 | "10.240.16.90:65535" 6 | ], 7 | "urnList":[ 8 | "urn:schemas-upnp-org:service:agent:0.1", 9 | "urn:schemas-upnp-org:service:lldp:0.1", 10 | "urn:schemas-upnp-org:service:catalog-compute:0.1", 11 | "urn:dmtf-org:service:redfish-rest:1.0", 12 | "urn:schemas-upnp-org:service:api:1.1", 13 | "urn:schemas-upnp-org:service:api:2.0", 14 | "urn:schemas-upnp-org:device:on-http:1" 15 | ] 16 | }, 17 | "Source2":{ 18 | "broadcastIP":[ 19 | "10.240.169165535" 20 | ], 21 | "urnList":[ 22 | "urn:dmtf-org:service:redfish-rest:1.0", 23 | "urn:schemas-upnp-org:service:agent:0.1", 24 | "urn:schemas-upnp-org:service:api:1.1", 25 | "urn:schemas-upnp-org:service:api:2.0", 26 | "urn:schemas-upnp-org:device:on-http:1" 27 | ] 28 | }, 29 | "NOUUID":{ 30 | "broadcastIP":[ 31 | "10.240.16.91:65535" 32 | ], 33 | "urnList":[ 34 | "urn:schemas-upnp-org:service:agent:0.1", 35 | "urn:schemas-upnp-org:service:lldp:0.1", 36 | "urn:schemas-upnp-org:service:catalog-compute:0.1" 37 | ] 38 | }, 39 | "Source3":{ 40 | "broadcastIP":[ 41 | "10.240.16.91:port" 42 | ], 43 | "urnList":[ 44 | "urn:schemas-upnp-org:service:agent:0.1", 45 | "urn:schemas-upnp-org:service:lldp:0.1", 46 | "urn:schemas-upnp-org:service:catalog-compute:0.1" 47 | ] 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rackhd/watcher/watcher.go: -------------------------------------------------------------------------------- 1 | package watcher 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/RackHD/neighborhood-manager/libreg" 8 | regStore "github.com/RackHD/neighborhood-manager/libreg/registry" 9 | ) 10 | 11 | // Monitor is a struct to initialize a BackendStore 12 | type Monitor struct { 13 | Store regStore.Registry 14 | Datacenter string 15 | ServiceName string 16 | } 17 | 18 | // NewMonitor initializes a new Monitor object 19 | // creates backend store (CONSUL, ETCD, etc) 20 | func NewMonitor(serviceName, datacenter, backendAddr string, backend regStore.Backend) (*Monitor, error) { 21 | n := &Monitor{} 22 | r, err := libreg.NewRegistry(backend, []string{backendAddr}, nil) 23 | if err != nil { 24 | log.Printf("Error creating backend store: %s\n", err) 25 | return nil, err 26 | } 27 | n.Store = r 28 | n.Datacenter = datacenter 29 | n.ServiceName = serviceName 30 | return n, err 31 | } 32 | 33 | // GetAddresses calls GetService and passes our desired ServiceName. 34 | // It then creates a map with the (ip:port)'s retrieved from the GetService call 35 | func (m *Monitor) GetAddresses() (map[string]struct{}, error) { 36 | service, err := m.GetService(m.ServiceName) 37 | if err != nil { 38 | log.Printf("Error fetching %s catalog entries ==> %s\n", m.ServiceName, err) 39 | return nil, err 40 | } 41 | addresses := make(map[string]struct{}) 42 | for _, entry := range service { 43 | addr := fmt.Sprintf("%s:%d", entry.ServiceAddress, entry.ServicePort) 44 | addresses[addr] = struct{}{} 45 | } 46 | return addresses, nil 47 | } 48 | 49 | // GetService fetches catalog entries for the given serviceName 50 | func (m *Monitor) GetService(serviceName string) ([]*regStore.CatalogService, error) { 51 | entries, err := m.Store.Service(serviceName, "", nil) 52 | if err != nil { 53 | log.Printf("Error fetching %s ==> %s\n", serviceName, err) 54 | return nil, err 55 | } 56 | return entries, err 57 | } 58 | -------------------------------------------------------------------------------- /rackhd/cmd/utils/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "net/url" 9 | "strconv" 10 | 11 | "github.com/RackHD/neighborhood-manager/libreg/registry" 12 | "github.com/RackHD/neighborhood-manager/rackhd/cmd/utils/api" 13 | ) 14 | 15 | var binaryName, buildDate, buildUser, commitHash, goVersion, osArch, releaseVersion string 16 | var backendAddr, endpointAddr, serviceName, datacenter string 17 | 18 | // init takes in configurable flags 19 | func init() { 20 | flag.StringVar(&backendAddr, "backend-address", "127.0.0.1:8500", "address:port of the backend store") 21 | flag.StringVar(&endpointAddr, "endpoint-address", "http://0.0.0.0:10002", "http://address:port of the endpoint server") 22 | flag.StringVar(&serviceName, "service-name", "RackHD-service:api:2.0:TEST", "The service being spoofed") 23 | flag.StringVar(&datacenter, "datacenter", "dc1", "The consul datacenter string") 24 | } 25 | 26 | // extractIPPort splits the Address flag into an ip string anf port int 27 | func extractIPPort(location string) (string, int, error) { 28 | addr, err := url.Parse(location) 29 | if err != nil { 30 | return "", 0, err 31 | } 32 | 33 | agentIP, portStr, err := net.SplitHostPort(addr.Host) 34 | if err != nil { 35 | return "", 0, err 36 | } 37 | 38 | agentPort, err := strconv.Atoi(portStr) 39 | if err != nil { 40 | return "", 0, err 41 | } 42 | 43 | return agentIP, agentPort, nil 44 | } 45 | 46 | // Main 47 | func main() { 48 | 49 | flag.Parse() 50 | 51 | // Parse proxyAddr 52 | endpointIP, endpointPort, err := extractIPPort(endpointAddr) 53 | if err != nil { 54 | log.Fatalf("Error parsing endpoint address: %s\n", err) 55 | } 56 | 57 | // Proxy server configuration 58 | h, err := api.NewServer(endpointIP, serviceName, datacenter, backendAddr, registry.CONSUL, endpointPort) 59 | if err != nil { 60 | log.Fatalf("Endpoint server configuration failed: %s\n", err) 61 | } 62 | 63 | h.Register(datacenter, serviceName) 64 | 65 | fmt.Printf("Endpoint is served on => %s:%d\n", h.Address, h.Port) 66 | 67 | // Start the handler server for the proxy endpoint 68 | h.Serve() 69 | } 70 | -------------------------------------------------------------------------------- /registry/docker-compose-registry.yaml: -------------------------------------------------------------------------------- 1 | #Service Registry 2 | 3 | version: "2" 4 | 5 | services: 6 | consulserver: 7 | image: "rackhd/consul:server" 8 | container_name: "consulserver" 9 | hostname: "consulserver" 10 | ports: 11 | - "8300:8300" 12 | - "8301:8301" 13 | - "8080:8080" 14 | - "8301:8301/udp" 15 | - "8302:8302" 16 | - "8302:8302/udp" 17 | - "8400:8400" 18 | - "8500:8500" 19 | - "8600:8600" 20 | - "8600:8600/udp" 21 | command: "agent -config-dir /etc/consul.d/server.json -bootstrap" 22 | registry: 23 | build: 24 | context: . 25 | dockerfile: Dockerfile-registry 26 | image: "rackhd/registry:latest" 27 | container_name: "registry" 28 | hostname: "registry" 29 | links: 30 | - consulclient 31 | expose: 32 | - "1900" 33 | - "8080" 34 | - "1900/udp" 35 | - "8300" 36 | - "8301" 37 | - "8301/udp" 38 | - "8302" 39 | - "8302/udp" 40 | - "8400" 41 | - "8500" 42 | - "8500/udp" 43 | - "8600" 44 | - "8600/udp" 45 | command: "-address=consulclient:8500 -datacenter=dc-docker" 46 | depends_on: 47 | - consulclient 48 | ssdpspoofer: 49 | build: 50 | context: . 51 | dockerfile: Dockerfile-ssdp 52 | image: "rackhd/ssdpspoofer:latest" 53 | container_name: "ssdpspoofer" 54 | hostname: "ssdpspoofer" 55 | expose: 56 | - "1900" 57 | - "1900/udp" 58 | depends_on: 59 | - consulserver 60 | rackhdspoofer: 61 | build: 62 | context: . 63 | dockerfile: Dockerfile-rackhd 64 | image: "rackhd/rackhdspoofer:latest" 65 | container_name: "rackhdspoofer" 66 | hostname: "rackhdspoofer" 67 | expose: 68 | - "8080" 69 | depends_on: 70 | - consulserver 71 | consulclient: 72 | image: "rackhd/consul:client" 73 | container_name: "consulclient" 74 | hostname: "consulclient" 75 | expose: 76 | - "8300" 77 | - "8080" 78 | - "8301" 79 | - "8301/udp" 80 | - "8302" 81 | - "8302/udp" 82 | - "8400" 83 | - "8500" 84 | - "8500/udp" 85 | - "8600" 86 | - "8600/udp" 87 | depends_on: 88 | - consulserver 89 | -------------------------------------------------------------------------------- /registry/cmd/registry/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | 9 | libreg "github.com/RackHD/neighborhood-manager/libreg/registry" 10 | "github.com/RackHD/neighborhood-manager/libreg/registry/consul" 11 | "github.com/RackHD/neighborhood-manager/registry" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | var binaryName, buildDate, buildUser, commitHash, goVersion, osArch, releaseVersion string 16 | var backendAddr, datacenter string 17 | 18 | func init() { 19 | flag.StringVar(&backendAddr, "address", "127.0.0.1:8500", "Address:port of the backend store") 20 | flag.StringVar(&datacenter, "datacenter", "dc1", "Datacenter name of the backend storage") 21 | } 22 | 23 | func main() { 24 | log.Println(binaryName) 25 | log.Println(" Release version: " + releaseVersion) 26 | log.Println(" Built On: " + buildDate) 27 | log.Println(" Build By: " + buildUser) 28 | log.Println(" Commit Hash: " + commitHash) 29 | log.Println(" Go version: " + goVersion) 30 | log.Println(" OS/Arch: " + osArch) 31 | 32 | flag.Parse() 33 | 34 | consul.Register() 35 | registry, err := registry.NewRegistry(libreg.CONSUL, datacenter, backendAddr) 36 | if err != nil { 37 | log.Fatalf("Error creating new Service Registry: %s", err) 38 | } 39 | 40 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 41 | if err != nil { 42 | log.Fatalf("Service Registry Configuration Error: %s\n", err) 43 | } 44 | 45 | // Set viper configurations 46 | viper.SetConfigName("registry") 47 | viper.SetConfigType("json") 48 | viper.AddConfigPath("/etc/infranm.d") 49 | viper.AddConfigPath(dir) 50 | viper.AddConfigPath("$GOPATH/bin") 51 | viper.AddConfigPath("$HOME") 52 | 53 | err = viper.ReadInConfig() 54 | if err != nil { 55 | log.Fatalf("Service Registry Configuration Error: %s\n", err) 56 | } 57 | 58 | log.Printf("Service Registry Configuration: %s\n", viper.ConfigFileUsed()) 59 | 60 | // Parse all urns in config file and add to whitelist of search terms 61 | tags := viper.GetStringMapString("ssdp.tags") 62 | for tag := range tags { 63 | urns := viper.GetStringSlice("ssdp.tags." + tag) 64 | for _, urn := range urns { 65 | registry.AddSearchTerm(urn, tag) 66 | } 67 | } 68 | 69 | registry.Run() 70 | 71 | return 72 | } 73 | -------------------------------------------------------------------------------- /rackhd/cmd/rackhd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "net/url" 9 | "strconv" 10 | 11 | "github.com/RackHD/neighborhood-manager/libreg/registry" 12 | "github.com/RackHD/neighborhood-manager/rackhd/proxy" 13 | ) 14 | 15 | var binaryName, buildDate, buildUser, commitHash, goVersion, osArch, releaseVersion string 16 | var backendAddr, proxyAddr, serviceName, datacenter string 17 | 18 | // init takes in configurable flags 19 | func init() { 20 | flag.StringVar(&backendAddr, "backend-address", "127.0.0.1:8500", "address:port of the backend store") 21 | flag.StringVar(&proxyAddr, "proxy-address", "http://0.0.0.0:10001", "http://address:port of the proxy server") 22 | flag.StringVar(&serviceName, "service-name", "RackHD-service:api:2.0", "The service being proxied") 23 | flag.StringVar(&datacenter, "datacenter", "dc1", "The consul datacenter string") 24 | } 25 | 26 | // extractIPPort splits the Address flag into an ip string anf port int 27 | func extractIPPort(location string) (string, int, error) { 28 | addr, err := url.Parse(location) 29 | if err != nil { 30 | return "", 0, err 31 | } 32 | 33 | agentIP, portStr, err := net.SplitHostPort(addr.Host) 34 | if err != nil { 35 | return "", 0, err 36 | } 37 | 38 | agentPort, err := strconv.Atoi(portStr) 39 | if err != nil { 40 | return "", 0, err 41 | } 42 | 43 | return agentIP, agentPort, nil 44 | } 45 | 46 | // Main 47 | func main() { 48 | 49 | log.Println(binaryName) 50 | log.Println(" Release version: " + releaseVersion) 51 | log.Println(" Built On: " + buildDate) 52 | log.Println(" Build By: " + buildUser) 53 | log.Println(" Commit Hash: " + commitHash) 54 | log.Println(" Go version: " + goVersion) 55 | log.Println(" OS/Arch: " + osArch) 56 | 57 | flag.Parse() 58 | 59 | // Parse proxyAddr 60 | proxyIP, proxyPort, err := extractIPPort(proxyAddr) 61 | if err != nil { 62 | log.Fatalf("Error parsing proxy address: %s\n", err) 63 | } 64 | 65 | // Proxy server configuration 66 | h, err := proxy.NewServer(proxyIP, serviceName, datacenter, backendAddr, registry.CONSUL, proxyPort) 67 | if err != nil { 68 | log.Fatalf("Proxy server configuration failed: %s\n", err) 69 | } 70 | 71 | fmt.Printf("Service name is => %s\n", serviceName) 72 | fmt.Printf("Proxy is served on => %s:%d\n", h.Address, h.Port) 73 | 74 | // Start the handler server for the proxy endpoint 75 | h.Serve() 76 | } 77 | -------------------------------------------------------------------------------- /rackhd/models/nodes.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | const ( 8 | // nodesPrefix is the area under rhd where nodes are stored 9 | nodesPrefix = "nodes/" 10 | ) 11 | 12 | // RhdNode stores relevant cache data about a node 13 | type RhdNode struct { 14 | ID string 15 | RhdID string 16 | } 17 | 18 | // NewRhdNode creates a new RhdNode object for storage 19 | func NewRhdNode(rhdID string, nodeID string) (*RhdNode, error) { 20 | return &RhdNode{ 21 | RhdID: rhdID, 22 | ID: nodeID, 23 | }, nil 24 | } 25 | 26 | // CreateNode allows creating a node on the backend 27 | func CreateNode(node *RhdNode) error { 28 | return UpdateNode(node) 29 | } 30 | 31 | // UpdateNode updates a RhdNode on the backend 32 | func UpdateNode(node *RhdNode) error { 33 | nodePath := rhdPrefix + node.RhdID + "/" + nodesPrefix + node.ID 34 | if err := CreateNodeCache(node); err != nil { 35 | return err 36 | } 37 | b, err := json.Marshal(node) 38 | if err != nil { 39 | return err 40 | } 41 | return db.Put(nodePath, b, nil) 42 | } 43 | 44 | // GetAllNodes is currently stubbed out but unsupported 45 | func GetAllNodes() ([]*RhdNode, error) { 46 | var nodes []*RhdNode 47 | return nodes, nil 48 | } 49 | 50 | // GetAllNodesByRhdID is currently stubbed out but unsupported 51 | func GetAllNodesByRhdID(id string) ([]*RhdNode, error) { 52 | var rhds []*RhdNode 53 | return rhds, nil 54 | } 55 | 56 | // GetNodeByRhdIDByNodeID is currently stubbed out but unsupported 57 | func GetNodeByRhdIDByNodeID(rid string, nid string) (*RhdNode, error) { 58 | return &RhdNode{}, nil 59 | } 60 | 61 | // GetNodesByRhdIDByNodeIDs is currently stubbed out but unsupported 62 | func GetNodesByRhdIDByNodeIDs(rid string, ids []string) ([]*RhdNode, error) { 63 | var nodes []*RhdNode 64 | return nodes, nil 65 | } 66 | 67 | // CreateNodeCache stores the cache layer lookup functionality 68 | func CreateNodeCache(node *RhdNode) error { 69 | nodePath := nodesPrefix + node.ID 70 | return db.Put(nodePath, []byte(node.RhdID), nil) 71 | } 72 | 73 | // GetRhdIDByNodeID returns the RHD ID for a node 74 | func GetRhdIDByNodeID(id string) (string, error) { 75 | nodesPath := nodesPrefix + id 76 | pair, err := db.Get(nodesPath) 77 | return string(pair.Value), err 78 | } 79 | 80 | // DeleteNodeCache removes a cache index 81 | func DeleteNodeCache(id string) error { 82 | nodePath := nodesPrefix + id 83 | return db.DeleteTree(nodePath) 84 | } 85 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | services: 3 | - docker 4 | language: go 5 | go: 6 | - 1.7 7 | env: 8 | matrix: 9 | - TARGET=test-proxy 10 | - TARGET=test-reg 11 | global: 12 | - secure: YjrmJs3//Dsa6G02WeIC+l2rRwMNk7ADQ5WbV0N1WdKMYEz/7rudfFrgkC4kMmjPYLQdvs+Iri9Zva/SU9oEBNC8Jv52kXsYXXwMc1UwutmAyp3jSTZEl/1ffQXU/UlSBOJ7KDrrMvGgn4WirUZ6U98piIyvN3kUSnOVuo2gJzZ9xw/3sqQTnhRcuX57FEKJVXug45RPWVR3pyI0yplVSkCAs1QbDV4B5XkMJyCyxZhqi0I2ljdHQe85fXZb+798h2oxN5WYyc67UO9WcLoWg/F417HMyRHxYgV8lRYZZCEQXZZOy8dScGUjhT6FObyUhJNwYoblAa230CcPhQ4QPmzUYcBp72AJYVpURy5DNz88S+l5sGBej6XQkaP2UX8Ho4gGbTKHG48I8aRPaeqg8KvYVrMV4BLc6HraEHvELNugNTay50XnUcWnqbpOFXqRsHRc8MZN2nB2UhqOA7qQlXonNrwLOUaDRC601bWAhBHSnacOhDyCfzHRyRTNGum/RXPSktNA39aElYos191K7fgwD/V0cx+YlZNmMkx32Pmf3waRkaMT9tJfIDB2YFeTbbP5Xf7hStOmoA20cAk3g1GP5dUbm9utFdZCDj4jG5adqLvklMuU2QmZdmSvdnwPAbaRPx6WDRU00UAlkNnVPKT1VN8kHUjVh9ToClBFT3Y= 13 | - secure: OYDVEVJOfG2HZZ3m+OkaBEXonl/Pe3QXcdkhFJenmdPoyzCaRehK5YRy/Glog0R48fOMcV4//w3pOhzalP48wJjK+N+uK+C5bsPAjM7xMIEyN26TYuDfFzUzWLdtdN1FYOmL+taGOf6I4ne7fxQrVNCGVBgxvFyX7l5dxyDis19Qm7psm3ueMPikEjUfce3wT4rhWGiEQxymNtDQpYKpO8PDu0jXgROiEUK7mwkmKJYeKRUCordWBuw1Xjclx1/PL4u4arAqz3IDhLk1Hb2tbalgdnPJUUiXa/M5JA5gKMW/K4cQuEiep+iMM71FrIHR7195oTcP7RUvalak7SGXk7WkET0YgHsKrck47KeX3TPLtXk29axJqcSk3FqktqXrUr2OsIBxgZVfRMtiDCVxcH0SaZfMLfQfGAMNnl02M8YX6y6dNWXOJfvxarUpaNEYGPnHCufJ2B6433p4OpxecdKT0sHQFcKEAV9aX2p+iIrVRmuc7MnUlJEX7UwdenS+hYTgV8Xsap86kd08QpP3FEmt6T6VJgOnzyhhT6QBObosQYRxT4hGmy6r6SJDwPvIJKbMEEFrEveFYimZyH02UZP0XE/HZJd9RsV8uCFxQal6VYIKo4uz26uYLyk352XVrm+ilmPBczBGsds2g5GdjZP13qNj7Ek1BDcxO04a2OQ= 14 | before_install: 15 | - echo -e "machine github.com\n login $GITHUB_USER\n password $GITHUB_PASS" >> ~/.netrc 16 | script: 17 | - make deps 18 | - make $TARGET 19 | after_success: 20 | - make coveralls 21 | notifications: 22 | slack: 23 | secure: xz5NZkxMVIRot1u6Zq1BD/Ag1CmfXLWdSJGRXJsiPALuUQnUrtMMJ+kqoJRTcyhuwA6LfZFOkRg1aLqybiVZyY6WW6FhgaPedrsmFXteURG+hadGK64IpJlRzcTnibflmosMSEFhf1k5PMoAj4T6tZD4a5Dt9B1kwAbwMEYIs4EsCZ/WcDv29NDYBl812u8egccC1pgPbXp46HDdHwwocMHs30Oa9SaTlkoq+CdgTsk++8dUhhfciwzL4ivsuE9QwriK6AW1+drxsdq18Dc/oUDRjoY1THf1iGGjDyCqbmEOkzXo2pYeu9feKMS7K5ragS6UDt/lI0aQHGissw6rMdgePQArLAOH2Q8oyZ+rfcwWw5guhShGEv8FwT6h3heQSsHgYCvLi3PI5M//x+C9Gt1GAa+zciRz3AP3GwdMyRHzT3HhYnR3oeu3T64HvMEnZ22efDD3/p8kMZI3CFGNUBKE4ku4r+W0Yvcg6ZDjWLmq5L1KbV+hL7ZEaqIYp2AHFvPg5ijlRQhs7iKVNPr1CVptBjVOEGTb1Kzzmw9B4thpfulajMm6PNkjSXcnoLC5vHYqtd8w3mVrsQYFvweygbAKr1r2suscmQqtmhxi/RTl6+swelEo8HdCvL8Nwi4mrGYXxJbZteNM/Sw98jjVfFCaWK0jIU6UVrwiu0dBEhs= 24 | -------------------------------------------------------------------------------- /registry/utils/ClearConsul/main.go.bak: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/RackHD/neighborhood-manager/libreg" 7 | "github.com/RackHD/neighborhood-manager/libreg/registry" 8 | "github.com/RackHD/neighborhood-manager/libreg/registry/consul" 9 | ) 10 | 11 | func main() { 12 | 13 | consul.Register() 14 | 15 | store, err := libreg.NewRegistry(registry.CONSUL, []string{"localhost:8500"}, nil) 16 | if err != nil { 17 | log.Printf("Could not connect to consul: %s\n", err) 18 | return 19 | } 20 | 21 | deregisterServices(store) 22 | deregisterNodes(store) 23 | } 24 | 25 | func deregisterServices(store registry.Registry) error { 26 | 27 | // Dereg services 28 | services, err := store.Services(nil) 29 | if err != nil { 30 | log.Printf("Could not get services: %s\n", err) 31 | return err 32 | } 33 | 34 | for service := range services { 35 | 36 | // Dont delete the consul service 37 | if service == "consul" { 38 | continue 39 | } 40 | 41 | nodes, err := store.Service(service, "", nil) 42 | if err != nil { 43 | log.Printf("Could not get service nodes: %s\n", err) 44 | return err 45 | } 46 | 47 | for _, node := range nodes { 48 | 49 | err := store.Deregister( 50 | ®istry.CatalogDeregistration{ 51 | Node: node.Node, 52 | Address: node.Address, 53 | Datacenter: "dc1", 54 | ServiceID: node.ServiceID, 55 | CheckID: node.ServiceID, 56 | }, nil) 57 | if err != nil { 58 | log.Printf("Error Deregistering node: %s\n", err) 59 | } 60 | 61 | } 62 | } 63 | 64 | return nil 65 | } 66 | 67 | func deregisterNodes(store registry.Registry) error { 68 | // Dereg Nodes 69 | nodes, err := store.Nodes(nil) 70 | if err != nil { 71 | log.Printf("Could not get nodes: %s\n", err) 72 | return err 73 | } 74 | 75 | for _, node := range nodes { 76 | 77 | // Don't delete consul_server nodes or localhost (local consul client) 78 | if node.Node == "consul_server1" || 79 | node.Node == "consul_server2" || 80 | node.Node == "consul_server3" || 81 | node.Address == "127.0.0.1" { 82 | continue 83 | } 84 | 85 | err := store.Deregister( 86 | ®istry.CatalogDeregistration{ 87 | Node: node.Node, 88 | Address: node.Address, 89 | Datacenter: "dc1", 90 | ServiceID: "", 91 | CheckID: "", 92 | }, nil) 93 | if err != nil { 94 | log.Printf("Error Deregistering node: %s\n", err) 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | -------------------------------------------------------------------------------- /rackhd/docker-compose-rackhd.yaml: -------------------------------------------------------------------------------- 1 | #neighborhood-manager 2 | #rackhd 3 | 4 | version: "2" 5 | 6 | services: 7 | consulserver: 8 | image: "rackhd/consul:server" 9 | container_name: "consulserver" 10 | hostname: "consulserver" 11 | expose: 12 | - "8300" 13 | - "8301" 14 | - "8301/udp" 15 | - "8302" 16 | - "8302/udp" 17 | - "8400" 18 | - "8500" 19 | - "8600" 20 | - "8600/udp" 21 | command: "agent -config-dir /etc/consul.d/server.json -bootstrap" 22 | consulclient: 23 | image: "rackhd/consul:client" 24 | container_name: "consulclient" 25 | hostname: "consulclient" 26 | ports: 27 | - "8500:8500" 28 | expose: 29 | - "8300" 30 | - "8301" 31 | - "8301/udp" 32 | - "8302" 33 | - "8302/udp" 34 | - "8400" 35 | - "8500" 36 | - "8500/udp" 37 | - "8600" 38 | - "8600/udp" 39 | depends_on: 40 | - consulserver 41 | rackhd: 42 | build: 43 | context: . 44 | dockerfile: Dockerfile-proxy 45 | image: "rackhd/rackhd:latest" 46 | container_name: "rackhd" 47 | hostname: "rackhd" 48 | links: 49 | - consulclient 50 | - endpoint1 51 | - endpoint2 52 | ports: 53 | - "10001:10001" 54 | expose: 55 | - "10001" 56 | - "10002" 57 | - "10003" 58 | - "8500" 59 | command: "-proxy-address=http://rackhd:10001 -backend-address=consulclient:8500 -service-name=RackHD-service:api:2.0:TEST -datacenter=dc-docker" 60 | depends_on: 61 | - consulclient 62 | endpoint1: 63 | build: 64 | context: . 65 | dockerfile: Dockerfile-endpoint 66 | image: "rackhd/endpoint:latest" 67 | container_name: "endpoint1" 68 | hostname: "endpoint1" 69 | links: 70 | - consulclient 71 | ports: 72 | - "10002:10002" 73 | expose: 74 | - "10001" 75 | - "10002" 76 | - "8500" 77 | command: "-endpoint-address=http://endpoint1:10002 -backend-address=consulclient:8500 -datacenter=dc-docker" 78 | depends_on: 79 | - consulclient 80 | endpoint2: 81 | build: 82 | context: . 83 | dockerfile: Dockerfile-endpoint 84 | image: "rackhd/endpoint:latest" 85 | container_name: "endpoint2" 86 | hostname: "endpoint2" 87 | links: 88 | - consulclient 89 | ports: 90 | - "10003:10003" 91 | expose: 92 | - "10001" 93 | - "10003" 94 | - "8500" 95 | command: "-endpoint-address=http://endpoint2:10003 -backend-address=consulclient:8500 -datacenter=dc-docker" 96 | depends_on: 97 | - consulclient 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Neighborhood Manager 2 | ==================== 3 | 4 | [![Build Status](https://travis-ci.org/RackHD/neighborhood-manager.svg?branch=master)](https://travis-ci.org/RackHD/neighborhood-manager) 5 | [![Coverage Status](https://coveralls.io/repos/github/RackHD/neighborhood-manager/badge.svg?branch=master)](https://coveralls.io/github/RackHD/neighborhood-manager?branch=master) 6 | 7 | Copyright © 2017 Dell Inc. or its subsidiaries. All Rights Reserved. 8 | 9 | ## Service Registry [![GoDoc](https://godoc.org/github.com/RackHD/neighborhood-manager/registry?status.svg)](https://godoc.org/github.com/RackHD/neighborhood-manager/registry) 10 | The [Service Registry](https://github.com/RackHD/neighborhood-manager/tree/master/registry) is a utility that will listen on the network for services that advertise themselves. The specific list of services a user would like to be notified about is configurable. When an advertisement is seen on the network from a configured service, the Service Registry collects information about the service, and registers the details with a storage backend of the user's choosing. Other applications can access this storage for aggregated information about all services available on a given network. 11 | 12 | ## RackHD Proxy [![GoDoc](https://godoc.org/github.com/RackHD/neighborhood-manager/rackhd?status.svg)](https://godoc.org/github.com/RackHD/neighborhood-manager/rackhd) 13 | The [RackHD Proxy](https://github.com/RackHD/neighborhood-manager/tree/master/rackhd) is a utility that acts as a proxy to the RackHD API. 14 | 15 | ## Libreg [![GoDoc](https://godoc.org/github.com/RackHD/neighborhood-manager/libreg?status.svg)](https://godoc.org/github.com/RackHD/neighborhood-manager/libreg) 16 | 17 | [Libreg](https://github.com/RackHD/neighborhood-manager/tree/master/libreg) is an abstraction library to interface with a backend datastore. Currently supports Consul. 18 | 19 | Basic Architecture Diagram 20 | ------------------ 21 | 22 | ![diagram](https://github.com/RackHD/neighborhood-manager/blob/gh-pages/NM_02OCT2016-1.png?raw=true) 23 | 24 | Prerequisites 25 | ------------- 26 | 27 | This project is written in golang and some commands in the documentation assume a working go installation. 28 | 29 | This project also uses docker and for those commands we assume the latest docker version is installed. 30 | 31 | Contribute 32 | ---------- 33 | 34 | Neighborhood Manager is a collection of libraries and applications housed at https://github.com/RackHD/neighborhood-manager. The code for Neighborhood Manager is written in Golang and makes use of Makefiles. It is available under the Apache 2.0 license (or compatible sublicences for library dependencies). 35 | 36 | Code and bug submissions are handled on GitHub using the Issues tab for this repository above. 37 | 38 | Community 39 | --------- 40 | 41 | We also have a #RackHD Slack channel: You can request an invite at http://community.emccode.com. 42 | 43 | 44 | Licensing 45 | --------- 46 | 47 | Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 48 | 49 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 50 | 51 | RackHD is a Trademark of Dell EMC 52 | 53 | Support 54 | ------- 55 | 56 | Please file bugs and issues at the GitHub issues page. The code and documentation are released with no warranties or SLAs and are intended to be supported through a community driven process. 57 | -------------------------------------------------------------------------------- /rackhd/watcher/watcher_test.go: -------------------------------------------------------------------------------- 1 | package watcher_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | regStore "github.com/RackHD/neighborhood-manager/libreg/registry" 7 | "github.com/RackHD/neighborhood-manager/libreg/registry/mock" 8 | "github.com/RackHD/neighborhood-manager/rackhd/watcher" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Watcher", func() { 15 | var ( 16 | serviceName string 17 | badServiceName string 18 | datacenter string 19 | backendAddr string 20 | badBackend regStore.Backend 21 | endpointPort int 22 | endpointAddr string 23 | ) 24 | BeforeEach(func() { 25 | serviceName = "RackHD-service:api:2.0" 26 | badServiceName = "service_error_injection" 27 | datacenter = "dc-test" 28 | backendAddr = "127.0.0.1:8500" 29 | endpointPort = 9090 30 | endpointAddr = "10.240.16.69" 31 | 32 | }) 33 | 34 | Describe("NewMonitor", func() { 35 | It("should return a Monitor struct using mock backend", func() { 36 | mock.Register() 37 | m, err := watcher.NewMonitor(serviceName, datacenter, backendAddr, regStore.MOCK) 38 | 39 | Expect(m).To(BeAssignableToTypeOf(&watcher.Monitor{})) 40 | Expect(err).ToNot(HaveOccurred()) 41 | Expect(m.Datacenter).To(Equal(datacenter)) 42 | Expect(m.ServiceName).To(Equal(serviceName)) 43 | }) 44 | 45 | It("should reject invalid backend", func() { 46 | mock.Register() 47 | _, err := watcher.NewMonitor(serviceName, datacenter, backendAddr, badBackend) 48 | Expect(err).To(HaveOccurred()) 49 | }) 50 | 51 | }) 52 | 53 | Describe("MonitorFunctions", func() { 54 | var ( 55 | m *watcher.Monitor 56 | ) 57 | BeforeEach(func() { 58 | mock.Register() 59 | m, _ = watcher.NewMonitor(serviceName, datacenter, backendAddr, regStore.MOCK) 60 | _ = m.Store.Register(®Store.CatalogRegistration{ 61 | Node: "42192294-095f-a13c-8dad-52c27c87ec66", 62 | Address: endpointAddr, 63 | Datacenter: datacenter, 64 | Service: ®Store.AgentService{ 65 | ID: serviceName, 66 | Service: serviceName, 67 | Port: endpointPort, 68 | Address: endpointAddr, 69 | }, 70 | Check: ®Store.AgentCheck{ 71 | Node: "42192294-095f-a13c-8dad-52c27c87ec66", 72 | CheckID: "service:" + serviceName, 73 | Name: "Service '" + serviceName + "' check", 74 | Status: "passing", 75 | ServiceID: serviceName, 76 | ServiceName: serviceName, 77 | }, 78 | }, nil) 79 | 80 | }) 81 | 82 | It("should return a map[string]struct{} of addresses from the backend store", func() { 83 | addresses, err := m.GetAddresses() 84 | Expect(addresses).To(BeAssignableToTypeOf(map[string]struct{}{})) 85 | Expect(err).ToNot(HaveOccurred()) 86 | 87 | }) 88 | 89 | It("should check that addresses[] has a valid address stored in the map", func() { 90 | addresses, err := m.GetAddresses() 91 | _, ok := addresses[fmt.Sprintf("%s:%d", endpointAddr, endpointPort)] 92 | Expect(ok).To(BeTrue()) 93 | Expect(err).ToNot(HaveOccurred()) 94 | }) 95 | 96 | It("should return a backend array for the given serviceName", func() { 97 | s, err := m.GetService(serviceName) 98 | 99 | Expect(s).To(BeAssignableToTypeOf([]*regStore.CatalogService{})) 100 | Expect(err).ToNot(HaveOccurred()) 101 | }) 102 | 103 | It("should return an error for a badServiceName", func() { 104 | _, err := m.GetService(badServiceName) 105 | 106 | Expect(err).To(HaveOccurred()) 107 | }) 108 | 109 | It("should return an error for a badServiceName", func() { 110 | m.ServiceName = badServiceName 111 | _, err := m.GetAddresses() 112 | 113 | Expect(err).To(HaveOccurred()) 114 | }) 115 | }) 116 | 117 | }) 118 | -------------------------------------------------------------------------------- /rackhd/cmd/utils/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "math/rand" 10 | "net/http" 11 | "time" 12 | 13 | "github.com/RackHD/neighborhood-manager/libreg" 14 | regStore "github.com/RackHD/neighborhood-manager/libreg/registry" 15 | "github.com/RackHD/neighborhood-manager/libreg/registry/consul" 16 | ) 17 | 18 | // Server is the proxy server struct 19 | type Server struct { 20 | Address string 21 | Port int 22 | Store regStore.Registry 23 | } 24 | 25 | //NodeObject is a object struct 26 | type NodeObject struct { 27 | Name string `json:"name"` 28 | Thing1 string `json:"thing1"` 29 | Thing2 string `json:"thing2"` 30 | Numbers int `json:"numbers"` 31 | Time time.Time `json:"time"` 32 | } 33 | 34 | // Serve starts the Server on address:port and handles the routes 35 | func (e *Server) Serve() { 36 | m := http.NewServeMux() 37 | m.HandleFunc("/test", e.HandleTest) 38 | m.HandleFunc("/object", e.HandleServeObject) 39 | m.HandleFunc("/array", e.HandleServeArray) 40 | http.ListenAndServe(fmt.Sprintf("%s:%d", e.Address, e.Port), m) 41 | } 42 | 43 | // NewServer initializes a new Server 44 | func NewServer(endpointIP, serviceName, datacenter, backendAddr string, backend regStore.Backend, endpointPort int) (*Server, error) { 45 | consul.Register() 46 | s := &Server{} 47 | r, err := libreg.NewRegistry(backend, []string{backendAddr}, nil) 48 | if err != nil { 49 | log.Printf("Error creating backend store: %s\n", err) 50 | return nil, err 51 | } 52 | s.Store = r 53 | s.Address = endpointIP 54 | s.Port = endpointPort 55 | 56 | for i := 0; i < 5; i++ { 57 | leader, err := s.Store.Leader() 58 | if err == nil && leader != "" { 59 | return s, nil 60 | } 61 | time.Sleep(5 * time.Second) 62 | } 63 | 64 | return nil, errors.New("Unable to find backend cluster") 65 | } 66 | 67 | // HandleTest is....well a test 68 | func (e *Server) HandleTest(w http.ResponseWriter, r *http.Request) { 69 | io.WriteString(w, "Hello world!") 70 | } 71 | 72 | // HandleServeArray serves an array back to the Http Endpoint 73 | func (e *Server) HandleServeArray(w http.ResponseWriter, r *http.Request) { 74 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 75 | var nodes []NodeObject 76 | n := rand.Intn(100) 77 | for i := 1; i <= n; i++ { 78 | node := NodeObject{ 79 | Name: fmt.Sprintf("TESTNODE: %+v", rand.Intn(100)), 80 | Thing1: "arrays for days", 81 | Thing2: e.Address, 82 | Numbers: i, 83 | } 84 | nodes = append(nodes, node) 85 | } 86 | 87 | json.NewEncoder(w).Encode(nodes) 88 | } 89 | 90 | // HandleServeObject serves an object back to the Http Endpint 91 | func (e *Server) HandleServeObject(w http.ResponseWriter, r *http.Request) { 92 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 93 | name := fmt.Sprintf("TESTNODE: %+v", rand.Intn(100)) 94 | object := NodeObject{ 95 | Name: name, 96 | Thing1: "objects FTW", 97 | Thing2: e.Address, 98 | Numbers: rand.Intn(90000), 99 | } 100 | json.NewEncoder(w).Encode(object) 101 | } 102 | 103 | // Register is... 104 | func (e *Server) Register(datacenter, serviceName string) { 105 | rGen := rand.New(rand.NewSource(time.Now().UnixNano())) 106 | n := fmt.Sprintf("%d", rGen.Int()) 107 | if err := e.Store.Register(®Store.CatalogRegistration{ 108 | Node: n, 109 | Address: e.Address, 110 | Datacenter: datacenter, 111 | Service: ®Store.AgentService{ 112 | ID: serviceName, 113 | Service: serviceName, 114 | Port: e.Port, 115 | Address: e.Address, 116 | }, 117 | Check: ®Store.AgentCheck{ 118 | Node: n, 119 | CheckID: "service:" + serviceName, 120 | Name: "Service '" + serviceName + "' check", 121 | Status: "passing", 122 | ServiceID: serviceName, 123 | ServiceName: serviceName, 124 | }, 125 | }, nil); err != nil { 126 | log.Printf("Error registering serviceName: %s\n", err) 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 7c32608f6d6cd807c9980e7a2d4a1a33b8228ef96330356cdb61e5e43238b693 2 | updated: 2017-03-07T09:58:36.817203487-05:00 3 | imports: 4 | - name: github.com/boltdb/bolt 5 | version: e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd 6 | - name: github.com/docker/libkv 7 | version: aabc039ad04deb721e234f99cd1b4aa28ac71a40 8 | subpackages: 9 | - store 10 | - store/boltdb 11 | - store/consul 12 | - name: github.com/fsnotify/fsnotify 13 | version: f12c6236fe7b5cf6bcf30e5935d08cb079d78334 14 | - name: github.com/gin-gonic/gin 15 | version: e2212d40c62a98b388a5eb48ecbdcf88534688ba 16 | subpackages: 17 | - binding 18 | - render 19 | - name: github.com/golang/protobuf 20 | version: 2402d76f3d41f928c7902a765dfc872356dd3aad 21 | subpackages: 22 | - proto 23 | - name: github.com/hashicorp/consul 24 | version: 21f2d5ad0c02af6c4b32d8fd04f7c81e9b002d41 25 | subpackages: 26 | - api 27 | - name: github.com/hashicorp/go-cleanhttp 28 | version: ad28ea4487f05916463e2423a55166280e8254b5 29 | - name: github.com/hashicorp/hcl 30 | version: 37ab263305aaeb501a60eb16863e808d426e37f2 31 | subpackages: 32 | - hcl/ast 33 | - hcl/parser 34 | - hcl/scanner 35 | - hcl/strconv 36 | - hcl/token 37 | - json/parser 38 | - json/scanner 39 | - json/token 40 | - name: github.com/hashicorp/serf 41 | version: cb45b412ee4f9d6cc2eeb2b2b7dd0f6cfd7545c1 42 | subpackages: 43 | - coordinate 44 | - name: github.com/king-jam/gossdp 45 | version: 83dbf4057d732d2a11fc27754f969eea50f7b132 46 | - name: github.com/kr/fs 47 | version: 2788f0dbd16903de03cb8186e5c7d97b69ad387b 48 | - name: github.com/magiconair/properties 49 | version: 61b492c03cf472e0c6419be5899b8e0dc28b1b88 50 | - name: github.com/manucorporat/sse 51 | version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d 52 | - name: github.com/mattn/go-isatty 53 | version: dda3de49cbfcec471bd7a70e6cc01fcc3ff90109 54 | - name: github.com/mitchellh/mapstructure 55 | version: bfdb1a85537d60bc7e954e600c250219ea497417 56 | - name: github.com/pelletier/go-buffruneio 57 | version: df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d 58 | - name: github.com/pelletier/go-toml 59 | version: 0049ab3dc4c4c70a9eee23087437b69c0dde2130 60 | - name: github.com/pkg/errors 61 | version: 17b591df37844cde689f4d5813e5cea0927d8dd2 62 | - name: github.com/pkg/sftp 63 | version: a71e8f580e3b622ebff585309160b1cc549ef4d2 64 | - name: github.com/spf13/afero 65 | version: 20500e2abd0d1f4564a499e83d11d6c73cd58c27 66 | subpackages: 67 | - mem 68 | - sftp 69 | - name: github.com/spf13/cast 70 | version: e31f36ffc91a2ba9ddb72a4b6a607ff9b3d3cb63 71 | - name: github.com/spf13/jwalterweatherman 72 | version: 33c24e77fb80341fe7130ee7c594256ff08ccc46 73 | - name: github.com/spf13/pflag 74 | version: 5ccb023bc27df288a957c5e994cd44fd19619465 75 | - name: github.com/spf13/viper 76 | version: 7fb2782df3d83e0036cc89f461ed0422628776f4 77 | - name: github.com/streadway/amqp 78 | version: d75c3a341ff43309ad0cb69ac8bdbd1d8772775f 79 | - name: golang.org/x/crypto 80 | version: 1f22c0103821b9390939b6776727195525381532 81 | subpackages: 82 | - curve25519 83 | - ssh 84 | - name: golang.org/x/net 85 | version: f315505cf3349909cdf013ea56690da34e96a451 86 | subpackages: 87 | - context 88 | - internal/iana 89 | - ipv4 90 | - name: golang.org/x/sys 91 | version: 478fcf54317e52ab69f40bb4c7a1520288d7f7ea 92 | subpackages: 93 | - unix 94 | - name: golang.org/x/text 95 | version: d69c40b4be55797923cec7457fac7a244d91a9b6 96 | subpackages: 97 | - transform 98 | - unicode/norm 99 | - name: gopkg.in/gin-gonic/gin.v1 100 | version: e2212d40c62a98b388a5eb48ecbdcf88534688ba 101 | - name: gopkg.in/go-playground/validator.v8 102 | version: c193cecd124b5cc722d7ee5538e945bdb3348435 103 | - name: gopkg.in/yaml.v2 104 | version: a5b47d31c556af34a302ce5d659e6fea44d90de0 105 | testImports: 106 | - name: github.com/onsi/ginkgo 107 | version: 462326b1628e124b23f42e87a8f2750e3c4e2d24 108 | subpackages: 109 | - config 110 | - internal/codelocation 111 | - internal/containernode 112 | - internal/failer 113 | - internal/leafnodes 114 | - internal/remote 115 | - internal/spec 116 | - internal/specrunner 117 | - internal/suite 118 | - internal/testingtproxy 119 | - internal/writer 120 | - reporters 121 | - reporters/stenographer 122 | - types 123 | - name: github.com/onsi/gomega 124 | version: a78ae492d53aad5a7a232d0d0462c14c400e3ee7 125 | subpackages: 126 | - format 127 | - internal/assertion 128 | - internal/asyncassertion 129 | - internal/testingtsupport 130 | - matchers 131 | - matchers/support/goraph/bipartitegraph 132 | - matchers/support/goraph/edge 133 | - matchers/support/goraph/node 134 | - matchers/support/goraph/util 135 | - types 136 | -------------------------------------------------------------------------------- /rackhd/README.md: -------------------------------------------------------------------------------- 1 | # Neighborhood-Manager RackHD Proxy 2 | 3 | The proxy is a utility that exposes a http endpoint on the network for api calls to hit. It routes those calls to all RackHD instances that are registered in the backend store (consul). It then takes all those responses and aggregates them together and serves them back to the initial caller as one completed http request. 4 | 5 | To view the RackHD API that is currently supported click here => [RackHD API]. 6 | 7 | 8 | ## Background Information 9 | This section details how to set up a production environment for the proxy. If you are interested in a quick demo of the functionality, skip to the Try It Out section to start everything up quickly in containers. 10 | 11 | ### Backend 12 | Some type of storage backend is needed for the proxy to lookup endpoint nodes to forward calls to. At this time, the only backend supported is [Consul]. The proxy also supports query string inputs for direct calls to known endpoints (see Configuration below). 13 | 14 | The proxy assumes the user already has a consul environment setup. Best practice is to run a consul agent-client on the same host as the proxy. The proxy is configured to talk to this agent-client through a flag (see Configuration below). 15 | 16 | #### Consul Server 17 | To create a cluster of Consul servers, reference this article on [configuring consul], and see the section **Creating the Bootstrap Configuration**, then follow into the next section **Creating the Regular Server Configuration** 18 | 19 | #### Consul Client 20 | When a cluster is in place, a consul client must be started on the same host that Service Registry will run on. The most extensible way to do this is to put all parameters in a config file, such as `/etc/consul.d/client/config.json`. 21 | ``` 22 | { 23 | "server":false, 24 | "datacenter": "dc1", 25 | "data_dir": "/var/consul", 26 | "log_level": "DEBUG", 27 | "enable_syslog": true, 28 | "start_join": ["192.168.1.1", "192.168.1.2", "192.168.1.3"], 29 | "bind_addr": "192.168.1.4" 30 | } 31 | ``` 32 | Where the three addresses in the `start_join` field are the addresses of the three clustered servers, and the `bind_addr` field is the address of the host machine that this client will run on. 33 | 34 | The consul client can then be started with `consul agent -config-dir /etc/consul.d/client/config.json` 35 | 36 | ## Accessing Consul Registry 37 | To retrieve a list of services from consul one can use curl (or a client like Postman) to hit the consul API. 38 | 39 | This call: Lists services in a given DC: 40 | `curl http://address:port/v1/catalog/services` where address:port is the address and port that the consul agent-client is listening on. 41 | 42 | ## Building 43 | We use docker to build our binaries. The following steps assume a host is installed with the latest docker version. 44 | Download the [source] from GitHub 45 | `git clone https://github.com/RackHD/neighborhood-manager.git` 46 | 47 | Build the dependencies 48 | `make deps` 49 | 50 | Build the proxy 51 | `make build` 52 | 53 | If the host system does not have docker installed the following commands can be run to build locally. 54 | 55 | Build the dependencies 56 | `make deps-local` 57 | 58 | Build the Service Registry 59 | `make build-proxy-local` 60 | 61 | ## Running 62 | After building, the proxy binary (named `rackhd`) will be in the `rackhd/bin/` folder of from the source directory. 63 | 64 | Move to that directory and run the proxy. 65 | ``` 66 | cd /rackhd/bin 67 | ./rackhd 68 | ``` 69 | 70 | This will likely error as no configuration parameters have been specified. See Configuration below for config flags 71 | 72 | 73 | ## Configuration 74 | 75 | The proxy has 4 configuration variables: 76 | 77 | `-proxy-address=http://address:port` (Default is `http://0.0.0.0:10001`) 78 | 79 | Sets the address and port that the proxy is bound to. 80 | 81 | `-backend-address=address:port` (Default is `127.0.0.1:8500`) 82 | 83 | Sets the proxy to connect to the backend store at address:port. 84 | 85 | `-service-name=some-string` (Default is `RackHD-service:api:2.0`) 86 | 87 | To change this variable see the Accessing Consul Registry section above. 88 | 89 | `-datacenter=some-string` (Default is `dc1`) 90 | 91 | Comes from the consul server setup and is the datacenter of the consul cluster. 92 | 93 | These flags are passed at the time of starting the binary in a format as such: 94 | 95 | `./rackhd -proxy-address=http://10.10.10.1:10001 -datacenter=My-Datacenter` 96 | 97 | 98 | 99 | [configuring consul]: https://www.digitalocean.com/community/tutorials/how-to-configure-consul-in-a-production-environment-on-ubuntu-14-04 100 | [RackHD API]: http://rackhd.readthedocs.io/en/latest/rackhd/index.html 101 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ORGANIZATION = RackHD 2 | PROJECT = neighborhood-manager 3 | RACKHD = rackhd 4 | REGISTRY = registry 5 | 6 | TTY = $(shell if [ -t 0 ]; then echo "-ti"; fi) 7 | 8 | DOCKER_DIR = /go/src/github.com/${ORGANIZATION}/${PROJECT} 9 | DOCKER_IMAGE = rackhd/golang:1.8.0 10 | DOCKER_CMD = docker run --rm -v ${PWD}:${DOCKER_DIR} ${TTY} -w ${DOCKER_DIR} ${DOCKER_IMAGE} 11 | 12 | # variable definitions 13 | COMMITHASH = $(shell git describe --tags --always --dirty) 14 | BUILDDATE = $(shell date -u) 15 | BUILDER = $(shell echo "`git config user.name` <`git config user.email`>") 16 | GOVERSION = $(shell go version) 17 | OSARCH = $(shell uname -sm) 18 | RELEASEVERSION = 0.1 19 | 20 | #Flags to pass to main.go 21 | RACKHDFLAGS = -ldflags "-X 'main.binaryName=${RACKHD}' \ 22 | -X 'main.buildDate=${BUILDDATE}' \ 23 | -X 'main.buildUser=${BUILDER}' \ 24 | -X 'main.commitHash=${COMMITHASH}' \ 25 | -X 'main.goVersion=${GOVERSION}' \ 26 | -X 'main.osArch=${OSARCH}' \ 27 | -X 'main.releaseVersion=${RELEASEVERSION}' " 28 | 29 | REGFLAGS = -ldflags "-X 'main.binaryName=${REGISTRY}' \ 30 | -X 'main.buildDate=${BUILDDATE}' \ 31 | -X 'main.buildUser=${BUILDER}' \ 32 | -X 'main.commitHash=${COMMITHASH}' \ 33 | -X 'main.goVersion=${GOVERSION}' \ 34 | -X 'main.osArch=${OSARCH}' \ 35 | -X 'main.releaseVersion=${RELEASEVERSION}' " 36 | 37 | #Some tests need to run for 5+ seconds, which trips Ginkgo Slow Test warning 38 | SLOWTEST = 10 39 | 40 | .PHONY: shell deps deps-local build build-local lint lint-local test test-local release 41 | 42 | default: deps test 43 | 44 | coveralls: 45 | @go get github.com/mattn/goveralls 46 | @go get github.com/modocache/gover 47 | @go get golang.org/x/tools/cmd/cover 48 | @gover 49 | @goveralls -coverprofile=gover.coverprofile -service=travis-ci 50 | 51 | shell: 52 | @${DOCKER_CMD} /bin/bash 53 | 54 | consul-shell: 55 | @docker run --rm -ti --net registry_default -v ${PWD}:${DOCKER_DIR} -w ${DOCKER_DIR} ${DOCKER_IMAGE} /bin/bash 56 | 57 | clean: 58 | @${DOCKER_CMD} make clean-local 59 | @-docker-compose -f ${RACKHD}/docker-compose-${RACKHD}.yaml kill 60 | @-docker-compose -f ${RACKHD}/docker-compose-${RACKHD}.yaml rm -f 61 | @-docker rmi rackhd/${RACKHD} 62 | @-docker rmi rackhd/endpoint 63 | @-docker-compose -f ${REGISTRY}/docker-compose-${REGISTRY}.yaml kill 64 | @-docker-compose -f ${REGISTRY}/docker-compose-${REGISTRY}.yaml rm -f 65 | @-docker rmi rackhd/${REGISTRY} 66 | @-docker rmi rackhd/ssdpspoofer 67 | @-docker rm rackhd/consul:server 68 | @-docker rm rackhd/consul:client 69 | 70 | clean-local: 71 | @rm -rf ${RACKHD}/bin ${REGISTRY}/bin vendor 72 | 73 | deps: 74 | @${DOCKER_CMD} make deps-local 75 | 76 | deps-local: 77 | @if ! [ -f glide.yaml ]; then glide init --non-interactive; fi 78 | @glide install 79 | 80 | build: 81 | @make build-proxy 82 | @make build-reg 83 | 84 | build-proxy: 85 | @${DOCKER_CMD} make build-proxy-local 86 | 87 | build-proxy-local: lint-local 88 | @go build -o ${RACKHD}/bin/${RACKHD} ${RACKHDFLAGS} rackhd/cmd/rackhd/*.go 89 | @go build -o rackhd/bin/endpoint rackhd/cmd/utils/*.go 90 | 91 | build-reg: 92 | @${DOCKER_CMD} make build-reg-local 93 | 94 | build-reg-local: lint-local 95 | @go build -o ${REGISTRY}/bin/${REGISTRY} ${REGFLAGS} registry/cmd/registry/*.go 96 | @go build -o registry/bin/ssdpspoofer registry/cmd/ssdpspoofer/*.go 97 | @go build -o registry/bin/rackhdspoofer registry/cmd/rackHDSpoofer/*.go 98 | 99 | lint: 100 | @${DOCKER_CMD} make lint-local 101 | 102 | lint-local: 103 | @gometalinter --vendor --fast --disable=dupl --disable=gotype --skip=grpc --skip=rackhd ./... 104 | 105 | test: 106 | @make test-proxy 107 | @make test-reg 108 | 109 | test-proxy: 110 | @${DOCKER_CMD} make test-proxy-local 111 | @make build-proxy 112 | 113 | test-proxy-local: lint-local 114 | @ginkgo -r -race -trace -cover -randomizeAllSpecs ${RACKHD} 115 | 116 | test-reg: 117 | @${DOCKER_CMD} make test-reg-local 118 | @make build-reg 119 | 120 | test-reg-local: lint-local 121 | @ginkgo -r -race -trace -cover -randomizeAllSpecs --slowSpecThreshold=${SLOWTEST} ${REGISTRY} 122 | 123 | release: deps build 124 | @docker build -t rackhd/${RACKHD} -f ${RACKHD}/Dockerfile-${RACKHD} ${RACKHD}/ 125 | @docker build -t rackhd/endpoint -f ${RACKHD}/Dockerfile-endpoint ${RACKHD}/ 126 | @docker build -t rackhd/${REGISTRY} -f ${REGISTRY}/Dockerfile-${REGISTRY} ${REGISTRY}/ 127 | @docker build -t rackhd/ssdpspoofer -f ${REGISTRY}/Dockerfile-ssdp ${REGISTRY}/ 128 | @docker build -t rackhd/rackhdspoofer -f ${REGISTRY}/Dockerfile-rackhd ${REGISTRY}/ 129 | 130 | 131 | run-proxy: release 132 | @docker-compose -f ${RACKHD}/docker-compose-${RACKHD}.yaml up --force-recreate 133 | 134 | run-reg: release 135 | @docker-compose -f ${REGISTRY}/docker-compose-${REGISTRY}.yaml up --force-recreate 136 | -------------------------------------------------------------------------------- /rackhd/models/rackhd.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/streadway/amqp" 6 | "net/url" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | // managePrefix is a directory structure that the data will be kept under 13 | managerPrefix = "rhdman/" 14 | 15 | // rhdPrefix is a directory shortcut to where all RHD objects are stored 16 | rhdPrefix = managerPrefix + "rhd/" 17 | 18 | // httpPrefix is a directory under a RHD ID where the http URL is stored 19 | httpPrefix = "httpConf" 20 | 21 | // amqpPrefix is a directory under a RHD ID where the amqp URL is stroed 22 | amqpPrefix = "amqpConf" 23 | ) 24 | 25 | var ( 26 | // 27 | rx *regexp.Regexp 28 | ) 29 | 30 | func init() { 31 | rx = regexp.MustCompile(`rhdman\/rhd\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}`) 32 | } 33 | 34 | // RackHD stores the relevant data about RackHD nodes under management 35 | type RackHD struct { 36 | ID string 37 | HTTPConf HTTPConfig 38 | AmqpConf AmqpConfig 39 | Nodes []*RhdNode // can be found in the nodes.go file 40 | } 41 | 42 | // HTTPConfig struct wrapper of URL for future expansion 43 | type HTTPConfig struct { 44 | URL *url.URL 45 | } 46 | 47 | // AmqpConfig struct wrapper of amqp URI for future expansion 48 | type AmqpConfig struct { 49 | URI amqp.URI 50 | } 51 | 52 | // NewRhd creates a RackHD struct for storage 53 | func NewRhd(id string, httpURL string, amqpURI string) (*RackHD, error) { 54 | rhd := &RackHD{} 55 | rhd.ID = id 56 | url, err := url.Parse(httpURL) 57 | if err != nil { 58 | return rhd, fmt.Errorf("Failed to parse HTTP URL") 59 | } 60 | rhd.HTTPConf = HTTPConfig{ 61 | URL: url, 62 | } 63 | amqp, err := amqp.ParseURI(amqpURI) 64 | if err != nil { 65 | return rhd, fmt.Errorf("Failed to parse AMQP URI: %s", err) 66 | } 67 | rhd.AmqpConf = AmqpConfig{ 68 | URI: amqp, 69 | } 70 | return rhd, nil 71 | } 72 | 73 | // CreateRhd stores a RackHD instance on the backend 74 | func CreateRhd(rhd *RackHD) error { 75 | return UpdateRhd(rhd) 76 | } 77 | 78 | // UpdateRhd updates a RackHD instance on the backend 79 | func UpdateRhd(rhd *RackHD) error { 80 | basePath := rhdPrefix + rhd.ID + "/" 81 | if err := db.Put(basePath+httpPrefix, []byte(rhd.HTTPConf.URL.String()), nil); err != nil { 82 | return err 83 | } 84 | return db.Put(basePath+amqpPrefix, []byte(rhd.AmqpConf.URI.String()), nil) 85 | } 86 | 87 | // GetAllRhd returns all RackHD object stored in the backend 88 | func GetAllRhd() ([]*RackHD, error) { 89 | var rackhds []*RackHD 90 | entries, err := db.List(rhdPrefix) 91 | if err != nil { 92 | return rackhds, fmt.Errorf("failed") 93 | } 94 | lastID := "" 95 | for _, pair := range entries { 96 | key := rx.FindString(pair.Key) 97 | if key != "" { 98 | tempArr := strings.Split(key, rhdPrefix) 99 | id := tempArr[len(tempArr)-1] 100 | // TODO handle 0 length 101 | if id == lastID { 102 | // we already saw this one 103 | continue 104 | } 105 | instance, err := getRhdInternal(id) 106 | if err != nil { 107 | fmt.Printf("Failed to get RHD") 108 | } 109 | rackhds = append(rackhds, instance) 110 | lastID = id 111 | } 112 | } 113 | return rackhds, nil 114 | } 115 | 116 | func getRhdInternal(id string) (*RackHD, error) { 117 | basePath := rhdPrefix + id + "/" 118 | rhd := &RackHD{ 119 | ID: id, 120 | } 121 | entries, err := db.List(basePath) 122 | if err != nil { 123 | return rhd, fmt.Errorf("failed") 124 | } 125 | for _, pair := range entries { 126 | switch pair.Key { 127 | case basePath + httpPrefix: 128 | u, _ := url.Parse(string(pair.Value)) 129 | // TODO handle err 130 | rhd.HTTPConf = HTTPConfig{ 131 | URL: u, 132 | } 133 | case basePath + amqpPrefix: 134 | a, _ := amqp.ParseURI(string(pair.Value)) 135 | // TODO handle err 136 | rhd.AmqpConf = AmqpConfig{ 137 | URI: a, 138 | } 139 | default: 140 | } 141 | } 142 | return rhd, nil 143 | } 144 | 145 | // GetRhdByID returns a RackHD based on its unique ID 146 | func GetRhdByID(id string) (*RackHD, error) { 147 | instance, err := getRhdInternal(id) 148 | if err != nil { 149 | return &RackHD{}, err 150 | } 151 | return instance, nil 152 | } 153 | 154 | // GetRhdsByIDs returns a group of RackHD structs based on an array of unique IDs 155 | func GetRhdsByIDs(ids []string) ([]*RackHD, error) { 156 | var rackhds []*RackHD 157 | for _, id := range ids { 158 | instance, err := getRhdInternal(id) 159 | if err != nil { 160 | continue 161 | } 162 | rackhds = append(rackhds, instance) 163 | } 164 | return rackhds, nil 165 | } 166 | 167 | // DeleteRhdByID removes a RackHD from the backend 168 | func DeleteRhdByID(id string) error { 169 | basePath := rhdPrefix + id 170 | return db.DeleteTree(basePath) 171 | } 172 | 173 | // DeleteRhdsByIDs removes multiple RackHDs from the backend 174 | func DeleteRhdsByIDs(ids []string) error { 175 | for _, id := range ids { 176 | if err := DeleteRhdByID(id); err != nil { 177 | return err 178 | } 179 | } 180 | return nil 181 | } 182 | -------------------------------------------------------------------------------- /rackhd/proxy/controller.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "sync" 11 | "time" 12 | 13 | regStore "github.com/RackHD/neighborhood-manager/libreg/registry" 14 | "github.com/RackHD/neighborhood-manager/libreg/registry/consul" 15 | "github.com/RackHD/neighborhood-manager/rackhd/watcher" 16 | "github.com/hashicorp/go-cleanhttp" 17 | ) 18 | 19 | // Responses is an array of Response structs 20 | type Responses []Response 21 | 22 | // Err creates an error message to print 23 | type Err struct { 24 | Msg string `json:"msg"` 25 | } 26 | 27 | // Server is the proxy server struct 28 | type Server struct { 29 | Address string 30 | Port int 31 | Store *watcher.Monitor 32 | wg *sync.WaitGroup 33 | } 34 | 35 | // Serve starts the Server on address:port and handles the routes 36 | func (p *Server) Serve() { 37 | m := http.NewServeMux() 38 | m.HandleFunc("/test", p.HandleTest) 39 | m.HandleFunc("/", p.HandleNodes) 40 | http.ListenAndServe(fmt.Sprintf("%s:%d", p.Address, p.Port), m) 41 | } 42 | 43 | // NewServer initializes a new Server 44 | func NewServer(proxyIP, serviceName, datacenter, backendAddr string, backend regStore.Backend, proxyPort int) (*Server, error) { 45 | consul.Register() 46 | m, err := watcher.NewMonitor(serviceName, datacenter, backendAddr, backend) 47 | if err != nil { 48 | return nil, err 49 | } 50 | proxyServer := &Server{ 51 | Address: proxyIP, 52 | Port: proxyPort, 53 | Store: m, 54 | wg: &sync.WaitGroup{}, 55 | } 56 | return proxyServer, nil 57 | } 58 | 59 | // HandleTest is....well a test 60 | func (p *Server) HandleTest(w http.ResponseWriter, r *http.Request) { 61 | io.WriteString(w, "Hello world!") 62 | } 63 | 64 | // HandleNodes sends, recieves, and processes all the data 65 | func (p *Server) HandleNodes(w http.ResponseWriter, r *http.Request) { 66 | start := time.Now() 67 | addrMap, err := p.GetAddresses(w, r) 68 | if len(addrMap) == 0 { 69 | w.WriteHeader(200) 70 | w.Write([]byte("[]")) 71 | return 72 | } 73 | if err != nil { 74 | w.WriteHeader(500) 75 | msg := Err{Msg: "Internal error fetching endpoint addresses."} 76 | json.NewEncoder(w).Encode(msg) 77 | return 78 | } 79 | ar := p.GetResp(r, addrMap) 80 | p.RespHeaderWriter(r, w, ar) 81 | p.RespCheck(r, w, ar) 82 | elapsed := time.Since(start) 83 | fmt.Printf("Total Request Time => %v\n", elapsed) 84 | } 85 | 86 | // GetResp makes channels for the response and errors from http.Get. 87 | // A go func is spun up for each http.Get and the responses are fed 88 | // into their respective channels. 89 | func (p *Server) GetResp(r *http.Request, addrs map[string]struct{}) Responses { 90 | cr := make(chan *Response, len(addrs)) 91 | for entry := range addrs { 92 | p.wg.Add(1) 93 | go func(entry string, r *http.Request) { 94 | defer p.wg.Done() 95 | req, err := NewRequest(r, entry) 96 | if err != nil { 97 | cr <- NewResponseFromError(err) 98 | return 99 | } 100 | client := cleanhttp.DefaultClient() 101 | start := time.Now() 102 | respGet, err := client.Do(req) 103 | elapsed := time.Since(start) 104 | fmt.Printf("Response time: %v => %s\n", elapsed, entry) 105 | if err != nil { 106 | cr <- NewResponseFromError(err) 107 | return 108 | } 109 | defer respGet.Body.Close() 110 | responseCopy, err := NewResponse(respGet) 111 | if err != nil { 112 | cr <- NewResponseFromError(err) 113 | return 114 | } 115 | cr <- responseCopy 116 | }(entry, r) 117 | } 118 | p.wg.Wait() 119 | close(cr) 120 | var ar Responses 121 | for entry := range cr { 122 | ar = append(ar, *entry) 123 | } 124 | return ar 125 | } 126 | 127 | // GetAddresses decides from where to retrieve the addresses 128 | func (p *Server) GetAddresses(w http.ResponseWriter, r *http.Request) (map[string]struct{}, error) { 129 | if err := r.ParseForm(); err != nil { 130 | http.Error(w, err.Error(), http.StatusBadRequest) 131 | return nil, err 132 | } 133 | querySlice := r.URL.Query() 134 | if len(querySlice["ip"]) > 0 { 135 | addrMap := p.GetQueryAddresses(querySlice["ip"]) 136 | return addrMap, nil 137 | } 138 | addrMap, err := p.GetStoredAddresses() 139 | if err != nil { 140 | return nil, err 141 | } 142 | return addrMap, nil 143 | } 144 | 145 | // GetStoredAddresses calls GetAddresses and returns a map of addresses 146 | func (p *Server) GetStoredAddresses() (map[string]struct{}, error) { 147 | addresses, err := p.Store.GetAddresses() 148 | if err != nil { 149 | log.Printf("Did not get IP List ==> %s\n", err) 150 | } 151 | return addresses, err 152 | } 153 | 154 | // GetQueryAddresses retrives a url flag and returns a map of address(es) 155 | func (p *Server) GetQueryAddresses(querySlice []string) map[string]struct{} { 156 | fmt.Println(querySlice) 157 | queryMap := make(map[string]struct{}) 158 | for _, elem := range querySlice { 159 | ip, port, err := net.SplitHostPort(elem) 160 | if err != nil { 161 | log.Printf("Invalid port => %s\n", err) 162 | return nil 163 | } 164 | if net.ParseIP(ip) != nil { 165 | queryMap[ip+":"+port] = struct{}{} 166 | } 167 | } 168 | return queryMap 169 | } 170 | 171 | // RespHeaderWriter writes the StatusCode and Headers 172 | func (p *Server) RespHeaderWriter(r *http.Request, w http.ResponseWriter, ar Responses) { 173 | var status int 174 | status = 500 175 | for _, s := range ar { 176 | if s.StatusCode < status { 177 | status = s.StatusCode 178 | } 179 | } 180 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 181 | w.WriteHeader(status) 182 | } 183 | 184 | // RespCheck identifies the type of initialResp.Body and calls the appropriate 185 | // helper function to write to the ResponseWriter. 186 | func (p *Server) RespCheck(r *http.Request, w http.ResponseWriter, ar Responses) { 187 | var cutSize int 188 | w.Write([]byte("[")) 189 | for i, r := range ar { 190 | if r.Body == nil || ((r.Body[0] == '[') && (r.Body[1] == ']')) { 191 | continue 192 | } 193 | if r.Body[0] == '[' { 194 | cutSize = 1 195 | } else if r.Body[0] == '{' { 196 | cutSize = 0 197 | } else { 198 | continue 199 | } 200 | w.Write(r.Body[cutSize : len(r.Body)-cutSize]) 201 | if i != len(ar)-1 { 202 | w.Write([]byte(",")) 203 | } 204 | } 205 | w.Write([]byte("]")) 206 | } 207 | -------------------------------------------------------------------------------- /libreg/registry/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | 7 | "github.com/RackHD/neighborhood-manager/libreg" 8 | "github.com/RackHD/neighborhood-manager/libreg/registry" 9 | ) 10 | 11 | // Mock is... 12 | type Mock struct { 13 | Catalog map[*registry.CatalogRegistration]bool 14 | sync.RWMutex 15 | } 16 | 17 | // Register inits a new MOCK 18 | func Register() { 19 | libreg.AddRegistry(registry.MOCK, New) 20 | } 21 | 22 | // New instantiates a new registry 23 | func New(endpoints []string, options *registry.Config) (registry.Registry, error) { 24 | m := &Mock{} 25 | m.Catalog = make(map[*registry.CatalogRegistration]bool) 26 | return m, nil 27 | } 28 | 29 | // GetCatalog returns the whitelist of SSDP URNs to act on 30 | func (m *Mock) GetCatalog() map[registry.CatalogRegistration]bool { 31 | c := make(map[registry.CatalogRegistration]bool) 32 | 33 | m.RLock() 34 | for r := range m.Catalog { 35 | c[*r] = true 36 | } 37 | m.RUnlock() 38 | 39 | return c 40 | } 41 | 42 | // ServiceRegister add a local agent service and its check 43 | func (m *Mock) ServiceRegister(serv *registry.AgentServiceRegistration) error { 44 | // 45 | // err := s.client.Agent().ServiceRegister( 46 | // &api.AgentServiceRegistration{ 47 | // ID: serv.ID, 48 | // Name: serv.Name, 49 | // Tags: serv.Tags, 50 | // Port: serv.Port, 51 | // Address: serv.Address, 52 | // EnableTagOverride: serv.EnableTagOverride, 53 | // Check: &api.AgentServiceCheck{ 54 | // HTTP: serv.Check.HTTP, 55 | // Interval: serv.Check.Interval, 56 | // DeregisterCriticalServiceAfter: serv.Check.DeregisterCriticalServiceAfter, 57 | // }, 58 | // }, 59 | // ) 60 | return nil 61 | } 62 | 63 | // Register creates a new node, service or check 64 | func (m *Mock) Register(reg *registry.CatalogRegistration, options *registry.WriteOptions) error { 65 | if reg.Address == "1.1.1.1" { 66 | return errors.New("Forced error: reg.Address=1.1.1.1:1") 67 | } 68 | m.Lock() 69 | m.Catalog[reg] = true 70 | m.Unlock() 71 | return nil 72 | } 73 | 74 | // Deregister removes a node, service or check 75 | func (m *Mock) Deregister(dereg *registry.CatalogDeregistration, options *registry.WriteOptions) error { 76 | m.Lock() 77 | for reg := range m.Catalog { 78 | if dereg.Node == reg.Node && 79 | dereg.Address == reg.Address && 80 | dereg.Datacenter == reg.Datacenter && 81 | dereg.ServiceID == reg.Service.ID && 82 | dereg.CheckID == reg.Check.CheckID { 83 | delete(m.Catalog, reg) 84 | } 85 | } 86 | m.Unlock() 87 | 88 | return nil 89 | } 90 | 91 | // Datacenters lists known datacenters 92 | func (m *Mock) Datacenters() ([]string, error) { 93 | d := make(map[string]bool) 94 | for reg := range m.GetCatalog() { 95 | d[reg.Datacenter] = true 96 | } 97 | 98 | var keys []string 99 | for k := range d { 100 | keys = append(keys, k) 101 | } 102 | return keys, nil 103 | } 104 | 105 | // Nodes lists all nodes in a given DC 106 | func (m *Mock) Nodes(options *registry.QueryOptions) ([]*registry.Node, error) { 107 | nodes := make(map[registry.Node]bool) 108 | for reg := range m.GetCatalog() { 109 | n := registry.Node{ 110 | Node: reg.Node, 111 | Address: reg.Address, 112 | } 113 | nodes[n] = true 114 | } 115 | 116 | var keys []*registry.Node 117 | for k := range nodes { 118 | keys = append(keys, &k) 119 | } 120 | return keys, nil 121 | } 122 | 123 | // NodesWatch watches for changes to the nodes in a given DC 124 | func (m *Mock) NodesWatch(options *registry.QueryOptions, stopChan <-chan struct{}) (<-chan []*registry.Node, error) { 125 | watchCh := make(chan []*registry.Node) 126 | return watchCh, nil 127 | } 128 | 129 | // Services lists all services in a given DC 130 | func (m *Mock) Services(options *registry.QueryOptions) (map[string][]string, error) { 131 | serviceMap := make(map[string][]string) 132 | 133 | catalog := m.GetCatalog() 134 | for r := range catalog { 135 | serviceMap[r.Service.Service] = append(serviceMap[r.Service.Service], r.Service.Tags...) 136 | if serviceMap[r.Service.Service] == nil { 137 | s := []string{} 138 | serviceMap[r.Service.Service] = s 139 | } 140 | } 141 | 142 | return serviceMap, nil 143 | } 144 | 145 | // ServicesWatch watches for changes to the list of services in a given DC 146 | func (m *Mock) ServicesWatch(options *registry.QueryOptions, stopChan <-chan struct{}) (<-chan map[string][]string, error) { 147 | watchCh := make(chan map[string][]string) 148 | return watchCh, nil 149 | } 150 | 151 | // Service lists the nodes in a given service 152 | func (m *Mock) Service(service, tag string, options *registry.QueryOptions) ([]*registry.CatalogService, error) { 153 | var c []*registry.CatalogService 154 | if service == "service_error_injection" { 155 | return nil, errors.New("Service Error Injection") 156 | } 157 | for r := range m.GetCatalog() { 158 | containsTag := false 159 | 160 | for _, serviceTag := range r.Service.Tags { 161 | if tag == serviceTag { 162 | containsTag = true 163 | break 164 | } 165 | } 166 | if tag == "" { 167 | containsTag = true 168 | } 169 | 170 | if r.Service.Service == service && containsTag { 171 | catalogService := registry.CatalogService{ 172 | Node: r.Node, 173 | Address: r.Address, 174 | ServiceID: r.Service.ID, 175 | ServiceName: r.Service.Service, 176 | ServiceAddress: r.Service.Address, 177 | ServiceTags: r.Service.Tags, 178 | ServicePort: r.Service.Port, 179 | ServiceEnableTagOverride: r.Service.EnableTagOverride, 180 | } 181 | c = append(c, &catalogService) 182 | } 183 | } 184 | return c, nil 185 | } 186 | 187 | // ServiceWatch watches for changes to the list of nodes under a given service 188 | func (m *Mock) ServiceWatch(service, tag string, options *registry.QueryOptions, stopChan <-chan struct{}) (<-chan []*registry.CatalogService, error) { 189 | watchCh := make(chan []*registry.CatalogService) 190 | return watchCh, nil 191 | } 192 | 193 | // Node lists the services provided by a given node 194 | func (m *Mock) Node(node string, options *registry.QueryOptions) (*registry.CatalogNode, error) { 195 | if node == "" { 196 | return nil, errors.New("Node UUID is empty") 197 | } 198 | 199 | catalogNode := registry.CatalogNode{ 200 | Node: ®istry.Node{}, 201 | Services: make(map[string]*registry.AgentService), 202 | } 203 | 204 | for c := range m.GetCatalog() { 205 | if c.Node == node { 206 | catalogNode.Node.Node = c.Node 207 | catalogNode.Node.Address = c.Address 208 | catalogNode.Services[c.Service.Service] = c.Service 209 | } 210 | } 211 | return &catalogNode, nil 212 | } 213 | 214 | // Leader returns the cluster leader 215 | func (m *Mock) Leader() (string, error) { 216 | return "", nil 217 | } 218 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "net/url" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | 12 | "github.com/RackHD/neighborhood-manager/libreg" 13 | regStore "github.com/RackHD/neighborhood-manager/libreg/registry" 14 | "github.com/king-jam/gossdp" 15 | ) 16 | 17 | // Registry is a service registry object 18 | type Registry struct { 19 | start chan bool 20 | stop chan bool 21 | wg *sync.WaitGroup 22 | ssdpServer *gossdp.Ssdp 23 | Store regStore.Registry 24 | searchTerms map[string]string 25 | datacenter string 26 | mut sync.RWMutex 27 | } 28 | 29 | // AddSearchTerm adds a new tagged URN to the whitelist of SSDP URNs to act on 30 | func (p *Registry) AddSearchTerm(urn, tag string) { 31 | p.mut.Lock() 32 | defer p.mut.Unlock() 33 | 34 | p.searchTerms[urn] = tag 35 | } 36 | 37 | // RemoveSearchTerm removes an entry from the whitelist of SSDP URNs to act on 38 | func (p *Registry) RemoveSearchTerm(urn string) { 39 | p.mut.Lock() 40 | defer p.mut.Unlock() 41 | 42 | delete(p.searchTerms, urn) 43 | } 44 | 45 | // GetSearchTerms returns the whitelist of SSDP URNs to act on 46 | func (p *Registry) GetSearchTerms() map[string]string { 47 | p.mut.RLock() 48 | defer p.mut.RUnlock() 49 | 50 | st := p.searchTerms 51 | return st 52 | } 53 | 54 | func extractIPPort(location string) (string, int, error) { 55 | addr, err := url.Parse(location) 56 | if err != nil { 57 | return "", 0, err 58 | } 59 | 60 | agentIP, portStr, err := net.SplitHostPort(addr.Host) 61 | if err != nil { 62 | return "", 0, err 63 | } 64 | 65 | agentPort, err := strconv.Atoi(portStr) 66 | if err != nil { 67 | return "", 0, err 68 | } 69 | 70 | return agentIP, agentPort, nil 71 | } 72 | 73 | // NotifyBye handles a NotifyBye message from the SSDP listener 74 | func (p *Registry) NotifyBye(message gossdp.ByeMessage) { 75 | // Should never hit this in normal execution. 76 | // Implemented to satisfy interface 77 | log.Printf("%+v\n", message) 78 | } 79 | 80 | // Response handles an M-Search Response message 81 | func (p *Registry) Response(message gossdp.ResponseMessage) { 82 | // Should never hit this in normal execution. 83 | // Implemented to satisfy interface 84 | log.Printf("%+v\n", message) 85 | } 86 | 87 | // NotifyAlive handles a NotifyAlive message from the SSDP listener 88 | func (p *Registry) NotifyAlive(message gossdp.AliveMessage) { 89 | // Check to see if URN is in whitelist, and get tag if yes 90 | st := p.GetSearchTerms() 91 | tag, goodUrn := st[message.Urn] 92 | 93 | if !goodUrn { 94 | return 95 | } 96 | 97 | //Expects URN for a service to be in form: 98 | // urn:dmtf-org:service:ServiceName:VersionNum 99 | //Extract "service:ServiceName:VersionNum" 100 | var serviceName string 101 | if strings.Count(message.Urn, ":") < 3 { 102 | serviceName = tag + "-" + message.Urn 103 | } else { 104 | parsedUrn := strings.SplitN(message.Urn, ":", 3) 105 | serviceName = tag + "-" + parsedUrn[2] 106 | } 107 | 108 | // Check to see if service is already registered for this node 109 | node, err := p.Store.Node(message.DeviceId, nil) 110 | if err != nil { 111 | log.Printf("Error checking for node: %s\n", err) 112 | return 113 | } 114 | if node != nil { 115 | for service := range node.Services { 116 | if service == serviceName { 117 | return 118 | 119 | } 120 | } 121 | 122 | } 123 | 124 | // Parse the IP broadcasted for IP and port 125 | agentIP, agentPort, err := extractIPPort(message.Location) 126 | if err != nil { 127 | log.Printf("Error parsing SSDP Message IP: %s\n", err) 128 | return 129 | } 130 | 131 | // Register the service with backend store 132 | err = p.register(agentIP, message.DeviceId, serviceName, agentPort) 133 | if err != nil { 134 | log.Printf("Error: Could not register service %s: %s\n", serviceName, err) 135 | return 136 | } 137 | 138 | if err = p.agentServiceRegister(serviceName, agentIP, "15s", agentPort); err != nil { 139 | log.Printf("Error: Could not register agent service check %s: %s\n", serviceName, err) 140 | return 141 | } 142 | 143 | log.Printf("New Service <%s> was registered for Node %s\n", serviceName, message.DeviceId) 144 | } 145 | 146 | // agentServiceRegister registers a local agent service and its check 147 | func (p *Registry) agentServiceRegister(pluginName, agentIP, interval string, pluginPort int) error { 148 | err := p.Store.ServiceRegister( 149 | ®Store.AgentServiceRegistration{ 150 | ID: pluginName, 151 | Name: pluginName, 152 | Port: pluginPort, 153 | Address: agentIP, 154 | EnableTagOverride: false, 155 | Check: ®Store.AgentServiceCheck{ 156 | HTTP: "http://" + fmt.Sprintf("%s:%d/api/2.0/nodes", agentIP, pluginPort), 157 | Interval: interval, 158 | DeregisterCriticalServiceAfter: "5m", 159 | }, 160 | }, 161 | ) 162 | return err 163 | } 164 | 165 | // register adds a service to the backend store 166 | func (p *Registry) register(agentIP, deviceID, pluginName string, pluginPort int) error { 167 | 168 | // Register node with backend store 169 | err := p.Store.Register( 170 | ®Store.CatalogRegistration{ 171 | Node: deviceID, 172 | Address: agentIP, 173 | Datacenter: p.datacenter, 174 | Service: ®Store.AgentService{ 175 | ID: pluginName, 176 | Service: pluginName, 177 | Port: pluginPort, 178 | Address: agentIP, 179 | }, 180 | Check: ®Store.AgentCheck{ 181 | Node: deviceID, 182 | CheckID: "service:" + pluginName, 183 | Name: "Service '" + pluginName + "' check", 184 | Status: "passing", 185 | ServiceID: pluginName, 186 | ServiceName: pluginName, 187 | }, 188 | }, 189 | nil, 190 | ) 191 | return err 192 | } 193 | 194 | // NewRegistry creates a registry object 195 | func NewRegistry(backend regStore.Backend, dc, backendAddr string) (*Registry, error) { 196 | 197 | r := &Registry{} 198 | 199 | // Create backend store (CONSUL, ETCD, etc) 200 | s, err := libreg.NewRegistry(backend, []string{backendAddr}, nil) 201 | if err != nil { 202 | log.Printf("Error creating backend store: %s\n", err) 203 | return nil, err 204 | } 205 | r.Store = s 206 | 207 | // Create client for capturing SSDP broadcasts 208 | client, err := gossdp.NewSsdp(r) 209 | if err != nil { 210 | log.Println("Failed to start client: ", err) 211 | return nil, err 212 | } 213 | r.ssdpServer = client 214 | 215 | r.start = make(chan bool) 216 | r.wg = &sync.WaitGroup{} 217 | r.searchTerms = make(map[string]string) 218 | r.datacenter = dc 219 | 220 | return r, nil 221 | } 222 | 223 | // Start blocks until Stop is called 224 | func (p *Registry) Start() { 225 | 226 | for { 227 | select { 228 | case <-p.start: 229 | case <-p.stop: 230 | p.wg.Done() 231 | log.Println("Registry stopped listening") 232 | return 233 | } 234 | } 235 | } 236 | 237 | // Run handles Service Registry startup 238 | func (p *Registry) Run() { 239 | p.stop = make(chan bool) 240 | 241 | p.wg.Add(1) 242 | go p.ssdpServer.Start() 243 | 244 | p.wg.Add(1) 245 | go p.Start() 246 | 247 | p.start <- true 248 | 249 | log.Println("Service Registry Starting.") 250 | p.wg.Wait() 251 | } 252 | 253 | // Stop closes a channel that should stop all capture 254 | func (p *Registry) Stop() { 255 | log.Println("Service Registry Stopping.") 256 | 257 | p.ssdpServer.Stop() 258 | p.wg.Done() 259 | close(p.stop) 260 | 261 | p.wg.Wait() 262 | } 263 | -------------------------------------------------------------------------------- /libreg/registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "time" 7 | ) 8 | 9 | // Backend represents a Service Registry Backend 10 | type Backend string 11 | 12 | const ( 13 | // CONSUL backend 14 | CONSUL Backend = "consul" 15 | 16 | // MOCK backend 17 | MOCK Backend = "mock" 18 | ) 19 | 20 | var ( 21 | // ErrBackendNotSupported is thrown when the backend registry is not supported by libreg 22 | ErrBackendNotSupported = errors.New("Backend registry not supported yet, please choose one of") 23 | // ErrCallNotSupported is thrown when a method is not implemented/supported by the current registry 24 | ErrCallNotSupported = errors.New("The current call is not supported with this backend") 25 | // ErrNotReachable is thrown when the API cannot be reached for issuing common registry operations 26 | ErrNotReachable = errors.New("API not reachable") 27 | // ErrNodeNotFound is thrown when the node is not found int he registry 28 | ErrNodeNotFound = errors.New("Node not found in registry") 29 | // ErrSvcNotFound is thrown when the service is not found in the registry 30 | ErrSvcNotFound = errors.New("Service not found in registry") 31 | ) 32 | 33 | // Config contains the options for a registry client 34 | type Config struct { 35 | ClientTLS *ClientTLSConfig 36 | TLS *tls.Config 37 | ConnectionTimeout time.Duration 38 | PersistConnection bool 39 | Username string 40 | Password string 41 | } 42 | 43 | // ClientTLSConfig contains data for a Client TLS configuration in the form 44 | // the etcd client wants it. Eventually we'll adapt it for ZK and Consul. 45 | type ClientTLSConfig struct { 46 | CertFile string 47 | KeyFile string 48 | CACertFile string 49 | } 50 | 51 | // AgentServiceRegistration is used to register a new service 52 | type AgentServiceRegistration struct { 53 | ID string 54 | Name string 55 | Tags []string 56 | Port int 57 | Address string 58 | EnableTagOverride bool 59 | Check *AgentServiceCheck 60 | Checks AgentServiceChecks 61 | } 62 | 63 | // AgentServiceCheck is used to define a node or service level check 64 | type AgentServiceCheck struct { 65 | Script string 66 | DockerContainerID string 67 | Shell string // Only supported for Docker. 68 | Interval string 69 | Timeout string 70 | TTL string 71 | HTTP string 72 | TCP string 73 | Status string 74 | Notes string 75 | TLSSkipVerify bool 76 | 77 | // In Consul 0.7 and later, checks that are associated with a service 78 | // may also contain this optional DeregisterCriticalServiceAfter field, 79 | // which is a timeout in the same Go time format as Interval and TTL. If 80 | // a check is in the critical state for more than this configured value, 81 | // then its associated service (and all of its associated checks) will 82 | // automatically be deregistered. 83 | DeregisterCriticalServiceAfter string 84 | } 85 | 86 | // AgentServiceChecks is an array of AgentServiceCheck 87 | type AgentServiceChecks []*AgentServiceCheck 88 | 89 | // Registry represents the backend registry storage 90 | // Each registry should support every call listed 91 | // here or it cannot be utilized as a registry backend 92 | type Registry interface { 93 | // Registers a new local service 94 | ServiceRegister(serv *AgentServiceRegistration) error 95 | // Register creates a new node, service or check 96 | Register(reg *CatalogRegistration, options *WriteOptions) error 97 | 98 | // Deregister removes a node, service or check 99 | Deregister(dereg *CatalogDeregistration, options *WriteOptions) error 100 | 101 | // Datacenters lists known datacenters 102 | Datacenters() ([]string, error) 103 | 104 | // Nodes lists all nodes in a given DC 105 | Nodes(options *QueryOptions) ([]*Node, error) 106 | 107 | // NodesWatch watches for changes to the nodes list 108 | NodesWatch(options *QueryOptions, stopCh <-chan struct{}) (<-chan []*Node, error) 109 | 110 | // Services lists all services in a given DC 111 | Services(options *QueryOptions) (map[string][]string, error) 112 | 113 | // ServicesWatch watches for changes to the list of services in a given DC 114 | ServicesWatch(options *QueryOptions, stopCh <-chan struct{}) (<-chan map[string][]string, error) 115 | 116 | // Service lists the nodes in a given service 117 | Service(service, tag string, options *QueryOptions) ([]*CatalogService, error) 118 | 119 | // ServiceWatch watches for changes to a service in a given DC 120 | ServiceWatch(service, tag string, options *QueryOptions, stopChan <-chan struct{}) (<-chan []*CatalogService, error) 121 | 122 | // Node lists the services provided by a given node 123 | Node(node string, options *QueryOptions) (*CatalogNode, error) 124 | 125 | // Leader returns the leader of the cluster 126 | Leader() (string, error) 127 | } 128 | 129 | // Node is ... 130 | type Node struct { 131 | Node string 132 | Address string 133 | } 134 | 135 | // CatalogService is ... 136 | type CatalogService struct { 137 | Node string 138 | Address string 139 | ServiceID string 140 | ServiceName string 141 | ServiceAddress string 142 | ServiceTags []string 143 | ServicePort int 144 | ServiceEnableTagOverride bool 145 | } 146 | 147 | // CatalogNode is ... 148 | type CatalogNode struct { 149 | Node *Node 150 | Services map[string]*AgentService 151 | } 152 | 153 | // CatalogRegistration is ... 154 | type CatalogRegistration struct { 155 | Node string 156 | Address string 157 | Datacenter string 158 | Service *AgentService 159 | Check *AgentCheck 160 | } 161 | 162 | //CatalogDeregistration is ... 163 | type CatalogDeregistration struct { 164 | Node string 165 | Address string 166 | Datacenter string 167 | ServiceID string 168 | CheckID string 169 | } 170 | 171 | // WriteOptions are used to parameterize a write 172 | type WriteOptions struct { 173 | // Providing a datacenter overwrites the DC provided 174 | // by the Config 175 | Datacenter string 176 | 177 | // Token is used to provide a per-request ACL token 178 | // which overrides the agent's default token. 179 | Token string 180 | } 181 | 182 | // QueryOptions are used to parameterize a query 183 | type QueryOptions struct { 184 | // Providing a datacenter overwrites the DC provided 185 | // by the Config 186 | Datacenter string 187 | 188 | // AllowStale allows any Consul server (non-leader) to service 189 | // a read. This allows for lower latency and higher throughput 190 | AllowStale bool 191 | 192 | // RequireConsistent forces the read to be fully consistent. 193 | // This is more expensive but prevents ever performing a stale 194 | // read. 195 | RequireConsistent bool 196 | 197 | // WaitIndex is used to enable a blocking query. Waits 198 | // until the timeout or the next index is reached 199 | WaitIndex uint64 200 | 201 | // WaitTime is used to bound the duration of a wait. 202 | // Defaults to that of the Config, but can be overridden. 203 | WaitTime time.Duration 204 | 205 | // Token is used to provide a per-request ACL token 206 | // which overrides the agent's default token. 207 | Token string 208 | 209 | // Near is used to provide a node name that will sort the results 210 | // in ascending order based on the estimated round trip time from 211 | // that node. Setting this to "_agent" will use the agent's node 212 | // for the sort. 213 | Near string 214 | } 215 | 216 | // AgentCheck represents a check known to the agent 217 | type AgentCheck struct { 218 | Node string 219 | CheckID string 220 | Name string 221 | Status string 222 | Notes string 223 | Output string 224 | ServiceID string 225 | ServiceName string 226 | } 227 | 228 | // AgentService represents a service known to the agent 229 | type AgentService struct { 230 | ID string 231 | Service string 232 | Tags []string 233 | Port int 234 | Address string 235 | EnableTagOverride bool 236 | } 237 | -------------------------------------------------------------------------------- /registry/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/RackHD/neighborhood-manager.svg?branch=master)](https://travis-ci.org/RackHD/neighborhood-manager) 2 | 3 | 4 | # Neighborhood Manager Service Registry 5 | The Service Registry is a utility that will sit on the network and listen for services that advertise themselves via [SSDP]. The list of services a user wants to know about can be configured. When an advertisement is seen from a matching service, the Service Registry collects information about the service, and registers it with a storage backend of the user's choosing. Other applications can access this storage for aggregated information about all services on a network. 6 | 7 | The Service Registry must have the ability to see multicast broadcasts from the services that advertise themselves. If there is a need to register services separated in multiple subnets, many instances of the Service Registry can be deployed to each broadcast domain and aggregation will still successfully occur. 8 | 9 | ## Background Information 10 | This section details how to set up a production environment for the Service Registry. If you are interested in a quick demo of the functionality, skip to the Try It Out section to start everything up quickly in containers. 11 | 12 | ### Backend 13 | Some type of storage backend is needed for the Service Registry to store information. At this time, the only backend supported is [Consul]. 14 | 15 | The best practice for using Consul is to set up a cluster of at least three servers on different hosts, and run a client on the same host that the Service Registry will run on. This allows uninterrupted service if one of the servers in the cluster dies, and is more robust than running a server locally on the same host as the Service Registry. 16 | 17 | #### Server 18 | To create a cluster of Consul servers, reference this article on [configuring consul], and see the section **Creating the Bootstrap Configuration**, then follow into the next section **Creating the Regular Server Configuration** 19 | 20 | #### Client 21 | When a cluster is in place, a consul client must be started on the same host that Service Registry will run on. The most extensible way to do this is to put all parameters in a config file, such as `/etc/consul.d/client/config.json`. 22 | ``` 23 | { 24 | "server":false, 25 | "datacenter": "dc1", 26 | "data_dir": "/var/consul", 27 | "log_level": "DEBUG", 28 | "enable_syslog": true, 29 | "start_join": ["192.168.1.1", "192.168.1.2", "192.168.1.3"], 30 | "bind_addr": "192.168.1.4" 31 | } 32 | ``` 33 | Where the three addresses in the `start_join` field are the addresses of the three clustered servers, and the `bind_addr` field is the address of the host machine that this client will run on. 34 | 35 | The consul client can then be started with `consul agent -config-dir /etc/consul.d/client/config.json` 36 | 37 | ## Configuration of URN list 38 | The Service Registry listens for services advertised by [SSDP] messages. These messages contain a URN that identifies the service, and an IP address where the service can be found. 39 | 40 | To configure the list of services that the Service Registry should listen for, open the `registry.json` file in the source code. 41 | The schema for the configuration is: 42 | ``` 43 | { 44 | "ssdp":{ 45 | "tags":{ 46 | "Service-Tag":[ 47 | "ServiceUrn", 48 | "ServiceUrn2" 49 | ], 50 | "ServiceTag2":[ 51 | "ServiceUrn3", 52 | "ServiceUrn4" 53 | ] 54 | } 55 | } 56 | } 57 | ``` 58 | In this file, the `ServiceTag` and `ServiceTag2` identifiers under the `tags` field describe the services, and the `ServiceUrn` listings within them are the URNs found in matching SSDP messages. 59 | 60 | The `registry.json` file found in the source code is currently configured to listen to advertisements from [Inservice-Agent] and [RackHD] 61 | 62 | ## Building 63 | We use docker to build our binaries. The following steps assume a host is installed with the latest docker version. 64 | Download the [source] from GitHub 65 | `git clone https://github.com/RackHD/neighborhood-manager.git` 66 | 67 | Build the dependencies 68 | `make deps` 69 | 70 | Build the Service Registry 71 | `make build` 72 | 73 | If the host system does not have docker installed the following commands can be run to build locally. 74 | 75 | Build the dependencies 76 | `make deps-local` 77 | 78 | Build the Service Registry 79 | `make build-registry-local` 80 | 81 | ## Running 82 | After building, the Service Registry binary (named `registry`) will be in the `bin/` folder of the source directory. To run it from there, copy `registry.json` to that location. 83 | `cp registry/registry.json registry/bin/` 84 | 85 | Move to that directory and run the Service Registry. 86 | ``` 87 | cd registry/bin 88 | ./registry 89 | ``` 90 | 91 | ## Try It Out 92 | The steps in this section will guide you through getting a test/dev environment up and running. Use these steps if you want to see the Service Registry in action. 93 | 94 | ### Prerequisites 95 | * [Git] 96 | * [Docker] 97 | * [Docker-compose] 98 | 99 | ### Starting the environment 100 | 1. Change to a directory where you can clone the source code 101 | 2. `git clone https://github.com/RackHD/neighborhood-manager.git` 102 | 3. `cd registry` 103 | 4. `make run-reg` (Note that this will take some time when running for the first time. Docker has to pull several images from the internet. If any of the downloads fail, simply re-do the `make run-reg` command) 104 | 105 | ### Interacting with the environment 106 | You now have four docker containers running: 107 | * Service Registry 108 | * Consul Client 109 | * Consul Server 110 | * SSDP Spoofer 111 | 112 | The SSDP Spoofer sends out dummy advertisement messages made to look like Inservice-Agent and RackHD. They are received by the Service Registry, passed to the Consul client, and sent to the Consul server for registration. You can interact with the Consul client in the same way the Service Registry does, to retrieve information that has been stored. 113 | 114 | 1. Open a new terminal prompt 115 | 2. Change to the `neighborhood-manager` source directory 116 | 3. `make consul-shell` 117 | 118 | Now you can use the [Consul Catalog API] to interact with the backend storage. For example, to retrieve all services that have been registered: 119 | ``` 120 | root@c1208c34725f:/go/src/github.com/RackHD/neighborhood-manager# curl -s http://consulclient:8500/v1/catalog/services | python -mjson.tool 121 | { 122 | "Inservice-service:agent:0.1": [], 123 | "Inservice-service:catalog-compute:0.1": [], 124 | "Inservice-service:lldp:0.1": [], 125 | "RackHD-device:on-http:1": [], 126 | "RackHD-service:api:1.1": [], 127 | "RackHD-service:api:2.0": [], 128 | "RackHD-service:redfish-rest:1.0": [], 129 | "consul": [] 130 | } 131 | ``` 132 | Notice the URL to which you are sending the request. The Docker containers have been configured in a way such that you can reference the container running the Consul client by name, even though the IP address of the container will change between runs. Port 8500 is the default port for HTTP communications in Consul. 133 | 134 | 135 | As another example, to see all nodes that are offering the RackHD 2.0 API service: 136 | ``` 137 | root@50283b1b6a03:/go/src/github.com/RackHD/neighborhood-manager# curl http://consulclient:8500/v1/catalog/service/"RackHD-service:api:2.0" | python -mjson.tool 138 | [ 139 | { 140 | "Address": "192.168.1.1", 141 | "CreateIndex": 11, 142 | "ModifyIndex": 11, 143 | "Node": "228617159241066160", 144 | "ServiceAddress": "192.168.1.1", 145 | "ServiceEnableTagOverride": false, 146 | "ServiceID": "RackHD-service:api:2.0", 147 | "ServiceName": "RackHD-service:api:2.0", 148 | "ServicePort": 65535, 149 | "ServiceTags": [] 150 | } 151 | ] 152 | ``` 153 | When you are done, run `exit` to stop the shell container, and `docker-compose kill` to stop the four containers running the Service Registry and its environment. 154 | 155 | [Docker-compose]: https://docs.docker.com/compose/install/ 156 | [Consul Catalog API]: https://www.consul.io/docs/agent/http/catalog.html 157 | [Consul]: https://www.consul.io/downloads.html 158 | [joined together]: https://www.consul.io/docs/guides/servers.html 159 | [agent]: https://www.consul.io/docs/agent/basics.html 160 | [SSDP]: https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol 161 | [Inservice-Agent]: https://github.com/RackHD/InService 162 | [RackHD]: https://github.com/RackHD 163 | [configuring consul]: https://www.digitalocean.com/community/tutorials/how-to-configure-consul-in-a-production-environment-on-ubuntu-14-04 164 | [source]: https://github.com/RackHD/neighborhood-manager 165 | [Git]: https://git-scm.com/ 166 | [Docker]: https://docs.docker.com/engine/installation/ 167 | -------------------------------------------------------------------------------- /rackhd/models/rackhd_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docker/libkv" 6 | "github.com/docker/libkv/store" 7 | "github.com/docker/libkv/store/boltdb" 8 | . "github.com/onsi/ginkgo" 9 | . "github.com/onsi/gomega" 10 | "os" 11 | ) 12 | 13 | var _ = Describe("RackHD Models", func() { 14 | var rhd1UUID string 15 | var rhd1HTTP string 16 | var rhd1AMQP string 17 | var rhd2UUID string 18 | var rhd2HTTP string 19 | var rhd2AMQP string 20 | var rhd3UUID string 21 | var rhd3HTTP string 22 | var rhd3AMQP string 23 | var rhd *RackHD 24 | 25 | wd, _ := os.Getwd() 26 | testDB := wd + "/test.db" 27 | 28 | BeforeEach(func() { 29 | boltdb.Register() 30 | var err error 31 | db, err = libkv.NewStore( 32 | store.BOLTDB, 33 | []string{testDB}, 34 | &store.Config{ 35 | PersistConnection: true, 36 | Bucket: "test", 37 | }, 38 | ) 39 | if err != nil { 40 | fmt.Printf("Failed to init DB!\n") 41 | } 42 | }) 43 | 44 | AfterEach(func() { 45 | db.Close() 46 | _ = os.Remove(testDB) 47 | }) 48 | 49 | Describe("Creating RackHD Structs", func() { 50 | var err error 51 | BeforeEach(func() { 52 | rhd1UUID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 53 | rhd1HTTP = "http://10.10.10.10:2020" 54 | rhd1AMQP = "amqp://localhost/" 55 | }) 56 | 57 | JustBeforeEach(func() { 58 | rhd, err = NewRhd(rhd1UUID, rhd1HTTP, rhd1AMQP) 59 | }) 60 | 61 | Context("when valid data is provided", func() { 62 | It("should return a RackHD struct", func() { 63 | Expect(rhd).Should(BeAssignableToTypeOf(&RackHD{})) 64 | }) 65 | 66 | It("should populate the fields correctly", func() { 67 | Expect(rhd.HTTPConf).Should(BeAssignableToTypeOf(HTTPConfig{})) 68 | Expect(rhd.AmqpConf).Should(BeAssignableToTypeOf(AmqpConfig{})) 69 | Expect(rhd.ID).To(Equal(rhd1UUID)) 70 | Expect(rhd.HTTPConf.URL.String()).To(Equal(rhd1HTTP)) 71 | Expect(rhd.AmqpConf.URI.String()).To(Equal(rhd1AMQP)) 72 | }) 73 | 74 | It("should not have errored", func() { 75 | Expect(err).NotTo(HaveOccurred()) 76 | }) 77 | }) 78 | 79 | Context("when invalid http data is provided", func() { 80 | BeforeEach(func() { 81 | rhd1UUID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 82 | rhd1HTTP = "^$%#$#$" 83 | rhd1AMQP = "amqp://localhost/" 84 | }) 85 | 86 | It("should return a partial RackHD struct", func() { 87 | Expect(rhd).Should(BeAssignableToTypeOf(&RackHD{})) 88 | }) 89 | 90 | It("should have errored", func() { 91 | Expect(err).To(HaveOccurred()) 92 | }) 93 | 94 | }) 95 | 96 | Context("when invalid amqp data is provided", func() { 97 | BeforeEach(func() { 98 | rhd1UUID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 99 | rhd1HTTP = "http://10.10.10.10:2020" 100 | rhd1AMQP = "312" 101 | }) 102 | 103 | It("should return a partial RackHD struct", func() { 104 | Expect(rhd).Should(BeAssignableToTypeOf(&RackHD{})) 105 | }) 106 | 107 | It("should populate the HTTP field correctly", func() { 108 | Expect(rhd.HTTPConf).Should(BeAssignableToTypeOf(HTTPConfig{})) 109 | Expect(rhd.AmqpConf).Should(BeAssignableToTypeOf(AmqpConfig{})) 110 | Expect(rhd.HTTPConf.URL.String()).To(Equal(rhd1HTTP)) 111 | Expect(rhd.AmqpConf.URI.String()).NotTo(Equal(rhd1AMQP)) 112 | }) 113 | 114 | It("should have errored", func() { 115 | Expect(err).To(HaveOccurred()) 116 | }) 117 | 118 | }) 119 | }) 120 | 121 | Describe("Creating RackHD Instances", func() { 122 | var err error 123 | 124 | BeforeEach(func() { 125 | rhd1UUID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 126 | rhd1HTTP = "http://10.10.10.10:2020" 127 | rhd1AMQP = "amqp://localhost/" 128 | }) 129 | 130 | AfterEach(func() { 131 | db.DeleteTree("rhdman/rhd/" + rhd1UUID) 132 | }) 133 | 134 | JustBeforeEach(func() { 135 | rhd, err = NewRhd(rhd1UUID, rhd1HTTP, rhd1AMQP) 136 | if err != nil { 137 | Fail("Could not create object") 138 | } 139 | err = CreateRhd(rhd) 140 | }) 141 | 142 | Context("when valid data is provided", func() { 143 | It("should create an instance without error", func() { 144 | Expect(err).ToNot(HaveOccurred()) 145 | }) 146 | 147 | It("should populate the DB with the correct values", func() { 148 | pair, err := db.Get("rhdman/rhd/" + rhd1UUID + "/" + "httpConf") 149 | if err != nil { 150 | Fail("Unable to get value from the DB for testing") 151 | } 152 | Expect(string(pair.Value)).To(Equal(rhd1HTTP)) 153 | pair, err = db.Get("rhdman/rhd/" + rhd1UUID + "/" + "amqpConf") 154 | if err != nil { 155 | Fail("Unable to get value from the DB for testing") 156 | } 157 | Expect(string(pair.Value)).To(Equal(rhd1AMQP)) 158 | }) 159 | }) 160 | }) 161 | 162 | Describe("Retrieving RackHD Instances", func() { 163 | BeforeEach(func() { 164 | rhd1UUID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 165 | rhd1HTTP = "http://10.10.10.10:2020" 166 | rhd1AMQP = "amqp://localhost/" 167 | rhd1, _ := NewRhd(rhd1UUID, rhd1HTTP, rhd1AMQP) 168 | _ = CreateRhd(rhd1) 169 | rhd2UUID = "2e650685-bbe7-44f6-9c66-bd466d1bc1ab" 170 | rhd2HTTP = "http://20.20.20.20:4020" 171 | rhd2AMQP = "amqp://localhost/" 172 | rhd2, _ := NewRhd(rhd2UUID, rhd2HTTP, rhd2AMQP) 173 | _ = CreateRhd(rhd2) 174 | rhd3UUID = "6c04c204-5c5c-4daf-9d68-7e3f2c76de2d" 175 | rhd3HTTP = "http://30.30.30.30:6020" 176 | rhd3AMQP = "amqp://localhost/" 177 | rhd3, _ := NewRhd(rhd3UUID, rhd3HTTP, rhd3AMQP) 178 | _ = CreateRhd(rhd3) 179 | 180 | }) 181 | 182 | AfterEach(func() { 183 | db.DeleteTree("rhdman/rhd/" + rhd1UUID) 184 | db.DeleteTree("rhdman/rhd/" + rhd2UUID) 185 | db.DeleteTree("rhdman/rhd/" + rhd3UUID) 186 | }) 187 | 188 | Context("Getting All RackHD Instances", func() { 189 | 190 | It("should not have errored", func() { 191 | _, err := GetAllRhd() 192 | Expect(err).ToNot(HaveOccurred()) 193 | }) 194 | 195 | It("should get all instances in the DB", func() { 196 | rhds, _ := GetAllRhd() 197 | Expect(len(rhds)).To(Equal(3)) 198 | }) 199 | }) 200 | 201 | Context("Getting RackHD by ID", func() { 202 | 203 | It("should not have errored", func() { 204 | _, err := GetRhdByID(rhd2UUID) 205 | Expect(err).ToNot(HaveOccurred()) 206 | }) 207 | 208 | It("should return the right RHD", func() { 209 | rhd, _ := GetRhdByID(rhd2UUID) 210 | Expect(rhd.ID).To(Equal(rhd2UUID)) 211 | Expect(rhd.HTTPConf.URL.String()).To(Equal(rhd2HTTP)) 212 | Expect(rhd.AmqpConf.URI.String()).To(Equal(rhd2AMQP)) 213 | }) 214 | }) 215 | 216 | Context("Getting multiple RackHDs by multiple IDs", func() { 217 | 218 | It("should not have errored", func() { 219 | arr := []string{rhd2UUID, rhd3UUID} 220 | _, err := GetRhdsByIDs(arr) 221 | Expect(err).ToNot(HaveOccurred()) 222 | }) 223 | 224 | It("should return only 2 RHDs", func() { 225 | arr := []string{rhd2UUID, rhd3UUID} 226 | rhds, _ := GetRhdsByIDs(arr) 227 | Expect(len(rhds)).To(Equal(2)) 228 | }) 229 | }) 230 | }) 231 | 232 | Describe("Deleting RackHD Instances", func() { 233 | var err error 234 | 235 | BeforeEach(func() { 236 | rhd1UUID = "6ba7b810-9dad-11d1-80b4-00c04fd430c8" 237 | rhd1HTTP = "http://10.10.10.10:2020" 238 | rhd1AMQP = "amqp://localhost/" 239 | rhd1, _ := NewRhd(rhd1UUID, rhd1HTTP, rhd1AMQP) 240 | _ = CreateRhd(rhd1) 241 | rhd2UUID = "2e650685-bbe7-44f6-9c66-bd466d1bc1ab" 242 | rhd2HTTP = "http://20.20.20.20:4020" 243 | rhd2AMQP = "amqp://localhost/" 244 | rhd2, _ := NewRhd(rhd2UUID, rhd2HTTP, rhd2AMQP) 245 | _ = CreateRhd(rhd2) 246 | rhd3UUID = "6c04c204-5c5c-4daf-9d68-7e3f2c76de2d" 247 | rhd3HTTP = "http://30.30.30.30:6020" 248 | rhd3AMQP = "amqp://localhost/" 249 | rhd3, _ := NewRhd(rhd3UUID, rhd3HTTP, rhd3AMQP) 250 | _ = CreateRhd(rhd3) 251 | }) 252 | 253 | AfterEach(func() { 254 | db.DeleteTree("rhdman/rhd/" + rhd1UUID) 255 | db.DeleteTree("rhdman/rhd/" + rhd2UUID) 256 | db.DeleteTree("rhdman/rhd/" + rhd3UUID) 257 | }) 258 | 259 | Context("Deleting RackHD Instance by ID", func() { 260 | It("should not have errored", func() { 261 | err = DeleteRhdByID(rhd1UUID) 262 | Expect(err).ToNot(HaveOccurred()) 263 | }) 264 | 265 | It("should have removed the instance", func() { 266 | _ = DeleteRhdByID(rhd1UUID) 267 | rhds, _ := GetAllRhd() 268 | Expect(len(rhds)).To(Equal(2)) 269 | }) 270 | }) 271 | 272 | Context("Deleting multiple RackHDs by multiple IDs", func() { 273 | It("should not have errored", func() { 274 | arr := []string{rhd2UUID, rhd3UUID} 275 | err := DeleteRhdsByIDs(arr) 276 | Expect(err).ToNot(HaveOccurred()) 277 | }) 278 | 279 | It("should have removed multiple instances", func() { 280 | arr := []string{rhd2UUID, rhd3UUID} 281 | _ = DeleteRhdsByIDs(arr) 282 | rhds, _ := GetAllRhd() 283 | Expect(len(rhds)).To(Equal(1)) 284 | }) 285 | }) 286 | }) 287 | }) 288 | -------------------------------------------------------------------------------- /registry/registry_test.go: -------------------------------------------------------------------------------- 1 | package registry_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "strconv" 8 | "time" 9 | 10 | libreg "github.com/RackHD/neighborhood-manager/libreg/registry" 11 | "github.com/RackHD/neighborhood-manager/libreg/registry/mock" 12 | "github.com/RackHD/neighborhood-manager/registry" 13 | "github.com/king-jam/gossdp" 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | ) 17 | 18 | var _ = Describe("Registry", func() { 19 | 20 | var ( 21 | backendAddr string 22 | datacenter string 23 | ) 24 | BeforeEach(func() { 25 | backendAddr = "127.0.0.1:8500" 26 | datacenter = "dc-test" 27 | }) 28 | 29 | Describe("NewRegistry", func() { 30 | var ( 31 | badBackend libreg.Backend 32 | ) 33 | 34 | It("should return a Registry struct using mock backend", func() { 35 | var r *registry.Registry 36 | 37 | mock.Register() 38 | r, _ = registry.NewRegistry(libreg.MOCK, datacenter, backendAddr) 39 | Expect(r).To(BeAssignableToTypeOf(®istry.Registry{})) 40 | }) 41 | It("should reject invalid backends", func() { 42 | badBackend = "BADBACKEND" 43 | 44 | mock.Register() 45 | _, err := registry.NewRegistry(badBackend, datacenter, backendAddr) 46 | Expect(err).To(HaveOccurred()) 47 | }) 48 | }) 49 | 50 | Describe("SSDP URN Management", func() { 51 | 52 | It("should add a new URN to the list", func() { 53 | var r *registry.Registry 54 | mock.Register() 55 | r, _ = registry.NewRegistry(libreg.MOCK, datacenter, backendAddr) 56 | 57 | r.RemoveSearchTerm("URN:DUMMY:INFO") 58 | r.AddSearchTerm("URN:DUMMY:INFO", "TEST DATA") 59 | st := r.GetSearchTerms() 60 | Expect(st["URN:DUMMY:INFO"]).To(Equal("TEST DATA")) 61 | }) 62 | 63 | It("should remove a URN from the list", func() { 64 | var r *registry.Registry 65 | mock.Register() 66 | r, _ = registry.NewRegistry(libreg.MOCK, datacenter, backendAddr) 67 | 68 | r.AddSearchTerm("URN:DUMMY:INFO", "TEST DATA") 69 | r.RemoveSearchTerm("URN:DUMMY:INFO") 70 | st := r.GetSearchTerms() 71 | Expect(st["URN:DUMMY:INFO"]).To(Equal("")) 72 | }) 73 | }) 74 | 75 | Describe("Service Registration", func() { 76 | var ( 77 | r *registry.Registry 78 | timeout time.Duration 79 | pollingInterval time.Duration 80 | urnIS string 81 | urnRHD string 82 | ipRHD string 83 | ipIS string 84 | rGen *rand.Rand 85 | uuidRHD string 86 | uuidIS string 87 | err error 88 | ssdp *gossdp.Ssdp 89 | serverIS gossdp.AdvertisableServer 90 | serviceNameIS string 91 | serviceTagIS string 92 | serverRHD gossdp.AdvertisableServer 93 | serviceNameRHD string 94 | serviceTagRHD string 95 | ) 96 | 97 | BeforeEach(func() { 98 | // Timeout of 5s because default 1s isn't long enough for SSDP message to be seen 99 | timeout = time.Second * 5 100 | 101 | // Poll at 500ms because default 10ms is too fast to be useful 102 | pollingInterval = time.Millisecond * 500 103 | 104 | mock.Register() 105 | r, _ = registry.NewRegistry(libreg.MOCK, datacenter, backendAddr) 106 | urnIS = "urn:schemas-upnp-org:service:agent:0.1" 107 | 108 | ipIS = "192.168.1.1:65535" 109 | rGen = rand.New(rand.NewSource(time.Now().UnixNano())) 110 | uuidIS = strconv.Itoa(rGen.Int()) 111 | 112 | urnRHD = "urn:schemas-upnp-org:service:api:2.0" 113 | 114 | ipRHD = "192.168.1.1:65535" 115 | uuidRHD = strconv.Itoa(rGen.Int()) 116 | 117 | ssdp, err = gossdp.NewSsdp(nil) 118 | if err != nil { 119 | log.Printf("err: %s\n", err) 120 | } 121 | Expect(err).NotTo(HaveOccurred()) 122 | 123 | serverIS = gossdp.AdvertisableServer{ 124 | ServiceType: urnIS, 125 | DeviceUuid: uuidIS, 126 | Location: fmt.Sprintf("%s%s%s", "http://", ipIS, "/fakepath"), 127 | MaxAge: 2, 128 | } 129 | serviceNameIS = "Inservice-service:agent:0.1" 130 | serviceTagIS = "Inservice" 131 | 132 | serverRHD = gossdp.AdvertisableServer{ 133 | ServiceType: urnRHD, 134 | DeviceUuid: uuidRHD, 135 | Location: fmt.Sprintf("%s%s%s", "http://", ipRHD, "/fakepath"), 136 | MaxAge: 2, 137 | } 138 | serviceNameRHD = "RackHD-service:api:2.0" 139 | serviceTagRHD = "RackHD" 140 | }) 141 | 142 | It("should successfully register an RackHD-on-http service", func() { 143 | ssdp.AdvertiseServer(serverRHD) 144 | go ssdp.Start() 145 | 146 | // Start the test and wait for SSDP message to be seen 147 | r.AddSearchTerm(urnRHD, serviceTagRHD) 148 | go r.Run() 149 | 150 | Eventually(func() bool { 151 | services, _ := r.Store.Services(nil) 152 | return services[serviceNameRHD] != nil 153 | }, timeout, pollingInterval).Should(BeTrue()) 154 | 155 | Consistently(func() bool { 156 | service, _ := r.Store.Service(serviceNameRHD, "", nil) 157 | return service[0].ServiceName == serviceNameRHD 158 | }, timeout, pollingInterval).Should(BeTrue()) 159 | 160 | ssdp.Stop() 161 | r.Stop() 162 | log.Println("Finished test: Register RHD successfully") 163 | 164 | }) 165 | 166 | It("should successfully register an Inservive-Agent service", func() { 167 | ssdp.AdvertiseServer(serverIS) 168 | go ssdp.Start() 169 | 170 | // Start the test and wait for SSDP message to be seen 171 | r.AddSearchTerm(urnIS, serviceTagIS) 172 | go r.Run() 173 | 174 | Eventually(func() bool { 175 | services, _ := r.Store.Services(nil) 176 | return services[serviceNameIS] != nil 177 | }, timeout, pollingInterval).Should(BeTrue()) 178 | 179 | Consistently(func() bool { 180 | service, _ := r.Store.Service(serviceNameIS, "", nil) 181 | return service[0].ServiceName == serviceNameIS 182 | }, timeout, pollingInterval).Should(BeTrue()) 183 | 184 | ssdp.Stop() 185 | r.Stop() 186 | log.Println("Finished test: Register IA successfully") 187 | 188 | }) 189 | 190 | It("should handle badly formed URNs if they're whitelisted", func() { 191 | serviceNameIS = "Inservice-urn:MalformedData" 192 | urnIS = "urn:MalformedData" 193 | serverIS.ServiceType = urnIS 194 | 195 | ssdp.AdvertiseServer(serverIS) 196 | go ssdp.Start() 197 | 198 | // Start the test and wait for SSDP message to be seen 199 | r.AddSearchTerm(urnIS, serviceTagIS) 200 | go r.Run() 201 | 202 | Eventually(func() bool { 203 | services, _ := r.Store.Services(nil) 204 | return services[serviceNameIS] != nil 205 | }, timeout, pollingInterval).Should(BeTrue()) 206 | 207 | Consistently(func() bool { 208 | service, _ := r.Store.Service(serviceNameIS, "", nil) 209 | return service[0].ServiceName == serviceNameIS 210 | }, timeout, pollingInterval).Should(BeTrue()) 211 | 212 | r.Stop() 213 | ssdp.Stop() 214 | log.Println("Finished test: Handle Malformed URN") 215 | 216 | }) 217 | 218 | It("should ignore SSDP messages with unknown URNs", func() { 219 | 220 | ssdp.AdvertiseServer(serverIS) 221 | go ssdp.Start() 222 | 223 | // Start the test and wait for SSDP message to be seen 224 | urnIS = "urn:BAD:URN" 225 | r.AddSearchTerm(urnIS, serviceTagIS) 226 | go r.Run() 227 | 228 | Consistently(func() bool { 229 | services, _ := r.Store.Services(nil) 230 | return services[serviceNameIS] == nil 231 | }, timeout, pollingInterval).Should(BeTrue()) 232 | 233 | r.Stop() 234 | ssdp.Stop() 235 | log.Println("Finished test: Ignore unknown URN") 236 | 237 | }) 238 | 239 | It("should fail to parse SSDP messages with a bad IP address", func() { 240 | 241 | badIP := "192.168.1165535" 242 | badScheme := "://" 243 | 244 | serverIS.Location = fmt.Sprintf("%s%s%s", "http://", badIP, "/fakepath") 245 | ssdp.AdvertiseServer(serverIS) 246 | 247 | noSchemeServer := serverIS 248 | noSchemeServer.Location = fmt.Sprintf("%s%s%s", badScheme, ipIS, "/fakepath") 249 | ssdp.AdvertiseServer(noSchemeServer) 250 | go ssdp.Start() 251 | 252 | r.AddSearchTerm(urnIS, serviceTagIS) 253 | 254 | // Start the test and wait for SSDP message to be seen 255 | go r.Run() 256 | 257 | Consistently(func() bool { 258 | services, _ := r.Store.Services(nil) 259 | return services[serviceNameIS] == nil 260 | }, timeout, pollingInterval).Should(BeTrue()) 261 | 262 | r.Stop() 263 | ssdp.Stop() 264 | log.Println("Finished test: Fail to parse bad IP") 265 | 266 | }) 267 | 268 | It("should fail to parse SSDP messages with a bad port", func() { 269 | 270 | ipIS := "192.168.1.1:INVALIDPORT" 271 | serverIS.Location = fmt.Sprintf("%s%s%s", "http://", ipIS, "/fakepath") 272 | ssdp.AdvertiseServer(serverIS) 273 | go ssdp.Start() 274 | 275 | // Start the test and wait for SSDP message to be seen 276 | r.AddSearchTerm(urnIS, serviceTagIS) 277 | go r.Run() 278 | 279 | Consistently(func() bool { 280 | services, _ := r.Store.Services(nil) 281 | return services[serviceNameIS] == nil 282 | }, timeout, pollingInterval).Should(BeTrue()) 283 | 284 | r.Stop() 285 | ssdp.Stop() 286 | log.Println("Finished test: Fail to parse bad port") 287 | 288 | }) 289 | 290 | It("should fail to register SSDP messages with no Node UUID", func() { 291 | 292 | serverIS.DeviceUuid = "" 293 | ssdp.AdvertiseServer(serverIS) 294 | go ssdp.Start() 295 | 296 | // Start the test and wait for SSDP message to be seen 297 | r.AddSearchTerm(urnIS, serviceTagIS) 298 | go r.Run() 299 | 300 | Consistently(func() bool { 301 | services, _ := r.Store.Services(nil) 302 | return services[serviceNameIS] == nil 303 | }, timeout, pollingInterval).Should(BeTrue()) 304 | 305 | r.Stop() 306 | ssdp.Stop() 307 | log.Println("Finished test: Fail to register message without UUID") 308 | 309 | }) 310 | }) 311 | }) 312 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /registry/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /libreg/registry/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "net/http" 7 | "sync" 8 | "time" 9 | 10 | "github.com/RackHD/neighborhood-manager/libreg" 11 | "github.com/RackHD/neighborhood-manager/libreg/registry" 12 | "github.com/hashicorp/consul/api" 13 | ) 14 | 15 | const ( 16 | // DefaultWatchWaitTime is how long we will block when watching 17 | // for services or nodes to change/update 18 | DefaultWatchWaitTime = 15 * time.Second 19 | ) 20 | 21 | var ( 22 | // ErrMultipleEndpointsUnsupported is thrown when there are 23 | // multiple endpoints specified for Consul 24 | ErrMultipleEndpointsUnsupported = errors.New("consul does not support multiple endpoints") 25 | 26 | // ErrSessionRenew is thrown when the session can't be 27 | // renewed because the Consul version does not support sessions 28 | ErrSessionRenew = errors.New("cannot set or renew session for ttl, unable to operate on sessions") 29 | ) 30 | 31 | // Consul is the receiver type for the 32 | // Store interface 33 | type Consul struct { 34 | sync.Mutex 35 | config *api.Config 36 | client *api.Client 37 | } 38 | 39 | // Register registers consul to libreg 40 | func Register() { 41 | libreg.AddRegistry(registry.CONSUL, New) 42 | } 43 | 44 | // New creates a new Consul client given a list 45 | // of endpoints and optional tls config 46 | func New(endpoints []string, options *registry.Config) (registry.Registry, error) { 47 | if len(endpoints) > 1 { 48 | return nil, ErrMultipleEndpointsUnsupported 49 | } 50 | 51 | s := &Consul{} 52 | 53 | // Create Consul client 54 | config := api.DefaultConfig() 55 | s.config = config 56 | config.HttpClient = http.DefaultClient 57 | config.Address = endpoints[0] 58 | config.Scheme = "http" 59 | 60 | // Set options 61 | if options != nil { 62 | if options.TLS != nil { 63 | s.setTLS(options.TLS) 64 | } 65 | if options.ConnectionTimeout != 0 { 66 | s.setTimeout(options.ConnectionTimeout) 67 | } 68 | } 69 | 70 | // Creates a new client 71 | client, err := api.NewClient(config) 72 | if err != nil { 73 | return nil, err 74 | } 75 | s.client = client 76 | 77 | return s, nil 78 | } 79 | 80 | // SetTLS sets Consul TLS options 81 | func (s *Consul) setTLS(tls *tls.Config) { 82 | s.config.HttpClient.Transport = &http.Transport{ 83 | TLSClientConfig: tls, 84 | } 85 | s.config.Scheme = "https" 86 | } 87 | 88 | // SetTimeout sets the timeout for connecting to Consul 89 | func (s *Consul) setTimeout(time time.Duration) { 90 | s.config.WaitTime = time 91 | } 92 | 93 | // ServiceRegister add a local agent service and its check 94 | func (s *Consul) ServiceRegister(serv *registry.AgentServiceRegistration) error { 95 | 96 | err := s.client.Agent().ServiceRegister( 97 | &api.AgentServiceRegistration{ 98 | ID: serv.ID, 99 | Name: serv.Name, 100 | Tags: serv.Tags, 101 | Port: serv.Port, 102 | Address: serv.Address, 103 | EnableTagOverride: serv.EnableTagOverride, 104 | Check: &api.AgentServiceCheck{ 105 | HTTP: serv.Check.HTTP, 106 | Interval: serv.Check.Interval, 107 | DeregisterCriticalServiceAfter: serv.Check.DeregisterCriticalServiceAfter, 108 | }, 109 | }, 110 | ) 111 | return err 112 | } 113 | 114 | // Register adds an entry into the consul backend 115 | func (s *Consul) Register(reg *registry.CatalogRegistration, options *registry.WriteOptions) error { 116 | catalog := s.client.Catalog() 117 | writeOps := s.getWriteOptions(options) 118 | _, err := catalog.Register( 119 | &api.CatalogRegistration{ 120 | Node: reg.Node, 121 | Address: reg.Address, 122 | Datacenter: reg.Datacenter, 123 | Service: &api.AgentService{ 124 | ID: reg.Service.ID, 125 | Service: reg.Service.Service, 126 | Tags: reg.Service.Tags, 127 | Port: reg.Service.Port, 128 | Address: reg.Service.Address, 129 | EnableTagOverride: reg.Service.EnableTagOverride, 130 | }, 131 | Check: &api.AgentCheck{ 132 | Node: reg.Check.Node, 133 | CheckID: reg.Check.CheckID, 134 | Name: reg.Check.Name, 135 | Status: reg.Check.Status, 136 | Notes: reg.Check.Notes, 137 | Output: reg.Check.Output, 138 | ServiceID: reg.Check.ServiceID, 139 | ServiceName: reg.Check.ServiceName, 140 | }, 141 | }, 142 | writeOps, 143 | ) 144 | return err 145 | } 146 | 147 | // Deregister removes a node, service or check 148 | func (s *Consul) Deregister(dereg *registry.CatalogDeregistration, options *registry.WriteOptions) error { 149 | catalog := s.client.Catalog() 150 | writeOps := s.getWriteOptions(options) 151 | _, err := catalog.Deregister( 152 | &api.CatalogDeregistration{ 153 | Node: dereg.Node, 154 | Address: dereg.Address, 155 | Datacenter: dereg.Datacenter, 156 | ServiceID: dereg.ServiceID, 157 | CheckID: dereg.CheckID, 158 | }, 159 | writeOps, 160 | ) 161 | return err 162 | } 163 | 164 | // Datacenters lists known datacenters 165 | func (s *Consul) Datacenters() ([]string, error) { 166 | catalog := s.client.Catalog() 167 | dc, err := catalog.Datacenters() 168 | return dc, err 169 | } 170 | 171 | // Nodes lists all nodes in a given DC 172 | func (s *Consul) Nodes(options *registry.QueryOptions) ([]*registry.Node, error) { 173 | catalog := s.client.Catalog() 174 | queryOps := s.getQueryOptions(options) 175 | nodes, _, err := catalog.Nodes(queryOps) 176 | var retNodes []*registry.Node 177 | for _, v := range nodes { 178 | retNodes = append(retNodes, ®istry.Node{ 179 | Node: v.Node, 180 | Address: v.Address, 181 | }) 182 | } 183 | return retNodes, err 184 | } 185 | 186 | // NodesWatch watches for changes to the nodes in a given DC 187 | func (s *Consul) NodesWatch(options *registry.QueryOptions, stopChan <-chan struct{}) (<-chan []*registry.Node, error) { 188 | catalog := s.client.Catalog() 189 | queryOps := s.getQueryOptions(options) 190 | watchCh := make(chan []*registry.Node) 191 | 192 | go func() { 193 | defer close(watchCh) 194 | 195 | // Override the wait time option to create the watch 196 | queryOps.WaitTime = DefaultWatchWaitTime 197 | 198 | for { 199 | select { 200 | case <-stopChan: 201 | return 202 | default: 203 | } 204 | 205 | nodes, meta, err := catalog.Nodes(queryOps) 206 | if err != nil { 207 | return 208 | } 209 | 210 | // If LastIndex didn't change then it means `Get` returned 211 | // because of the WaitTime and the key didn't change. 212 | if queryOps.WaitIndex == meta.LastIndex { 213 | continue 214 | } 215 | 216 | queryOps.WaitIndex = meta.LastIndex 217 | 218 | var retNodes []*registry.Node 219 | for _, v := range nodes { 220 | retNodes = append(retNodes, ®istry.Node{ 221 | Node: v.Node, 222 | Address: v.Address, 223 | }) 224 | } 225 | 226 | watchCh <- retNodes 227 | } 228 | }() 229 | 230 | return watchCh, nil 231 | } 232 | 233 | // Services lists all services in a given DC 234 | func (s *Consul) Services(options *registry.QueryOptions) (map[string][]string, error) { 235 | catalog := s.client.Catalog() 236 | queryOps := s.getQueryOptions(options) 237 | services, _, err := catalog.Services(queryOps) 238 | return services, err 239 | } 240 | 241 | // ServicesWatch watches for changes to the list of services in a given DC 242 | func (s *Consul) ServicesWatch(options *registry.QueryOptions, stopChan <-chan struct{}) (<-chan map[string][]string, error) { 243 | catalog := s.client.Catalog() 244 | queryOps := s.getQueryOptions(options) 245 | watchCh := make(chan map[string][]string) 246 | 247 | go func() { 248 | defer close(watchCh) 249 | 250 | // Override the wait time option to create the watch 251 | queryOps.WaitTime = DefaultWatchWaitTime 252 | 253 | for { 254 | select { 255 | case <-stopChan: 256 | return 257 | default: 258 | } 259 | 260 | services, meta, err := catalog.Services(queryOps) 261 | if err != nil { 262 | return 263 | } 264 | 265 | // If LastIndex didn't change then it means `Get` returned 266 | // because of the WaitTime and the key didn't change. 267 | if queryOps.WaitIndex == meta.LastIndex { 268 | continue 269 | } 270 | 271 | queryOps.WaitIndex = meta.LastIndex 272 | 273 | if services != nil { 274 | watchCh <- services 275 | } 276 | } 277 | }() 278 | 279 | return watchCh, nil 280 | } 281 | 282 | // Service lists the nodes in a given service 283 | func (s *Consul) Service(service, tag string, options *registry.QueryOptions) ([]*registry.CatalogService, error) { 284 | catalog := s.client.Catalog() 285 | queryOps := s.getQueryOptions(options) 286 | services, _, err := catalog.Service( 287 | service, 288 | tag, 289 | queryOps) 290 | var retServices []*registry.CatalogService 291 | for _, v := range services { 292 | retServices = append(retServices, ®istry.CatalogService{ 293 | Node: v.Node, 294 | Address: v.Address, 295 | ServiceID: v.ServiceID, 296 | ServiceName: v.ServiceName, 297 | ServiceAddress: v.ServiceAddress, 298 | ServiceTags: v.ServiceTags, 299 | ServicePort: v.ServicePort, 300 | ServiceEnableTagOverride: v.ServiceEnableTagOverride, 301 | }) 302 | } 303 | return retServices, err 304 | } 305 | 306 | // ServiceWatch watches for changes to the list of nodes under a given service 307 | func (s *Consul) ServiceWatch(service, tag string, options *registry.QueryOptions, stopChan <-chan struct{}) (<-chan []*registry.CatalogService, error) { 308 | catalog := s.client.Catalog() 309 | queryOps := s.getQueryOptions(options) 310 | watchCh := make(chan []*registry.CatalogService) 311 | 312 | go func() { 313 | defer close(watchCh) 314 | 315 | // Override the wait time option to create the watch 316 | queryOps.WaitTime = DefaultWatchWaitTime 317 | 318 | for { 319 | select { 320 | case <-stopChan: 321 | return 322 | default: 323 | } 324 | 325 | services, meta, err := catalog.Service( 326 | service, 327 | tag, 328 | queryOps) 329 | if err != nil { 330 | return 331 | } 332 | 333 | // If LastIndex didn't change then it means `Get` returned 334 | // because of the WaitTime and the key didn't change. 335 | if queryOps.WaitIndex == meta.LastIndex { 336 | continue 337 | } 338 | 339 | queryOps.WaitIndex = meta.LastIndex 340 | 341 | var retServices []*registry.CatalogService 342 | for _, v := range services { 343 | retServices = append(retServices, ®istry.CatalogService{ 344 | Node: v.Node, 345 | Address: v.Address, 346 | ServiceID: v.ServiceID, 347 | ServiceName: v.ServiceName, 348 | ServiceAddress: v.ServiceAddress, 349 | ServiceTags: v.ServiceTags, 350 | ServicePort: v.ServicePort, 351 | ServiceEnableTagOverride: v.ServiceEnableTagOverride, 352 | }) 353 | } 354 | 355 | watchCh <- retServices 356 | } 357 | }() 358 | 359 | return watchCh, nil 360 | } 361 | 362 | // Node lists the services provided by a given node 363 | func (s *Consul) Node(node string, options *registry.QueryOptions) (*registry.CatalogNode, error) { 364 | catalog := s.client.Catalog() 365 | queryOps := s.getQueryOptions(options) 366 | n, _, err := catalog.Node( 367 | node, 368 | queryOps, 369 | ) 370 | if err != nil { 371 | return nil, err 372 | } 373 | if n == nil { 374 | return nil, nil 375 | } 376 | 377 | var retNode = ®istry.Node{ 378 | Node: n.Node.Node, 379 | Address: n.Node.Address, 380 | } 381 | var retService = make(map[string]*registry.AgentService) 382 | for k, v := range n.Services { 383 | retService[k] = ®istry.AgentService{ 384 | ID: v.ID, 385 | Service: v.Service, 386 | Tags: v.Tags, 387 | Port: v.Port, 388 | Address: v.Address, 389 | EnableTagOverride: v.EnableTagOverride, 390 | } 391 | } 392 | return ®istry.CatalogNode{ 393 | Node: retNode, 394 | Services: retService, 395 | }, err 396 | } 397 | 398 | func (s *Consul) getWriteOptions(options *registry.WriteOptions) *api.WriteOptions { 399 | ops := &api.WriteOptions{} 400 | if options != nil { 401 | if options.Datacenter != "" { 402 | ops.Datacenter = options.Datacenter 403 | } 404 | if options.Token != "" { 405 | ops.Token = options.Token 406 | } 407 | } 408 | return ops 409 | 410 | } 411 | 412 | func (s *Consul) getQueryOptions(options *registry.QueryOptions) *api.QueryOptions { 413 | if options == nil { 414 | return nil 415 | } 416 | ops := &api.QueryOptions{} 417 | if options != nil { 418 | ops.Datacenter = options.Datacenter 419 | ops.AllowStale = options.AllowStale 420 | ops.RequireConsistent = options.RequireConsistent 421 | ops.WaitIndex = options.WaitIndex 422 | ops.WaitTime = options.WaitTime 423 | ops.Token = options.Token 424 | ops.Near = options.Near 425 | } 426 | return ops 427 | } 428 | 429 | // Leader returns the cluster leader 430 | func (s *Consul) Leader() (string, error) { 431 | status := s.client.Status() 432 | return status.Leader() 433 | } 434 | --------------------------------------------------------------------------------