Uyuni login less often (#10072)

* Initial implementation of caching the uyuni login token

Signed-off-by: David N Perkins <David.N.Perkins@ibm.com>
This commit is contained in:
David N Perkins 2022-02-15 10:38:38 -05:00 committed by GitHub
parent afdd1357e0
commit fcb6f18122
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 29 deletions

View File

@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/go-kit/log" "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/kolo/xmlrpc" "github.com/kolo/xmlrpc"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/common/config" "github.com/prometheus/common/config"
@ -47,6 +46,8 @@ const (
uyuniLabelProxyModule = uyuniMetaLabelPrefix + "proxy_module" uyuniLabelProxyModule = uyuniMetaLabelPrefix + "proxy_module"
uyuniLabelMetricsPath = uyuniMetaLabelPrefix + "metrics_path" uyuniLabelMetricsPath = uyuniMetaLabelPrefix + "metrics_path"
uyuniLabelScheme = uyuniMetaLabelPrefix + "scheme" uyuniLabelScheme = uyuniMetaLabelPrefix + "scheme"
tokenDuration = 10 * time.Minute
) )
// DefaultSDConfig is the default Uyuni SD configuration. // DefaultSDConfig is the default Uyuni SD configuration.
@ -96,14 +97,16 @@ type endpointInfo struct {
// Discovery periodically performs Uyuni API requests. It implements the Discoverer interface. // Discovery periodically performs Uyuni API requests. It implements the Discoverer interface.
type Discovery struct { type Discovery struct {
*refresh.Discovery *refresh.Discovery
apiURL *url.URL apiURL *url.URL
roundTripper http.RoundTripper roundTripper http.RoundTripper
username string username string
password string password string
entitlement string token string
separator string tokenExpiration time.Time
interval time.Duration entitlement string
logger log.Logger separator string
interval time.Duration
logger log.Logger
} }
// Name returns the name of the Config. // Name returns the name of the Config.
@ -140,16 +143,12 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil return nil
} }
func login(rpcclient *xmlrpc.Client, user, pass string) (string, error) { func login(rpcclient *xmlrpc.Client, user, pass string, duration int) (string, error) {
var result string var result string
err := rpcclient.Call("auth.login", []interface{}{user, pass}, &result) err := rpcclient.Call("auth.login", []interface{}{user, pass, duration}, &result)
return result, err return result, err
} }
func logout(rpcclient *xmlrpc.Client, token string) error {
return rpcclient.Call("auth.logout", token, nil)
}
func getSystemGroupsInfoOfMonitoredClients(rpcclient *xmlrpc.Client, token, entitlement string) (map[int][]systemGroupID, error) { func getSystemGroupsInfoOfMonitoredClients(rpcclient *xmlrpc.Client, token, entitlement string) (map[int][]systemGroupID, error) {
var systemGroupsInfos []struct { var systemGroupsInfos []struct {
SystemID int `xmlrpc:"id"` SystemID int `xmlrpc:"id"`
@ -271,12 +270,11 @@ func getSystemGroupNames(systemGroupsIDs []systemGroupID) []string {
func (d *Discovery) getTargetsForSystems( func (d *Discovery) getTargetsForSystems(
rpcClient *xmlrpc.Client, rpcClient *xmlrpc.Client,
token string,
entitlement string, entitlement string,
) ([]model.LabelSet, error) { ) ([]model.LabelSet, error) {
result := make([]model.LabelSet, 0) result := make([]model.LabelSet, 0)
systemGroupIDsBySystemID, err := getSystemGroupsInfoOfMonitoredClients(rpcClient, token, entitlement) systemGroupIDsBySystemID, err := getSystemGroupsInfoOfMonitoredClients(rpcClient, d.token, entitlement)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to get the managed system groups information of monitored clients") return nil, errors.Wrap(err, "unable to get the managed system groups information of monitored clients")
} }
@ -286,12 +284,12 @@ func (d *Discovery) getTargetsForSystems(
systemIDs = append(systemIDs, systemID) systemIDs = append(systemIDs, systemID)
} }
endpointInfos, err := getEndpointInfoForSystems(rpcClient, token, systemIDs) endpointInfos, err := getEndpointInfoForSystems(rpcClient, d.token, systemIDs)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to get endpoints information") return nil, errors.Wrap(err, "unable to get endpoints information")
} }
networkInfoBySystemID, err := getNetworkInformationForSystems(rpcClient, token, systemIDs) networkInfoBySystemID, err := getNetworkInformationForSystems(rpcClient, d.token, systemIDs)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "unable to get the systems network information") return nil, errors.Wrap(err, "unable to get the systems network information")
} }
@ -308,25 +306,27 @@ func (d *Discovery) getTargetsForSystems(
return result, nil return result, nil
} }
func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) { func (d *Discovery) refresh(_ context.Context) ([]*targetgroup.Group, error) {
rpcClient, err := xmlrpc.NewClient(d.apiURL.String(), d.roundTripper) rpcClient, err := xmlrpc.NewClient(d.apiURL.String(), d.roundTripper)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rpcClient.Close() defer rpcClient.Close()
token, err := login(rpcClient, d.username, d.password) if time.Now().After(d.tokenExpiration) {
if err != nil { // Uyuni API takes duration in seconds.
return nil, errors.Wrap(err, "unable to login to Uyuni API") d.token, err = login(rpcClient, d.username, d.password, int(tokenDuration.Seconds()))
} if err != nil {
defer func() { return nil, errors.Wrap(err, "unable to login to Uyuni API")
if err := logout(rpcClient, token); err != nil {
level.Debug(d.logger).Log("msg", "Failed to log out from Uyuni API", "err", err)
} }
}() // Login again at half the token lifetime.
d.tokenExpiration = time.Now().Add(tokenDuration / 2)
}
targetsForSystems, err := d.getTargetsForSystems(rpcClient, token, d.entitlement) targetsForSystems, err := d.getTargetsForSystems(rpcClient, d.entitlement)
if err != nil { if err != nil {
// Force the renewal of the token on next refresh.
d.tokenExpiration = time.Now()
return nil, err return nil, err
} }

View File

@ -19,6 +19,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -56,3 +57,67 @@ func TestUyuniSDHandleError(t *testing.T) {
require.EqualError(t, err, errTesting) require.EqualError(t, err, errTesting)
require.Equal(t, len(tgs), 0) require.Equal(t, len(tgs), 0)
} }
func TestUyuniSDLogin(t *testing.T) {
var (
errTesting = "unable to get the managed system groups information of monitored clients: request error: bad status code - 500"
call = 0
respHandler = func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/xml")
switch call {
case 0:
w.WriteHeader(http.StatusOK)
io.WriteString(w, `<?xml version="1.0"?>
<methodResponse>
<params>
<param>
<value>
a token
</value>
</param>
</params>
</methodResponse>`)
case 1:
w.WriteHeader(http.StatusInternalServerError)
io.WriteString(w, ``)
}
call++
}
)
tgs, err := testUpdateServices(respHandler)
require.EqualError(t, err, errTesting)
require.Equal(t, len(tgs), 0)
}
func TestUyuniSDSkipLogin(t *testing.T) {
var (
errTesting = "unable to get the managed system groups information of monitored clients: request error: bad status code - 500"
respHandler = func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Header().Set("Content-Type", "application/xml")
io.WriteString(w, ``)
}
)
// Create a test server with mock HTTP handler.
ts := httptest.NewServer(http.HandlerFunc(respHandler))
defer ts.Close()
conf := SDConfig{
Server: ts.URL,
}
md, err := NewDiscovery(&conf, nil)
if err != nil {
t.Error(err)
}
// simulate a cached token
md.token = `a token`
md.tokenExpiration = time.Now().Add(time.Minute)
tgs, err := md.refresh(context.Background())
require.EqualError(t, err, errTesting)
require.Equal(t, len(tgs), 0)
}