// 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("DEBUG: Adding service:", svcCfg.Name) if _, ok := s.svcs[svcCfg.Name]; ok { log.Debug("DEBUG: Service already exists:", svcCfg.Name) return ErrDuplicateService } s.setServiceDefaults(&svcCfg) svcCfg = svcCfg.SetDefaults() service := NewService(svcCfg) err := service.start() if err != nil { log.Errorf("ERROR: Unable to start service '%s'", svcCfg.Name) 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 }
func (s *HTTPSuite) TestMaintenanceMode(c *C) { mainServer := s.backendServers[0] errServer := s.backendServers[1] svcCfg := client.ServiceConfig{ Name: "VHostTest1", Addr: "127.0.0.1:9000", VirtualHosts: []string{"vhost1.test"}, Backends: []client.BackendConfig{ {Addr: mainServer.addr}, }, MaintenanceMode: true, } if err := Registry.AddService(svcCfg); err != nil { c.Fatal(err) } // No error page is registered, so we should just get a 503 error with no body checkHTTP("https://vhost1.test:"+s.httpsPort+"/addr", "vhost1.test", "", 503, c) // Use another backend to provide the error page svcCfg.ErrorPages = map[string][]int{ "http://" + errServer.addr + "/error?code=503": []int{503}, } if err := Registry.UpdateService(svcCfg); err != nil { c.Fatal(err) } // Get a 503 error with the cached body checkHTTP("https://vhost1.test:"+s.httpsPort+"/addr", "vhost1.test", errServer.addr, 503, c) // Turn maintenance mode off svcCfg.MaintenanceMode = false if err := Registry.UpdateService(svcCfg); err != nil { c.Fatal(err) } checkHTTP("https://vhost1.test:"+s.httpsPort+"/addr", "vhost1.test", mainServer.addr, 200, c) // Turn it back on svcCfg.MaintenanceMode = true if err := Registry.UpdateService(svcCfg); err != nil { c.Fatal(err) } checkHTTP("https://vhost1.test:"+s.httpsPort+"/addr", "vhost1.test", errServer.addr, 503, c) }
// 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 *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)) } }
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)) } }