http-add-on/pkg/routing/config_map.go

157 lines
4.0 KiB
Go

package routing
import (
"context"
"fmt"
"github.com/go-logr/logr"
"github.com/kedacore/http-add-on/pkg/k8s"
"github.com/kedacore/http-add-on/pkg/queue"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
// the name of the ConfigMap that stores the routing table
ConfigMapRoutingTableName = "keda-http-routing-table"
// the key in the ConfigMap data that stores the JSON routing table
configMapRoutingTableKey = "routing-table"
)
// SaveTableToConfigMap saves the contents of table to the Data field in
// configMap
func SaveTableToConfigMap(table *Table, configMap *corev1.ConfigMap) error {
tableAsJSON, err := table.MarshalJSON()
if err != nil {
return err
}
configMap.Data[configMapRoutingTableKey] = string(tableAsJSON)
return nil
}
// FetchTableFromConfigMap fetches the Data field from configMap, converts it
// to a routing table, and returns it
func FetchTableFromConfigMap(configMap *corev1.ConfigMap, q queue.Counter) (*Table, error) {
data, found := configMap.Data[configMapRoutingTableKey]
if !found {
return nil, fmt.Errorf(
"no '%s' key found in the %s ConfigMap",
configMapRoutingTableKey,
ConfigMapRoutingTableName,
)
}
ret := NewTable()
if err := ret.UnmarshalJSON([]byte(data)); err != nil {
retErr := errors.Wrap(
err,
fmt.Sprintf(
"error decoding '%s' key in %s ConfigMap",
configMapRoutingTableKey,
ConfigMapRoutingTableName,
),
)
return nil, retErr
}
return ret, nil
}
// updateQueueFromTable ensures that every host in the routing table
// exists in the given queue, and no hosts exist in the queue that
// don't exist in the routing table. It uses q.Ensure() and q.Remove()
// to do those things, respectively.
func updateQueueFromTable(
lggr logr.Logger,
table *Table,
q queue.Counter,
) error {
// ensure that every host is in the queue, even if it has
// zero pending requests. This is important so that the
// scaler can report on all applications.
for host := range table.m {
q.Ensure(host)
}
// ensure that the queue doesn't have any extra hosts that don't exist in the table
qCur, err := q.Current()
if err != nil {
lggr.Error(
err,
"failed to get current queue counts (in order to prune it of missing routing table hosts)",
)
return errors.Wrap(err, "pkg.routing.updateQueueFromTable")
}
for host := range qCur.Counts {
if _, err := table.Lookup(host); err != nil {
q.Remove(host)
}
}
return nil
}
// GetTable fetches the contents of the appropriate ConfigMap that stores
// the routing table, then tries to decode it into a temporary routing table
// data structure.
//
// If that succeeds, it calls table.Replace(newTable), then ensures that
// every host in the routing table exists in the given queue, and no hosts
// exist in the queue that don't exist in the routing table. It uses q.Ensure()
// and q.Remove() to do those things, respectively.
func GetTable(
ctx context.Context,
lggr logr.Logger,
getter k8s.ConfigMapGetter,
table *Table,
q queue.Counter,
) error {
lggr = lggr.WithName("pkg.routing.GetTable")
cm, err := getter.Get(
ctx,
ConfigMapRoutingTableName,
metav1.GetOptions{},
)
if err != nil {
lggr.Error(
err,
"failed to fetch routing table config map",
"configMapName",
ConfigMapRoutingTableName,
)
return errors.Wrap(
err,
fmt.Sprintf(
"failed to fetch ConfigMap %s",
ConfigMapRoutingTableName,
),
)
}
newTable, err := FetchTableFromConfigMap(cm, q)
if err != nil {
lggr.Error(
err,
"failed decoding routing table ConfigMap",
"configMapName",
ConfigMapRoutingTableName,
)
return errors.Wrap(
err,
fmt.Sprintf(
"failed decoding ConfigMap %s into a routing table",
ConfigMapRoutingTableName,
),
)
}
table.Replace(newTable)
if err := updateQueueFromTable(lggr, table, q); err != nil {
lggr.Error(
err,
"unable to update the queue from the new routing table",
)
return errors.Wrap(err, "pkg.routing.GetTable")
}
return nil
}