Add Generic interface

This commit is contained in:
Manuel de Brito Fontes 2016-11-16 15:24:26 -03:00
parent f2b627486d
commit 5a8e090736
36 changed files with 58014 additions and 675 deletions

View file

@ -25,14 +25,16 @@ import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/defaults"
"errors"
"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/kubernetes/pkg/api"
)
var (
@ -75,6 +77,8 @@ Error loading new template : %v
}
n.t = ngxTpl
go n.Start()
return n
}
@ -85,7 +89,7 @@ type NGINXController struct {
binary string
}
// Start ...
// Start start a new NGINX master process running in foreground.
func (n NGINXController) Start() {
glog.Info("starting NGINX process...")
cmd := exec.Command(n.binary, "-c", cfgPath)
@ -99,14 +103,13 @@ func (n NGINXController) Start() {
}
}
// Stop ...
func (n NGINXController) Stop() error {
n.t.Close()
return exec.Command(n.binary, "-s", "stop").Run()
}
// Reload checks if the running configuration file is different
// to the specified and reload nginx if required
func (n NGINXController) Reload(data []byte) ([]byte, error) {
if !n.isReloadRequired(data) {
return nil, fmt.Errorf("Reload not required")
}
// Restart ...
func (n NGINXController) Restart(data []byte) ([]byte, error) {
err := ioutil.WriteFile(cfgPath, data, 0644)
if err != nil {
return nil, err
@ -120,15 +123,15 @@ func (n NGINXController) Test(file string) *exec.Cmd {
return exec.Command(n.binary, "-t", "-c", file)
}
// UpstreamDefaults returns the nginx defaults
func (n NGINXController) UpstreamDefaults() defaults.Backend {
// BackendDefaults returns the nginx defaults
func (n NGINXController) BackendDefaults() defaults.Backend {
d := config.NewDefault()
return d.Backend
}
// IsReloadRequired check if the new configuration file is different
// from the current one.
func (n NGINXController) IsReloadRequired(data []byte) bool {
func (n NGINXController) isReloadRequired(data []byte) bool {
in, err := os.Open(cfgPath)
if err != nil {
return false
@ -167,8 +170,13 @@ func (n NGINXController) IsReloadRequired(data []byte) bool {
}
// Info return build information
func (n NGINXController) Info() string {
return fmt.Sprintf("build version %v from repo %v commit %v", version.RELEASE, version.REPO, version.COMMIT)
func (n NGINXController) Info() *ingress.BackendInfo {
return &ingress.BackendInfo{
Name: "NGINX",
Release: version.RELEASE,
Build: version.COMMIT,
Repository: version.REPO,
}
}
// testTemplate checks if the NGINX configuration inside the byte array is valid
@ -183,12 +191,13 @@ func (n NGINXController) testTemplate(cfg []byte) error {
out, err := n.Test(tmpfile.Name()).CombinedOutput()
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
return fmt.Errorf(`
oe := fmt.Sprintf(`
-------------------------------------------------------------------------------
Error: %v
%v
-------------------------------------------------------------------------------
`, err, string(out))
return errors.New(oe)
}
os.Remove(tmpfile.Name())
@ -207,9 +216,9 @@ func (n NGINXController) OnUpdate(cmap *api.ConfigMap, ingressCfg ingress.Config
var longestName int
var serverNames int
for _, srv := range ingressCfg.Servers {
serverNames += len([]byte(srv.Name))
if longestName < len(srv.Name) {
longestName = len(srv.Name)
serverNames += len([]byte(srv.Hostname))
if longestName < len(srv.Hostname) {
longestName = len(srv.Hostname)
}
}
@ -234,21 +243,17 @@ func (n NGINXController) OnUpdate(cmap *api.ConfigMap, ingressCfg ingress.Config
cfg.ServerNameHashMaxSize = serverNameHashMaxSize
}
conf := make(map[string]interface{})
// adjust the size of the backlog
conf["backlogSize"] = sysctlSomaxconn()
conf["upstreams"] = ingressCfg.Upstreams
conf["passthroughUpstreams"] = ingressCfg.PassthroughUpstreams
conf["servers"] = ingressCfg.Servers
conf["tcpUpstreams"] = ingressCfg.TCPEndpoints
conf["udpUpstreams"] = ingressCfg.UPDEndpoints
conf["healthzURL"] = ingressCfg.HealthzURL
conf["defResolver"] = cfg.Resolver
conf["sslDHParam"] = ""
conf["customErrors"] = len(cfg.CustomHTTPErrors) > 0
conf["cfg"] = ngx_template.StandarizeKeyNames(cfg)
return n.t.Write(conf, n.testTemplate)
return n.t.Write(config.TemplateConfig{
BacklogSize: sysctlSomaxconn(),
Backends: ingressCfg.Backends,
PassthrougBackends: ingressCfg.PassthroughBackends,
Servers: ingressCfg.Servers,
TCPBackends: ingressCfg.TCPEndpoints,
UDPBackends: ingressCfg.UPDEndpoints,
HealthzURI: "/healthz",
CustomErrors: len(cfg.CustomHTTPErrors) > 0,
Cfg: cfg,
}, n.testTemplate)
}
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2

View file

@ -19,9 +19,10 @@ package config
import (
"runtime"
"k8s.io/ingress/core/pkg/ingress/defaults"
"github.com/golang/glog"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
const (
@ -216,8 +217,7 @@ type Configuration struct {
WorkerProcesses int `structs:"worker-processes,omitempty"`
}
// NewDefault returns the default configuration contained
// in the file default-conf.json
// NewDefault returns the default nginx configuration
func NewDefault() Configuration {
cfg := Configuration{
BodySize: bodySize,
@ -264,3 +264,15 @@ func NewDefault() Configuration {
return cfg
}
type TemplateConfig struct {
BacklogSize int
Backends []*ingress.Backend
PassthrougBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []*ingress.Location
UDPBackends []*ingress.Location
HealthzURI string
CustomErrors bool
Cfg Configuration
}

View file

@ -27,10 +27,10 @@ import (
"github.com/mitchellh/mapstructure"
go_camelcase "github.com/segmentio/go-camelcase"
"k8s.io/kubernetes/pkg/api"
"k8s.io/ingress/controllers/nginx/pkg/config"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/kubernetes/pkg/api"
)
const (
@ -50,9 +50,9 @@ func ReadConfig(conf *api.ConfigMap) config.Configuration {
return config.NewDefault()
}
var errors []int
var skipUrls []string
var whitelist []string
errors := make([]int, 0)
skipUrls := make([]string, 0)
whitelist := make([]string, 0)
if val, ok := conf.Data[customHTTPErrors]; ok {
delete(conf.Data, customHTTPErrors)

View file

@ -27,6 +27,7 @@ import (
"github.com/golang/glog"
"k8s.io/ingress/controllers/nginx/pkg/config"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/watch"
)
@ -73,12 +74,19 @@ func (t *Template) 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 map[string]interface{},
isValidTemplate func([]byte) error) ([]byte, error) {
func (t *Template) Write(conf config.TemplateConfig, isValidTemplate func([]byte) error) ([]byte, error) {
defer t.tmplBuf.Reset()
defer t.outCmdBuf.Reset()
defer func() {
if t.s < t.tmplBuf.Cap() {
glog.V(2).Infof("adjusting template buffer size from %v to %v", t.s, t.tmplBuf.Cap())
t.s = t.tmplBuf.Cap()
t.tmplBuf = bytes.NewBuffer(make([]byte, 0, t.tmplBuf.Cap()))
t.outCmdBuf = bytes.NewBuffer(make([]byte, 0, t.outCmdBuf.Cap()))
}
}()
if glog.V(3) {
b, err := json.Marshal(conf)
if err != nil {
@ -88,12 +96,8 @@ func (t *Template) Write(conf map[string]interface{},
}
err := t.tmpl.Execute(t.tmplBuf, conf)
if t.s < t.tmplBuf.Cap() {
glog.V(2).Infof("adjusting template buffer size from %v to %v", t.s, t.tmplBuf.Cap())
t.s = t.tmplBuf.Cap()
t.tmplBuf = bytes.NewBuffer(make([]byte, 0, t.tmplBuf.Cap()))
t.outCmdBuf = bytes.NewBuffer(make([]byte, 0, t.outCmdBuf.Cap()))
if err != nil {
return nil, err
}
// squeezes multiple adjacent empty lines to be single
@ -124,12 +128,12 @@ var (
}
return true
},
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"buildProxyPass": buildProxyPass,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"getSSPassthroughUpstream": getSSPassthroughUpstream,
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"buildProxyPass": buildProxyPass,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"buildSSPassthroughUpstreams": buildSSPassthroughUpstreams,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
@ -139,13 +143,32 @@ var (
}
)
func getSSPassthroughUpstream(input interface{}) string {
s, ok := input.(*ingress.Server)
if !ok {
return ""
func buildSSPassthroughUpstreams(b interface{}, sslb interface{}) string {
backends := b.([]*ingress.Backend)
sslBackends := sslb.([]*ingress.SSLPassthroughBackend)
buf := bytes.NewBuffer(make([]byte, 0, 10))
// multiple services can use the same upstream.
// avoid duplications using a map[name]=true
u := make(map[string]bool)
for _, passthrough := range sslBackends {
if u[passthrough.Backend] {
continue
}
u[passthrough.Backend] = true
fmt.Fprintf(buf, "upstream %v {\n", passthrough.Backend)
for _, backend := range backends {
if backend.Name == passthrough.Backend {
for _, server := range backend.Endpoints {
fmt.Fprintf(buf, "\t\tserver %v:%v;\n", server.Address, server.Port)
}
break
}
}
fmt.Fprint(buf, "\t}\n\n")
}
return s.Name
return buf.String()
}
// buildLocation produces the location string, if the ingress has redirects
@ -184,20 +207,27 @@ func buildAuthLocation(input interface{}) string {
// (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(input interface{}) string {
location, ok := input.(*ingress.Location)
func buildProxyPass(b interface{}, loc interface{}) string {
backends := b.([]*ingress.Backend)
location, ok := loc.(*ingress.Location)
if !ok {
return ""
}
path := location.Path
proto := "http"
if location.SecureUpstream {
proto = "https"
for _, backend := range backends {
if backend.Name == location.Backend {
if backend.Secure {
proto = "https"
}
break
}
}
// defProxyPass returns the default proxy_pass, just the name of the upstream
defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, location.Backend.Name)
defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, location.Backend)
// if the path in the ingress rule is equals to the target: no special rewrite
if path == location.Redirect.Target {
return defProxyPass
@ -227,13 +257,13 @@ func buildProxyPass(input interface{}) string {
rewrite %s(.*) /$1 break;
rewrite %s / break;
proxy_pass %s://%s;
%v`, path, location.Path, proto, location.Backend.Name, abu)
%v`, path, location.Path, proto, location.Backend, abu)
}
return fmt.Sprintf(`
rewrite %s(.*) %s/$1 break;
proxy_pass %s://%s;
%v`, path, location.Redirect.Target, proto, location.Backend.Name, abu)
%v`, path, location.Redirect.Target, proto, location.Backend, abu)
}
// default proxy_pass

View file

@ -17,14 +17,21 @@ limitations under the License.
package template
import (
"encoding/json"
"os"
"path"
"strings"
"testing"
"io/ioutil"
"k8s.io/ingress/controllers/nginx/pkg/config"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
)
var (
// TODO: add tests for secure endpoints
tmplFuncTestcases = map[string]struct {
Path string
Target string
@ -88,12 +95,77 @@ func TestBuildProxyPass(t *testing.T) {
loc := &ingress.Location{
Path: tc.Path,
Redirect: rewrite.Redirect{Target: tc.Target, AddBaseURL: tc.AddBaseURL},
Upstream: ingress.Backend{Name: "upstream-name"},
Backend: "upstream-name",
}
pp := buildProxyPass(loc)
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 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)
}
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, func(b []byte) error { return nil })
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(b []byte) error { return nil })
}
}