text: // Copyright 2016 The Serviced Authors.
  // 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
  //
  // 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.
  
  package web
  
  import (
  	"crypto/tls"
  	"errors"
  	"net"
  	"sync"
  
  	log "github.com/Sirupsen/logrus"
  	"github.com/control-center/serviced/utils"
  	"github.com/control-center/serviced/zzk/registry"
  )
  
  var ErrPortServerRunning = errors.New("port server is already running")
  
  // PublicPortManager manages all the port servers for a particular host id
  type PublicPortManager struct {
  	hostID    string
  	certFile  string
  	keyFile   string
  	onFailure func(portNumber string, err error)
  	mu        *sync.RWMutex
  	ports     map[string]*PublicPortHandler
  }
  
  // NewPublicPortManager creates a new public port manager for a host id
  func NewPublicPortManager(hostID, certFile, keyFile string, onFailure func(portAddr string, err error)) *PublicPortManager {
  	return &PublicPortManager{
  		hostID:    hostID,
  		certFile:  certFile,
  		keyFile:   keyFile,
  		onFailure: onFailure,
  		mu:        &sync.RWMutex{},
  		ports:     make(map[string]*PublicPortHandler),
  	}
  }
  
  // Enable implements starts the public port server at the port address
  func (m *PublicPortManager) Enable(portAddr, protocol string, useTLS bool) {
  	m.mu.Lock()
  	defer m.mu.Unlock()
  
  	// get the port handler or create if it doesn't exist
  	h, ok := m.ports[portAddr]
  	if !ok {
  		h = NewPublicPortHandler(portAddr)
  		m.ports[portAddr] = h
  	}
  
  	// start the port server
  	if err := h.Serve(protocol, useTLS, m.certFile, m.keyFile); err != nil {
  		m.onFailure(portAddr, err)
  	}
  }
  
  // Disable stops the public port server at the port address
  func (m *PublicPortManager) Disable(portAddr string) {
  	m.mu.RLock()
  	defer m.mu.RUnlock()
  
  	// get the port handler and stop it if it exists
  	h, ok := m.ports[portAddr]
  	if ok {
  		h.Stop()
  	}
  }
  
  // Set updates the exports for a particular port handler
  func (m *PublicPortManager) Set(portAddr string, data []registry.ExportDetails) {
  	m.mu.Lock()
  	defer m.mu.Unlock()
  
  	h, ok := m.ports[portAddr]
  	if ok {
  		h.SetExports(data)
  	} else {
  		h = NewPublicPortHandler(portAddr, data...)
  		m.ports[portAddr] = h
  	}
  }
  
  // PublicPortHandler manages the port server at a specific port address
  type PublicPortHandler struct {
  	portAddr string
  	exports  Exports
  	cancel   chan struct{}
  	wg       *sync.WaitGroup
  }
  
  // NewPublicPortHandler sets up a new public port at the given port address
  func NewPublicPortHandler(portAddr string, data ...registry.ExportDetails) *PublicPortHandler {
  	cancel := make(chan struct{})
  	close(cancel)
  
  	return &PublicPortHandler{
  		portAddr: portAddr,
  		exports:  NewRoundRobinExports(data), // round-robin is the default
  		cancel:   cancel,
  		wg:       &sync.WaitGroup{},
  	}
  }
  
  // Serve starts the port server at address
  func (h *PublicPortHandler) Serve(protocol string, useTLS bool, certFile, keyFile string) error {
  	logger := plog.WithFields(log.Fields{
  		"portaddress": h.portAddr,
  		"protocol":    protocol,
  		"usetls":      useTLS,
  	})
  
  	// don't start the server if it is already running
  	select {
  	case <-h.cancel:
  		h.cancel = make(chan struct{})
  	default:
  		logger.Debug("Port server is already running")
  		return ErrPortServerRunning
  	}
  
  	var tlsConfig *tls.Config
  	if useTLS {
  
  		// get the certificate
  		certFile, keyFile = GetCertFiles(certFile, keyFile)
  		cert, err := tls.LoadX509KeyPair(certFile, keyFile)
  		if err != nil {
  			logger.WithError(err).Debug("Could not set up certificate")
  			return err
  		}
  
  		// cipher suites and tls min version change may not be needed with
  		// golang 1.5:
  		// https://github.com/golang/go/issues/10094
  		// https://github.com/golang/go/issues/9364
  		tlsConfig = &tls.Config{
  			MinVersion:               utils.MinTLS("http"),
  			PreferServerCipherSuites: true,
  			CipherSuites:             utils.CipherSuites("http"),
  			Certificates:             []tls.Certificate{cert},
  		}
  
  		logger.Debug("Set up tls certificate")
  	}
  
  	// start listening on the port with a non-tls connection
  	listener, err := net.Listen("tcp", h.portAddr)
  	if err != nil {
  		logger.WithError(err).Debug("Could not start TCP listener")
  		return err
  	}
  
  	h.wg.Add(1)
  	go func() {
  		logger.Info("Starting port server")
  		defer logger.Debug("Port server exited")
  
  		if protocol == "http" || protocol == "https" {
  			ServeHTTP(h.cancel, h.portAddr, protocol, listener, tlsConfig, h.exports)
  		} else {
  			ServeTCP(h.cancel, listener, tlsConfig, h.exports)
  		}
  		h.wg.Done()
  	}()
  
  	return nil
  }
  
  // Stop shuts down the port server
  func (h *PublicPortHandler) Stop() {
  	select {
  	case <-h.cancel:
  	default:
  		close(h.cancel)
  	}
  	h.wg.Wait()
  }
  
  // SetExports updates the export list for the port handler
  func (h *PublicPortHandler) SetExports(data []registry.ExportDetails) {
  	h.exports.Set(data)
  }
  