func (s *ServiceRegistry) RemoveService(name string) error { s.Lock() defer s.Unlock() svc, ok := s.svcs[name] if ok { log.Debugf("Removing Service %s", svc.Name) delete(s.svcs, name) svc.stop() for host, vhost := range s.vhosts { vhost.Remove(svc) removeVhost := true for _, service := range s.svcs { for _, h := range service.VirtualHosts { if host == h { // FIXME: is this still correct? NOT TESTED! // vhost exists in another service, so leave it removeVhost = false break } } } if removeVhost { log.Debugf("Removing VirtualHost %s", host) delete(s.vhosts, host) } } return nil } return ErrNoService }
func (b *Backend) Proxy(srvConn, cliConn net.Conn) { log.Debugf("Initiating proxy: %s/%s-%s/%s", cliConn.RemoteAddr(), cliConn.LocalAddr(), srvConn.LocalAddr(), srvConn.RemoteAddr(), ) // Backend is a pointer receiver so we can get the address of the fields, // but all updates will be done atomically. bConn := &shuttleConn{ TCPConn: srvConn.(*net.TCPConn), rwTimeout: b.rwTimeout, read: &b.Rcvd, written: &b.Sent, } // TODO: No way to force shutdown. Do we need it, or should we always just // let a connection run out? atomic.AddInt64(&b.Conns, 1) atomic.AddInt64(&b.Active, 1) defer atomic.AddInt64(&b.Active, -1) // channels to wait on close event backendClosed := make(chan bool, 1) clientClosed := make(chan bool, 1) go broker(bConn, cliConn, clientClosed, &b.Sent, &b.Errors) go broker(cliConn, bConn, backendClosed, &b.Rcvd, &b.Errors) // wait for one half of the proxy to exit, then trigger a shutdown of the // other half by calling CloseRead(). This will break the read loop in the // broker and fully close the connection. var waitFor chan bool select { case <-clientClosed: log.Debugf("Client %s/%s closed connection", cliConn.RemoteAddr(), cliConn.LocalAddr()) // the client closed first, so any more packets here are invalid, and // we can SetLinger(0) to recycle the port faster. bConn.TCPConn.SetLinger(0) bConn.CloseRead() waitFor = backendClosed case <-backendClosed: log.Debugf("Server %s/%s closed connection", srvConn.RemoteAddr(), srvConn.LocalAddr()) cliConn.(closeReader).CloseRead() waitFor = clientClosed } // wait for the other connection to close <-waitFor }
func (v *VirtualHost) Remove(svc *Service) { v.Lock() defer v.Unlock() found := -1 for i, s := range v.services { if s.Name == svc.Name { found = i break } } if found < 0 { log.Debugf("Service %s not found under VirtualHost %s", svc.Name, v.Name) return } // safe way to get the backends info for logging svcCfg := svc.Config() // Now removing this Service for _, backend := range svcCfg.Backends { log.Printf("Removing backend http://%s from VirtualHost %s", backend.Addr, v.Name) } v.services = append(v.services[:found], v.services[found+1:]...) }
// find certs in and is the named directory, and match them up by their base // name using '.pem' and '.key' as extensions. func loadCerts(certDir string) (*tls.Config, error) { abs, err := filepath.Abs(certDir) if err != nil { return nil, err } dir, err := ioutil.ReadDir(abs) if err != nil { return nil, err } // [cert, key] pairs pairs := make(map[string][2]string) for _, f := range dir { name := f.Name() if strings.HasSuffix(name, ".pem") { p := pairs[name[:len(name)-4]] p[0] = filepath.Join(abs, name) pairs[name[:len(name)-4]] = p } if strings.HasSuffix(name, ".key") { p := pairs[name[:len(name)-4]] p[1] = filepath.Join(abs, name) pairs[name[:len(name)-4]] = p } } tlsCfg := &tls.Config{ NextProtos: []string{"http/1.1"}, } for key, pair := range pairs { if pair[0] == "" { log.Errorf("missing cert for key: %s", pair[1]) continue } if pair[1] == "" { log.Errorf("missing key for cert: %s", pair[0]) continue } cert, err := tls.LoadX509KeyPair(pair[0], pair[1]) if err != nil { log.Error(err) continue } tlsCfg.Certificates = append(tlsCfg.Certificates, cert) log.Debugf("loaded X509KeyPair for %s", key) } if len(tlsCfg.Certificates) == 0 { return nil, fmt.Errorf("no tls certificates loaded") } tlsCfg.BuildNameToCertificate() return tlsCfg, nil }
func (b *Backend) check() { if b.CheckAddr == "" { return } up := true if c, e := net.DialTimeout("tcp", b.CheckAddr, b.dialTimeout); e == nil { c.(*net.TCPConn).SetLinger(0) c.Close() } else { log.Debug("Check error:", e) up = false } b.Lock() defer b.Unlock() if up { log.Debugf("Check OK for %s/%s", b.Name, b.CheckAddr) b.fallCount = 0 b.riseCount++ b.checkOK++ if b.riseCount >= b.rise { if !b.up { log.Debugf("Marking backend %s Up", b.Name) } b.up = true } } else { log.Debugf("Check failed for %s/%s", b.Name, b.CheckAddr) b.riseCount = 0 b.fallCount++ b.checkFail++ if b.fallCount >= b.fall { if b.up { log.Debugf("Marking backend %s Down", b.Name) } b.up = false } } }
// Add or update a Backend on an existing Service. func (s *ServiceRegistry) AddBackend(svcName string, backendCfg client.BackendConfig) error { s.Lock() defer s.Unlock() service, ok := s.svcs[svcName] if !ok { return ErrNoService } log.Debugf("Adding Backend %s/%s", service.Name, backendCfg.Name) service.add(NewBackend(backendCfg)) return nil }
// Remove a Backend from an existing Service. func (s *ServiceRegistry) RemoveBackend(svcName, backendName string) error { s.Lock() defer s.Unlock() log.Debugf("Removing Backend %s/%s", svcName, backendName) service, ok := s.svcs[svcName] if !ok { return ErrNoService } if !service.remove(backendName) { return ErrNoBackend } return nil }
// Insert a service // do nothing if the service already is registered func (v *VirtualHost) Add(svc *Service) { v.Lock() defer v.Unlock() for _, s := range v.services { if s.Name == svc.Name { log.Debugf("Service %s already registered in VirtualHost %s", svc.Name, v.Name) return } } // TODO: is this the best place to log these? svcCfg := svc.Config() for _, backend := range svcCfg.Backends { log.Printf("Adding backend http://%s to VirtualHost %s", backend.Addr, v.Name) } v.services = append(v.services, svc) }
func (e *ErrorResponse) fetch(page *ErrorPage) { log.Debugf("Fetching error page from %s", page.Location) resp, err := e.client.Get(page.Location) if err != nil { log.Warnf("Could not fetch %s: %s", page.Location, err.Error()) return } defer resp.Body.Close() // If the StatusCode matches any of our registered codes, it's OK for _, code := range page.StatusCodes { if resp.StatusCode == code { resp.StatusCode = http.StatusOK break } } if resp.StatusCode != http.StatusOK { log.Warnf("Server returned %d when fetching %s", resp.StatusCode, page.Location) return } header := make(map[string][]string) for _, key := range ErrorHeaders { if hdr, ok := resp.Header[key]; ok { header[key] = hdr } } // set the headers along with the body below body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Warnf("Error reading response from %s: %s", page.Location, err.Error()) return } if len(body) > 0 { page.SetHeader(header) page.SetBody(body) return } log.Warnf("Empty response from %s", page.Location) }
// 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 }