2017-02-05 15:26:15 -02:00
/*
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.
*/
2017-04-05 20:10:22 -03:00
package controller
2017-02-05 15:26:15 -02:00
import (
"github.com/golang/glog"
2017-11-20 07:16:59 -02:00
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress"
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/controller"
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/defaults"
2018-04-22 21:41:59 -03:00
"github.com/jcmoraisjr/haproxy-ingress/pkg/controller/dynconfig"
2017-06-08 16:49:14 +02:00
"github.com/jcmoraisjr/haproxy-ingress/pkg/types"
2017-02-05 15:26:15 -02:00
"github.com/jcmoraisjr/haproxy-ingress/pkg/version"
2017-02-17 22:24:21 -02:00
"github.com/spf13/pflag"
2017-09-07 18:10:10 -03:00
"io/ioutil"
2017-07-26 22:47:10 -03:00
api "k8s.io/api/core/v1"
2017-07-30 17:24:59 -03:00
extensions "k8s.io/api/extensions/v1beta1"
2017-02-05 15:26:15 -02:00
"net/http"
2017-09-07 18:10:10 -03:00
"os"
2017-02-05 15:26:15 -02:00
"os/exec"
2018-03-25 19:40:26 -03:00
"sort"
2017-09-07 18:10:10 -03:00
"strings"
2018-02-13 11:33:36 -07:00
"time"
2017-02-05 15:26:15 -02:00
)
2017-04-05 20:10:22 -03:00
// HAProxyController has internal data of a HAProxyController instance
type HAProxyController struct {
2018-03-25 19:40:26 -03:00
controller * controller . GenericController
configMap * api . ConfigMap
storeLister * ingress . StoreLister
command string
reloadStrategy * string
configDir string
configFilePrefix string
configFileSuffix string
maxOldConfigFiles * int
template * template
currentConfig * types . ControllerConfig
2017-02-05 15:26:15 -02:00
}
2017-04-05 20:10:22 -03:00
// NewHAProxyController constructor
func NewHAProxyController ( ) * HAProxyController {
2017-05-18 22:25:23 -03:00
return & HAProxyController { }
2017-02-05 15:26:15 -02:00
}
2017-04-05 20:10:22 -03:00
// Info provides controller name and repository infos
func ( haproxy * HAProxyController ) Info ( ) * ingress . BackendInfo {
2017-02-05 15:26:15 -02:00
return & ingress . BackendInfo {
Name : "HAProxy" ,
Release : version . RELEASE ,
Build : version . COMMIT ,
Repository : version . REPO ,
}
}
2017-04-05 20:10:22 -03:00
// Start starts the controller
func ( haproxy * HAProxyController ) Start ( ) {
haproxy . controller = controller . NewIngressController ( haproxy )
2018-04-20 07:12:36 -03:00
if * haproxy . reloadStrategy == "multibinder" {
glog . Warningf ( "multibinder is deprecated, using reusesocket strategy instead. update your deployment configuration" )
}
2017-02-05 15:26:15 -02:00
haproxy . controller . Start ( )
}
2017-04-05 20:10:22 -03:00
// Stop shutdown the controller process
func ( haproxy * HAProxyController ) Stop ( ) error {
2017-02-05 15:26:15 -02:00
err := haproxy . controller . Stop ( )
return err
}
2017-04-05 20:10:22 -03:00
// Name provides the complete name of the controller
func ( haproxy * HAProxyController ) Name ( ) string {
2017-02-05 15:26:15 -02:00
return "HAProxy Ingress Controller"
}
2017-04-05 20:10:22 -03:00
// DefaultIngressClass returns the ingress class name
func ( haproxy * HAProxyController ) DefaultIngressClass ( ) string {
2017-03-09 21:11:10 -03:00
return "haproxy"
}
2017-04-05 20:10:22 -03:00
// Check health check implementation
func ( haproxy * HAProxyController ) Check ( _ * http . Request ) error {
2017-02-05 15:26:15 -02:00
return nil
}
2017-04-05 20:10:22 -03:00
// SetListers give access to the store listers
2017-09-27 22:13:02 -03:00
func ( haproxy * HAProxyController ) SetListers ( lister * ingress . StoreLister ) {
haproxy . storeLister = lister
2017-02-17 22:24:21 -02:00
}
2017-07-30 17:24:59 -03:00
// UpdateIngressStatus custom callback used to update the status in an Ingress rule
// If the function returns nil the standard functions will be executed.
func ( haproxy * HAProxyController ) UpdateIngressStatus ( * extensions . Ingress ) [ ] api . LoadBalancerIngress {
return nil
}
2017-05-29 19:31:49 -03:00
// ConfigureFlags allow to configure more flags before the parsing of
// command line arguments
func ( haproxy * HAProxyController ) ConfigureFlags ( flags * pflag . FlagSet ) {
2017-05-31 15:16:43 -03:00
haproxy . reloadStrategy = flags . String ( "reload-strategy" , "native" ,
2018-04-20 07:12:36 -03:00
` Name of the reload strategy. Options are: native (default) or reusesocket ` )
2018-03-19 17:00:53 -06:00
haproxy . maxOldConfigFiles = flags . Int ( "max-old-config-files" , 0 ,
` Maximum old haproxy timestamped config files to allow before being cleaned up. A value <= 0 indicates a single non-timestamped config file will be used ` )
2017-06-07 20:40:22 -03:00
ingressClass := flags . Lookup ( "ingress-class" )
if ingressClass != nil {
ingressClass . Value . Set ( "haproxy" )
ingressClass . DefValue = "haproxy"
}
2017-05-29 19:31:49 -03:00
}
2017-04-05 20:10:22 -03:00
// OverrideFlags allows controller to override command line parameter flags
2017-05-18 22:25:23 -03:00
func ( haproxy * HAProxyController ) OverrideFlags ( flags * pflag . FlagSet ) {
2018-02-13 11:33:36 -07:00
haproxy . configDir = "/etc/haproxy"
haproxy . configFilePrefix = "haproxy"
haproxy . configFileSuffix = ".cfg"
2018-04-20 07:12:36 -03:00
haproxy . template = newTemplate ( "haproxy.tmpl" , "/etc/haproxy/template/haproxy.tmpl" )
haproxy . command = "/haproxy-reload.sh"
2018-02-13 11:33:36 -07:00
if ! ( * haproxy . reloadStrategy == "native" || * haproxy . reloadStrategy == "reusesocket" || * haproxy . reloadStrategy == "multibinder" ) {
2017-05-31 15:16:43 -03:00
glog . Fatalf ( "Unsupported reload strategy: %v" , * haproxy . reloadStrategy )
2017-05-18 22:25:23 -03:00
}
2017-02-17 22:24:21 -02:00
}
2017-04-05 20:10:22 -03:00
// SetConfig receives the ConfigMap the user has configured
func ( haproxy * HAProxyController ) SetConfig ( configMap * api . ConfigMap ) {
2017-02-05 15:26:15 -02:00
haproxy . configMap = configMap
}
2017-04-05 20:10:22 -03:00
// BackendDefaults defines default values to the ingress core
func ( haproxy * HAProxyController ) BackendDefaults ( ) defaults . Backend {
2017-04-26 19:43:41 -03:00
return newHAProxyConfig ( haproxy ) . Backend
2017-02-05 15:26:15 -02:00
}
2017-08-27 21:16:26 -03:00
// DefaultEndpoint returns the Endpoint to use as default when the
// referenced service does not exists
func ( haproxy * HAProxyController ) DefaultEndpoint ( ) ingress . Endpoint {
return ingress . Endpoint {
2018-04-20 07:12:36 -03:00
Address : "127.0.0.1" ,
Port : "8181" ,
Draining : false ,
Target : & api . ObjectReference { } ,
2017-08-27 21:16:26 -03:00
}
}
2018-04-20 07:12:36 -03:00
// DrainSupport indicates whether or not this controller supports a "drain" mode where
// unavailable and terminating pods are included in the list of returned pods and used to
// direct certain traffic (e.g., traffic using persistence) to terminating/unavailable pods.
2018-04-09 16:07:12 -04:00
func ( haproxy * HAProxyController ) DrainSupport ( ) ( drainSupport bool ) {
if haproxy . currentConfig != nil {
drainSupport = haproxy . currentConfig . Cfg . DrainSupport
}
return
2018-04-08 12:49:41 -04:00
}
2017-04-05 20:10:22 -03:00
// OnUpdate regenerate the configuration file of the backend
2017-06-26 21:13:36 -03:00
func ( haproxy * HAProxyController ) OnUpdate ( cfg ingress . Configuration ) error {
2018-03-25 19:40:26 -03:00
updatedConfig , err := newControllerConfig ( & cfg , haproxy )
if err != nil {
return err
}
2017-06-08 16:49:14 +02:00
2018-04-22 21:41:59 -03:00
reloadRequired := ! dynconfig . ConfigBackends ( haproxy . currentConfig , updatedConfig )
2017-06-23 15:37:35 +02:00
haproxy . currentConfig = updatedConfig
2017-06-08 16:49:14 +02:00
data , err := haproxy . template . execute ( updatedConfig )
2017-02-05 15:26:15 -02:00
if err != nil {
2017-06-26 21:13:36 -03:00
return err
2017-02-05 15:26:15 -02:00
}
2018-02-13 11:33:36 -07:00
configFile , err := haproxy . rewriteConfigFiles ( data )
2017-02-05 15:26:15 -02:00
if err != nil {
2017-06-26 21:13:36 -03:00
return err
2017-02-05 15:26:15 -02:00
}
2017-06-08 16:49:14 +02:00
2017-06-26 21:13:36 -03:00
if ! reloadRequired {
2018-05-11 14:51:33 +02:00
glog . Infoln ( "HAProxy updated without needing to reload" )
2017-06-26 21:13:36 -03:00
return nil
2017-06-08 16:49:14 +02:00
}
2018-03-19 17:00:53 -06:00
reloadCmd := exec . Command ( haproxy . command , * haproxy . reloadStrategy , configFile )
2018-02-13 11:33:36 -07:00
out , err := reloadCmd . CombinedOutput ( )
2017-02-05 15:26:15 -02:00
if len ( out ) > 0 {
2018-02-13 11:33:36 -07:00
glog . Infof ( "HAProxy[pid=%v] output:\n%v" , reloadCmd . Process . Pid , string ( out ) )
2017-02-05 15:26:15 -02:00
}
2017-06-26 21:13:36 -03:00
return err
2017-02-05 15:26:15 -02:00
}
2017-09-07 18:10:10 -03:00
// RewriteConfigFiles safely replaces configuration files with new contents after validation
2018-02-13 11:33:36 -07:00
func ( haproxy * HAProxyController ) rewriteConfigFiles ( data [ ] byte ) ( string , error ) {
// Include timestamp in config file name to aid troubleshooting. When using a single, ever-changing config file it
// was difficult to know what config was loaded by any given haproxy process
2018-04-20 07:12:36 -03:00
timestamp := ""
if * haproxy . maxOldConfigFiles > 0 {
timestamp = time . Now ( ) . Format ( "-20060102-150405.000" )
2017-09-07 18:10:10 -03:00
}
2018-02-13 11:33:36 -07:00
configFile := haproxy . configDir + "/" + haproxy . configFilePrefix + timestamp + haproxy . configFileSuffix
2017-09-07 18:10:10 -03:00
2018-04-20 07:12:36 -03:00
// Write directly to configFile
if err := ioutil . WriteFile ( configFile , data , 644 ) ; err != nil {
glog . Warningln ( "Error writing rendered template to file" )
return "" , err
}
2018-02-13 11:33:36 -07:00
2018-04-20 07:12:36 -03:00
// Validate haproxy config
if err := checkValidity ( configFile ) ; err != nil {
return "" , err
2017-09-07 18:10:10 -03:00
}
2018-02-13 11:33:36 -07:00
2018-03-19 17:00:53 -06:00
if * haproxy . maxOldConfigFiles > 0 {
if err := haproxy . removeOldConfigFiles ( ) ; err != nil {
glog . Warningf ( "Problem removing old config files, but continuing in case it was a fluke. err=%v" , err )
}
}
2018-02-13 11:33:36 -07:00
return configFile , nil
}
2018-03-19 17:00:53 -06:00
func ( haproxy * HAProxyController ) removeOldConfigFiles ( ) error {
2018-02-13 11:33:36 -07:00
files , err := ioutil . ReadDir ( haproxy . configDir )
2017-09-07 18:10:10 -03:00
if err != nil {
return err
}
2018-02-13 11:33:36 -07:00
// Sort with most recently modified first
2018-03-25 19:40:26 -03:00
sort . Slice ( files , func ( i , j int ) bool {
2018-02-13 11:33:36 -07:00
return files [ i ] . ModTime ( ) . After ( files [ j ] . ModTime ( ) )
} )
matchesFound := 0
for _ , f := range files {
2018-03-19 17:00:53 -06:00
if ! f . IsDir ( ) && strings . HasPrefix ( f . Name ( ) , haproxy . configFilePrefix ) && strings . HasSuffix ( f . Name ( ) , haproxy . configFileSuffix ) {
2018-02-13 11:33:36 -07:00
matchesFound = matchesFound + 1
2018-03-19 17:00:53 -06:00
if matchesFound > * haproxy . maxOldConfigFiles {
2018-02-13 11:33:36 -07:00
filePath := haproxy . configDir + "/" + f . Name ( )
2018-03-19 17:00:53 -06:00
glog . Infof ( "Removing old config file (%v). maxOldConfigFiles=%v" , filePath , * haproxy . maxOldConfigFiles )
if err := os . Remove ( filePath ) ; err != nil {
return err
}
2018-02-13 11:33:36 -07:00
}
}
}
2017-09-07 18:10:10 -03:00
return nil
}
// checkValidity runs a HAProxy configuration validity check on a file
func checkValidity ( configFile string ) error {
out , err := exec . Command ( "haproxy" , "-c" , "-f" , configFile ) . CombinedOutput ( )
if err != nil {
glog . Warningf ( "Error validating config file:\n%v" , string ( out ) )
return err
}
return nil
}