// Add a new service to the Registry. // Do not replace an existing service. func (s *ServiceRegistry) AddService(svcCfg client.ServiceConfig) error { s.Lock() defer s.Unlock() log.Debug("Adding service:", svcCfg.Name) if _, ok := s.svcs[svcCfg.Name]; ok { log.Debug("Service already exists:", svcCfg.Name) return ErrDuplicateService } s.setServiceDefaults(&svcCfg) svcCfg = svcCfg.SetDefaults() service := NewService(svcCfg) err := service.start() if err != nil { return err } s.svcs[service.Name] = service svcCfg.VirtualHosts = filterEmpty(svcCfg.VirtualHosts) for _, name := range svcCfg.VirtualHosts { vhost := s.vhosts[name] if vhost == nil { vhost = &VirtualHost{Name: name} s.vhosts[name] = vhost } vhost.Add(service) } return nil }
// Make HTTP calls over the TCP proxy for comparison to ReverseProxy func BenchmarkTCPProxy(b *testing.B) { setupBench(b) defer tearDownBench(b) svcCfg := client.ServiceConfig{ Name: "VHostTest", Addr: "127.0.0.1:9000", VirtualHosts: []string{"test-vhost"}, } for _, srv := range benchBackends { cfg := client.BackendConfig{ Addr: srv.addr, Name: srv.addr, } svcCfg.Backends = append(svcCfg.Backends, cfg) } err := Registry.AddService(svcCfg) if err != nil { b.Fatal(err) } req, err := http.NewRequest("GET", "http://127.0.0.1:9000/addr", nil) if err != nil { b.Fatal(err) } req.Host = "test-vhost" http.DefaultTransport.(*http.Transport).DisableKeepAlives = true runtime.GC() b.ResetTimer() for i := 0; i < b.N; i++ { resp, err := http.DefaultClient.Do(req) if err != nil { b.Fatal("Error during GET:", err) } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { b.Fatal("Error during Read:", err) } if len(body) < 7 { b.Fatalf("Error in Response: %s", body) } } runtime.GC() }
// set any missing global configuration on a new ServiceConfig. // ServiceRegistry *must* be locked. func (s *ServiceRegistry) setServiceDefaults(svc *client.ServiceConfig) { if svc.Balance == "" && s.cfg.Balance != "" { svc.Balance = s.cfg.Balance } if svc.CheckInterval == 0 && s.cfg.CheckInterval != 0 { svc.CheckInterval = s.cfg.CheckInterval } if svc.Fall == 0 && s.cfg.Fall != 0 { svc.Fall = s.cfg.Fall } if svc.Rise == 0 && s.cfg.Rise != 0 { svc.Rise = s.cfg.Rise } if svc.ClientTimeout == 0 && s.cfg.ClientTimeout != 0 { svc.ClientTimeout = s.cfg.ClientTimeout } if svc.ServerTimeout == 0 && s.cfg.ServerTimeout != 0 { svc.ServerTimeout = s.cfg.ServerTimeout } if svc.DialTimeout == 0 && s.cfg.DialTimeout != 0 { svc.DialTimeout = s.cfg.DialTimeout } if s.cfg.HTTPSRedirect { svc.HTTPSRedirect = true } }
func (s *HTTPSuite) TestErrorPage(c *C) { svcCfg := client.ServiceConfig{ Name: "VHostTest", Addr: "127.0.0.1:9000", VirtualHosts: []string{"test-vhost"}, } okServer := s.backendServers[0] errServer := s.backendServers[1] // Add one backend to service requests cfg := client.BackendConfig{ Addr: okServer.addr, Name: okServer.addr, } svcCfg.Backends = append(svcCfg.Backends, cfg) // use another backend to provide the error page svcCfg.ErrorPages = map[string][]int{ "http://" + errServer.addr + "/error": []int{400, 503}, } err := Registry.AddService(svcCfg) if err != nil { c.Fatal(err) } // check that the normal response comes from srv1 checkHTTP("http://"+s.httpAddr+"/addr", "test-vhost", okServer.addr, 200, c) // verify that an unregistered error doesn't give the cached page checkHTTP("http://"+s.httpAddr+"/error?code=504", "test-vhost", okServer.addr, 504, c) // now see if the registered error comes from srv2 checkHTTP("http://"+s.httpAddr+"/error?code=503", "test-vhost", errServer.addr, 503, c) // now check that we got the header cached in the error page as well req, err := http.NewRequest("GET", "http://"+s.httpAddr+"/error?code=503", nil) if err != nil { c.Fatal(err) } req.Host = "test-vhost" resp, err := http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } c.Assert(resp.StatusCode, Equals, 503) c.Assert(resp.Header.Get("Last-Modified"), Equals, errServer.addr) }
func (s *HTTPSuite) TestSimulAdd(c *C) { start := make(chan struct{}) testWG := new(sync.WaitGroup) svcCfg := client.ServiceConfig{ Name: "TestService", Addr: "127.0.0.1:9000", VirtualHosts: []string{"test-vhost"}, Backends: []client.BackendConfig{ client.BackendConfig{ Name: "vhost1", Addr: "127.0.0.1:9001", }, client.BackendConfig{ Name: "vhost2", Addr: "127.0.0.1:9002", }, }, } for i := 0; i < 8; i++ { testWG.Add(1) go func() { defer testWG.Done() //wait to start all at once <-start svcDef := bytes.NewReader(svcCfg.Marshal()) req, _ := http.NewRequest("PUT", s.httpSvr.URL+"/TestService", svcDef) resp, err := http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } body, _ := ioutil.ReadAll(resp.Body) respCfg := client.Config{} err = json.Unmarshal(body, &respCfg) // We're only checking to ensure we have 1 service with the proper number of backends c.Assert(len(respCfg.Services), Equals, 1) c.Assert(len(respCfg.Services[0].Backends), Equals, 2) c.Assert(len(respCfg.Services[0].VirtualHosts), Equals, 1) }() } close(start) testWG.Wait() }
func (s *HTTPSuite) TestAddRemoveVHosts(c *C) { svcCfg := client.ServiceConfig{ Name: "VHostTest", Addr: "127.0.0.1:9000", VirtualHosts: []string{"test-vhost"}, } for _, srv := range s.backendServers { cfg := client.BackendConfig{ Addr: srv.addr, Name: srv.addr, } svcCfg.Backends = append(svcCfg.Backends, cfg) } err := Registry.AddService(svcCfg) if err != nil { c.Fatal(err) } // now update the service with another vhost svcCfg.VirtualHosts = append(svcCfg.VirtualHosts, "test-vhost-2") err = Registry.UpdateService(svcCfg) if err != nil { c.Fatal(err) } if Registry.VHostsLen() != 2 { c.Fatal("missing new vhost") } // remove the first vhost svcCfg.VirtualHosts = []string{"test-vhost-2"} err = Registry.UpdateService(svcCfg) if err != nil { c.Fatal(err) } if Registry.VHostsLen() != 1 { c.Fatal("extra vhost:", Registry.VHostsLen()) } // check responses from this new vhost for _, srv := range s.backendServers { checkHTTP("http://"+s.httpAddr+"/addr", "test-vhost-2", srv.addr, 200, c) } }
// Set some global defaults, and check that a new service inherits them all func (s *HTTPSuite) TestGlobalDefaults(c *C) { globalCfg := client.Config{ Balance: "LC", CheckInterval: 101, Fall: 7, Rise: 8, ClientTimeout: 102, ServerTimeout: 103, DialTimeout: 104, } globalDef := bytes.NewBuffer(globalCfg.Marshal()) req, _ := http.NewRequest("PUT", s.httpSvr.URL+"/", globalDef) resp, err := http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } resp.Body.Close() svcCfg := client.ServiceConfig{ Name: "TestService", Addr: "127.0.0.1:9000", } svcDef := bytes.NewBuffer(svcCfg.Marshal()) req, _ = http.NewRequest("PUT", s.httpSvr.URL+"/TestService", svcDef) resp, err = http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } resp.Body.Close() config := Registry.Config() c.Assert(len(config.Services), Equals, 1) service := config.Services[0] c.Assert(globalCfg.Balance, Equals, service.Balance) c.Assert(globalCfg.CheckInterval, Equals, service.CheckInterval) c.Assert(globalCfg.Fall, Equals, service.Fall) c.Assert(globalCfg.Rise, Equals, service.Rise) c.Assert(globalCfg.ClientTimeout, Equals, service.ClientTimeout) c.Assert(globalCfg.ServerTimeout, Equals, service.ServerTimeout) c.Assert(globalCfg.DialTimeout, Equals, service.DialTimeout) }
// Add multiple services under the same VirtualHost // Each proxy request should round-robin through the two of them func (s *HTTPSuite) TestMultiServiceVHost(c *C) { svcCfgOne := client.ServiceConfig{ Name: "VHostTest", Addr: "127.0.0.1:9000", VirtualHosts: []string{"test-vhost"}, } svcCfgTwo := client.ServiceConfig{ Name: "VHostTest2", Addr: "127.0.0.1:9001", VirtualHosts: []string{"test-vhost-2"}, } var backends []client.BackendConfig for _, srv := range s.backendServers { cfg := client.BackendConfig{ Addr: srv.addr, Name: srv.addr, } backends = append(backends, cfg) } svcCfgOne.Backends = backends svcCfgTwo.Backends = backends err := Registry.AddService(svcCfgOne) if err != nil { c.Fatal(err) } err = Registry.AddService(svcCfgTwo) if err != nil { c.Fatal(err) } for _, srv := range s.backendServers { checkHTTP("http://"+s.httpAddr+"/addr", "test-vhost", srv.addr, 200, c) checkHTTP("http://"+s.httpAddr+"/addr", "test-vhost-2", srv.addr, 200, c) } }
func (s *BasicSuite) TestInvalidUpdateService(c *C) { svcCfg := client.ServiceConfig{ Name: "Update", Addr: "127.0.0.1:9324", } if err := Registry.AddService(svcCfg); err != nil { c.Fatal(err) } svc := Registry.GetService("Update") if svc == nil { c.Fatal(ErrNoService) } svcCfg.Addr = "127.0.0.1:9425" // Make sure we can't add the same service again if err := Registry.AddService(svcCfg); err == nil { c.Fatal(err) } // the update should fail, because it would require a new listener if err := Registry.UpdateService(svcCfg); err == nil { c.Fatal(err) } // change the addres back, and try to update ClientTimeout svcCfg.Addr = "127.0.0.1:9324" svcCfg.ClientTimeout = 1234 // the update should fail, because it would require a new listener if err := Registry.UpdateService(svcCfg); err == nil { c.Fatal(err) } if err := Registry.RemoveService("Update"); err != nil { c.Fatal(err) } }
func (s *Service) config() client.ServiceConfig { config := client.ServiceConfig{ Name: s.Name, Addr: s.Addr, VirtualHosts: s.VirtualHosts, HTTPSRedirect: s.HTTPSRedirect, Balance: s.Balance, CheckInterval: s.CheckInterval, Fall: s.Fall, Rise: s.Rise, ClientTimeout: int(s.ClientTimeout / time.Millisecond), ServerTimeout: int(s.ServerTimeout / time.Millisecond), DialTimeout: int(s.DialTimeout / time.Millisecond), ErrorPages: s.errPagesCfg, Network: s.Network, } for _, b := range s.Backends { config.Backends = append(config.Backends, b.Config()) } return config }
func (s *HTTPSuite) TestRouter(c *C) { svcCfg := client.ServiceConfig{ Name: "VHostTest", Addr: "127.0.0.1:9000", VirtualHosts: []string{"test-vhost"}, } for _, srv := range s.backendServers { cfg := client.BackendConfig{ Addr: srv.addr, Name: srv.addr, } svcCfg.Backends = append(svcCfg.Backends, cfg) } err := Registry.AddService(svcCfg) if err != nil { c.Fatal(err) } for _, srv := range s.backendServers { checkHTTP("http://"+s.httpAddr+"/addr", "test-vhost", srv.addr, 200, c) } }
func (s *HTTPSuite) TestUpdateServiceDefaults(c *C) { svcCfg := client.ServiceConfig{ Name: "TestService", Addr: "127.0.0.1:9000", Backends: []client.BackendConfig{ client.BackendConfig{ Name: "Backend1", Addr: "127.0.0.1:9001", }, }, } svcDef := bytes.NewBuffer(svcCfg.Marshal()) req, _ := http.NewRequest("PUT", s.httpSvr.URL+"/TestService", svcDef) resp, err := http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } resp.Body.Close() // Now update the Service in-place svcCfg.ServerTimeout = 1234 svcDef.Reset() svcDef.Write(svcCfg.Marshal()) req, _ = http.NewRequest("PUT", s.httpSvr.URL+"/TestService", svcDef) resp, err = http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } body, _ := ioutil.ReadAll(resp.Body) resp.Body.Close() config := client.Config{} err = json.Unmarshal(body, &config) if err != nil { c.Fatal(err) } // make sure we don't see a second value found := false for _, svc := range config.Services { if svc.Name == "TestService" { if svc.ServerTimeout != svcCfg.ServerTimeout { c.Fatal("Service not updated") } else if found { c.Fatal("Multiple Service Definitions") } found = true } } }
func (s *HTTPSuite) TestAddRemoveBackends(c *C) { svcCfg := client.ServiceConfig{ Name: "VHostTest", Addr: "127.0.0.1:9000", } err := Registry.AddService(svcCfg) if err != nil { c.Fatal(err) } for _, srv := range s.backendServers { cfg := client.BackendConfig{ Addr: srv.addr, Name: srv.addr, } svcCfg.Backends = append(svcCfg.Backends, cfg) } err = Registry.UpdateService(svcCfg) if err != nil { c.Fatal(err) } cfg := Registry.Config() if !svcCfg.DeepEqual(cfg.Services[0]) { c.Errorf("we should have 1 service, we have %d", len(cfg.Services)) c.Errorf("we should have 4 backends, we have %d", len(cfg.Services[0].Backends)) } svcCfg.Backends = svcCfg.Backends[:3] err = Registry.UpdateService(svcCfg) if err != nil { c.Fatal(err) } cfg = Registry.Config() if !svcCfg.DeepEqual(cfg.Services[0]) { c.Errorf("we should have 1 service, we have %d", len(cfg.Services)) c.Errorf("we should have 3 backends, we have %d", len(cfg.Services[0].Backends)) } }
// check valid service updates func (s *BasicSuite) TestUpdateService(c *C) { svcCfg := client.ServiceConfig{ Name: "Update2", Addr: "127.0.0.1:9324", } if err := Registry.AddService(svcCfg); err != nil { c.Fatal(err) } svc := Registry.GetService("Update2") if svc == nil { c.Fatal(ErrNoService) } svcCfg.ServerTimeout = 1234 svcCfg.HTTPSRedirect = true svcCfg.Fall = 5 svcCfg.Rise = 6 svcCfg.Balance = "LC" // Now update the service for real if err := Registry.UpdateService(svcCfg); err != nil { c.Fatal(err) } svc = Registry.GetService("Update2") if svc == nil { c.Fatal(ErrNoService) } c.Assert(svc.ServerTimeout, Equals, 1234*time.Millisecond) c.Assert(svc.HTTPSRedirect, Equals, true) c.Assert(svc.Fall, Equals, 5) c.Assert(svc.Rise, Equals, 6) c.Assert(svc.Balance, Equals, "LC") if err := Registry.RemoveService("Update2"); err != nil { c.Fatal(err) } }
func (s *HTTPSuite) TestHTTPAddRemoveBackends(c *C) { svcCfg := client.ServiceConfig{ Name: "VHostTest", Addr: "127.0.0.1:9000", } err := Registry.AddService(svcCfg) if err != nil { c.Fatal(err) } for _, srv := range s.backendServers { cfg := client.BackendConfig{ Addr: srv.addr, Name: srv.addr, } svcCfg.Backends = append(svcCfg.Backends, cfg) } req, _ := http.NewRequest("PUT", s.httpSvr.URL+"/VHostTest", bytes.NewReader(svcCfg.Marshal())) _, err = http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } cfg := Registry.Config() if !svcCfg.DeepEqual(cfg.Services[0]) { c.Errorf("we should have 1 service, we have %d", len(cfg.Services)) c.Errorf("we should have 4 backends, we have %d", len(cfg.Services[0].Backends)) } // remove a backend from the config and submit it again svcCfg.Backends = svcCfg.Backends[:3] err = Registry.UpdateService(svcCfg) if err != nil { c.Fatal(err) } req, _ = http.NewRequest("PUT", s.httpSvr.URL+"/VHostTest", bytes.NewReader(svcCfg.Marshal())) _, err = http.DefaultClient.Do(req) if err != nil { c.Fatal(err) } // now check the config via what's returned from the http server resp, err := http.Get(s.httpSvr.URL + "/_config") if err != nil { c.Fatal(err) } defer resp.Body.Close() cfg = client.Config{} body, _ := ioutil.ReadAll(resp.Body) err = json.Unmarshal(body, &cfg) if err != nil { c.Fatal(err) } if !svcCfg.DeepEqual(cfg.Services[0]) { c.Errorf("we should have 1 service, we have %d", len(cfg.Services)) c.Errorf("we should have 3 backends, we have %d", len(cfg.Services[0].Backends)) } }
// Replace the service's configuration, or update its list of backends. // Replacing a configuration will shutdown the existing service, and start a // new one, which will cause the listening socket to be temporarily // unavailable. func (s *ServiceRegistry) UpdateService(newCfg client.ServiceConfig) error { s.Lock() defer s.Unlock() log.Debug("Updating Service:", newCfg.Name) service, ok := s.svcs[newCfg.Name] if !ok { log.Debug("Service not found:", newCfg.Name) return ErrNoService } currentCfg := service.Config() newCfg.Merge(currentCfg) if err := service.UpdateConfig(newCfg); err != nil { return err } // Lots of looping here (including fetching the Config, but the cardinality // of Backends shouldn't be very large, and the default RoundRobin balancing // is much simpler with a slice. // we're going to update just the backends for this config // get a map of what's already running currentBackends := make(map[string]client.BackendConfig) for _, backendCfg := range currentCfg.Backends { currentBackends[backendCfg.Name] = backendCfg } // Keep existing backends when they have equivalent config. // Update changed backends, and add new ones. for _, newBackend := range newCfg.Backends { current, ok := currentBackends[newBackend.Name] if ok && current.Equal(newBackend) { log.Debugf("Backend %s/%s unchanged", service.Name, current.Name) // no change for this one delete(currentBackends, current.Name) continue } // we need to remove and re-add this backend log.Debugf("Updating Backend %s/%s", service.Name, newBackend.Name) service.remove(newBackend.Name) service.add(NewBackend(newBackend)) delete(currentBackends, newBackend.Name) } // remove any left over backends for name := range currentBackends { log.Debugf("Removing Backend %s/%s", service.Name, name) service.remove(name) } if currentCfg.Equal(newCfg) { log.Debugf("Service Unchanged %s", service.Name) return nil } // replace error pages if there's any change if !reflect.DeepEqual(service.errPagesCfg, newCfg.ErrorPages) { log.Debugf("Updating ErrorPages") service.errPagesCfg = newCfg.ErrorPages service.errorPages.Update(newCfg.ErrorPages) } s.updateVHosts(service, filterEmpty(newCfg.VirtualHosts)) return nil }