Move nginx to root directory
This commit is contained in:
parent
cf2ab5f5f7
commit
2139ee85e7
53 changed files with 666 additions and 1733 deletions
57
pkg/cmd/controller/main.go
Normal file
57
pkg/cmd/controller/main.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// start a new nginx controller
|
||||
ngx := newNGINXController()
|
||||
|
||||
go handleSigterm(ngx)
|
||||
// start the controller
|
||||
ngx.Start()
|
||||
// wait
|
||||
glog.Infof("shutting down Ingress controller...")
|
||||
for {
|
||||
glog.Infof("Handled quit, awaiting pod deletion")
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSigterm(ngx *NGINXController) {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, syscall.SIGTERM)
|
||||
<-signalChan
|
||||
glog.Infof("Received SIGTERM, shutting down")
|
||||
|
||||
exitCode := 0
|
||||
if err := ngx.Stop(); err != nil {
|
||||
glog.Infof("Error during shutdown %v", err)
|
||||
exitCode = 1
|
||||
}
|
||||
|
||||
glog.Infof("Exiting with %v", exitCode)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
97
pkg/cmd/controller/metrics.go
Normal file
97
pkg/cmd/controller/metrics.go
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes 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 main
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"k8s.io/ingress/controllers/nginx/pkg/metric/collector"
|
||||
)
|
||||
|
||||
const (
|
||||
ngxStatusPath = "/nginx_status"
|
||||
ngxVtsPath = "/nginx_status/format/json"
|
||||
)
|
||||
|
||||
func (n *NGINXController) setupMonitor(sm statusModule) {
|
||||
csm := n.statusModule
|
||||
if csm != sm {
|
||||
glog.Infof("changing prometheus collector from %v to %v", csm, sm)
|
||||
n.stats.stop(csm)
|
||||
n.stats.start(sm)
|
||||
n.statusModule = sm
|
||||
}
|
||||
}
|
||||
|
||||
type statsCollector struct {
|
||||
process prometheus.Collector
|
||||
basic collector.Stopable
|
||||
vts collector.Stopable
|
||||
|
||||
namespace string
|
||||
watchClass string
|
||||
|
||||
port int
|
||||
}
|
||||
|
||||
func (s *statsCollector) stop(sm statusModule) {
|
||||
switch sm {
|
||||
case defaultStatusModule:
|
||||
s.basic.Stop()
|
||||
prometheus.Unregister(s.basic)
|
||||
case vtsStatusModule:
|
||||
s.vts.Stop()
|
||||
prometheus.Unregister(s.vts)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *statsCollector) start(sm statusModule) {
|
||||
switch sm {
|
||||
case defaultStatusModule:
|
||||
s.basic = collector.NewNginxStatus(s.namespace, s.watchClass, s.port, ngxStatusPath)
|
||||
prometheus.Register(s.basic)
|
||||
break
|
||||
case vtsStatusModule:
|
||||
s.vts = collector.NewNGINXVTSCollector(s.namespace, s.watchClass, s.port, ngxVtsPath)
|
||||
prometheus.Register(s.vts)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func newStatsCollector(ns, class, binary string, port int) *statsCollector {
|
||||
glog.Infof("starting new nginx stats collector for Ingress controller running in namespace %v (class %v)", ns, class)
|
||||
glog.Infof("collector extracting information from port %v", port)
|
||||
pc, err := collector.NewNamedProcess(true, collector.BinaryNameMatcher{
|
||||
Name: "nginx",
|
||||
Binary: binary,
|
||||
})
|
||||
if err != nil {
|
||||
glog.Fatalf("unexpected error registering nginx collector: %v", err)
|
||||
}
|
||||
err = prometheus.Register(pc)
|
||||
if err != nil {
|
||||
glog.Fatalf("unexpected error registering nginx collector: %v", err)
|
||||
}
|
||||
|
||||
return &statsCollector{
|
||||
namespace: ns,
|
||||
watchClass: class,
|
||||
process: pc,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
799
pkg/cmd/controller/nginx.go
Normal file
799
pkg/cmd/controller/nginx.go
Normal file
|
|
@ -0,0 +1,799 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/mitchellh/go-ps"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
proxyproto "github.com/armon/go-proxyproto"
|
||||
"github.com/ncabatoff/process-exporter/proc"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
|
||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||
ngx_template "k8s.io/ingress/controllers/nginx/pkg/template"
|
||||
"k8s.io/ingress/controllers/nginx/pkg/version"
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/controller"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
"k8s.io/ingress/core/pkg/net/dns"
|
||||
"k8s.io/ingress/core/pkg/net/ssl"
|
||||
)
|
||||
|
||||
type statusModule string
|
||||
|
||||
const (
|
||||
ngxHealthPath = "/healthz"
|
||||
|
||||
defaultStatusModule statusModule = "default"
|
||||
vtsStatusModule statusModule = "vts"
|
||||
|
||||
defUpstreamName = "upstream-default-backend"
|
||||
)
|
||||
|
||||
var (
|
||||
tmplPath = "/etc/nginx/template/nginx.tmpl"
|
||||
cfgPath = "/etc/nginx/nginx.conf"
|
||||
nginxBinary = "/usr/sbin/nginx"
|
||||
defIngressClass = "nginx"
|
||||
)
|
||||
|
||||
// newNGINXController creates a new NGINX Ingress controller.
|
||||
// If the environment variable NGINX_BINARY exists it will be used
|
||||
// as source for nginx commands
|
||||
func newNGINXController() *NGINXController {
|
||||
ngx := os.Getenv("NGINX_BINARY")
|
||||
if ngx == "" {
|
||||
ngx = nginxBinary
|
||||
}
|
||||
|
||||
h, err := dns.GetSystemNameServers()
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error reading system nameservers: %v", err)
|
||||
}
|
||||
|
||||
n := &NGINXController{
|
||||
binary: ngx,
|
||||
configmap: &apiv1.ConfigMap{},
|
||||
isIPV6Enabled: isIPv6Enabled(),
|
||||
resolver: h,
|
||||
ports: &config.ListenPorts{},
|
||||
backendDefaults: config.NewDefault().Backend,
|
||||
}
|
||||
|
||||
var onChange func()
|
||||
onChange = func() {
|
||||
template, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||
if err != nil {
|
||||
// this error is different from the rest because it must be clear why nginx is not working
|
||||
glog.Errorf(`
|
||||
-------------------------------------------------------------------------------
|
||||
Error loading new template : %v
|
||||
-------------------------------------------------------------------------------
|
||||
`, err)
|
||||
return
|
||||
}
|
||||
|
||||
n.t.Close()
|
||||
n.t = template
|
||||
glog.Info("new NGINX template loaded")
|
||||
}
|
||||
|
||||
ngxTpl, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||
if err != nil {
|
||||
glog.Fatalf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
||||
n.t = ngxTpl
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// NGINXController ...
|
||||
type NGINXController struct {
|
||||
controller *controller.GenericController
|
||||
t *ngx_template.Template
|
||||
|
||||
configmap *apiv1.ConfigMap
|
||||
|
||||
storeLister *ingress.StoreLister
|
||||
|
||||
binary string
|
||||
resolver []net.IP
|
||||
|
||||
cmdArgs []string
|
||||
|
||||
stats *statsCollector
|
||||
statusModule statusModule
|
||||
|
||||
// returns true if IPV6 is enabled in the pod
|
||||
isIPV6Enabled bool
|
||||
|
||||
// returns true if proxy protocol es enabled
|
||||
isProxyProtocolEnabled bool
|
||||
|
||||
isSSLPassthroughEnabled bool
|
||||
|
||||
isShuttingDown bool
|
||||
|
||||
proxy *proxy
|
||||
|
||||
ports *config.ListenPorts
|
||||
|
||||
backendDefaults defaults.Backend
|
||||
}
|
||||
|
||||
// Start start a new NGINX master process running in foreground.
|
||||
func (n *NGINXController) Start() {
|
||||
n.isShuttingDown = false
|
||||
|
||||
n.controller = controller.NewIngressController(n)
|
||||
go n.controller.Start()
|
||||
|
||||
done := make(chan error, 1)
|
||||
cmd := exec.Command(n.binary, "-c", cfgPath)
|
||||
|
||||
// put nginx in another process group to prevent it
|
||||
// to receive signals meant for the controller
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
|
||||
glog.Info("starting NGINX process...")
|
||||
n.start(cmd, done)
|
||||
|
||||
// if the nginx master process dies the workers continue to process requests,
|
||||
// passing checks but in case of updates in ingress no updates will be
|
||||
// reflected in the nginx configuration which can lead to confusion and report
|
||||
// issues because of this behavior.
|
||||
// To avoid this issue we restart nginx in case of errors.
|
||||
for {
|
||||
err := <-done
|
||||
|
||||
if n.isShuttingDown {
|
||||
break
|
||||
}
|
||||
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
waitStatus := exitError.Sys().(syscall.WaitStatus)
|
||||
glog.Warningf(`
|
||||
-------------------------------------------------------------------------------
|
||||
NGINX master process died (%v): %v
|
||||
-------------------------------------------------------------------------------
|
||||
`, waitStatus.ExitStatus(), err)
|
||||
}
|
||||
cmd.Process.Release()
|
||||
cmd = exec.Command(n.binary, "-c", cfgPath)
|
||||
// we wait until the workers are killed
|
||||
for {
|
||||
conn, err := net.DialTimeout("tcp", "127.0.0.1:80", 1*time.Second)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
conn.Close()
|
||||
// kill nginx worker processes
|
||||
fs, err := proc.NewFS("/proc")
|
||||
procs, _ := fs.FS.AllProcs()
|
||||
for _, p := range procs {
|
||||
pn, err := p.Comm()
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error obtaining process information: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if pn == "nginx" {
|
||||
osp, err := os.FindProcess(p.PID)
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error obtaining process information: %v", err)
|
||||
continue
|
||||
}
|
||||
osp.Signal(syscall.SIGQUIT)
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
// restart a new nginx master process if the controller
|
||||
// is not being stopped
|
||||
n.start(cmd, done)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop gracefully stops the NGINX master process.
|
||||
func (n *NGINXController) Stop() error {
|
||||
n.isShuttingDown = true
|
||||
n.controller.Stop()
|
||||
|
||||
// Send stop signal to Nginx
|
||||
glog.Info("stopping NGINX process...")
|
||||
cmd := exec.Command(n.binary, "-c", cfgPath, "-s", "quit")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for the Nginx process disappear
|
||||
waitForNginxShutdown()
|
||||
glog.Info("NGINX process has stopped")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NGINXController) start(cmd *exec.Cmd, done chan error) {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
glog.Fatalf("nginx error: %v", err)
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
n.cmdArgs = cmd.Args
|
||||
|
||||
go func() {
|
||||
done <- cmd.Wait()
|
||||
}()
|
||||
}
|
||||
|
||||
// BackendDefaults returns the nginx defaults
|
||||
func (n NGINXController) BackendDefaults() defaults.Backend {
|
||||
return n.backendDefaults
|
||||
}
|
||||
|
||||
// printDiff returns the difference between the running configuration
|
||||
// and the new one
|
||||
func (n NGINXController) printDiff(data []byte) {
|
||||
if !glog.V(2) {
|
||||
return
|
||||
}
|
||||
|
||||
in, err := os.Open(cfgPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
src, err := ioutil.ReadAll(in)
|
||||
in.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(src, data) {
|
||||
tmpfile, err := ioutil.TempFile("", "nginx-cfg-diff")
|
||||
if err != nil {
|
||||
glog.Errorf("error creating temporal file: %s", err)
|
||||
return
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
err = ioutil.WriteFile(tmpfile.Name(), data, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
diffOutput, err := diff(src, data)
|
||||
if err != nil {
|
||||
glog.Errorf("error computing diff: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
glog.Infof("NGINX configuration diff\n")
|
||||
glog.Infof("%v", string(diffOutput))
|
||||
|
||||
os.Remove(tmpfile.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Info return build information
|
||||
func (n NGINXController) Info() *ingress.BackendInfo {
|
||||
return &ingress.BackendInfo{
|
||||
Name: "NGINX",
|
||||
Release: version.RELEASE,
|
||||
Build: version.COMMIT,
|
||||
Repository: version.REPO,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultEndpoint returns the default endpoint to be use as default server that returns 404.
|
||||
func (n NGINXController) DefaultEndpoint() ingress.Endpoint {
|
||||
return ingress.Endpoint{
|
||||
Address: "127.0.0.1",
|
||||
Port: fmt.Sprintf("%v", n.ports.Default),
|
||||
Target: &apiv1.ObjectReference{},
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigureFlags allow to configure more flags before the parsing of
|
||||
// command line arguments
|
||||
func (n *NGINXController) ConfigureFlags(flags *pflag.FlagSet) {
|
||||
flags.BoolVar(&n.isSSLPassthroughEnabled, "enable-ssl-passthrough", false, `Enable SSL passthrough feature. Default is disabled`)
|
||||
flags.IntVar(&n.ports.HTTP, "http-port", 80, `Indicates the port to use for HTTP traffic`)
|
||||
flags.IntVar(&n.ports.HTTPS, "https-port", 443, `Indicates the port to use for HTTPS traffic`)
|
||||
flags.IntVar(&n.ports.Status, "status-port", 18080, `Indicates the TCP port to use for exposing the nginx status page`)
|
||||
flags.IntVar(&n.ports.SSLProxy, "ssl-passtrough-proxy-port", 442, `Default port to use internally for SSL when SSL Passthgough is enabled`)
|
||||
flags.IntVar(&n.ports.Default, "default-server-port", 8181, `Default port to use for exposing the default server (catch all)`)
|
||||
}
|
||||
|
||||
// OverrideFlags customize NGINX controller flags
|
||||
func (n *NGINXController) OverrideFlags(flags *pflag.FlagSet) {
|
||||
// we check port collisions
|
||||
if !isPortAvailable(n.ports.HTTP) {
|
||||
glog.Fatalf("Port %v is already in use. Please check the flag --http-port", n.ports.HTTP)
|
||||
}
|
||||
if !isPortAvailable(n.ports.HTTPS) {
|
||||
glog.Fatalf("Port %v is already in use. Please check the flag --https-port", n.ports.HTTPS)
|
||||
}
|
||||
if !isPortAvailable(n.ports.Status) {
|
||||
glog.Fatalf("Port %v is already in use. Please check the flag --status-port", n.ports.Status)
|
||||
}
|
||||
if !isPortAvailable(n.ports.Default) {
|
||||
glog.Fatalf("Port %v is already in use. Please check the flag --default-server-port", n.ports.Default)
|
||||
}
|
||||
|
||||
ic, _ := flags.GetString("ingress-class")
|
||||
wc, _ := flags.GetString("watch-namespace")
|
||||
|
||||
if ic == "" {
|
||||
ic = defIngressClass
|
||||
}
|
||||
|
||||
if ic != defIngressClass {
|
||||
glog.Warningf("only Ingress with class %v will be processed by this ingress controller", ic)
|
||||
}
|
||||
|
||||
flags.Set("ingress-class", ic)
|
||||
|
||||
h, _ := flags.GetInt("healthz-port")
|
||||
n.ports.Health = h
|
||||
|
||||
n.stats = newStatsCollector(wc, ic, n.binary, n.ports.Status)
|
||||
|
||||
if n.isSSLPassthroughEnabled {
|
||||
if !isPortAvailable(n.ports.SSLProxy) {
|
||||
glog.Fatalf("Port %v is already in use. Please check the flag --ssl-passtrough-proxy-port", n.ports.SSLProxy)
|
||||
}
|
||||
|
||||
glog.Info("starting TLS proxy for SSL passthrough")
|
||||
n.proxy = &proxy{
|
||||
Default: &server{
|
||||
Hostname: "localhost",
|
||||
IP: "127.0.0.1",
|
||||
Port: n.ports.SSLProxy,
|
||||
ProxyProtocol: true,
|
||||
},
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", n.ports.HTTPS))
|
||||
if err != nil {
|
||||
glog.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
proxyList := &proxyproto.Listener{Listener: listener}
|
||||
|
||||
// start goroutine that accepts tcp connections in port 443
|
||||
go func() {
|
||||
for {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if n.isProxyProtocolEnabled {
|
||||
// we need to wrap the listener in order to decode
|
||||
// proxy protocol before handling the connection
|
||||
conn, err = proxyList.Accept()
|
||||
} else {
|
||||
conn, err = listener.Accept()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error accepting tcp connection: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
glog.V(3).Infof("remote address %s to local %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
go n.proxy.Handle(conn)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultIngressClass just return the default ingress class
|
||||
func (n NGINXController) DefaultIngressClass() string {
|
||||
return defIngressClass
|
||||
}
|
||||
|
||||
// testTemplate checks if the NGINX configuration inside the byte array is valid
|
||||
// running the command "nginx -t" using a temporal file.
|
||||
func (n NGINXController) testTemplate(cfg []byte) error {
|
||||
if len(cfg) == 0 {
|
||||
return fmt.Errorf("invalid nginx configuration (empty)")
|
||||
}
|
||||
tmpfile, err := ioutil.TempFile("", "nginx-cfg")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpfile.Close()
|
||||
err = ioutil.WriteFile(tmpfile.Name(), cfg, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := exec.Command(n.binary, "-t", "-c", tmpfile.Name()).CombinedOutput()
|
||||
if err != nil {
|
||||
// this error is different from the rest because it must be clear why nginx is not working
|
||||
oe := fmt.Sprintf(`
|
||||
-------------------------------------------------------------------------------
|
||||
Error: %v
|
||||
%v
|
||||
-------------------------------------------------------------------------------
|
||||
`, err, string(out))
|
||||
return errors.New(oe)
|
||||
}
|
||||
|
||||
os.Remove(tmpfile.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetConfig sets the configured configmap
|
||||
func (n *NGINXController) SetConfig(cmap *apiv1.ConfigMap) {
|
||||
n.configmap = cmap
|
||||
n.isProxyProtocolEnabled = false
|
||||
|
||||
m := map[string]string{}
|
||||
if cmap != nil {
|
||||
m = cmap.Data
|
||||
}
|
||||
|
||||
val, ok := m["use-proxy-protocol"]
|
||||
if ok {
|
||||
b, err := strconv.ParseBool(val)
|
||||
if err == nil {
|
||||
n.isProxyProtocolEnabled = b
|
||||
}
|
||||
}
|
||||
|
||||
n.backendDefaults = ngx_template.ReadConfig(m).Backend
|
||||
}
|
||||
|
||||
// SetListers sets the configured store listers in the generic ingress controller
|
||||
func (n *NGINXController) SetListers(lister *ingress.StoreLister) {
|
||||
n.storeLister = lister
|
||||
}
|
||||
|
||||
// UpdateIngressStatus custom Ingress status update
|
||||
func (n *NGINXController) UpdateIngressStatus(*extensions.Ingress) []apiv1.LoadBalancerIngress {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnUpdate is called by syncQueue in https://github.com/kubernetes/ingress/blob/master/core/pkg/ingress/controller/controller.go#L426
|
||||
// periodically to keep the configuration in sync.
|
||||
//
|
||||
// convert configmap to custom configuration object (different in each implementation)
|
||||
// write the custom template (the complexity depends on the implementation)
|
||||
// write the configuration file
|
||||
// returning nill implies the backend will be reloaded.
|
||||
// if an error is returned means requeue the update
|
||||
func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
||||
cfg := ngx_template.ReadConfig(n.configmap.Data)
|
||||
cfg.Resolver = n.resolver
|
||||
|
||||
servers := []*server{}
|
||||
for _, pb := range ingressCfg.PassthroughBackends {
|
||||
svc := pb.Service
|
||||
if svc == nil {
|
||||
glog.Warningf("missing service for PassthroughBackends %v", pb.Backend)
|
||||
continue
|
||||
}
|
||||
port, err := strconv.Atoi(pb.Port.String())
|
||||
if err != nil {
|
||||
for _, sp := range svc.Spec.Ports {
|
||||
if sp.Name == pb.Port.String() {
|
||||
port = int(sp.Port)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, sp := range svc.Spec.Ports {
|
||||
if sp.Port == int32(port) {
|
||||
port = int(sp.Port)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Allow PassthroughBackends to specify they support proxy-protocol
|
||||
servers = append(servers, &server{
|
||||
Hostname: pb.Hostname,
|
||||
IP: svc.Spec.ClusterIP,
|
||||
Port: port,
|
||||
ProxyProtocol: false,
|
||||
})
|
||||
}
|
||||
|
||||
if n.isSSLPassthroughEnabled {
|
||||
n.proxy.ServerList = servers
|
||||
}
|
||||
|
||||
// we need to check if the status module configuration changed
|
||||
if cfg.EnableVtsStatus {
|
||||
n.setupMonitor(vtsStatusModule)
|
||||
} else {
|
||||
n.setupMonitor(defaultStatusModule)
|
||||
}
|
||||
|
||||
// NGINX cannot resize the hash tables used to store server names.
|
||||
// For this reason we check if the defined size defined is correct
|
||||
// for the FQDN defined in the ingress rules adjusting the value
|
||||
// if is required.
|
||||
// https://trac.nginx.org/nginx/ticket/352
|
||||
// https://trac.nginx.org/nginx/ticket/631
|
||||
var longestName int
|
||||
var serverNameBytes int
|
||||
redirectServers := make(map[string]string)
|
||||
for _, srv := range ingressCfg.Servers {
|
||||
if longestName < len(srv.Hostname) {
|
||||
longestName = len(srv.Hostname)
|
||||
}
|
||||
serverNameBytes += len(srv.Hostname)
|
||||
if srv.RedirectFromToWWW {
|
||||
var n string
|
||||
if strings.HasPrefix(srv.Hostname, "www.") {
|
||||
n = strings.TrimLeft(srv.Hostname, "www.")
|
||||
} else {
|
||||
n = fmt.Sprintf("www.%v", srv.Hostname)
|
||||
}
|
||||
glog.V(3).Infof("creating redirect from %v to %v", srv.Hostname, n)
|
||||
if _, ok := redirectServers[n]; !ok {
|
||||
found := false
|
||||
for _, esrv := range ingressCfg.Servers {
|
||||
if esrv.Hostname == n {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
redirectServers[n] = srv.Hostname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cfg.ServerNameHashBucketSize == 0 {
|
||||
nameHashBucketSize := nginxHashBucketSize(longestName)
|
||||
glog.V(3).Infof("adjusting ServerNameHashBucketSize variable to %v", nameHashBucketSize)
|
||||
cfg.ServerNameHashBucketSize = nameHashBucketSize
|
||||
}
|
||||
serverNameHashMaxSize := nextPowerOf2(serverNameBytes)
|
||||
if cfg.ServerNameHashMaxSize < serverNameHashMaxSize {
|
||||
glog.V(3).Infof("adjusting ServerNameHashMaxSize variable to %v", serverNameHashMaxSize)
|
||||
cfg.ServerNameHashMaxSize = serverNameHashMaxSize
|
||||
}
|
||||
|
||||
// the limit of open files is per worker process
|
||||
// and we leave some room to avoid consuming all the FDs available
|
||||
wp, err := strconv.Atoi(cfg.WorkerProcesses)
|
||||
glog.V(3).Infof("number of worker processes: %v", wp)
|
||||
if err != nil {
|
||||
wp = 1
|
||||
}
|
||||
maxOpenFiles := (sysctlFSFileMax() / wp) - 1024
|
||||
glog.V(3).Infof("maximum number of open file descriptors : %v", sysctlFSFileMax())
|
||||
if maxOpenFiles < 1024 {
|
||||
// this means the value of RLIMIT_NOFILE is too low.
|
||||
maxOpenFiles = 1024
|
||||
}
|
||||
|
||||
setHeaders := map[string]string{}
|
||||
if cfg.ProxySetHeaders != "" {
|
||||
cmap, exists, err := n.storeLister.ConfigMap.GetByKey(cfg.ProxySetHeaders)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error reading configmap %v: %v", cfg.ProxySetHeaders, err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
setHeaders = cmap.(*apiv1.ConfigMap).Data
|
||||
}
|
||||
}
|
||||
|
||||
addHeaders := map[string]string{}
|
||||
if cfg.AddHeaders != "" {
|
||||
cmap, exists, err := n.storeLister.ConfigMap.GetByKey(cfg.AddHeaders)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error reading configmap %v: %v", cfg.AddHeaders, err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
addHeaders = cmap.(*apiv1.ConfigMap).Data
|
||||
}
|
||||
}
|
||||
|
||||
sslDHParam := ""
|
||||
if cfg.SSLDHParam != "" {
|
||||
secretName := cfg.SSLDHParam
|
||||
s, exists, err := n.storeLister.Secret.GetByKey(secretName)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error reading secret %v: %v", secretName, err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
secret := s.(*apiv1.Secret)
|
||||
nsSecName := strings.Replace(secretName, "/", "-", -1)
|
||||
|
||||
dh, ok := secret.Data["dhparam.pem"]
|
||||
if ok {
|
||||
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error adding or updating dhparam %v file: %v", nsSecName, err)
|
||||
} else {
|
||||
sslDHParam = pemFileName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.SSLDHParam = sslDHParam
|
||||
|
||||
tc := config.TemplateConfig{
|
||||
ProxySetHeaders: setHeaders,
|
||||
AddHeaders: addHeaders,
|
||||
MaxOpenFiles: maxOpenFiles,
|
||||
BacklogSize: sysctlSomaxconn(),
|
||||
Backends: ingressCfg.Backends,
|
||||
PassthroughBackends: ingressCfg.PassthroughBackends,
|
||||
Servers: ingressCfg.Servers,
|
||||
TCPBackends: ingressCfg.TCPEndpoints,
|
||||
UDPBackends: ingressCfg.UDPEndpoints,
|
||||
HealthzURI: ngxHealthPath,
|
||||
CustomErrors: len(cfg.CustomHTTPErrors) > 0,
|
||||
Cfg: cfg,
|
||||
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
||||
RedirectServers: redirectServers,
|
||||
IsSSLPassthroughEnabled: n.isSSLPassthroughEnabled,
|
||||
ListenPorts: n.ports,
|
||||
PublishService: n.controller.GetPublishService(),
|
||||
}
|
||||
|
||||
content, err := n.t.Write(tc)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = n.testTemplate(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.printDiff(content)
|
||||
|
||||
err = ioutil.WriteFile(cfgPath, content, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o, err := exec.Command(n.binary, "-s", "reload", "-c", cfgPath).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v\n%v", err, string(o))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nginxHashBucketSize computes the correct nginx hash_bucket_size for a hash with the given longest key
|
||||
func nginxHashBucketSize(longestString int) int {
|
||||
// See https://github.com/kubernetes/ingress/issues/623 for an explanation
|
||||
wordSize := 8 // Assume 64 bit CPU
|
||||
n := longestString + 2
|
||||
aligned := (n + wordSize - 1) & ^(wordSize - 1)
|
||||
rawSize := wordSize + wordSize + aligned
|
||||
return nextPowerOf2(rawSize)
|
||||
}
|
||||
|
||||
// Name returns the healthcheck name
|
||||
func (n NGINXController) Name() string {
|
||||
return "Ingress Controller"
|
||||
}
|
||||
|
||||
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
|
||||
func (n NGINXController) Check(_ *http.Request) error {
|
||||
res, err := http.Get(fmt.Sprintf("http://localhost:%v%v", n.ports.Status, ngxHealthPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("ingress controller is not healthy")
|
||||
}
|
||||
|
||||
// check the nginx master process is running
|
||||
fs, err := proc.NewFS("/proc")
|
||||
if err != nil {
|
||||
glog.Errorf("%v", err)
|
||||
return err
|
||||
}
|
||||
f, err := ioutil.ReadFile("/run/nginx.pid")
|
||||
if err != nil {
|
||||
glog.Errorf("%v", err)
|
||||
return err
|
||||
}
|
||||
pid, err := strconv.Atoi(strings.TrimRight(string(f), "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fs.NewProc(int(pid))
|
||||
if err != nil {
|
||||
glog.Errorf("%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
// https://play.golang.org/p/TVSyCcdxUh
|
||||
func nextPowerOf2(v int) int {
|
||||
v--
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
v++
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func isIPv6Enabled() bool {
|
||||
cmd := exec.Command("test", "-f", "/proc/net/if_inet6")
|
||||
return cmd.Run() == nil
|
||||
}
|
||||
|
||||
// isNginxRunning returns true if a process with the name 'nginx' is found
|
||||
func isNginxProcessPresent() bool {
|
||||
processes, _ := ps.Processes()
|
||||
for _, p := range processes {
|
||||
if p.Executable() == "nginx" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func waitForNginxShutdown() {
|
||||
timer := time.NewTicker(time.Second * 1)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
if !isNginxProcessPresent() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
pkg/cmd/controller/nginx_test.go
Normal file
59
pkg/cmd/controller/nginx_test.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes 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 main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNginxHashBucketSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
n int
|
||||
expected int
|
||||
}{
|
||||
{0, 32},
|
||||
{1, 32},
|
||||
{2, 32},
|
||||
{3, 32},
|
||||
// ...
|
||||
{13, 32},
|
||||
{14, 32},
|
||||
{15, 64},
|
||||
{16, 64},
|
||||
// ...
|
||||
{45, 64},
|
||||
{46, 64},
|
||||
{47, 128},
|
||||
{48, 128},
|
||||
// ...
|
||||
// ...
|
||||
{109, 128},
|
||||
{110, 128},
|
||||
{111, 256},
|
||||
{112, 256},
|
||||
// ...
|
||||
{237, 256},
|
||||
{238, 256},
|
||||
{239, 512},
|
||||
{240, 512},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
actual := nginxHashBucketSize(test.n)
|
||||
if actual != test.expected {
|
||||
t.Errorf("Test nginxHashBucketSize(%d): expected %d but returned %d", test.n, test.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
110
pkg/cmd/controller/tcp.go
Normal file
110
pkg/cmd/controller/tcp.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/paultag/sniff/parser"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
Hostname string
|
||||
IP string
|
||||
Port int
|
||||
ProxyProtocol bool
|
||||
}
|
||||
|
||||
type proxy struct {
|
||||
ServerList []*server
|
||||
Default *server
|
||||
}
|
||||
|
||||
func (p *proxy) Get(host string) *server {
|
||||
if p.ServerList == nil {
|
||||
return p.Default
|
||||
}
|
||||
|
||||
for _, s := range p.ServerList {
|
||||
if s.Hostname == host {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return p.Default
|
||||
}
|
||||
|
||||
func (p *proxy) Handle(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
data := make([]byte, 4096)
|
||||
|
||||
length, err := conn.Read(data)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("error reading the first 4k of the connection: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
proxy := p.Default
|
||||
hostname, err := parser.GetHostname(data[:])
|
||||
if err == nil {
|
||||
glog.V(4).Infof("parsed hostname from TLS Client Hello: %s", hostname)
|
||||
proxy = p.Get(hostname)
|
||||
}
|
||||
|
||||
if proxy == nil {
|
||||
glog.V(4).Infof("there is no configured proxy for SSL connections")
|
||||
return
|
||||
}
|
||||
|
||||
clientConn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", proxy.IP, proxy.Port))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer clientConn.Close()
|
||||
|
||||
if proxy.ProxyProtocol {
|
||||
//Write out the proxy-protocol header
|
||||
localAddr := conn.LocalAddr().(*net.TCPAddr)
|
||||
remoteAddr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
protocol := "UNKNOWN"
|
||||
if remoteAddr.IP.To4() != nil {
|
||||
protocol = "TCP4"
|
||||
} else if remoteAddr.IP.To16() != nil {
|
||||
protocol = "TCP6"
|
||||
}
|
||||
proxyProtocolHeader := fmt.Sprintf("PROXY %s %s %s %d %d\r\n", protocol, remoteAddr.IP.String(), localAddr.IP.String(), remoteAddr.Port, localAddr.Port)
|
||||
glog.V(4).Infof("Writing proxy protocol header - %s", proxyProtocolHeader)
|
||||
_, err = fmt.Fprintf(clientConn, proxyProtocolHeader)
|
||||
}
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error writing proxy-protocol header: %s", err)
|
||||
clientConn.Close()
|
||||
} else {
|
||||
_, err = clientConn.Write(data[:length])
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error writing first 4k of proxy data: %s", err)
|
||||
clientConn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
pipe(clientConn, conn)
|
||||
}
|
||||
|
||||
func pipe(client, server net.Conn) {
|
||||
doCopy := func(s, c net.Conn, cancel chan<- bool) {
|
||||
io.Copy(s, c)
|
||||
cancel <- true
|
||||
}
|
||||
|
||||
cancel := make(chan bool, 2)
|
||||
|
||||
go doCopy(server, client, cancel)
|
||||
go doCopy(client, server, cancel)
|
||||
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
}
|
||||
}
|
||||
87
pkg/cmd/controller/utils.go
Normal file
87
pkg/cmd/controller/utils.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/sysctl"
|
||||
)
|
||||
|
||||
// sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
|
||||
// maximum number of connections that can be queued for acceptance
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
|
||||
func sysctlSomaxconn() int {
|
||||
maxConns, err := sysctl.New().GetSysctl("net/core/somaxconn")
|
||||
if err != nil || maxConns < 512 {
|
||||
glog.V(3).Infof("system net.core.somaxconn=%v (using system default)", maxConns)
|
||||
return 511
|
||||
}
|
||||
|
||||
return maxConns
|
||||
}
|
||||
|
||||
// sysctlFSFileMax returns the value of fs.file-max, i.e.
|
||||
// maximum number of open file descriptors
|
||||
func sysctlFSFileMax() int {
|
||||
var rLimit syscall.Rlimit
|
||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error reading system maximum number of open file descriptors (RLIMIT_NOFILE): %v", err)
|
||||
// returning 0 means don't render the value
|
||||
return 0
|
||||
}
|
||||
return int(rLimit.Max)
|
||||
}
|
||||
|
||||
func diff(b1, b2 []byte) ([]byte, error) {
|
||||
f1, err := ioutil.TempFile("", "a")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f1.Close()
|
||||
defer os.Remove(f1.Name())
|
||||
|
||||
f2, err := ioutil.TempFile("", "b")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f2.Close()
|
||||
defer os.Remove(f2.Name())
|
||||
|
||||
f1.Write(b1)
|
||||
f2.Write(b2)
|
||||
|
||||
out, _ := exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func isPortAvailable(p int) bool {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%v", p))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ln.Close()
|
||||
return true
|
||||
}
|
||||
41
pkg/cmd/controller/utils_test.go
Normal file
41
pkg/cmd/controller/utils_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDiff(t *testing.T) {
|
||||
tests := []struct {
|
||||
a []byte
|
||||
b []byte
|
||||
empty bool
|
||||
}{
|
||||
{[]byte(""), []byte(""), true},
|
||||
{[]byte("a"), []byte("a"), true},
|
||||
{[]byte("a"), []byte("b"), false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
b, err := diff(test.a, test.b)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error returned: %v", err)
|
||||
}
|
||||
if len(b) == 0 && !test.empty {
|
||||
t.Fatalf("expected empty but returned %s", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
529
pkg/config/config.go
Normal file
529
pkg/config/config.go
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes 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 config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
const (
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
||||
// Sets the maximum allowed size of the client request body
|
||||
bodySize = "1m"
|
||||
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
||||
// Configures logging level [debug | info | notice | warn | error | crit | alert | emerg]
|
||||
// Log levels above are listed in the order of increasing severity
|
||||
errorLevel = "notice"
|
||||
|
||||
// HTTP Strict Transport Security (often abbreviated as HSTS) is a security feature (HTTP header)
|
||||
// that tell browsers that it should only be communicated with using HTTPS, instead of using HTTP.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
|
||||
// max-age is the time, in seconds, that the browser should remember that this site is only to be accessed using HTTPS.
|
||||
hstsMaxAge = "15724800"
|
||||
|
||||
gzipTypes = "application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component"
|
||||
|
||||
logFormatUpstream = `%v - [$the_real_ip] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status`
|
||||
|
||||
logFormatStream = `[$time_local] $protocol $status $bytes_sent $bytes_received $session_time`
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
||||
sslBufferSize = "4k"
|
||||
|
||||
// Enabled ciphers list to enabled. The ciphers are specified in the format understood by the OpenSSL library
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
|
||||
sslCiphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
|
||||
|
||||
// SSL enabled protocols to use
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
sslProtocols = "TLSv1 TLSv1.1 TLSv1.2"
|
||||
|
||||
// Time during which a client may reuse the session parameters stored in a cache.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
sslSessionTimeout = "10m"
|
||||
|
||||
// Size of the SSL shared cache between all worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
sslSessionCacheSize = "10m"
|
||||
|
||||
// Default setting for load balancer algorithm
|
||||
defaultLoadBalancerAlgorithm = "least_conn"
|
||||
|
||||
// Parameters for a shared memory zone that will keep states for various keys.
|
||||
// http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone
|
||||
defaultLimitConnZoneVariable = "$binary_remote_addr"
|
||||
)
|
||||
|
||||
// Configuration represents the content of nginx.conf file
|
||||
type Configuration struct {
|
||||
defaults.Backend `json:",squash"`
|
||||
|
||||
// Sets the name of the configmap that contains the headers to pass to the client
|
||||
AddHeaders string `json:"add-headers,omitempty"`
|
||||
|
||||
// AllowBackendServerHeader enables the return of the header Server from the backend
|
||||
// instead of the generic nginx string.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_hide_header
|
||||
// By default this is disabled
|
||||
AllowBackendServerHeader bool `json:"allow-backend-server-header"`
|
||||
|
||||
// AccessLogPath sets the path of the access logs if enabled
|
||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
|
||||
// By default access logs go to /var/log/nginx/access.log
|
||||
AccessLogPath string `json:"access-log-path,omitempty"`
|
||||
|
||||
// ErrorLogPath sets the path of the error logs
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
||||
// By default error logs go to /var/log/nginx/error.log
|
||||
ErrorLogPath string `json:"error-log-path,omitempty"`
|
||||
|
||||
// EnableDynamicTLSRecords enables dynamic TLS record sizes
|
||||
// https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency
|
||||
// By default this is enabled
|
||||
EnableDynamicTLSRecords bool `json:"enable-dynamic-tls-records"`
|
||||
|
||||
// ClientHeaderBufferSize allows to configure a custom buffer
|
||||
// size for reading client request header
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_buffer_size
|
||||
ClientHeaderBufferSize string `json:"client-header-buffer-size"`
|
||||
|
||||
// Defines a timeout for reading client request header, in seconds
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_timeout
|
||||
ClientHeaderTimeout int `json:"client-header-timeout,omitempty"`
|
||||
|
||||
// Sets buffer size for reading client request body
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size
|
||||
ClientBodyBufferSize string `json:"client-body-buffer-size,omitempty"`
|
||||
|
||||
// Defines a timeout for reading client request body, in seconds
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout
|
||||
ClientBodyTimeout int `json:"client-body-timeout,omitempty"`
|
||||
|
||||
// DisableAccessLog disables the Access Log globally from NGINX ingress controller
|
||||
//http://nginx.org/en/docs/http/ngx_http_log_module.html
|
||||
DisableAccessLog bool `json:"disable-access-log,omitempty"`
|
||||
|
||||
// DisableIpv6 disable listening on ipv6 address
|
||||
DisableIpv6 bool `json:"disable-ipv6,omitempty"`
|
||||
|
||||
// EnableUnderscoresInHeaders enables underscores in header names
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
|
||||
// By default this is disabled
|
||||
EnableUnderscoresInHeaders bool `json:"enable-underscores-in-headers"`
|
||||
|
||||
// IgnoreInvalidHeaders set if header fields with invalid names should be ignored
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
|
||||
// By default this is enabled
|
||||
IgnoreInvalidHeaders bool `json:"ignore-invalid-headers"`
|
||||
|
||||
// EnableVtsStatus allows the replacement of the default status page with a third party module named
|
||||
// nginx-module-vts - https://github.com/vozlt/nginx-module-vts
|
||||
// By default this is disabled
|
||||
EnableVtsStatus bool `json:"enable-vts-status,omitempty"`
|
||||
|
||||
// Vts config on http level
|
||||
// Description: Sets parameters for a shared memory zone that will keep states for various keys. The cache is shared between all worker processe
|
||||
// https://github.com/vozlt/nginx-module-vts#vhost_traffic_status_zone
|
||||
// Default value is 10m
|
||||
VtsStatusZoneSize string `json:"vts-status-zone-size,omitempty"`
|
||||
|
||||
// Vts config on http level
|
||||
// Description: Enables the keys by user defined variable. The key is a key string to calculate traffic.
|
||||
// The name is a group string to calculate traffic. The key and name can contain variables such as $host,
|
||||
// $server_name. The name's group belongs to filterZones if specified. The key's group belongs to serverZones
|
||||
// if not specified second argument name. The example with geoip module is as follows:
|
||||
// https://github.com/vozlt/nginx-module-vts#vhost_traffic_status_filter_by_set_key
|
||||
// Default value is $geoip_country_code country::*
|
||||
VtsDefaultFilterKey string `json:"vts-default-filter-key,omitempty"`
|
||||
|
||||
// RetryNonIdempotent since 1.9.13 NGINX will not retry non-idempotent requests (POST, LOCK, PATCH)
|
||||
// in case of an error. The previous behavior can be restored using the value true
|
||||
RetryNonIdempotent bool `json:"retry-non-idempotent"`
|
||||
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
||||
// Configures logging level [debug | info | notice | warn | error | crit | alert | emerg]
|
||||
// Log levels above are listed in the order of increasing severity
|
||||
ErrorLogLevel string `json:"error-log-level,omitempty"`
|
||||
|
||||
// https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_field_size
|
||||
// HTTP2MaxFieldSize Limits the maximum size of an HPACK-compressed request header field
|
||||
HTTP2MaxFieldSize string `json:"http2-max-field-size,omitempty"`
|
||||
|
||||
// https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_header_size
|
||||
// HTTP2MaxHeaderSize Limits the maximum size of the entire request header list after HPACK decompression
|
||||
HTTP2MaxHeaderSize string `json:"http2-max-header-size,omitempty"`
|
||||
|
||||
// Enables or disables the header HSTS in servers running SSL
|
||||
HSTS bool `json:"hsts,omitempty"`
|
||||
|
||||
// Enables or disables the use of HSTS in all the subdomains of the servername
|
||||
// Default: true
|
||||
HSTSIncludeSubdomains bool `json:"hsts-include-subdomains,omitempty"`
|
||||
|
||||
// HTTP Strict Transport Security (often abbreviated as HSTS) is a security feature (HTTP header)
|
||||
// that tell browsers that it should only be communicated with using HTTPS, instead of using HTTP.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
|
||||
// max-age is the time, in seconds, that the browser should remember that this site is only to be
|
||||
// accessed using HTTPS.
|
||||
HSTSMaxAge string `json:"hsts-max-age,omitempty"`
|
||||
|
||||
// Enables or disables the preload attribute in HSTS feature
|
||||
HSTSPreload bool `json:"hsts-preload,omitempty"`
|
||||
|
||||
// Time during which a keep-alive client connection will stay open on the server side.
|
||||
// The zero value disables keep-alive client connections
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
|
||||
KeepAlive int `json:"keep-alive,omitempty"`
|
||||
|
||||
// Sets the maximum number of requests that can be served through one keep-alive connection.
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests
|
||||
KeepAliveRequests int `json:"keep-alive-requests,omitempty"`
|
||||
|
||||
// LargeClientHeaderBuffers Sets the maximum number and size of buffers used for reading
|
||||
// large client request header.
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers
|
||||
// Default: 4 8k
|
||||
LargeClientHeaderBuffers string `json:"large-client-header-buffers"`
|
||||
|
||||
// Enable json escaping
|
||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
|
||||
LogFormatEscapeJSON bool `json:"log-format-escape-json,omitempty"`
|
||||
|
||||
// Customize upstream log_format
|
||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
|
||||
LogFormatUpstream string `json:"log-format-upstream,omitempty"`
|
||||
|
||||
// Customize stream log_format
|
||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
|
||||
LogFormatStream string `json:"log-format-stream,omitempty"`
|
||||
|
||||
// Maximum number of simultaneous connections that can be opened by each worker process
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_connections
|
||||
MaxWorkerConnections int `json:"max-worker-connections,omitempty"`
|
||||
|
||||
// Sets the bucket size for the map variables hash tables.
|
||||
// Default value depends on the processor’s cache line size.
|
||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#map_hash_bucket_size
|
||||
MapHashBucketSize int `json:"map-hash-bucket-size,omitempty"`
|
||||
|
||||
// If UseProxyProtocol is enabled ProxyRealIPCIDR defines the default the IP/network address
|
||||
// of your external load balancer
|
||||
ProxyRealIPCIDR []string `json:"proxy-real-ip-cidr,omitempty"`
|
||||
|
||||
// Sets the name of the configmap that contains the headers to pass to the backend
|
||||
ProxySetHeaders string `json:"proxy-set-headers,omitempty"`
|
||||
|
||||
// Maximum size of the server names hash tables used in server names, map directive’s values,
|
||||
// MIME types, names of request header strings, etcd.
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_max_size
|
||||
ServerNameHashMaxSize int `json:"server-name-hash-max-size,omitempty"`
|
||||
|
||||
// Size of the bucket for the server names hash tables
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_bucket_size
|
||||
ServerNameHashBucketSize int `json:"server-name-hash-bucket-size,omitempty"`
|
||||
|
||||
// Size of the bucket for the proxy headers hash tables
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_headers_hash_max_size
|
||||
ProxyHeadersHashMaxSize int `json:"proxy-headers-hash-max-size,omitempty"`
|
||||
|
||||
// Maximum size of the bucket for the proxy headers hash tables
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_headers_hash_bucket_size
|
||||
ProxyHeadersHashBucketSize int `json:"proxy-headers-hash-bucket-size,omitempty"`
|
||||
|
||||
// Enables or disables emitting nginx version in error messages and in the “Server” response header field.
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens
|
||||
// Default: true
|
||||
ShowServerTokens bool `json:"server-tokens"`
|
||||
|
||||
// Enabled ciphers list to enabled. The ciphers are specified in the format understood by
|
||||
// the OpenSSL library
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
|
||||
SSLCiphers string `json:"ssl-ciphers,omitempty"`
|
||||
|
||||
// Specifies a curve for ECDHE ciphers.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ecdh_curve
|
||||
SSLECDHCurve string `json:"ssl-ecdh-curve,omitempty"`
|
||||
|
||||
// The secret that contains Diffie-Hellman key to help with "Perfect Forward Secrecy"
|
||||
// https://www.openssl.org/docs/manmaster/apps/dhparam.html
|
||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
||||
SSLDHParam string `json:"ssl-dh-param,omitempty"`
|
||||
|
||||
// SSL enabled protocols to use
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
SSLProtocols string `json:"ssl-protocols,omitempty"`
|
||||
|
||||
// Enables or disables the use of shared SSL cache among worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCache bool `json:"ssl-session-cache,omitempty"`
|
||||
|
||||
// Size of the SSL shared cache between all worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCacheSize string `json:"ssl-session-cache-size,omitempty"`
|
||||
|
||||
// Enables or disables session resumption through TLS session tickets.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_tickets
|
||||
SSLSessionTickets bool `json:"ssl-session-tickets,omitempty"`
|
||||
|
||||
// Time during which a client may reuse the session parameters stored in a cache.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
SSLSessionTimeout string `json:"ssl-session-timeout,omitempty"`
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
||||
SSLBufferSize string `json:"ssl-buffer-size,omitempty"`
|
||||
|
||||
// Enables or disables the use of the PROXY protocol to receive client connection
|
||||
// (real IP address) information passed through proxy servers and load balancers
|
||||
// such as HAproxy and Amazon Elastic Load Balancer (ELB).
|
||||
// https://www.nginx.com/resources/admin-guide/proxy-protocol/
|
||||
UseProxyProtocol bool `json:"use-proxy-protocol,omitempty"`
|
||||
|
||||
// Enables or disables the use of the nginx module that compresses responses using the "gzip" method
|
||||
// http://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
||||
UseGzip bool `json:"use-gzip,omitempty"`
|
||||
|
||||
// Enables or disables the HTTP/2 support in secure connections
|
||||
// http://nginx.org/en/docs/http/ngx_http_v2_module.html
|
||||
// Default: true
|
||||
UseHTTP2 bool `json:"use-http2,omitempty"`
|
||||
|
||||
// MIME types in addition to "text/html" to compress. The special value “*” matches any MIME type.
|
||||
// Responses with the “text/html” type are always compressed if UseGzip is enabled
|
||||
GzipTypes string `json:"gzip-types,omitempty"`
|
||||
|
||||
// Defines the number of worker processes. By default auto means number of available CPU cores
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
|
||||
WorkerProcesses string `json:"worker-processes,omitempty"`
|
||||
|
||||
// Defines a timeout for a graceful shutdown of worker processes
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_shutdown_timeout
|
||||
WorkerShutdownTimeout string `json:"worker-shutdown-timeout,omitempty"`
|
||||
|
||||
// Defines the load balancing algorithm to use. The deault is round-robin
|
||||
LoadBalanceAlgorithm string `json:"load-balance,omitempty"`
|
||||
|
||||
// Sets the bucket size for the variables hash table.
|
||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#variables_hash_bucket_size
|
||||
VariablesHashBucketSize int `json:"variables-hash-bucket-size,omitempty"`
|
||||
|
||||
// Sets the maximum size of the variables hash table.
|
||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#variables_hash_max_size
|
||||
VariablesHashMaxSize int `json:"variables-hash-max-size,omitempty"`
|
||||
|
||||
// Activates the cache for connections to upstream servers.
|
||||
// The connections parameter sets the maximum number of idle keepalive connections to
|
||||
// upstream servers that are preserved in the cache of each worker process. When this
|
||||
// number is exceeded, the least recently used connections are closed.
|
||||
// http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
|
||||
// Default: 32
|
||||
UpstreamKeepaliveConnections int `json:"upstream-keepalive-connections,omitempty"`
|
||||
|
||||
// Sets the maximum size of the variables hash table.
|
||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#variables_hash_max_size
|
||||
LimitConnZoneVariable string `json:"limit-conn-zone-variable,omitempty"`
|
||||
|
||||
// Sets the timeout between two successive read or write operations on client or proxied server connections.
|
||||
// If no data is transmitted within this time, the connection is closed.
|
||||
// http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_timeout
|
||||
ProxyStreamTimeout string `json:"proxy-stream-timeout,omitempty"`
|
||||
|
||||
// Sets the ipv4 addresses on which the server will accept requests.
|
||||
BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"`
|
||||
|
||||
// Sets the ipv6 addresses on which the server will accept requests.
|
||||
BindAddressIpv6 []string `json:"bind-address-ipv6,omitempty"`
|
||||
|
||||
// Sets the header field for identifying the originating IP address of a client
|
||||
// Default is X-Forwarded-For
|
||||
ForwardedForHeader string `json:"forwarded-for-header,omitempty"`
|
||||
|
||||
// EnableOpentracing enables the nginx Opentracing extension
|
||||
// https://github.com/rnburn/nginx-opentracing
|
||||
// By default this is disabled
|
||||
EnableOpentracing bool `json:"enable-opentracing"`
|
||||
|
||||
// ZipkinCollectorHost specifies the host to use when uploading traces
|
||||
ZipkinCollectorHost string `json:"zipkin-collector-host"`
|
||||
|
||||
// ZipkinCollectorPort specifies the port to use when uploading traces
|
||||
ZipkinCollectorPort int `json:"zipkin-collector-port"`
|
||||
|
||||
// ZipkinServiceName specifies the service name to use for any traces created
|
||||
// Default: nginx
|
||||
ZipkinServiceName string `json:"zipkin-service-name"`
|
||||
|
||||
// HTTPSnippet adds custom configuration to the http section of the nginx configuration
|
||||
HTTPSnippet string `json:"http-snippet"`
|
||||
|
||||
// ServerSnippet adds custom configuration to all the servers in the nginx configuration
|
||||
ServerSnippet string `json:"server-snippet"`
|
||||
|
||||
// LocationSnippet adds custom configuration to all the locations in the nginx configuration
|
||||
LocationSnippet string `json:"location-snippet"`
|
||||
}
|
||||
|
||||
// NewDefault returns the default nginx configuration
|
||||
func NewDefault() Configuration {
|
||||
defIPCIDR := make([]string, 0)
|
||||
defIPCIDR = append(defIPCIDR, "0.0.0.0/0")
|
||||
defBindAddress := make([]string, 0)
|
||||
cfg := Configuration{
|
||||
AllowBackendServerHeader: false,
|
||||
AccessLogPath: "/var/log/nginx/access.log",
|
||||
ErrorLogPath: "/var/log/nginx/error.log",
|
||||
ClientHeaderBufferSize: "1k",
|
||||
ClientHeaderTimeout: 60,
|
||||
ClientBodyBufferSize: "8k",
|
||||
ClientBodyTimeout: 60,
|
||||
EnableDynamicTLSRecords: true,
|
||||
EnableUnderscoresInHeaders: false,
|
||||
ErrorLogLevel: errorLevel,
|
||||
ForwardedForHeader: "X-Forwarded-For",
|
||||
HTTP2MaxFieldSize: "4k",
|
||||
HTTP2MaxHeaderSize: "16k",
|
||||
HSTS: true,
|
||||
HSTSIncludeSubdomains: true,
|
||||
HSTSMaxAge: hstsMaxAge,
|
||||
HSTSPreload: false,
|
||||
IgnoreInvalidHeaders: true,
|
||||
GzipTypes: gzipTypes,
|
||||
KeepAlive: 75,
|
||||
KeepAliveRequests: 100,
|
||||
LargeClientHeaderBuffers: "4 8k",
|
||||
LogFormatEscapeJSON: false,
|
||||
LogFormatStream: logFormatStream,
|
||||
LogFormatUpstream: logFormatUpstream,
|
||||
MaxWorkerConnections: 16384,
|
||||
MapHashBucketSize: 64,
|
||||
ProxyRealIPCIDR: defIPCIDR,
|
||||
ServerNameHashMaxSize: 1024,
|
||||
ProxyHeadersHashMaxSize: 512,
|
||||
ProxyHeadersHashBucketSize: 64,
|
||||
ShowServerTokens: true,
|
||||
SSLBufferSize: sslBufferSize,
|
||||
SSLCiphers: sslCiphers,
|
||||
SSLECDHCurve: "auto",
|
||||
SSLProtocols: sslProtocols,
|
||||
SSLSessionCache: true,
|
||||
SSLSessionCacheSize: sslSessionCacheSize,
|
||||
SSLSessionTickets: true,
|
||||
SSLSessionTimeout: sslSessionTimeout,
|
||||
UseGzip: true,
|
||||
WorkerProcesses: strconv.Itoa(runtime.NumCPU()),
|
||||
WorkerShutdownTimeout: "10s",
|
||||
LoadBalanceAlgorithm: defaultLoadBalancerAlgorithm,
|
||||
VtsStatusZoneSize: "10m",
|
||||
VtsDefaultFilterKey: "$geoip_country_code country::*",
|
||||
VariablesHashBucketSize: 64,
|
||||
VariablesHashMaxSize: 2048,
|
||||
UseHTTP2: true,
|
||||
ProxyStreamTimeout: "600s",
|
||||
Backend: defaults.Backend{
|
||||
ProxyBodySize: bodySize,
|
||||
ProxyConnectTimeout: 5,
|
||||
ProxyReadTimeout: 60,
|
||||
ProxySendTimeout: 60,
|
||||
ProxyBufferSize: "4k",
|
||||
ProxyCookieDomain: "off",
|
||||
ProxyCookiePath: "off",
|
||||
ProxyNextUpstream: "error timeout invalid_header http_502 http_503 http_504",
|
||||
ProxyRequestBuffering: "on",
|
||||
SSLRedirect: true,
|
||||
CustomHTTPErrors: []int{},
|
||||
WhitelistSourceRange: []string{},
|
||||
SkipAccessLogURLs: []string{},
|
||||
LimitRate: 0,
|
||||
LimitRateAfter: 0,
|
||||
},
|
||||
UpstreamKeepaliveConnections: 32,
|
||||
LimitConnZoneVariable: defaultLimitConnZoneVariable,
|
||||
BindAddressIpv4: defBindAddress,
|
||||
BindAddressIpv6: defBindAddress,
|
||||
ZipkinCollectorPort: 9411,
|
||||
ZipkinServiceName: "nginx",
|
||||
}
|
||||
|
||||
if glog.V(5) {
|
||||
cfg.ErrorLogLevel = "debug"
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
// BuildLogFormatUpstream format the log_format upstream using
|
||||
// proxy_protocol_addr as remote client address if UseProxyProtocol
|
||||
// is enabled.
|
||||
func (cfg Configuration) BuildLogFormatUpstream() string {
|
||||
if cfg.LogFormatUpstream == logFormatUpstream {
|
||||
return fmt.Sprintf(cfg.LogFormatUpstream, "$the_real_ip")
|
||||
}
|
||||
|
||||
return cfg.LogFormatUpstream
|
||||
}
|
||||
|
||||
// TemplateConfig contains the nginx configuration to render the file nginx.conf
|
||||
type TemplateConfig struct {
|
||||
ProxySetHeaders map[string]string
|
||||
AddHeaders map[string]string
|
||||
MaxOpenFiles int
|
||||
BacklogSize int
|
||||
Backends []*ingress.Backend
|
||||
PassthroughBackends []*ingress.SSLPassthroughBackend
|
||||
Servers []*ingress.Server
|
||||
TCPBackends []ingress.L4Service
|
||||
UDPBackends []ingress.L4Service
|
||||
HealthzURI string
|
||||
CustomErrors bool
|
||||
Cfg Configuration
|
||||
IsIPV6Enabled bool
|
||||
IsSSLPassthroughEnabled bool
|
||||
RedirectServers map[string]string
|
||||
ListenPorts *ListenPorts
|
||||
PublishService *apiv1.Service
|
||||
}
|
||||
|
||||
// ListenPorts describe the ports required to run the
|
||||
// NGINX Ingress controller
|
||||
type ListenPorts struct {
|
||||
HTTP int
|
||||
HTTPS int
|
||||
Status int
|
||||
Health int
|
||||
Default int
|
||||
SSLProxy int
|
||||
}
|
||||
46
pkg/config/config_test.go
Normal file
46
pkg/config/config_test.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes 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 config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildLogFormatUpstream(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
useProxyProtocol bool // use proxy protocol
|
||||
curLogFormat string
|
||||
expected string
|
||||
}{
|
||||
{true, logFormatUpstream, fmt.Sprintf(logFormatUpstream, "$the_real_ip")},
|
||||
{false, logFormatUpstream, fmt.Sprintf(logFormatUpstream, "$the_real_ip")},
|
||||
{true, "my-log-format", "my-log-format"},
|
||||
{false, "john-log-format", "john-log-format"},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
cfg := NewDefault()
|
||||
cfg.UseProxyProtocol = testCase.useProxyProtocol
|
||||
cfg.LogFormatUpstream = testCase.curLogFormat
|
||||
result := cfg.BuildLogFormatUpstream()
|
||||
if result != testCase.expected {
|
||||
t.Errorf(" expected %v but return %v", testCase.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
151
pkg/metric/collector/nginx.go
Normal file
151
pkg/metric/collector/nginx.go
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes 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 collector
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type (
|
||||
nginxStatusCollector struct {
|
||||
scrapeChan chan scrapeRequest
|
||||
ngxHealthPort int
|
||||
ngxVtsPath string
|
||||
data *nginxStatusData
|
||||
watchNamespace string
|
||||
ingressClass string
|
||||
}
|
||||
|
||||
nginxStatusData struct {
|
||||
active *prometheus.Desc
|
||||
accepted *prometheus.Desc
|
||||
handled *prometheus.Desc
|
||||
requests *prometheus.Desc
|
||||
reading *prometheus.Desc
|
||||
writing *prometheus.Desc
|
||||
waiting *prometheus.Desc
|
||||
}
|
||||
)
|
||||
|
||||
// NewNginxStatus returns a new prometheus collector the default nginx status module
|
||||
func NewNginxStatus(watchNamespace, ingressClass string, ngxHealthPort int, ngxVtsPath string) Stopable {
|
||||
|
||||
p := nginxStatusCollector{
|
||||
scrapeChan: make(chan scrapeRequest),
|
||||
ngxHealthPort: ngxHealthPort,
|
||||
ngxVtsPath: ngxVtsPath,
|
||||
watchNamespace: watchNamespace,
|
||||
ingressClass: ingressClass,
|
||||
}
|
||||
|
||||
p.data = &nginxStatusData{
|
||||
active: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "active_connections"),
|
||||
"total number of active connections",
|
||||
[]string{"ingress_class", "namespace"}, nil),
|
||||
|
||||
accepted: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "accepted_connections"),
|
||||
"total number of accepted client connections",
|
||||
[]string{"ingress_class", "namespace"}, nil),
|
||||
|
||||
handled: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "handled_connections"),
|
||||
"total number of handled connections",
|
||||
[]string{"ingress_class", "namespace"}, nil),
|
||||
|
||||
requests: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "total_requests"),
|
||||
"total number of client requests",
|
||||
[]string{"ingress_class", "namespace"}, nil),
|
||||
|
||||
reading: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "current_reading_connections"),
|
||||
"current number of connections where nginx is reading the request header",
|
||||
[]string{"ingress_class", "namespace"}, nil),
|
||||
|
||||
writing: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "current_writing_connections"),
|
||||
"current number of connections where nginx is writing the response back to the client",
|
||||
[]string{"ingress_class", "namespace"}, nil),
|
||||
|
||||
waiting: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "current_waiting_connections"),
|
||||
"current number of idle client connections waiting for a request",
|
||||
[]string{"ingress_class", "namespace"}, nil),
|
||||
}
|
||||
|
||||
go p.start()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
func (p nginxStatusCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- p.data.active
|
||||
ch <- p.data.accepted
|
||||
ch <- p.data.handled
|
||||
ch <- p.data.requests
|
||||
ch <- p.data.reading
|
||||
ch <- p.data.writing
|
||||
ch <- p.data.waiting
|
||||
}
|
||||
|
||||
// Collect implements prometheus.Collector.
|
||||
func (p nginxStatusCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
req := scrapeRequest{results: ch, done: make(chan struct{})}
|
||||
p.scrapeChan <- req
|
||||
<-req.done
|
||||
}
|
||||
|
||||
func (p nginxStatusCollector) start() {
|
||||
for req := range p.scrapeChan {
|
||||
ch := req.results
|
||||
p.scrape(ch)
|
||||
req.done <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p nginxStatusCollector) Stop() {
|
||||
close(p.scrapeChan)
|
||||
}
|
||||
|
||||
// nginxStatusCollector scrape the nginx status
|
||||
func (p nginxStatusCollector) scrape(ch chan<- prometheus.Metric) {
|
||||
s, err := getNginxStatus(p.ngxHealthPort, p.ngxVtsPath)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error obtaining nginx status info: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(p.data.active,
|
||||
prometheus.GaugeValue, float64(s.Active), p.ingressClass, p.watchNamespace)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.accepted,
|
||||
prometheus.GaugeValue, float64(s.Accepted), p.ingressClass, p.watchNamespace)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.handled,
|
||||
prometheus.GaugeValue, float64(s.Handled), p.ingressClass, p.watchNamespace)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.requests,
|
||||
prometheus.GaugeValue, float64(s.Requests), p.ingressClass, p.watchNamespace)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.reading,
|
||||
prometheus.GaugeValue, float64(s.Reading), p.ingressClass, p.watchNamespace)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.writing,
|
||||
prometheus.GaugeValue, float64(s.Writing), p.ingressClass, p.watchNamespace)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.waiting,
|
||||
prometheus.GaugeValue, float64(s.Waiting), p.ingressClass, p.watchNamespace)
|
||||
}
|
||||
174
pkg/metric/collector/process.go
Normal file
174
pkg/metric/collector/process.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes 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 collector
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
common "github.com/ncabatoff/process-exporter"
|
||||
"github.com/ncabatoff/process-exporter/proc"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// BinaryNameMatcher ...
|
||||
type BinaryNameMatcher struct {
|
||||
Name string
|
||||
Binary string
|
||||
}
|
||||
|
||||
// MatchAndName returns false if the match failed, otherwise
|
||||
// true and the resulting name.
|
||||
func (em BinaryNameMatcher) MatchAndName(nacl common.NameAndCmdline) (bool, string) {
|
||||
if len(nacl.Cmdline) == 0 {
|
||||
return false, ""
|
||||
}
|
||||
cmd := filepath.Base(em.Binary)
|
||||
return em.Name == cmd, ""
|
||||
}
|
||||
|
||||
type namedProcessData struct {
|
||||
numProcs *prometheus.Desc
|
||||
cpuSecs *prometheus.Desc
|
||||
readBytes *prometheus.Desc
|
||||
writeBytes *prometheus.Desc
|
||||
memResidentbytes *prometheus.Desc
|
||||
memVirtualbytes *prometheus.Desc
|
||||
startTime *prometheus.Desc
|
||||
}
|
||||
|
||||
type namedProcess struct {
|
||||
*proc.Grouper
|
||||
|
||||
scrapeChan chan scrapeRequest
|
||||
fs *proc.FS
|
||||
data namedProcessData
|
||||
}
|
||||
|
||||
// NewNamedProcess returns a new prometheus collector for the nginx process
|
||||
func NewNamedProcess(children bool, mn common.MatchNamer) (prometheus.Collector, error) {
|
||||
fs, err := proc.NewFS("/proc")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := namedProcess{
|
||||
scrapeChan: make(chan scrapeRequest),
|
||||
Grouper: proc.NewGrouper(children, mn),
|
||||
fs: fs,
|
||||
}
|
||||
_, err = p.Update(p.fs.AllProcs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.data = namedProcessData{
|
||||
numProcs: prometheus.NewDesc(
|
||||
"num_procs",
|
||||
"number of processes",
|
||||
nil, nil),
|
||||
|
||||
cpuSecs: prometheus.NewDesc(
|
||||
"cpu_seconds_total",
|
||||
"Cpu usage in seconds",
|
||||
nil, nil),
|
||||
|
||||
readBytes: prometheus.NewDesc(
|
||||
"read_bytes_total",
|
||||
"number of bytes read",
|
||||
nil, nil),
|
||||
|
||||
writeBytes: prometheus.NewDesc(
|
||||
"write_bytes_total",
|
||||
"number of bytes written",
|
||||
nil, nil),
|
||||
|
||||
memResidentbytes: prometheus.NewDesc(
|
||||
"resident_memory_bytes",
|
||||
"number of bytes of memory in use",
|
||||
nil, nil),
|
||||
|
||||
memVirtualbytes: prometheus.NewDesc(
|
||||
"virtual_memory_bytes",
|
||||
"number of bytes of memory in use",
|
||||
nil, nil),
|
||||
|
||||
startTime: prometheus.NewDesc(
|
||||
"oldest_start_time_seconds",
|
||||
"start time in seconds since 1970/01/01",
|
||||
nil, nil),
|
||||
}
|
||||
|
||||
go p.start()
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
func (p namedProcess) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- p.data.cpuSecs
|
||||
ch <- p.data.numProcs
|
||||
ch <- p.data.readBytes
|
||||
ch <- p.data.writeBytes
|
||||
ch <- p.data.memResidentbytes
|
||||
ch <- p.data.memVirtualbytes
|
||||
ch <- p.data.startTime
|
||||
}
|
||||
|
||||
// Collect implements prometheus.Collector.
|
||||
func (p namedProcess) Collect(ch chan<- prometheus.Metric) {
|
||||
req := scrapeRequest{results: ch, done: make(chan struct{})}
|
||||
p.scrapeChan <- req
|
||||
<-req.done
|
||||
}
|
||||
|
||||
func (p namedProcess) start() {
|
||||
for req := range p.scrapeChan {
|
||||
ch := req.results
|
||||
p.scrape(ch)
|
||||
req.done <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p namedProcess) Stop() {
|
||||
close(p.scrapeChan)
|
||||
}
|
||||
|
||||
func (p namedProcess) scrape(ch chan<- prometheus.Metric) {
|
||||
_, err := p.Update(p.fs.AllProcs())
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error obtaining nginx process info: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, gcounts := range p.Groups() {
|
||||
ch <- prometheus.MustNewConstMetric(p.data.numProcs,
|
||||
prometheus.GaugeValue, float64(gcounts.Procs))
|
||||
ch <- prometheus.MustNewConstMetric(p.data.memResidentbytes,
|
||||
prometheus.GaugeValue, float64(gcounts.Memresident))
|
||||
ch <- prometheus.MustNewConstMetric(p.data.memVirtualbytes,
|
||||
prometheus.GaugeValue, float64(gcounts.Memvirtual))
|
||||
ch <- prometheus.MustNewConstMetric(p.data.startTime,
|
||||
prometheus.GaugeValue, float64(gcounts.OldestStartTime.Unix()))
|
||||
ch <- prometheus.MustNewConstMetric(p.data.cpuSecs,
|
||||
prometheus.CounterValue, gcounts.Cpu)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.readBytes,
|
||||
prometheus.CounterValue, float64(gcounts.ReadBytes))
|
||||
ch <- prometheus.MustNewConstMetric(p.data.writeBytes,
|
||||
prometheus.CounterValue, float64(gcounts.WriteBytes))
|
||||
}
|
||||
}
|
||||
30
pkg/metric/collector/scrape.go
Normal file
30
pkg/metric/collector/scrape.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes 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 collector
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
// Stopable defines a prometheus collector that can be stopped
|
||||
type Stopable interface {
|
||||
prometheus.Collector
|
||||
Stop()
|
||||
}
|
||||
|
||||
type scrapeRequest struct {
|
||||
results chan<- prometheus.Metric
|
||||
done chan struct{}
|
||||
}
|
||||
225
pkg/metric/collector/status.go
Normal file
225
pkg/metric/collector/status.go
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes 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 collector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
var (
|
||||
ac = regexp.MustCompile(`Active connections: (\d+)`)
|
||||
sahr = regexp.MustCompile(`(\d+)\s(\d+)\s(\d+)`)
|
||||
reading = regexp.MustCompile(`Reading: (\d+)`)
|
||||
writing = regexp.MustCompile(`Writing: (\d+)`)
|
||||
waiting = regexp.MustCompile(`Waiting: (\d+)`)
|
||||
)
|
||||
|
||||
type basicStatus struct {
|
||||
// Active total number of active connections
|
||||
Active int
|
||||
// Accepted total number of accepted client connections
|
||||
Accepted int
|
||||
// Handled total number of handled connections. Generally, the parameter value is the same as accepts unless some resource limits have been reached (for example, the worker_connections limit).
|
||||
Handled int
|
||||
// Requests total number of client requests.
|
||||
Requests int
|
||||
// Reading current number of connections where nginx is reading the request header.
|
||||
Reading int
|
||||
// Writing current number of connections where nginx is writing the response back to the client.
|
||||
Writing int
|
||||
// Waiting current number of idle client connections waiting for a request.
|
||||
Waiting int
|
||||
}
|
||||
|
||||
// https://github.com/vozlt/nginx-module-vts
|
||||
type vts struct {
|
||||
NginxVersion string `json:"nginxVersion"`
|
||||
LoadMsec int `json:"loadMsec"`
|
||||
NowMsec int `json:"nowMsec"`
|
||||
// Total connections and requests(same as stub_status_module in NGINX)
|
||||
Connections connections `json:"connections"`
|
||||
// Traffic(in/out) and request and response counts and cache hit ratio per each server zone
|
||||
ServerZones map[string]serverZone `json:"serverZones"`
|
||||
// Traffic(in/out) and request and response counts and cache hit ratio per each server zone filtered through
|
||||
// the vhost_traffic_status_filter_by_set_key directive
|
||||
FilterZones map[string]map[string]filterZone `json:"filterZones"`
|
||||
// Traffic(in/out) and request and response counts per server in each upstream group
|
||||
UpstreamZones map[string][]upstreamZone `json:"upstreamZones"`
|
||||
}
|
||||
|
||||
type serverZone struct {
|
||||
RequestCounter float64 `json:"requestCounter"`
|
||||
InBytes float64 `json:"inBytes"`
|
||||
OutBytes float64 `json:"outBytes"`
|
||||
Responses response `json:"responses"`
|
||||
Cache cache `json:"cache"`
|
||||
}
|
||||
|
||||
type filterZone struct {
|
||||
RequestCounter float64 `json:"requestCounter"`
|
||||
InBytes float64 `json:"inBytes"`
|
||||
OutBytes float64 `json:"outBytes"`
|
||||
Cache cache `json:"cache"`
|
||||
Responses response `json:"responses"`
|
||||
}
|
||||
|
||||
type upstreamZone struct {
|
||||
Responses response `json:"responses"`
|
||||
Server string `json:"server"`
|
||||
RequestCounter float64 `json:"requestCounter"`
|
||||
InBytes float64 `json:"inBytes"`
|
||||
OutBytes float64 `json:"outBytes"`
|
||||
ResponseMsec float64 `json:"responseMsec"`
|
||||
Weight float64 `json:"weight"`
|
||||
MaxFails float64 `json:"maxFails"`
|
||||
FailTimeout float64 `json:"failTimeout"`
|
||||
Backup BoolToFloat64 `json:"backup"`
|
||||
Down BoolToFloat64 `json:"down"`
|
||||
}
|
||||
|
||||
type cache struct {
|
||||
Miss float64 `json:"miss"`
|
||||
Bypass float64 `json:"bypass"`
|
||||
Expired float64 `json:"expired"`
|
||||
Stale float64 `json:"stale"`
|
||||
Updating float64 `json:"updating"`
|
||||
Revalidated float64 `json:"revalidated"`
|
||||
Hit float64 `json:"hit"`
|
||||
Scarce float64 `json:"scarce"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
OneXx float64 `json:"1xx"`
|
||||
TwoXx float64 `json:"2xx"`
|
||||
TheeXx float64 `json:"3xx"`
|
||||
FourXx float64 `json:"4xx"`
|
||||
FiveXx float64 `json:"5xx"`
|
||||
}
|
||||
|
||||
type connections struct {
|
||||
Active float64 `json:"active"`
|
||||
Reading float64 `json:"reading"`
|
||||
Writing float64 `json:"writing"`
|
||||
Waiting float64 `json:"waiting"`
|
||||
Accepted float64 `json:"accepted"`
|
||||
Handled float64 `json:"handled"`
|
||||
Requests float64 `json:"requests"`
|
||||
}
|
||||
|
||||
// BoolToFloat64 ...
|
||||
type BoolToFloat64 float64
|
||||
|
||||
// UnmarshalJSON ...
|
||||
func (bit BoolToFloat64) UnmarshalJSON(data []byte) error {
|
||||
asString := string(data)
|
||||
if asString == "1" || asString == "true" {
|
||||
bit = 1
|
||||
} else if asString == "0" || asString == "false" {
|
||||
bit = 0
|
||||
} else {
|
||||
return fmt.Errorf(fmt.Sprintf("boolean unmarshal error: invalid input %s", asString))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNginxStatus(port int, path string) (*basicStatus, error) {
|
||||
url := fmt.Sprintf("http://localhost:%v%v", port, path)
|
||||
glog.V(3).Infof("start scraping url: %v", url)
|
||||
|
||||
data, err := httpBody(url)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error scraping nginx status page: %v", err)
|
||||
}
|
||||
|
||||
return parse(string(data)), nil
|
||||
}
|
||||
|
||||
func httpBody(url string) ([]byte, error) {
|
||||
resp, err := http.DefaultClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error scraping nginx : %v", err)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error scraping nginx (%v)", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("unexpected error scraping nginx (status %v)", resp.StatusCode)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func getNginxVtsMetrics(port int, path string) (*vts, error) {
|
||||
url := fmt.Sprintf("http://localhost:%v%v", port, path)
|
||||
glog.V(3).Infof("start scraping url: %v", url)
|
||||
|
||||
data, err := httpBody(url)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error scraping nginx vts (%v)", err)
|
||||
}
|
||||
|
||||
var vts *vts
|
||||
err = json.Unmarshal(data, &vts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unexpected error json unmarshal (%v)", err)
|
||||
}
|
||||
glog.V(3).Infof("scrape returned : %v", vts)
|
||||
return vts, nil
|
||||
}
|
||||
|
||||
func parse(data string) *basicStatus {
|
||||
acr := ac.FindStringSubmatch(data)
|
||||
sahrr := sahr.FindStringSubmatch(data)
|
||||
readingr := reading.FindStringSubmatch(data)
|
||||
writingr := writing.FindStringSubmatch(data)
|
||||
waitingr := waiting.FindStringSubmatch(data)
|
||||
|
||||
return &basicStatus{
|
||||
toInt(acr, 1),
|
||||
toInt(sahrr, 1),
|
||||
toInt(sahrr, 2),
|
||||
toInt(sahrr, 3),
|
||||
toInt(readingr, 1),
|
||||
toInt(writingr, 1),
|
||||
toInt(waitingr, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func toInt(data []string, pos int) int {
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
if pos > len(data) {
|
||||
return 0
|
||||
}
|
||||
if v, err := strconv.Atoi(data[pos]); err == nil {
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
72
pkg/metric/collector/status_test.go
Normal file
72
pkg/metric/collector/status_test.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 collector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
func TestParseStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
out *basicStatus
|
||||
}{
|
||||
{`Active connections: 43
|
||||
server accepts handled requests
|
||||
7368 7368 10993
|
||||
Reading: 0 Writing: 5 Waiting: 38`,
|
||||
&basicStatus{43, 7368, 7368, 10993, 0, 5, 38},
|
||||
},
|
||||
{`Active connections: 0
|
||||
server accepts handled requests
|
||||
1 7 0
|
||||
Reading: A Writing: B Waiting: 38`,
|
||||
&basicStatus{0, 1, 7, 0, 0, 0, 38},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
r := parse(test.in)
|
||||
if diff := pretty.Compare(r, test.out); diff != "" {
|
||||
t.Logf("%v", diff)
|
||||
t.Fatalf("expected %v but returned %v", test.out, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToint(t *testing.T) {
|
||||
tests := []struct {
|
||||
in []string
|
||||
pos int
|
||||
exp int
|
||||
}{
|
||||
{[]string{}, 0, 0},
|
||||
{[]string{}, 1, 0},
|
||||
{[]string{"A"}, 0, 0},
|
||||
{[]string{"1"}, 0, 1},
|
||||
{[]string{"a", "2"}, 1, 2},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
v := toInt(test.in, test.pos)
|
||||
if v != test.exp {
|
||||
t.Fatalf("expected %v but returned %v", test.exp, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
273
pkg/metric/collector/vts.go
Normal file
273
pkg/metric/collector/vts.go
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes 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 collector
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const ns = "nginx"
|
||||
|
||||
type (
|
||||
vtsCollector struct {
|
||||
scrapeChan chan scrapeRequest
|
||||
port int
|
||||
path string
|
||||
data *vtsData
|
||||
watchNamespace string
|
||||
ingressClass string
|
||||
}
|
||||
|
||||
vtsData struct {
|
||||
bytes *prometheus.Desc
|
||||
cache *prometheus.Desc
|
||||
connections *prometheus.Desc
|
||||
responses *prometheus.Desc
|
||||
requests *prometheus.Desc
|
||||
filterZoneBytes *prometheus.Desc
|
||||
filterZoneResponses *prometheus.Desc
|
||||
filterZoneCache *prometheus.Desc
|
||||
upstreamBackup *prometheus.Desc
|
||||
upstreamBytes *prometheus.Desc
|
||||
upstreamDown *prometheus.Desc
|
||||
upstreamFailTimeout *prometheus.Desc
|
||||
upstreamMaxFails *prometheus.Desc
|
||||
upstreamResponses *prometheus.Desc
|
||||
upstreamRequests *prometheus.Desc
|
||||
upstreamResponseMsec *prometheus.Desc
|
||||
upstreamWeight *prometheus.Desc
|
||||
}
|
||||
)
|
||||
|
||||
// NewNGINXVTSCollector returns a new prometheus collector for the VTS module
|
||||
func NewNGINXVTSCollector(watchNamespace, ingressClass string, port int, path string) Stopable {
|
||||
|
||||
p := vtsCollector{
|
||||
scrapeChan: make(chan scrapeRequest),
|
||||
port: port,
|
||||
path: path,
|
||||
watchNamespace: watchNamespace,
|
||||
ingressClass: ingressClass,
|
||||
}
|
||||
|
||||
p.data = &vtsData{
|
||||
bytes: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "bytes_total"),
|
||||
"Nginx bytes count",
|
||||
[]string{"ingress_class", "namespace", "server_zone", "direction"}, nil),
|
||||
|
||||
cache: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "cache_total"),
|
||||
"Nginx cache count",
|
||||
[]string{"ingress_class", "namespace", "server_zone", "type"}, nil),
|
||||
|
||||
connections: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "connections_total"),
|
||||
"Nginx connections count",
|
||||
[]string{"ingress_class", "namespace", "type"}, nil),
|
||||
|
||||
responses: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "responses_total"),
|
||||
"The number of responses with status codes 1xx, 2xx, 3xx, 4xx, and 5xx.",
|
||||
[]string{"ingress_class", "namespace", "server_zone", "status_code"}, nil),
|
||||
|
||||
requests: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "requests_total"),
|
||||
"The total number of requested client connections.",
|
||||
[]string{"ingress_class", "namespace", "server_zone"}, nil),
|
||||
|
||||
filterZoneBytes: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "filterzone_bytes_total"),
|
||||
"Nginx bytes count",
|
||||
[]string{"ingress_class", "namespace", "server_zone", "key", "direction"}, nil),
|
||||
|
||||
filterZoneResponses: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "filterzone_responses_total"),
|
||||
"The number of responses with status codes 1xx, 2xx, 3xx, 4xx, and 5xx.",
|
||||
[]string{"ingress_class", "namespace", "server_zone", "key", "status_code"}, nil),
|
||||
|
||||
filterZoneCache: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "filterzone_cache_total"),
|
||||
"Nginx cache count",
|
||||
[]string{"ingress_class", "namespace", "server_zone", "key", "type"}, nil),
|
||||
|
||||
upstreamBackup: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_backup"),
|
||||
"Current backup setting of the server.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server"}, nil),
|
||||
|
||||
upstreamBytes: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_bytes_total"),
|
||||
"The total number of bytes sent to this server.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server", "direction"}, nil),
|
||||
|
||||
upstreamDown: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "vts_upstream_down_total"),
|
||||
"Current down setting of the server.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server"}, nil),
|
||||
|
||||
upstreamFailTimeout: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_fail_timeout"),
|
||||
"Current fail_timeout setting of the server.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server"}, nil),
|
||||
|
||||
upstreamMaxFails: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_maxfails"),
|
||||
"Current max_fails setting of the server.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server"}, nil),
|
||||
|
||||
upstreamResponses: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_responses_total"),
|
||||
"The number of upstream responses with status codes 1xx, 2xx, 3xx, 4xx, and 5xx.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server", "status_code"}, nil),
|
||||
|
||||
upstreamRequests: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_requests_total"),
|
||||
"The total number of client connections forwarded to this server.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server"}, nil),
|
||||
|
||||
upstreamResponseMsec: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_response_msecs_avg"),
|
||||
"The average of only upstream response processing times in milliseconds.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server"}, nil),
|
||||
|
||||
upstreamWeight: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(ns, "", "upstream_weight"),
|
||||
"Current upstream weight setting of the server.",
|
||||
[]string{"ingress_class", "namespace", "upstream", "server"}, nil),
|
||||
}
|
||||
|
||||
go p.start()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
func (p vtsCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- p.data.bytes
|
||||
ch <- p.data.cache
|
||||
ch <- p.data.connections
|
||||
ch <- p.data.requests
|
||||
ch <- p.data.responses
|
||||
ch <- p.data.upstreamBackup
|
||||
ch <- p.data.upstreamBytes
|
||||
ch <- p.data.upstreamDown
|
||||
ch <- p.data.upstreamFailTimeout
|
||||
ch <- p.data.upstreamMaxFails
|
||||
ch <- p.data.upstreamRequests
|
||||
ch <- p.data.upstreamResponseMsec
|
||||
ch <- p.data.upstreamResponses
|
||||
ch <- p.data.upstreamWeight
|
||||
ch <- p.data.filterZoneBytes
|
||||
ch <- p.data.filterZoneCache
|
||||
ch <- p.data.filterZoneResponses
|
||||
}
|
||||
|
||||
// Collect implements prometheus.Collector.
|
||||
func (p vtsCollector) Collect(ch chan<- prometheus.Metric) {
|
||||
req := scrapeRequest{results: ch, done: make(chan struct{})}
|
||||
p.scrapeChan <- req
|
||||
<-req.done
|
||||
}
|
||||
|
||||
func (p vtsCollector) start() {
|
||||
for req := range p.scrapeChan {
|
||||
ch := req.results
|
||||
p.scrapeVts(ch)
|
||||
req.done <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (p vtsCollector) Stop() {
|
||||
close(p.scrapeChan)
|
||||
}
|
||||
|
||||
// scrapeVts scrape nginx vts metrics
|
||||
func (p vtsCollector) scrapeVts(ch chan<- prometheus.Metric) {
|
||||
nginxMetrics, err := getNginxVtsMetrics(p.port, p.path)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error obtaining nginx status info: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
reflectMetrics(&nginxMetrics.Connections, p.data.connections, ch, p.ingressClass, p.watchNamespace)
|
||||
|
||||
for name, zones := range nginxMetrics.UpstreamZones {
|
||||
for pos, value := range zones {
|
||||
reflectMetrics(&zones[pos].Responses, p.data.upstreamResponses, ch, p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamRequests,
|
||||
prometheus.CounterValue, zones[pos].RequestCounter, p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamDown,
|
||||
prometheus.CounterValue, float64(zones[pos].Down), p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamWeight,
|
||||
prometheus.CounterValue, zones[pos].Weight, p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamResponseMsec,
|
||||
prometheus.CounterValue, zones[pos].ResponseMsec, p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamBackup,
|
||||
prometheus.CounterValue, float64(zones[pos].Backup), p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamFailTimeout,
|
||||
prometheus.CounterValue, zones[pos].FailTimeout, p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamMaxFails,
|
||||
prometheus.CounterValue, zones[pos].MaxFails, p.ingressClass, p.watchNamespace, name, value.Server)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamBytes,
|
||||
prometheus.CounterValue, zones[pos].InBytes, p.ingressClass, p.watchNamespace, name, value.Server, "in")
|
||||
ch <- prometheus.MustNewConstMetric(p.data.upstreamBytes,
|
||||
prometheus.CounterValue, zones[pos].OutBytes, p.ingressClass, p.watchNamespace, name, value.Server, "out")
|
||||
}
|
||||
}
|
||||
|
||||
for name, zone := range nginxMetrics.ServerZones {
|
||||
reflectMetrics(&zone.Responses, p.data.responses, ch, p.ingressClass, p.watchNamespace, name)
|
||||
reflectMetrics(&zone.Cache, p.data.cache, ch, p.ingressClass, p.watchNamespace, name)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(p.data.requests,
|
||||
prometheus.CounterValue, zone.RequestCounter, p.ingressClass, p.watchNamespace, name)
|
||||
ch <- prometheus.MustNewConstMetric(p.data.bytes,
|
||||
prometheus.CounterValue, zone.InBytes, p.ingressClass, p.watchNamespace, name, "in")
|
||||
ch <- prometheus.MustNewConstMetric(p.data.bytes,
|
||||
prometheus.CounterValue, zone.OutBytes, p.ingressClass, p.watchNamespace, name, "out")
|
||||
}
|
||||
|
||||
for serverZone, keys := range nginxMetrics.FilterZones {
|
||||
for name, zone := range keys {
|
||||
reflectMetrics(&zone.Responses, p.data.filterZoneResponses, ch, p.ingressClass, p.watchNamespace, serverZone, name)
|
||||
reflectMetrics(&zone.Cache, p.data.filterZoneCache, ch, p.ingressClass, p.watchNamespace, serverZone, name)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(p.data.filterZoneBytes,
|
||||
prometheus.CounterValue, zone.InBytes, p.ingressClass, p.watchNamespace, serverZone, name, "in")
|
||||
ch <- prometheus.MustNewConstMetric(p.data.filterZoneBytes,
|
||||
prometheus.CounterValue, zone.OutBytes, p.ingressClass, p.watchNamespace, serverZone, name, "out")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reflectMetrics(value interface{}, desc *prometheus.Desc, ch chan<- prometheus.Metric, labels ...string) {
|
||||
val := reflect.ValueOf(value).Elem()
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
tag := val.Type().Field(i).Tag
|
||||
l := append(labels, tag.Get("json"))
|
||||
ch <- prometheus.MustNewConstMetric(desc,
|
||||
prometheus.CounterValue, val.Field(i).Interface().(float64),
|
||||
l...)
|
||||
}
|
||||
}
|
||||
137
pkg/template/configmap.go
Normal file
137
pkg/template/configmap.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||
ing_net "k8s.io/ingress/core/pkg/net"
|
||||
)
|
||||
|
||||
const (
|
||||
customHTTPErrors = "custom-http-errors"
|
||||
skipAccessLogUrls = "skip-access-log-urls"
|
||||
whitelistSourceRange = "whitelist-source-range"
|
||||
proxyRealIPCIDR = "proxy-real-ip-cidr"
|
||||
bindAddress = "bind-address"
|
||||
)
|
||||
|
||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||
func ReadConfig(src map[string]string) config.Configuration {
|
||||
conf := map[string]string{}
|
||||
if src != nil {
|
||||
// we need to copy the configmap data because the content is altered
|
||||
for k, v := range src {
|
||||
conf[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
errors := make([]int, 0)
|
||||
skipUrls := make([]string, 0)
|
||||
whitelist := make([]string, 0)
|
||||
proxylist := make([]string, 0)
|
||||
bindAddressIpv4List := make([]string, 0)
|
||||
bindAddressIpv6List := make([]string, 0)
|
||||
|
||||
if val, ok := conf[customHTTPErrors]; ok {
|
||||
delete(conf, customHTTPErrors)
|
||||
for _, i := range strings.Split(val, ",") {
|
||||
j, err := strconv.Atoi(i)
|
||||
if err != nil {
|
||||
glog.Warningf("%v is not a valid http code: %v", i, err)
|
||||
} else {
|
||||
errors = append(errors, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
if val, ok := conf[skipAccessLogUrls]; ok {
|
||||
delete(conf, skipAccessLogUrls)
|
||||
skipUrls = strings.Split(val, ",")
|
||||
}
|
||||
if val, ok := conf[whitelistSourceRange]; ok {
|
||||
delete(conf, whitelistSourceRange)
|
||||
whitelist = append(whitelist, strings.Split(val, ",")...)
|
||||
}
|
||||
if val, ok := conf[proxyRealIPCIDR]; ok {
|
||||
delete(conf, proxyRealIPCIDR)
|
||||
proxylist = append(proxylist, strings.Split(val, ",")...)
|
||||
} else {
|
||||
proxylist = append(proxylist, "0.0.0.0/0")
|
||||
}
|
||||
if val, ok := conf[bindAddress]; ok {
|
||||
delete(conf, bindAddress)
|
||||
for _, i := range strings.Split(val, ",") {
|
||||
ns := net.ParseIP(i)
|
||||
if ns != nil {
|
||||
if ing_net.IsIPV6(ns) {
|
||||
bindAddressIpv6List = append(bindAddressIpv6List, fmt.Sprintf("[%v]", ns))
|
||||
} else {
|
||||
bindAddressIpv4List = append(bindAddressIpv4List, fmt.Sprintf("%v", ns))
|
||||
}
|
||||
} else {
|
||||
glog.Warningf("%v is not a valid textual representation of an IP address", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
to := config.NewDefault()
|
||||
to.CustomHTTPErrors = filterErrors(errors)
|
||||
to.SkipAccessLogURLs = skipUrls
|
||||
to.WhitelistSourceRange = whitelist
|
||||
to.ProxyRealIPCIDR = proxylist
|
||||
to.BindAddressIpv4 = bindAddressIpv4List
|
||||
to.BindAddressIpv6 = bindAddressIpv6List
|
||||
|
||||
config := &mapstructure.DecoderConfig{
|
||||
Metadata: nil,
|
||||
WeaklyTypedInput: true,
|
||||
Result: &to,
|
||||
TagName: "json",
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(config)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error merging defaults: %v", err)
|
||||
}
|
||||
err = decoder.Decode(conf)
|
||||
if err != nil {
|
||||
glog.Warningf("unexpected error merging defaults: %v", err)
|
||||
}
|
||||
|
||||
return to
|
||||
}
|
||||
|
||||
func filterErrors(codes []int) []int {
|
||||
var fa []int
|
||||
for _, code := range codes {
|
||||
if code > 299 && code < 600 {
|
||||
fa = append(fa, code)
|
||||
} else {
|
||||
glog.Warningf("error code %v is not valid for custom error pages", code)
|
||||
}
|
||||
}
|
||||
|
||||
return fa
|
||||
}
|
||||
95
pkg/template/configmap_test.go
Normal file
95
pkg/template/configmap_test.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 template
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
|
||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||
)
|
||||
|
||||
func TestFilterErrors(t *testing.T) {
|
||||
e := filterErrors([]int{200, 300, 345, 500, 555, 999})
|
||||
if len(e) != 4 {
|
||||
t.Errorf("expected 4 elements but %v returned", len(e))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfigMapToStruct(t *testing.T) {
|
||||
conf := map[string]string{
|
||||
"custom-http-errors": "300,400,demo",
|
||||
"proxy-read-timeout": "1",
|
||||
"proxy-send-timeout": "2",
|
||||
"skip-access-log-urls": "/log,/demo,/test",
|
||||
"use-proxy-protocol": "true",
|
||||
"disable-access-log": "true",
|
||||
"access-log-path": "/var/log/test/access.log",
|
||||
"error-log-path": "/var/log/test/error.log",
|
||||
"use-gzip": "true",
|
||||
"enable-dynamic-tls-records": "false",
|
||||
"gzip-types": "text/html",
|
||||
"proxy-real-ip-cidr": "1.1.1.1/8,2.2.2.2/24",
|
||||
"bind-address": "1.1.1.1,2.2.2.2,3.3.3,2001:db8:a0b:12f0::1,3731:54:65fe:2::a7,33:33:33::33::33",
|
||||
"worker-shutdown-timeout": "99s",
|
||||
}
|
||||
def := config.NewDefault()
|
||||
def.CustomHTTPErrors = []int{300, 400}
|
||||
def.DisableAccessLog = true
|
||||
def.AccessLogPath = "/var/log/test/access.log"
|
||||
def.ErrorLogPath = "/var/log/test/error.log"
|
||||
def.SkipAccessLogURLs = []string{"/log", "/demo", "/test"}
|
||||
def.ProxyReadTimeout = 1
|
||||
def.ProxySendTimeout = 2
|
||||
def.EnableDynamicTLSRecords = false
|
||||
def.UseProxyProtocol = true
|
||||
def.GzipTypes = "text/html"
|
||||
def.ProxyRealIPCIDR = []string{"1.1.1.1/8", "2.2.2.2/24"}
|
||||
def.BindAddressIpv4 = []string{"1.1.1.1", "2.2.2.2"}
|
||||
def.BindAddressIpv6 = []string{"[2001:db8:a0b:12f0::1]", "[3731:54:65fe:2::a7]"}
|
||||
def.WorkerShutdownTimeout = "99s"
|
||||
|
||||
to := ReadConfig(conf)
|
||||
if diff := pretty.Compare(to, def); diff != "" {
|
||||
t.Errorf("unexpected diff: (-got +want)\n%s", diff)
|
||||
}
|
||||
|
||||
def = config.NewDefault()
|
||||
to = ReadConfig(map[string]string{})
|
||||
if diff := pretty.Compare(to, def); diff != "" {
|
||||
t.Errorf("unexpected diff: (-got +want)\n%s", diff)
|
||||
}
|
||||
|
||||
def = config.NewDefault()
|
||||
def.WhitelistSourceRange = []string{"1.1.1.1/32"}
|
||||
to = ReadConfig(map[string]string{
|
||||
"whitelist-source-range": "1.1.1.1/32",
|
||||
})
|
||||
|
||||
if diff := pretty.Compare(to, def); diff != "" {
|
||||
t.Errorf("unexpected diff: (-got +want)\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultLoadBalance(t *testing.T) {
|
||||
conf := map[string]string{}
|
||||
to := ReadConfig(conf)
|
||||
if to.LoadBalanceAlgorithm != "least_conn" {
|
||||
t.Errorf("default load balance algorithm wrong")
|
||||
}
|
||||
}
|
||||
690
pkg/template/template.go
Normal file
690
pkg/template/template.go
Normal file
|
|
@ -0,0 +1,690 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
text_template "text/template"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||
ing_net "k8s.io/ingress/core/pkg/net"
|
||||
"k8s.io/ingress/core/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
slash = "/"
|
||||
defBufferSize = 65535
|
||||
)
|
||||
|
||||
// Template ...
|
||||
type Template struct {
|
||||
tmpl *text_template.Template
|
||||
fw watch.FileWatcher
|
||||
s int
|
||||
}
|
||||
|
||||
//NewTemplate returns a new Template instance or an
|
||||
//error if the specified template file contains errors
|
||||
func NewTemplate(file string, onChange func()) (*Template, error) {
|
||||
tmpl, err := text_template.New("nginx.tmpl").Funcs(funcMap).ParseFiles(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fw, err := watch.NewFileWatcher(file, onChange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Template{
|
||||
tmpl: tmpl,
|
||||
fw: fw,
|
||||
s: defBufferSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close removes the file watcher
|
||||
func (t *Template) Close() {
|
||||
t.fw.Close()
|
||||
}
|
||||
|
||||
// Write populates a buffer using a template with NGINX configuration
|
||||
// and the servers and upstreams created by Ingress rules
|
||||
func (t *Template) Write(conf config.TemplateConfig) ([]byte, error) {
|
||||
tmplBuf := bytes.NewBuffer(make([]byte, 0, t.s))
|
||||
outCmdBuf := bytes.NewBuffer(make([]byte, 0, t.s))
|
||||
|
||||
defer func() {
|
||||
if t.s < tmplBuf.Cap() {
|
||||
glog.V(2).Infof("adjusting template buffer size from %v to %v", t.s, tmplBuf.Cap())
|
||||
t.s = tmplBuf.Cap()
|
||||
}
|
||||
}()
|
||||
|
||||
if glog.V(3) {
|
||||
b, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
glog.Infof("NGINX configuration: %v", string(b))
|
||||
}
|
||||
|
||||
err := t.tmpl.Execute(tmplBuf, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// squeezes multiple adjacent empty lines to be single
|
||||
// spaced this is to avoid the use of regular expressions
|
||||
cmd := exec.Command("/ingress-controller/clean-nginx-conf.sh")
|
||||
cmd.Stdin = tmplBuf
|
||||
cmd.Stdout = outCmdBuf
|
||||
if err := cmd.Run(); err != nil {
|
||||
glog.Warningf("unexpected error cleaning template: %v", err)
|
||||
return tmplBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
return outCmdBuf.Bytes(), nil
|
||||
}
|
||||
|
||||
var (
|
||||
funcMap = text_template.FuncMap{
|
||||
"empty": func(input interface{}) bool {
|
||||
check, ok := input.(string)
|
||||
if ok {
|
||||
return len(check) == 0
|
||||
}
|
||||
return true
|
||||
},
|
||||
"buildLocation": buildLocation,
|
||||
"buildAuthLocation": buildAuthLocation,
|
||||
"buildAuthResponseHeaders": buildAuthResponseHeaders,
|
||||
"buildProxyPass": buildProxyPass,
|
||||
"filterRateLimits": filterRateLimits,
|
||||
"buildRateLimitZones": buildRateLimitZones,
|
||||
"buildRateLimit": buildRateLimit,
|
||||
"buildResolvers": buildResolvers,
|
||||
"buildUpstreamName": buildUpstreamName,
|
||||
"isLocationAllowed": isLocationAllowed,
|
||||
"buildLogFormatUpstream": buildLogFormatUpstream,
|
||||
"buildDenyVariable": buildDenyVariable,
|
||||
"getenv": os.Getenv,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"toUpper": strings.ToUpper,
|
||||
"toLower": strings.ToLower,
|
||||
"formatIP": formatIP,
|
||||
"buildNextUpstream": buildNextUpstream,
|
||||
"getIngressInformation": getIngressInformation,
|
||||
"serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} {
|
||||
return struct{ First, Second interface{} }{all, server}
|
||||
},
|
||||
"isValidClientBodyBufferSize": isValidClientBodyBufferSize,
|
||||
"buildForwardedFor": buildForwardedFor,
|
||||
"buildAuthSignURL": buildAuthSignURL,
|
||||
}
|
||||
)
|
||||
|
||||
// formatIP will wrap IPv6 addresses in [] and return IPv4 addresses
|
||||
// without modification. If the input cannot be parsed as an IP address
|
||||
// it is returned without modification.
|
||||
func formatIP(input string) string {
|
||||
ip := net.ParseIP(input)
|
||||
if ip == nil {
|
||||
return input
|
||||
}
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
return input
|
||||
}
|
||||
return fmt.Sprintf("[%s]", input)
|
||||
}
|
||||
|
||||
// buildResolvers returns the resolvers reading the /etc/resolv.conf file
|
||||
func buildResolvers(input interface{}) string {
|
||||
// NGINX need IPV6 addresses to be surrounded by brackets
|
||||
nss, ok := input.([]net.IP)
|
||||
if !ok {
|
||||
glog.Errorf("expected a '[]net.IP' type but %T was returned", input)
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(nss) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
r := []string{"resolver"}
|
||||
for _, ns := range nss {
|
||||
if ing_net.IsIPV6(ns) {
|
||||
r = append(r, fmt.Sprintf("[%v]", ns))
|
||||
} else {
|
||||
r = append(r, fmt.Sprintf("%v", ns))
|
||||
}
|
||||
}
|
||||
r = append(r, "valid=30s;")
|
||||
|
||||
return strings.Join(r, " ")
|
||||
}
|
||||
|
||||
// buildLocation produces the location string, if the ingress has redirects
|
||||
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
|
||||
func buildLocation(input interface{}) string {
|
||||
location, ok := input.(*ingress.Location)
|
||||
if !ok {
|
||||
glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
||||
return slash
|
||||
}
|
||||
|
||||
path := location.Path
|
||||
if len(location.Rewrite.Target) > 0 && location.Rewrite.Target != path {
|
||||
if path == slash {
|
||||
return fmt.Sprintf("~* %s", path)
|
||||
}
|
||||
// baseuri regex will parse basename from the given location
|
||||
baseuri := `(?<baseuri>.*)`
|
||||
if !strings.HasSuffix(path, slash) {
|
||||
// Not treat the slash after "location path" as a part of baseuri
|
||||
baseuri = fmt.Sprintf(`\/?%s`, baseuri)
|
||||
}
|
||||
return fmt.Sprintf(`~* ^%s%s`, path, baseuri)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// TODO: Needs Unit Tests
|
||||
func buildAuthLocation(input interface{}) string {
|
||||
location, ok := input.(*ingress.Location)
|
||||
if !ok {
|
||||
glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
||||
return ""
|
||||
}
|
||||
|
||||
if location.ExternalAuth.URL == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
str := base64.URLEncoding.EncodeToString([]byte(location.Path))
|
||||
// avoid locations containing the = char
|
||||
str = strings.Replace(str, "=", "", -1)
|
||||
return fmt.Sprintf("/_external-auth-%v", str)
|
||||
}
|
||||
|
||||
func buildAuthResponseHeaders(input interface{}) []string {
|
||||
location, ok := input.(*ingress.Location)
|
||||
res := []string{}
|
||||
if !ok {
|
||||
glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
||||
return res
|
||||
}
|
||||
|
||||
if len(location.ExternalAuth.ResponseHeaders) == 0 {
|
||||
return res
|
||||
}
|
||||
|
||||
for i, h := range location.ExternalAuth.ResponseHeaders {
|
||||
hvar := strings.ToLower(h)
|
||||
hvar = strings.NewReplacer("-", "_").Replace(hvar)
|
||||
res = append(res, fmt.Sprintf("auth_request_set $authHeader%v $upstream_http_%v;", i, hvar))
|
||||
res = append(res, fmt.Sprintf("proxy_set_header '%v' $authHeader%v;", h, i))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func buildLogFormatUpstream(input interface{}) string {
|
||||
cfg, ok := input.(config.Configuration)
|
||||
if !ok {
|
||||
glog.Errorf("expected a 'config.Configuration' type but %T was returned", input)
|
||||
return ""
|
||||
}
|
||||
|
||||
return cfg.BuildLogFormatUpstream()
|
||||
}
|
||||
|
||||
// buildProxyPass produces the proxy pass string, if the ingress has redirects
|
||||
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
|
||||
// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will
|
||||
// add a base tag in the head of the response from the service
|
||||
func buildProxyPass(host string, b interface{}, loc interface{}) string {
|
||||
backends, ok := b.([]*ingress.Backend)
|
||||
if !ok {
|
||||
glog.Errorf("expected an '[]*ingress.Backend' type but %T was returned", b)
|
||||
return ""
|
||||
}
|
||||
|
||||
location, ok := loc.(*ingress.Location)
|
||||
if !ok {
|
||||
glog.Errorf("expected a '*ingress.Location' type but %T was returned", loc)
|
||||
return ""
|
||||
}
|
||||
|
||||
path := location.Path
|
||||
proto := "http"
|
||||
|
||||
upstreamName := location.Backend
|
||||
for _, backend := range backends {
|
||||
if backend.Name == location.Backend {
|
||||
if backend.Secure || backend.SSLPassthrough {
|
||||
proto = "https"
|
||||
}
|
||||
|
||||
if isSticky(host, location, backend.SessionAffinity.CookieSessionAffinity.Locations) {
|
||||
upstreamName = fmt.Sprintf("sticky-%v", upstreamName)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// defProxyPass returns the default proxy_pass, just the name of the upstream
|
||||
defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, upstreamName)
|
||||
// if the path in the ingress rule is equals to the target: no special rewrite
|
||||
if path == location.Rewrite.Target {
|
||||
return defProxyPass
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(path, slash) {
|
||||
path = fmt.Sprintf("%s/", path)
|
||||
}
|
||||
|
||||
if len(location.Rewrite.Target) > 0 {
|
||||
abu := ""
|
||||
if location.Rewrite.AddBaseURL {
|
||||
// path has a slash suffix, so that it can be connected with baseuri directly
|
||||
bPath := fmt.Sprintf("%s%s", path, "$baseuri")
|
||||
if len(location.Rewrite.BaseURLScheme) > 0 {
|
||||
abu = fmt.Sprintf(`subs_filter '<head(.*)>' '<head$1><base href="%v://$http_host%v">' r;
|
||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="%v://$http_host%v">' r;
|
||||
`, location.Rewrite.BaseURLScheme, bPath, location.Rewrite.BaseURLScheme, bPath)
|
||||
} else {
|
||||
abu = fmt.Sprintf(`subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host%v">' r;
|
||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host%v">' r;
|
||||
`, bPath, bPath)
|
||||
}
|
||||
}
|
||||
|
||||
if location.Rewrite.Target == slash {
|
||||
// special case redirect to /
|
||||
// ie /something to /
|
||||
return fmt.Sprintf(`
|
||||
rewrite %s(.*) /$1 break;
|
||||
rewrite %s / break;
|
||||
proxy_pass %s://%s;
|
||||
%v`, path, location.Path, proto, upstreamName, abu)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`
|
||||
rewrite %s(.*) %s/$1 break;
|
||||
proxy_pass %s://%s;
|
||||
%v`, path, location.Rewrite.Target, proto, upstreamName, abu)
|
||||
}
|
||||
|
||||
// default proxy_pass
|
||||
return defProxyPass
|
||||
}
|
||||
|
||||
// TODO: Needs Unit Tests
|
||||
func filterRateLimits(input interface{}) []ratelimit.RateLimit {
|
||||
ratelimits := []ratelimit.RateLimit{}
|
||||
found := sets.String{}
|
||||
|
||||
servers, ok := input.([]*ingress.Server)
|
||||
if !ok {
|
||||
glog.Errorf("expected a '[]ratelimit.RateLimit' type but %T was returned", input)
|
||||
return ratelimits
|
||||
}
|
||||
for _, server := range servers {
|
||||
for _, loc := range server.Locations {
|
||||
if loc.RateLimit.ID != "" && !found.Has(loc.RateLimit.ID) {
|
||||
found.Insert(loc.RateLimit.ID)
|
||||
ratelimits = append(ratelimits, loc.RateLimit)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ratelimits
|
||||
}
|
||||
|
||||
// TODO: Needs Unit Tests
|
||||
// buildRateLimitZones produces an array of limit_conn_zone in order to allow
|
||||
// rate limiting of request. Each Ingress rule could have up to three zones, one
|
||||
// for connection limit by IP address, one for limiting requests per minute, and
|
||||
// one for limiting requests per second.
|
||||
func buildRateLimitZones(input interface{}) []string {
|
||||
zones := sets.String{}
|
||||
|
||||
servers, ok := input.([]*ingress.Server)
|
||||
if !ok {
|
||||
glog.Errorf("expected a '[]*ingress.Server' type but %T was returned", input)
|
||||
return zones.List()
|
||||
}
|
||||
|
||||
for _, server := range servers {
|
||||
for _, loc := range server.Locations {
|
||||
if loc.RateLimit.Connections.Limit > 0 {
|
||||
zone := fmt.Sprintf("limit_conn_zone $limit_%s zone=%v:%vm;",
|
||||
loc.RateLimit.ID,
|
||||
loc.RateLimit.Connections.Name,
|
||||
loc.RateLimit.Connections.SharedSize)
|
||||
if !zones.Has(zone) {
|
||||
zones.Insert(zone)
|
||||
}
|
||||
}
|
||||
|
||||
if loc.RateLimit.RPM.Limit > 0 {
|
||||
zone := fmt.Sprintf("limit_req_zone $limit_%s zone=%v:%vm rate=%vr/m;",
|
||||
loc.RateLimit.ID,
|
||||
loc.RateLimit.RPM.Name,
|
||||
loc.RateLimit.RPM.SharedSize,
|
||||
loc.RateLimit.RPM.Limit)
|
||||
if !zones.Has(zone) {
|
||||
zones.Insert(zone)
|
||||
}
|
||||
}
|
||||
|
||||
if loc.RateLimit.RPS.Limit > 0 {
|
||||
zone := fmt.Sprintf("limit_req_zone $limit_%s zone=%v:%vm rate=%vr/s;",
|
||||
loc.RateLimit.ID,
|
||||
loc.RateLimit.RPS.Name,
|
||||
loc.RateLimit.RPS.SharedSize,
|
||||
loc.RateLimit.RPS.Limit)
|
||||
if !zones.Has(zone) {
|
||||
zones.Insert(zone)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return zones.List()
|
||||
}
|
||||
|
||||
// buildRateLimit produces an array of limit_req to be used inside the Path of
|
||||
// Ingress rules. The order: connections by IP first, then RPS, and RPM last.
|
||||
func buildRateLimit(input interface{}) []string {
|
||||
limits := []string{}
|
||||
|
||||
loc, ok := input.(*ingress.Location)
|
||||
if !ok {
|
||||
glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
||||
return limits
|
||||
}
|
||||
|
||||
if loc.RateLimit.Connections.Limit > 0 {
|
||||
limit := fmt.Sprintf("limit_conn %v %v;",
|
||||
loc.RateLimit.Connections.Name, loc.RateLimit.Connections.Limit)
|
||||
limits = append(limits, limit)
|
||||
}
|
||||
|
||||
if loc.RateLimit.RPS.Limit > 0 {
|
||||
limit := fmt.Sprintf("limit_req zone=%v burst=%v nodelay;",
|
||||
loc.RateLimit.RPS.Name, loc.RateLimit.RPS.Burst)
|
||||
limits = append(limits, limit)
|
||||
}
|
||||
|
||||
if loc.RateLimit.RPM.Limit > 0 {
|
||||
limit := fmt.Sprintf("limit_req zone=%v burst=%v nodelay;",
|
||||
loc.RateLimit.RPM.Name, loc.RateLimit.RPM.Burst)
|
||||
limits = append(limits, limit)
|
||||
}
|
||||
|
||||
if loc.RateLimit.LimitRateAfter > 0 {
|
||||
limit := fmt.Sprintf("limit_rate_after %vk;",
|
||||
loc.RateLimit.LimitRateAfter)
|
||||
limits = append(limits, limit)
|
||||
}
|
||||
|
||||
if loc.RateLimit.LimitRate > 0 {
|
||||
limit := fmt.Sprintf("limit_rate %vk;",
|
||||
loc.RateLimit.LimitRate)
|
||||
limits = append(limits, limit)
|
||||
}
|
||||
|
||||
return limits
|
||||
}
|
||||
|
||||
func isLocationAllowed(input interface{}) bool {
|
||||
loc, ok := input.(*ingress.Location)
|
||||
if !ok {
|
||||
glog.Errorf("expected an '*ingress.Location' type but %T was returned", input)
|
||||
return false
|
||||
}
|
||||
|
||||
return loc.Denied == nil
|
||||
}
|
||||
|
||||
var (
|
||||
denyPathSlugMap = map[string]string{}
|
||||
)
|
||||
|
||||
// buildDenyVariable returns a nginx variable for a location in a
|
||||
// server to be used in the whitelist check
|
||||
// This method uses a unique id generator library to reduce the
|
||||
// size of the string to be used as a variable in nginx to avoid
|
||||
// issue with the size of the variable bucket size directive
|
||||
func buildDenyVariable(a interface{}) string {
|
||||
l, ok := a.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected a 'string' type but %T was returned", a)
|
||||
return ""
|
||||
}
|
||||
|
||||
if _, ok := denyPathSlugMap[l]; !ok {
|
||||
denyPathSlugMap[l] = buildRandomUUID()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("$deny_%v", denyPathSlugMap[l])
|
||||
}
|
||||
|
||||
// TODO: Needs Unit Tests
|
||||
func buildUpstreamName(host string, b interface{}, loc interface{}) string {
|
||||
|
||||
backends, ok := b.([]*ingress.Backend)
|
||||
if !ok {
|
||||
glog.Errorf("expected an '[]*ingress.Backend' type but %T was returned", b)
|
||||
return ""
|
||||
}
|
||||
|
||||
location, ok := loc.(*ingress.Location)
|
||||
if !ok {
|
||||
glog.Errorf("expected a '*ingress.Location' type but %T was returned", loc)
|
||||
return ""
|
||||
}
|
||||
|
||||
upstreamName := location.Backend
|
||||
|
||||
for _, backend := range backends {
|
||||
if backend.Name == location.Backend {
|
||||
if backend.SessionAffinity.AffinityType == "cookie" &&
|
||||
isSticky(host, location, backend.SessionAffinity.CookieSessionAffinity.Locations) {
|
||||
upstreamName = fmt.Sprintf("sticky-%v", upstreamName)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return upstreamName
|
||||
}
|
||||
|
||||
// TODO: Needs Unit Tests
|
||||
func isSticky(host string, loc *ingress.Location, stickyLocations map[string][]string) bool {
|
||||
if _, ok := stickyLocations[host]; ok {
|
||||
for _, sl := range stickyLocations[host] {
|
||||
if sl == loc.Path {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func buildNextUpstream(input interface{}) string {
|
||||
nextUpstream, ok := input.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected a 'string' type but %T was returned", input)
|
||||
return ""
|
||||
}
|
||||
|
||||
parts := strings.Split(nextUpstream, " ")
|
||||
|
||||
nextUpstreamCodes := make([]string, 0, len(parts))
|
||||
for _, v := range parts {
|
||||
if v != "" && v != "non_idempotent" {
|
||||
nextUpstreamCodes = append(nextUpstreamCodes, v)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(nextUpstreamCodes, " ")
|
||||
}
|
||||
|
||||
// buildRandomUUID return a random string to be used in the template
|
||||
func buildRandomUUID() string {
|
||||
s := uuid.New()
|
||||
return strings.Replace(s, "-", "", -1)
|
||||
}
|
||||
|
||||
func isValidClientBodyBufferSize(input interface{}) bool {
|
||||
s, ok := input.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected an 'string' type but %T was returned", input)
|
||||
return false
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
sLowercase := strings.ToLower(s)
|
||||
|
||||
kCheck := strings.TrimSuffix(sLowercase, "k")
|
||||
_, err := strconv.Atoi(kCheck)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
mCheck := strings.TrimSuffix(sLowercase, "m")
|
||||
_, err = strconv.Atoi(mCheck)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
glog.Errorf("client-body-buffer-size '%v' was provided in an incorrect format, hence it will not be set.", s)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type ingressInformation struct {
|
||||
Namespace string
|
||||
Rule string
|
||||
Service string
|
||||
Annotations map[string]string
|
||||
}
|
||||
|
||||
func getIngressInformation(i, p interface{}) *ingressInformation {
|
||||
ing, ok := i.(*extensions.Ingress)
|
||||
if !ok {
|
||||
glog.Errorf("expected an '*extensions.Ingress' type but %T was returned", i)
|
||||
return &ingressInformation{}
|
||||
}
|
||||
|
||||
path, ok := p.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected a 'string' type but %T was returned", p)
|
||||
return &ingressInformation{}
|
||||
}
|
||||
|
||||
if ing == nil {
|
||||
return &ingressInformation{}
|
||||
}
|
||||
|
||||
info := &ingressInformation{
|
||||
Namespace: ing.GetNamespace(),
|
||||
Rule: ing.GetName(),
|
||||
Annotations: ing.Annotations,
|
||||
}
|
||||
|
||||
if ing.Spec.Backend != nil {
|
||||
info.Service = ing.Spec.Backend.ServiceName
|
||||
}
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if rule.HTTP == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, rPath := range rule.HTTP.Paths {
|
||||
if path == rPath.Path {
|
||||
info.Service = rPath.Backend.ServiceName
|
||||
return info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func buildForwardedFor(input interface{}) string {
|
||||
s, ok := input.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected a 'string' type but %T was returned", input)
|
||||
return ""
|
||||
}
|
||||
|
||||
ffh := strings.Replace(s, "-", "_", -1)
|
||||
ffh = strings.ToLower(ffh)
|
||||
return fmt.Sprintf("$http_%v", ffh)
|
||||
}
|
||||
|
||||
func buildAuthSignURL(input interface{}) string {
|
||||
s, ok := input.(string)
|
||||
if !ok {
|
||||
glog.Errorf("expected an 'string' type but %T was returned", input)
|
||||
return ""
|
||||
}
|
||||
|
||||
u, _ := url.Parse(s)
|
||||
q := u.Query()
|
||||
if len(q) == 0 {
|
||||
return fmt.Sprintf("%v?rd=$scheme://$http_host$request_uri", s)
|
||||
}
|
||||
|
||||
if q.Get("rd") != "" {
|
||||
return s
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v&rd=$scheme://$http_host$request_uri", s)
|
||||
}
|
||||
372
pkg/template/template_test.go
Normal file
372
pkg/template/template_test.go
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 template
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/ingress/controllers/nginx/pkg/config"
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO: add tests for secure endpoints
|
||||
tmplFuncTestcases = map[string]struct {
|
||||
Path string
|
||||
Target string
|
||||
Location string
|
||||
ProxyPass string
|
||||
AddBaseURL bool
|
||||
BaseURLScheme string
|
||||
}{
|
||||
"invalid redirect / to /": {"/", "/", "/", "proxy_pass http://upstream-name;", false, ""},
|
||||
"redirect / to /jenkins": {"/", "/jenkins", "~* /",
|
||||
`
|
||||
rewrite /(.*) /jenkins/$1 break;
|
||||
proxy_pass http://upstream-name;
|
||||
`, false, ""},
|
||||
"redirect /something to /": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
|
||||
rewrite /something/(.*) /$1 break;
|
||||
rewrite /something / break;
|
||||
proxy_pass http://upstream-name;
|
||||
`, false, ""},
|
||||
"redirect /end-with-slash/ to /not-root": {"/end-with-slash/", "/not-root", "~* ^/end-with-slash/(?<baseuri>.*)", `
|
||||
rewrite /end-with-slash/(.*) /not-root/$1 break;
|
||||
proxy_pass http://upstream-name;
|
||||
`, false, ""},
|
||||
"redirect /something-complex to /not-root": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
|
||||
rewrite /something-complex/(.*) /not-root/$1 break;
|
||||
proxy_pass http://upstream-name;
|
||||
`, false, ""},
|
||||
"redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", `
|
||||
rewrite /(.*) /jenkins/$1 break;
|
||||
proxy_pass http://upstream-name;
|
||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/$baseuri">' r;
|
||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/$baseuri">' r;
|
||||
`, true, ""},
|
||||
"redirect /something to / and rewrite": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
|
||||
rewrite /something/(.*) /$1 break;
|
||||
rewrite /something / break;
|
||||
proxy_pass http://upstream-name;
|
||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something/$baseuri">' r;
|
||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something/$baseuri">' r;
|
||||
`, true, ""},
|
||||
"redirect /end-with-slash/ to /not-root and rewrite": {"/end-with-slash/", "/not-root", `~* ^/end-with-slash/(?<baseuri>.*)`, `
|
||||
rewrite /end-with-slash/(.*) /not-root/$1 break;
|
||||
proxy_pass http://upstream-name;
|
||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
|
||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
|
||||
`, true, ""},
|
||||
"redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
|
||||
rewrite /something-complex/(.*) /not-root/$1 break;
|
||||
proxy_pass http://upstream-name;
|
||||
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
|
||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
|
||||
`, true, ""},
|
||||
"redirect /something to / and rewrite with specific scheme": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
|
||||
rewrite /something/(.*) /$1 break;
|
||||
rewrite /something / break;
|
||||
proxy_pass http://upstream-name;
|
||||
subs_filter '<head(.*)>' '<head$1><base href="http://$http_host/something/$baseuri">' r;
|
||||
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="http://$http_host/something/$baseuri">' r;
|
||||
`, true, "http"},
|
||||
}
|
||||
)
|
||||
|
||||
func TestFormatIP(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Input, Output string
|
||||
}{
|
||||
"ipv4-localhost": {"127.0.0.1", "127.0.0.1"},
|
||||
"ipv4-internet": {"8.8.8.8", "8.8.8.8"},
|
||||
"ipv6-localhost": {"::1", "[::1]"},
|
||||
"ipv6-internet": {"2001:4860:4860::8888", "[2001:4860:4860::8888]"},
|
||||
"invalid-ip": {"nonsense", "nonsense"},
|
||||
"empty-ip": {"", ""},
|
||||
}
|
||||
for k, tc := range cases {
|
||||
res := formatIP(tc.Input)
|
||||
if res != tc.Output {
|
||||
t.Errorf("%s: called formatIp('%s'); expected '%v' but returned '%v'", k, tc.Input, tc.Output, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildLocation(t *testing.T) {
|
||||
for k, tc := range tmplFuncTestcases {
|
||||
loc := &ingress.Location{
|
||||
Path: tc.Path,
|
||||
Rewrite: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
|
||||
}
|
||||
|
||||
newLoc := buildLocation(loc)
|
||||
if tc.Location != newLoc {
|
||||
t.Errorf("%s: expected '%v' but returned %v", k, tc.Location, newLoc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildProxyPass(t *testing.T) {
|
||||
for k, tc := range tmplFuncTestcases {
|
||||
loc := &ingress.Location{
|
||||
Path: tc.Path,
|
||||
Rewrite: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL, BaseURLScheme: tc.BaseURLScheme},
|
||||
Backend: "upstream-name",
|
||||
}
|
||||
|
||||
pp := buildProxyPass("", []*ingress.Backend{}, loc)
|
||||
if !strings.EqualFold(tc.ProxyPass, pp) {
|
||||
t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAuthResponseHeaders(t *testing.T) {
|
||||
loc := &ingress.Location{
|
||||
ExternalAuth: authreq.External{ResponseHeaders: []string{"h1", "H-With-Caps-And-Dashes"}},
|
||||
}
|
||||
headers := buildAuthResponseHeaders(loc)
|
||||
expected := []string{
|
||||
"auth_request_set $authHeader0 $upstream_http_h1;",
|
||||
"proxy_set_header 'h1' $authHeader0;",
|
||||
"auth_request_set $authHeader1 $upstream_http_h_with_caps_and_dashes;",
|
||||
"proxy_set_header 'H-With-Caps-And-Dashes' $authHeader1;",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, headers) {
|
||||
t.Errorf("Expected \n'%v'\nbut returned \n'%v'", expected, headers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateWithData(t *testing.T) {
|
||||
pwd, _ := os.Getwd()
|
||||
f, err := os.Open(path.Join(pwd, "../../test/data/config.json"))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error reading json file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
data, err := ioutil.ReadFile(f.Name())
|
||||
if err != nil {
|
||||
t.Error("unexpected error reading json file: ", err)
|
||||
}
|
||||
var dat config.TemplateConfig
|
||||
if err := json.Unmarshal(data, &dat); err != nil {
|
||||
t.Errorf("unexpected error unmarshalling json: %v", err)
|
||||
}
|
||||
if dat.ListenPorts == nil {
|
||||
dat.ListenPorts = &config.ListenPorts{}
|
||||
}
|
||||
tf, err := os.Open(path.Join(pwd, "../../rootfs/etc/nginx/template/nginx.tmpl"))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error reading json file: %v", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
ngxTpl, err := NewTemplate(tf.Name(), func() {})
|
||||
if err != nil {
|
||||
t.Errorf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
||||
_, err = ngxTpl.Write(dat)
|
||||
if err != nil {
|
||||
t.Errorf("invalid NGINX template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTemplateWithData(b *testing.B) {
|
||||
pwd, _ := os.Getwd()
|
||||
f, err := os.Open(path.Join(pwd, "../../test/data/config.json"))
|
||||
if err != nil {
|
||||
b.Errorf("unexpected error reading json file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
data, err := ioutil.ReadFile(f.Name())
|
||||
if err != nil {
|
||||
b.Error("unexpected error reading json file: ", err)
|
||||
}
|
||||
var dat config.TemplateConfig
|
||||
if err := json.Unmarshal(data, &dat); err != nil {
|
||||
b.Errorf("unexpected error unmarshalling json: %v", err)
|
||||
}
|
||||
|
||||
tf, err := os.Open(path.Join(pwd, "../../rootfs/etc/nginx/template/nginx.tmpl"))
|
||||
if err != nil {
|
||||
b.Errorf("unexpected error reading json file: %v", err)
|
||||
}
|
||||
defer tf.Close()
|
||||
|
||||
ngxTpl, err := NewTemplate(tf.Name(), func() {})
|
||||
if err != nil {
|
||||
b.Errorf("invalid NGINX template: %v", err)
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
ngxTpl.Write(dat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildDenyVariable(t *testing.T) {
|
||||
a := buildDenyVariable("host1.example.com_/.well-known/acme-challenge")
|
||||
b := buildDenyVariable("host1.example.com_/.well-known/acme-challenge")
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.Errorf("Expected '%v' but returned '%v'", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildClientBodyBufferSize(t *testing.T) {
|
||||
a := isValidClientBodyBufferSize("1000")
|
||||
if a != true {
|
||||
t.Errorf("Expected '%v' but returned '%v'", true, a)
|
||||
}
|
||||
b := isValidClientBodyBufferSize("1000k")
|
||||
if b != true {
|
||||
t.Errorf("Expected '%v' but returned '%v'", true, b)
|
||||
}
|
||||
c := isValidClientBodyBufferSize("1000m")
|
||||
if c != true {
|
||||
t.Errorf("Expected '%v' but returned '%v'", true, c)
|
||||
}
|
||||
d := isValidClientBodyBufferSize("1000km")
|
||||
if d != false {
|
||||
t.Errorf("Expected '%v' but returned '%v'", false, d)
|
||||
}
|
||||
e := isValidClientBodyBufferSize("1000mk")
|
||||
if e != false {
|
||||
t.Errorf("Expected '%v' but returned '%v'", false, e)
|
||||
}
|
||||
f := isValidClientBodyBufferSize("1000kk")
|
||||
if f != false {
|
||||
t.Errorf("Expected '%v' but returned '%v'", false, f)
|
||||
}
|
||||
g := isValidClientBodyBufferSize("1000mm")
|
||||
if g != false {
|
||||
t.Errorf("Expected '%v' but returned '%v'", false, g)
|
||||
}
|
||||
h := isValidClientBodyBufferSize(nil)
|
||||
if h != false {
|
||||
t.Errorf("Expected '%v' but returned '%v'", false, h)
|
||||
}
|
||||
i := isValidClientBodyBufferSize("")
|
||||
if i != false {
|
||||
t.Errorf("Expected '%v' but returned '%v'", false, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsLocationAllowed(t *testing.T) {
|
||||
loc := ingress.Location{
|
||||
Denied: nil,
|
||||
}
|
||||
|
||||
isAllowed := isLocationAllowed(&loc)
|
||||
if !isAllowed {
|
||||
t.Errorf("Expected '%v' but returned '%v'", true, isAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildForwardedFor(t *testing.T) {
|
||||
inputStr := "X-Forwarded-For"
|
||||
outputStr := buildForwardedFor(inputStr)
|
||||
|
||||
validStr := "$http_x_forwarded_for"
|
||||
|
||||
if outputStr != validStr {
|
||||
t.Errorf("Expected '%v' but returned '%v'", validStr, outputStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildResolvers(t *testing.T) {
|
||||
ipOne := net.ParseIP("192.0.0.1")
|
||||
ipTwo := net.ParseIP("2001:db8:1234:0000:0000:0000:0000:0000")
|
||||
ipList := []net.IP{ipOne, ipTwo}
|
||||
|
||||
validResolver := "resolver 192.0.0.1 [2001:db8:1234::] valid=30s;"
|
||||
resolver := buildResolvers(ipList)
|
||||
|
||||
if resolver != validResolver {
|
||||
t.Errorf("Expected '%v' but returned '%v'", validResolver, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNextUpstream(t *testing.T) {
|
||||
nextUpstream := "timeout http_500 http_502 non_idempotent"
|
||||
validNextUpstream := "timeout http_500 http_502"
|
||||
|
||||
buildNextUpstream := buildNextUpstream(nextUpstream)
|
||||
|
||||
if buildNextUpstream != validNextUpstream {
|
||||
t.Errorf("Expected '%v' but returned '%v'", validNextUpstream, buildNextUpstream)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildRateLimit(t *testing.T) {
|
||||
loc := &ingress.Location{}
|
||||
|
||||
loc.RateLimit.Connections.Name = "con"
|
||||
loc.RateLimit.Connections.Limit = 1
|
||||
|
||||
loc.RateLimit.RPS.Name = "rps"
|
||||
loc.RateLimit.RPS.Limit = 1
|
||||
loc.RateLimit.RPS.Burst = 1
|
||||
|
||||
loc.RateLimit.RPM.Name = "rpm"
|
||||
loc.RateLimit.RPM.Limit = 2
|
||||
loc.RateLimit.RPM.Burst = 2
|
||||
|
||||
loc.RateLimit.LimitRateAfter = 1
|
||||
loc.RateLimit.LimitRate = 1
|
||||
|
||||
validLimits := []string{
|
||||
"limit_conn con 1;",
|
||||
"limit_req zone=rps burst=1 nodelay;",
|
||||
"limit_req zone=rpm burst=2 nodelay;",
|
||||
"limit_rate_after 1k;",
|
||||
"limit_rate 1k;",
|
||||
}
|
||||
|
||||
limits := buildRateLimit(loc)
|
||||
|
||||
for i, limit := range limits {
|
||||
if limit != validLimits[i] {
|
||||
t.Errorf("Expected '%v' but returned '%v'", validLimits, limits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAuthSignURL(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Input, Output string
|
||||
}{
|
||||
"default url": {"http://google.com", "http://google.com?rd=$scheme://$http_host$request_uri"},
|
||||
"with random field": {"http://google.com?cat=0", "http://google.com?cat=0&rd=$scheme://$http_host$request_uri"},
|
||||
"with rd field": {"http://google.com?cat&rd=$request", "http://google.com?cat&rd=$request"},
|
||||
}
|
||||
for k, tc := range cases {
|
||||
res := buildAuthSignURL(tc.Input)
|
||||
if res != tc.Output {
|
||||
t.Errorf("%s: called buildAuthSignURL('%s'); expected '%v' but returned '%v'", k, tc.Input, tc.Output, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
pkg/version/version.go
Normal file
26
pkg/version/version.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes 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 version
|
||||
|
||||
var (
|
||||
// RELEASE returns the release version
|
||||
RELEASE = "UNKNOWN"
|
||||
// REPO returns the git repository URL
|
||||
REPO = "UNKNOWN"
|
||||
// COMMIT returns the short sha from git
|
||||
COMMIT = "UNKNOWN"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue