Commit 5779abd3 authored by Geoff Simmons's avatar Geoff Simmons

Run golint on pkg/haproxy/*.go, and fix to make golint happy.

For the most part by adding go docs, and by using Go naming
conventions in a few cases.

Remove some commented-out code while we're here.
parent c79d990b
......@@ -68,6 +68,7 @@ check: build
golint ./pkg/controller/...
golint ./pkg/interfaces/...
golint ./pkg/varnish/...
golint ./pkg/haproxy/...
golint ./pkg/apis/varnishingress/v1alpha1/...
golint ./cmd/...
go test -v ./pkg/controller/... ./pkg/interfaces/... ./pkg/varnish/...
......
......@@ -51,7 +51,7 @@ const (
sitesPath = "/v1/services/haproxy/sites"
reloadsPath = "/v1/services/haproxy/reloads"
versionHdr = "Configuration-Version"
reloadIdHdr = "Reload-ID"
reloadIDHdr = "Reload-ID"
varnishSock = "unix@/varnish.sock"
frontend = "offloader"
backend = "varnish"
......@@ -62,12 +62,17 @@ const (
var port = int64(443)
// ReloadStatus classifies the current state of a dataplane reload.
type ReloadStatus uint8
const (
// Unknown dataplane reload status.
Unknown ReloadStatus = iota
// Failed dataplane reload.
Failed
// InProgress dataplane reload.
InProgress
// Succeeded dataplane reload.
Succeeded
)
......@@ -97,16 +102,26 @@ func (status ReloadStatus) String() string {
}
}
// ReloadState encapsulates the dataplane API's reload object.
type ReloadState struct {
ID string
Response string
// ID is generated by the dataplane API (usually a UUID)-
ID string
// Response is the dataplane API's text message about the reload.
Response string
// Timestamp is set by the dataplane API.
Timestamp time.Time
Status ReloadStatus
// Status of the dataplane reload.
Status ReloadStatus
}
// DataplaneError represents an error response from the dataplane
// API. Satisifies the error interface.
type DataplaneError struct {
Err *models.Error
Status int
// Err encapsulates the dataplane API's error object.
Err *models.Error
// Status is the HTTP response code.
Status int
// Version is the configuration-version returned in the response.
Version int
}
......@@ -134,6 +149,12 @@ func (err *DataplaneError) Error() string {
return sb.String()
}
// DataplaneClient executes remote administration of a haproxy server
// using the dataplane API, which runs as a child process in the
// haproxy container.
//
// https://www.haproxy.com/documentation/hapee/1-9r1/configuration/dataplaneapi/
// https://www.haproxy.com/documentation/dataplaneapi/latest/
type DataplaneClient struct {
baseURL *url.URL
user string
......@@ -141,6 +162,11 @@ type DataplaneClient struct {
client *http.Client
}
// NewDataplaneClient returns a client for the dataplane API server
// listening at host, using the Basic Auth password pass.
//
// host may have the form "addr" or "addr:port", where addr may be a
// host name or IP address.
func NewDataplaneClient(host, pass string) *DataplaneClient {
return &DataplaneClient{
baseURL: &url.URL{
......@@ -253,21 +279,14 @@ func getError(resp *http.Response, body []byte) error {
return dplaneErr
}
// StartTx initiates a dataplane transaction (a POST to the endpoint
// /services/haproxy/transactions) based on the given configuration
// version.
//
// A non-nil error return may wrap a DataplaneError.
func (client *DataplaneClient) StartTx(
version int64) (tx *models.Transaction, err error) {
// path := &url.URL{Path: "/v1/services/haproxy/transactions"}
// url := client.baseURL.ResolveReference(path)
// req, err := http.NewRequest("POST", url.String(), nil)
// if err != nil {
// return
// }
// req.SetBasicAuth(client.user, client.password)
// query := req.URL.Query()
// query.Add("version", strconv.FormatInt(version, 10))
// req.URL.RawQuery = query.Encode()
// req.Header.Set("Accept", "application/json")
req, err := client.getReq(txPath, http.MethodPost, nil, false)
if err != nil {
return
......@@ -277,49 +296,30 @@ func (client *DataplaneClient) StartTx(
if err != nil {
return
}
// defer drainAndClose(resp.Body)
// // XXX read ContentLength bytes
// body, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return
// }
body, err := getBody(resp)
if err != nil {
return
}
if resp.StatusCode == http.StatusCreated {
// tx = &models.Transaction{}
// if err = tx.UnmarshalBinary(body); err != nil {
// err = tx.Validate(fmts)
// }
// return
return getTx(body)
}
// dplaneErr := &DataplaneError{
// Err: &models.Error{},
// Status: resp.StatusCode,
// }
// if dplaneVer, err := strconv.Atoi(
// resp.Header.Get("Configuration-Version")); err != nil {
// dplaneErr.Version = dplaneVer
// }
// dplaneErr.Err.UnmarshalJSON(body)
// return nil, dplaneErr
return nil, getError(resp, body)
}
// FinishTx completes the dataplane transaction tx. If successful, a
// haproxy configuration reload may be initiated.
//
// The DataplaneClient does not use the force-reload parameter. This
// means that dataplane does not reload the configuration
// synchronously, so as to avoid thrashing re-configuration. Reloads
// of pending transactions are performed at intervals (usually a few
// seconds).
//
// A non-nil error return may wrap a DataplaneError.
func (client *DataplaneClient) FinishTx(
tx *models.Transaction) (ReloadState, error) {
// path := &url.URL{Path: "/v1/services/haproxy/transactions/" + tx.ID}
// url := client.baseURL.ResolveReference(path)
// req, err := http.NewRequest("PUT", url.String(), nil)
// if err != nil {
// return false, err
// }
// req.SetBasicAuth(client.user, client.password)
// req.Header.Set("Accept", "application/json")
state := ReloadState{Status: Unknown}
req, err := client.getReq(txPath+"/"+tx.ID, http.MethodPut, nil, false)
......@@ -331,12 +331,6 @@ func (client *DataplaneClient) FinishTx(
if err != nil {
return state, err
}
// defer drainAndClose(resp.Body)
// // XXX read ContentLength bytes
// body, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return false, err
// }
body, err := getBody(resp)
if err != nil {
return state, err
......@@ -352,67 +346,16 @@ func (client *DataplaneClient) FinishTx(
state.Timestamp = time.Now()
return state, nil
}
state.ID = resp.Header.Get(reloadIdHdr)
state.ID = resp.Header.Get(reloadIDHdr)
state.Status = status(tx.Status)
return state, nil
default:
return state, getError(resp, body)
}
// if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusAccepted {
// if tx, err = body.getTx(); err != nil {
// return false, err
// }
// return resp.StatusCode == http.StatusOK, nil
// }
// dplaneErr := &DataplaneError{
// Err: &models.Error{},
// Status: resp.StatusCode,
// }
// if dplaneVer, err := strconv.Atoi(
// resp.Header.Get("Configuration-Version")); err != nil {
// dplaneErr.Version = dplaneVer
// }
// dplaneErr.Err.UnmarshalJSON(body)
// return false, dplaneErr
// return false, resp.getError(body)
}
func (client *DataplaneClient) configTLS(
tx *models.Transaction, spec Spec, path, method string) error {
// port := int64(443)
// site := &models.Site{
// Name: "offloader",
// Service: &models.SiteService{
// Listeners: []*models.Bind{
// &models.Bind{
// Name: "offloader",
// Port: &port,
// Ssl: true,
// SslCertificate: "ssl-cert-snakeoil.pem",
// },
// },
// },
// Farms: []*models.SiteFarm{
// &models.SiteFarm{
// Name: "varnish",
// UseAs: "default",
// Servers: []*models.Server{
// &models.Server{
// Name: "varnish",
// Address: varnishSock,
// Check: "enabled",
// SendProxyV2: "enabled",
// },
// },
// },
// },
// }
// siteBytes, err := site.MarshalBinary()
// if err != nil {
// return err
// }
var rdr *bytes.Reader
if method != http.MethodDelete {
site, err := getSite()
......@@ -422,18 +365,6 @@ func (client *DataplaneClient) configTLS(
rdr = bytes.NewReader(site)
}
// path := &url.URL{Path: "/v1/services/haproxy/sites"}
// url := client.baseURL.ResolveReference(path)
// req, err := http.NewRequest("POST", url.String(),
// bytes.NewReader(siteBytes))
// if err != nil {
// return err
// }
// query := req.URL.Query()
// query.Add("transaction_id", tx.ID)
// req.URL.RawQuery = query.Encode()
// req.SetBasicAuth(client.user, client.password)
// req.Header.Set("Accept", "application/json")
req, err := client.getReq(path, method, rdr,
method != http.MethodDelete)
if err != nil {
......@@ -448,12 +379,7 @@ func (client *DataplaneClient) configTLS(
if err != nil {
return err
}
// defer drainAndClose(resp.Body)
// // XXX read ContentLength bytes
// body, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return err
// }
body, err := getBody(resp)
if err != nil {
return err
......@@ -479,55 +405,57 @@ func (client *DataplaneClient) configTLS(
default:
return getError(resp, body)
}
// if resp.StatusCode == http.StatusCreated {
// // XXX ????
// panic("Got Created but did not set force_reload")
// }
// if resp.StatusCode != http.StatusAccepted {
// dplaneErr := &DataplaneError{
// Err: &models.Error{},
// Status: resp.StatusCode,
// }
// if dplaneVer, err := strconv.Atoi(
// resp.Header.Get("Configuration-Version")); err != nil {
// dplaneErr.Version = dplaneVer
// }
// dplaneErr.Err.UnmarshalJSON(body)
// return dplaneErr
// }
// site = &models.Site{}
// if err = site.UnmarshalBinary(body); err != nil {
// return err
// }
// if err = site.Validate(fmts); err != nil {
// return err
// }
// return nil
}
// AddTLS adds the offloader configuration for haproxy, as specified
// by spec, in the dataplane transaction tx.
//
// AddTLS MUST be used if the offloader was not configured previously
// since the haproxy container was started, or after deletion.
//
// A non-nil error return may wrap a DataplaneError.
func (client *DataplaneClient) AddTLS(
tx *models.Transaction, spec Spec) error {
return client.configTLS(tx, spec, sitesPath, http.MethodPost)
}
// UpdateTLS modifies the offloader configuration for haproxy to the
// specification in spec, in the dataplane transaction tx.
//
// UpdateTLS MUST be used if the offloader was previously added with
// AddTLS, and not removed with DeleteTLS.
//
// A non-nil error return may wrap a DataplaneError.
func (client *DataplaneClient) UpdateTLS(
tx *models.Transaction, spec Spec) error {
return client.configTLS(tx, spec, frontendSitePath, http.MethodPut)
}
// DeleteTLS removes the haproxy offloader configuration, in the
// dataplane transaction tx.
//
// A non-nil error return may wrap a DataplaneError.
func (client *DataplaneClient) DeleteTLS(tx *models.Transaction) error {
return client.configTLS(tx, Spec{}, frontendSitePath, http.MethodDelete)
}
// DeleteTx removes the dataplane transaction tx. This should be
// called after a successful invocation of FinishTx(tx).
//
// A non-nil error return may wrap a DataplaneError.
//
// XXX currently a no-op.
func (client *DataplaneClient) DeleteTx(tx *models.Transaction) error {
return nil
}
// Reloaded returns true if the reload identified by id has been
// successfully complete, with details about the reload state.
//
// A non-nil error return may wrap a DataplaneError.
func (client *DataplaneClient) Reloaded(id string) (bool, ReloadState, error) {
state := ReloadState{ID: id}
req, err := client.getReq(reloadsPath+"/"+id, http.MethodGet, nil,
......@@ -572,6 +500,10 @@ type sitesBody struct {
sites models.Sites `json:"data"`
}
// OffldrStatus returns true iff the offloader site has been loaded by
// the dataplane API, and returns the current configuration version.
//
// A non-nil error return may wrap a DataplaneError.
func (client *DataplaneClient) OffldrStatus() (
loaded bool, version int, err error) {
......
......@@ -33,6 +33,8 @@ import (
"net/url"
)
// FaccessError encapsulates an error response from an faccess server.
// Satisfies the error interface.
type FaccessError struct {
Status int
PEM string
......@@ -42,11 +44,22 @@ func (err FaccessError) Error() string {
return err.PEM + ": " + http.StatusText(err.Status)
}
// FaccessClient sends requests to an http-faccess server to determine
// if a file exists and is readable. This is used to find out when a
// PEM file exists (as projected into a SecretVolume), so that haproxy
// can be reloaded with the TLS certificate in its configuration.
//
// https://code.uplex.de/testing/http-faccess
type FaccessClient struct {
baseURL *url.URL
client *http.Client
}
// NewFaccessClient returns a client for the http-faccess server
// listening at host.
//
// host may have the form "addr" or "addr:port", where addr may be a
// host name or IP address.
func NewFaccessClient(host string) *FaccessClient {
return &FaccessClient{
baseURL: &url.URL{
......@@ -61,6 +74,10 @@ func (client *FaccessClient) getHost() string {
return client.baseURL.Host
}
// PEMExists returns true iff the http-faccess server reports that the
// PEM file corresponding to spec exists and is readable.
//
// A non-nil error return may wrap FaccessError.
func (client *FaccessClient) PEMExists(spec SecretSpec) (bool, error) {
relPath := &url.URL{Path: spec.CertName()}
url := client.baseURL.ResolveReference(relPath)
......
......@@ -47,8 +47,11 @@ import (
"github.com/sirupsen/logrus"
)
const linux_name_max = 255
const linuxNameMax = 255
// SecretSpec specifies an Ingress TLS Secret for the purposes of the
// haproxy controller. It suffices to identify the *exact* k8s
// configuration of the Secret, including UID and ResourceVersion.
type SecretSpec struct {
Namespace string
Name string
......@@ -60,6 +63,10 @@ func (spec SecretSpec) String() string {
return spec.Namespace + "/" + spec.Name
}
// Spec specifies the configuration of TLS offload for haproxy. It
// includes the namespace and name of the Varnish admin Service (the
// headless k8s Service specifying ports for remote administration),
// and a list of specs for Ingress TLS Secrets.
type Spec struct {
Namespace string
Name string
......@@ -88,12 +95,18 @@ func (spec SecretSpec) hashedName() string {
// and/or Name are too long.
func (spec SecretSpec) CertName() string {
name := spec.Namespace + "_" + spec.Name
if len(name) > linux_name_max {
if len(name) > linuxNameMax {
name = spec.hashedName()
}
return name + ".pem"
}
// OffldAddr encapsulate the networking information for remote
// administration of a TLS offloader for Ingress, implemented by
// haproxy.
//
// Includes the namespace and name of the Pod in which haproxy runs,
// and the addresses of the dataplane API and http-faccess server.
type OffldAddr struct {
PodNamespace string
PodName string
......@@ -102,6 +115,8 @@ type OffldAddr struct {
FaccessPort int32
}
// OffldrError encapsulates an error in the interaction of the haproxy
// controller with a haproxy container.
type OffldrError struct {
addr string
name string
......@@ -113,6 +128,16 @@ func (offldrErr OffldrError) Error() string {
offldrErr.addr, offldrErr.err)
}
// OffldrErrors encapsulates a group of errors in the interaction with
// a haproxy container. Most of the controllers actions apply to all
// of the replicas in a Pod, and the controller does not stop at the
// first error. So any errors encountered along the way, are
// collected and returned by this type. This makes it possible for an
// action to succeed for some of the replicas. If an action had no
// error, usually nil is returned for the error value (rather than an
// empty slice).
//
// This type satisfies the error interface.
type OffldrErrors []OffldrError
func (offldrErrs OffldrErrors) Error() string {
......@@ -150,6 +175,10 @@ type offldrSvc struct {
secrName string
}
// Controller (or haproxy controller) remotely administers a haproxy
// container to configure TLS offload for Ingress. For the most part,
// this is done with the dataplane API -- see the documentaion of
// DataplaneClient, and the links shown there.
type Controller struct {
log *logrus.Logger
svcEvt interfaces.SvcEventGenerator
......@@ -159,6 +188,13 @@ type Controller struct {
monIntvl time.Duration
}
// NewOffloaderController returns a controller to remotely administer
// a haproxy container for Ingress TLS offload, logging its work with
// the given logger.
//
// XXX monIntvl is meant to be the interval for a monitor loop,
// analogous to the monitor for Varnish instances; currently not
// implemented.
func NewOffloaderController(
log *logrus.Logger, monIntvl time.Duration) *Controller {
......@@ -172,11 +208,16 @@ func NewOffloaderController(
}
}
// Start initiates a haproxy controller.
//
// XXX currently little more than a no-op, will start the monitor
func (hc *Controller) Start() {
fmt.Printf("Offloader controller logging at level: %s\n", hc.log.Level)
// go hc.monitor(hc.monIntvl)
}
// HasOffloader returns true iff the controller has configured the TLS
// offloader designated by svcKey.
func (hc *Controller) HasOffloader(svcKey string) bool {
_, exists := hc.svcs[svcKey]
return exists
......@@ -371,9 +412,6 @@ func (hc *Controller) updateInstance(inst *haproxyInst, spec *Spec) error {
}
}
// updateVarnishSvc(name string) error
// updates each instance in vc.svcs[name]
func (inst *haproxyInst) mkError(err error) OffldrError {
return OffldrError{
addr: inst.dplane.getHost(),
......@@ -421,16 +459,6 @@ func (hc *Controller) updateOffldSvc(svcKey string) error {
return nil
}
// setCfgLabel(inst *varnishInst, cfg, lbl string, mayClose bool)
// Label cfg as lbl at Varnish instance inst. If mayClose is true, then
// losing the admin connection is not an error (Varnish may be
// shutting down).
// Changes readiness & regular
// removeVarnishInstances(insts []*varnishInst) error
// On Delete for a Varnish instance, we set it to the unready state.
// For haproxy delete the site.
func (hc *Controller) removeOffldrInstances(
insts []*haproxyInst) (errs OffldrErrors) {
......@@ -514,10 +542,6 @@ func (hc *Controller) removeOffldrInstances(
return errs
}
// updateVarnishSvcAddrs(key string, addrs []vcl.Address, secrPtr *[]byte,
// loadVCL bool)
// New Endpoints for the Service, constructs the new, remove and keep lists.
func offldAddr2haproxyInst(addr OffldAddr, dplanePasswd *string) *haproxyInst {
var passwd string
if dplanePasswd != nil {
......@@ -633,27 +657,10 @@ func (hc *Controller) getOffldStatus(inst *haproxyInst) error {
return nil
}
// AddOrUpdateVarnishSvc(key string, addrs []vcl.Address, secrName string,
// loadVCL bool) error
// AddOrUpdateVarnishSvc causes a sync for the Varnish Service
// identified by namespace/name key.
//
// addrs: list of admin addresses for instances in the Service
// (internal IPs and admin ports)
// secrName: namespace/name of the admin secret to use for the
// Service
// loadVCL: true if the VCL config for the Service should be
// reloaded
//
// - if vc.svcs[key] does not exist, create it, and its instances from addrs
// - set svc.secrName = secrName.
// - if vc.secrets[secrName] exists, set that secret for all instances
// - call updateVarnishSvcAddrs() -- updates each instance
//
// => called solely by worker.syncSvc() (Service updates)
// call *before* updating Varnish, so that haproxy is only ready when
// Varnish becomes ready
// AddOrUpdateOffloader sets the configuration for the offloader
// designated by key, using the given addresses for remote admin, and
// the Secret designated by secrName as the password for Basic Auth in
// requests to the dataplane API.
func (hc *Controller) AddOrUpdateOffloader(key string, addrs []OffldAddr,
secrName string) error {
......@@ -690,15 +697,9 @@ func (hc *Controller) AddOrUpdateOffloader(key string, addrs []OffldAddr,
return hc.updateOffldrAddrs(key, addrs, passwdPtr)
}
// DeleteVarnishSvc(key string) error
// DeleteVarnishSvc is called on the Delete event for the Varnish
// Service identified by the namespace/name key. The Varnish instance
// is set to the unready state, and no further action is taken (other
// resources in the cluster may shut down the Varnish instances).
//
// - dataplane.Delete() the Site
// - currently no way to set haproxy healthz to not ready
// DeleteOffldSvc removes the TLS offloader service designated by
// svcKey -- the haproxy configuration is deleted, and the
// specification is removed from the controller's configuration.
func (hc *Controller) DeleteOffldSvc(svcKey string) error {
svc, exists := hc.svcs[svcKey]
if !exists {
......@@ -712,19 +713,8 @@ func (hc *Controller) DeleteOffldSvc(svcKey string) error {
return err
}
// Update(svcKey string, spec vcl.Spec, ingsMeta map[string]Meta, vcfgMeta Meta,
// bcfgMeta map[string]Meta) error
// Update a Varnish Service to implement an configuration.
//
// svcKey: namespace/name key for the Service
// spec: VCL spec corresponding to the configuration
// ingsMeta: Ingress meta-data
// vcfgMeta: VarnishConfig meta-data
// bcfgMeta: BackendConfig meta-data
//
// - if vc.svcs[key] does not exist, create it
// - call updateVarnishSvc(svcKey)
// Update the TLS offloader designated by svcKey to the configuration
// given by spec.
func (hc *Controller) Update(svcKey string, spec Spec) error {
svc, exists := hc.svcs[svcKey]
if !exists {
......@@ -741,27 +731,9 @@ func (hc *Controller) Update(svcKey string, spec Spec) error {
return hc.updateOffldSvc(svcKey)
}
// SetNotReady(svcKey string) error
// SetNotReady may be called on the Delete event on an Ingress, if no
// Ingresses remain that are to be implemented by a Varnish Service.
// The Service is set to the not ready state, by relabelling VCL so
// that readiness checks are not answered with status 200.
// HasConfig(svcKey string, spec vcl.Spec, ingsMeta map[string]Meta,
// vcfgMeta Meta,bcfgMeta map[string]Meta)
// HasConfig returns true iff a configuration is already loaded for a
// Varnish Service (so a new sync attempt is not necessary).
//
// svcKey: namespace/name key for the Varnish Service
// spec: VCL specification derived from the configuration
// ingsMeta: Ingress meta-data
// vcfgMeta: VarnishConfig meta-data
// bcfgMeta: BackendConfig meta-data
// SetAdmSecret(key string, secret []byte)
// SetAdmSecret stores the Secret data identified by the
// namespace/name key.
// SetDataplaneSecret stores the secret to be used as the Basic Auth
// password used in requests to a dataplane API, under the name given
// in key (from the namespace/name of a k8s Secret).
func (hc *Controller) SetDataplaneSecret(key string, secret []byte) {
_, exists := hc.secrets[key]
if !exists {
......@@ -773,6 +745,10 @@ func (hc *Controller) SetDataplaneSecret(key string, secret []byte) {
*hc.secrets[key] = string(secret)
}
// SetOffldSecret specifies secretKey as the name of the Secret to be
// used to authorize use of the dataplane API for the TLS offloader
// designated by SetOffldSecret. SetDataplaneSecret(), in turns, sets
// the secret contents for secretKey.
func (hc *Controller) SetOffldSecret(svcKey, secretKey string) error {
svc, ok := hc.svcs[svcKey]
if !ok {
......@@ -789,15 +765,7 @@ func (hc *Controller) SetOffldSecret(svcKey, secretKey string) error {
return nil
}
// UpdateSvcForSecret(svcKey, secretKey string) error
// UpdateSvcForSecret associates the Secret identified by the
// namespace/name secretKey with the Varnish Service identified by the
// namespace/name svcKey. The Service is newly synced if necessary.
// DeleteAdmSecret(name string)
// DeleteAdmSecret removes the secret identified by the namespace/name
// key.
// DeleteDataplaneSecret removes the Secret designated by name.
func (hc *Controller) DeleteDataplaneSecret(name string) {
_, exists := hc.secrets[name]
if !exists {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment