mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-03-11 15:34:56 +00:00
lib/promscrape/discovery/openstack: code prettifying after cbe3cf683b
Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/728
This commit is contained in:
parent
991fad7855
commit
aba899c298
6 changed files with 110 additions and 177 deletions
|
@ -16,27 +16,22 @@ import (
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const authHeaderName = "X-Auth-Token" // for making requests to openstack api
|
|
||||||
|
|
||||||
var configMap = discoveryutils.NewConfigMap()
|
var configMap = discoveryutils.NewConfigMap()
|
||||||
|
|
||||||
// apiCredentials can be refreshed
|
// apiCredentials can be refreshed
|
||||||
type apiCredentials struct {
|
type apiCredentials struct {
|
||||||
// computeURL obtained from auth response and maybe changed
|
|
||||||
computeURL *url.URL
|
computeURL *url.URL
|
||||||
// value of authHeaderName
|
|
||||||
token string
|
token string
|
||||||
expiration time.Time
|
expiration time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiConfig struct {
|
type apiConfig struct {
|
||||||
// client may use tls
|
|
||||||
client *http.Client
|
client *http.Client
|
||||||
port int
|
port int
|
||||||
// tokenLock - guards creds refresh
|
// tokenLock guards creds refresh
|
||||||
tokenLock sync.Mutex
|
tokenLock sync.Mutex
|
||||||
creds *apiCredentials
|
creds *apiCredentials
|
||||||
// authTokenReq - request for apiCredentials
|
// authTokenReq contins request body for apiCredentials
|
||||||
authTokenReq []byte
|
authTokenReq []byte
|
||||||
// keystone endpoint
|
// keystone endpoint
|
||||||
endpoint *url.URL
|
endpoint *url.URL
|
||||||
|
@ -50,19 +45,17 @@ func (cfg *apiConfig) getFreshAPICredentials() (*apiCredentials, error) {
|
||||||
cfg.tokenLock.Lock()
|
cfg.tokenLock.Lock()
|
||||||
defer cfg.tokenLock.Unlock()
|
defer cfg.tokenLock.Unlock()
|
||||||
|
|
||||||
if time.Until(cfg.creds.expiration) > 10*time.Second {
|
if cfg.creds != nil && time.Until(cfg.creds.expiration) > 10*time.Second {
|
||||||
|
|
||||||
// Credentials aren't expired yet.
|
// Credentials aren't expired yet.
|
||||||
return cfg.creds, nil
|
return cfg.creds, nil
|
||||||
}
|
}
|
||||||
newCreds, err := getCreds(cfg)
|
newCreds, err := getCreds(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed token refresh: %w", err)
|
return nil, fmt.Errorf("cannot refresh OpenStack api token: %w", err)
|
||||||
}
|
}
|
||||||
logger.Infof("refreshed, next : %v", cfg.creds.expiration.String())
|
|
||||||
cfg.creds = newCreds
|
cfg.creds = newCreds
|
||||||
|
logger.Infof("successfully refreshed OpenStack api token; next expiration: %s", newCreds.expiration)
|
||||||
return cfg.creds, nil
|
return newCreds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||||
|
@ -75,22 +68,20 @@ func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||||
|
|
||||||
func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||||
cfg := &apiConfig{
|
cfg := &apiConfig{
|
||||||
client: discoveryutils.GetHTTPClient(),
|
client: &http.Client{},
|
||||||
availability: sdc.Availability,
|
availability: sdc.Availability,
|
||||||
region: sdc.Region,
|
region: sdc.Region,
|
||||||
allTenants: sdc.AllTenants,
|
allTenants: sdc.AllTenants,
|
||||||
port: sdc.Port,
|
port: sdc.Port,
|
||||||
}
|
}
|
||||||
|
|
||||||
if sdc.TLSConfig != nil {
|
if sdc.TLSConfig != nil {
|
||||||
config, err := promauth.NewConfig(baseDir, nil, "", "", sdc.TLSConfig)
|
config, err := promauth.NewConfig(baseDir, nil, "", "", sdc.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tr := &http.Transport{
|
cfg.client.Transport = &http.Transport{
|
||||||
TLSClientConfig: config.NewTLSConfig(),
|
TLSClientConfig: config.NewTLSConfig(),
|
||||||
}
|
}
|
||||||
cfg.client.Transport = tr
|
|
||||||
}
|
}
|
||||||
// use public compute endpoint by default
|
// use public compute endpoint by default
|
||||||
if len(cfg.availability) == 0 {
|
if len(cfg.availability) == 0 {
|
||||||
|
@ -100,7 +91,7 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||||
// create new variable to prevent side effects
|
// create new variable to prevent side effects
|
||||||
sdcAuth := *sdc
|
sdcAuth := *sdc
|
||||||
// special case if identity_endpoint is not defined
|
// special case if identity_endpoint is not defined
|
||||||
if len(sdc.IdentityEndpoint) == 0 {
|
if len(sdcAuth.IdentityEndpoint) == 0 {
|
||||||
// override sdc
|
// override sdc
|
||||||
sdcAuth = readCredentialsFromEnv()
|
sdcAuth = readCredentialsFromEnv()
|
||||||
}
|
}
|
||||||
|
@ -115,19 +106,15 @@ func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cfg.authTokenReq = tokenReq
|
cfg.authTokenReq = tokenReq
|
||||||
token, err := getCreds(cfg)
|
// cfg.creds is populated at getFreshAPICredentials
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cfg.creds = token
|
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCreds - make call to openstack keystone api and retrieves token and computeURL
|
// getCreds makes a call to openstack keystone api and retrieves token and computeURL
|
||||||
// https://docs.openstack.org/api-ref/identity/v3/
|
//
|
||||||
|
// See https://docs.openstack.org/api-ref/identity/v3/
|
||||||
func getCreds(cfg *apiConfig) (*apiCredentials, error) {
|
func getCreds(cfg *apiConfig) (*apiCredentials, error) {
|
||||||
|
|
||||||
apiURL := *cfg.endpoint
|
apiURL := *cfg.endpoint
|
||||||
apiURL.Path = path.Join(apiURL.Path, "auth", "tokens")
|
apiURL.Path = path.Join(apiURL.Path, "auth", "tokens")
|
||||||
|
|
||||||
|
@ -143,23 +130,19 @@ func getCreds(cfg *apiConfig) (*apiCredentials, error) {
|
||||||
if resp.StatusCode != http.StatusCreated {
|
if resp.StatusCode != http.StatusCreated {
|
||||||
return nil, fmt.Errorf("auth failed, bad status code: %d, want: 201", resp.StatusCode)
|
return nil, fmt.Errorf("auth failed, bad status code: %d, want: 201", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
at := resp.Header.Get("X-Subject-Token")
|
at := resp.Header.Get("X-Subject-Token")
|
||||||
if len(at) == 0 {
|
if len(at) == 0 {
|
||||||
return nil, fmt.Errorf("auth failed, response without X-Subject-Token")
|
return nil, fmt.Errorf("auth failed, response without X-Subject-Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
var ar authResponse
|
var ar authResponse
|
||||||
if err := json.Unmarshal(r, &ar); err != nil {
|
if err := json.Unmarshal(r, &ar); err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse auth credentials response: %w", err)
|
return nil, fmt.Errorf("cannot parse auth credentials response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
computeURL, err := getComputeEndpointURL(ar.Token.Catalog, cfg.availability, cfg.region)
|
computeURL, err := getComputeEndpointURL(ar.Token.Catalog, cfg.availability, cfg.region)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot get computeEndpoint, account doesn't have enough permissions,"+
|
return nil, fmt.Errorf("cannot get computeEndpoint, account doesn't have enough permissions, "+
|
||||||
" availability: %s, region: %s", cfg.availability, cfg.region)
|
"availability: %s, region: %s; error: %w", cfg.availability, cfg.region, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &apiCredentials{
|
return &apiCredentials{
|
||||||
token: at,
|
token: at,
|
||||||
expiration: ar.Token.ExpiresAt,
|
expiration: ar.Token.ExpiresAt,
|
||||||
|
@ -167,7 +150,7 @@ func getCreds(cfg *apiConfig) (*apiCredentials, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readResponseBody - reads body from http.Response.
|
// readResponseBody reads body from http.Response.
|
||||||
func readResponseBody(resp *http.Response, apiURL string) ([]byte, error) {
|
func readResponseBody(resp *http.Response, apiURL string) ([]byte, error) {
|
||||||
data, err := ioutil.ReadAll(resp.Body)
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
|
@ -178,26 +161,24 @@ func readResponseBody(resp *http.Response, apiURL string) ([]byte, error) {
|
||||||
return nil, fmt.Errorf("unexpected status code for %q; got %d; want %d; response body: %q",
|
return nil, fmt.Errorf("unexpected status code for %q; got %d; want %d; response body: %q",
|
||||||
apiURL, resp.StatusCode, http.StatusOK, data)
|
apiURL, resp.StatusCode, http.StatusOK, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAPIResponse - makes api call to openstack and returns response body
|
// getAPIResponse calls openstack apiURL and returns response body.
|
||||||
func getAPIResponse(href string, cfg *apiConfig) ([]byte, error) {
|
func getAPIResponse(apiURL string, cfg *apiConfig) ([]byte, error) {
|
||||||
token, err := cfg.getFreshAPICredentials()
|
creds, err := cfg.getFreshAPICredentials()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed refresh api credentials: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("GET", href, nil)
|
req, err := http.NewRequest("GET", apiURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot create new request for openstack api href: %s, err: %w", href, err)
|
return nil, fmt.Errorf("cannot create new request for openstack api url %s: %w", apiURL, err)
|
||||||
}
|
}
|
||||||
req.Header.Set(authHeaderName, token.token)
|
req.Header.Set("X-Auth-Token", creds.token)
|
||||||
resp, err := cfg.client.Do(req)
|
resp, err := cfg.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed query openstack api, href: %s, err : %w", href, err)
|
return nil, fmt.Errorf("cannot query openstack api url %s: %w", apiURL, err)
|
||||||
}
|
}
|
||||||
|
return readResponseBody(resp, apiURL)
|
||||||
return readResponseBody(resp, href)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authResponse - identity api response
|
// authResponse represents identity api response
|
||||||
// https://docs.openstack.org/api-ref/identity/v3/?expanded=create-credential-detail,password-authentication-with-unscoped-authorization-detail#authentication-and-token-management
|
//
|
||||||
|
// See https://docs.openstack.org/api-ref/identity/v3/#authentication-and-token-management
|
||||||
type authResponse struct {
|
type authResponse struct {
|
||||||
Token struct {
|
Token struct {
|
||||||
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
||||||
|
@ -24,7 +25,8 @@ type catalogItem struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// openstack api endpoint
|
// openstack api endpoint
|
||||||
// https://docs.openstack.org/api-ref/identity/v3/?expanded=create-credential-detail,password-authentication-with-unscoped-authorization-detail,token-authentication-with-scoped-authorization-detail#list-endpoints
|
//
|
||||||
|
// See https://docs.openstack.org/api-ref/identity/v3/#list-endpoints
|
||||||
type endpoint struct {
|
type endpoint struct {
|
||||||
RegionID string `json:"region_id"`
|
RegionID string `json:"region_id"`
|
||||||
RegionName string `json:"region_name"`
|
RegionName string `json:"region_name"`
|
||||||
|
@ -34,33 +36,30 @@ type endpoint struct {
|
||||||
Interface string `json:"interface"`
|
Interface string `json:"interface"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getComputeEndpointURL extracts compute url endpoint with given filters from keystone catalog
|
// getComputeEndpointURL extracts compute endpoint url with given filters from keystone catalog
|
||||||
func getComputeEndpointURL(catalog []catalogItem, availability, region string) (*url.URL, error) {
|
func getComputeEndpointURL(catalog []catalogItem, availability, region string) (*url.URL, error) {
|
||||||
for _, eps := range catalog {
|
for _, eps := range catalog {
|
||||||
if eps.Type == "compute" {
|
if eps.Type != "compute" {
|
||||||
for _, ep := range eps.Endpoints {
|
continue
|
||||||
if ep.Interface == availability && (len(region) == 0 || region == ep.RegionID || region == ep.RegionName) {
|
}
|
||||||
return url.Parse(ep.URL)
|
for _, ep := range eps.Endpoints {
|
||||||
}
|
if ep.Interface == availability && (len(region) == 0 || region == ep.RegionID || region == ep.RegionName) {
|
||||||
|
return url.Parse(ep.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("cannot excract compute url from catalog, availability: %s, region: %s ", availability, region)
|
return nil, fmt.Errorf("cannot find compute url for the given availability: %q, region: %q", availability, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildAuthRequestBody - builds request for authentication.
|
// buildAuthRequestBody builds request for authentication
|
||||||
func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
|
|
||||||
// fast path
|
|
||||||
if len(sdc.Password) == 0 && len(sdc.ApplicationCredentialID) == 0 && len(sdc.ApplicationCredentialName) == 0 {
|
if len(sdc.Password) == 0 && len(sdc.ApplicationCredentialID) == 0 && len(sdc.ApplicationCredentialName) == 0 {
|
||||||
return nil, fmt.Errorf("password and application credentials is missing")
|
return nil, fmt.Errorf("password and application credentials are missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
type domainReq struct {
|
type domainReq struct {
|
||||||
ID *string `json:"id,omitempty"`
|
ID *string `json:"id,omitempty"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type userReq struct {
|
type userReq struct {
|
||||||
ID *string `json:"id,omitempty"`
|
ID *string `json:"id,omitempty"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
|
@ -68,34 +67,28 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
Passcode *string `json:"passcode,omitempty"`
|
Passcode *string `json:"passcode,omitempty"`
|
||||||
Domain *domainReq `json:"domain,omitempty"`
|
Domain *domainReq `json:"domain,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type passwordReq struct {
|
type passwordReq struct {
|
||||||
User userReq `json:"user"`
|
User userReq `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tokenReq struct {
|
type tokenReq struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type applicationCredentialReq struct {
|
type applicationCredentialReq struct {
|
||||||
ID *string `json:"id,omitempty"`
|
ID *string `json:"id,omitempty"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
User *userReq `json:"user,omitempty"`
|
User *userReq `json:"user,omitempty"`
|
||||||
Secret *string `json:"secret,omitempty"`
|
Secret *string `json:"secret,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type identityReq struct {
|
type identityReq struct {
|
||||||
Methods []string `json:"methods"`
|
Methods []string `json:"methods"`
|
||||||
Password *passwordReq `json:"password,omitempty"`
|
Password *passwordReq `json:"password,omitempty"`
|
||||||
Token *tokenReq `json:"token,omitempty"`
|
Token *tokenReq `json:"token,omitempty"`
|
||||||
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
|
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type authReq struct {
|
type authReq struct {
|
||||||
Identity identityReq `json:"identity"`
|
Identity identityReq `json:"identity"`
|
||||||
Scope map[string]interface{} `json:"scope,omitempty"`
|
Scope map[string]interface{} `json:"scope,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type request struct {
|
type request struct {
|
||||||
Auth authReq `json:"auth"`
|
Auth authReq `json:"auth"`
|
||||||
}
|
}
|
||||||
|
@ -118,84 +111,62 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
ID: &sdc.ApplicationCredentialID,
|
ID: &sdc.ApplicationCredentialID,
|
||||||
Secret: &sdc.ApplicationCredentialSecret,
|
Secret: &sdc.ApplicationCredentialSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
// fast path unscoped
|
|
||||||
return json.Marshal(req)
|
return json.Marshal(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// application_credential_name auth
|
|
||||||
if len(sdc.ApplicationCredentialSecret) == 0 {
|
if len(sdc.ApplicationCredentialSecret) == 0 {
|
||||||
return nil, fmt.Errorf("application_credential_name is not empty and application_credential_secret is empty")
|
return nil, fmt.Errorf("missing application_credential_secret when application_credential_name is set")
|
||||||
}
|
}
|
||||||
|
|
||||||
var userRequest *userReq
|
var userRequest *userReq
|
||||||
|
|
||||||
if len(sdc.UserID) > 0 {
|
if len(sdc.UserID) > 0 {
|
||||||
// UserID could be used without the domain information
|
// UserID could be used without the domain information
|
||||||
userRequest = &userReq{
|
userRequest = &userReq{
|
||||||
ID: &sdc.UserID,
|
ID: &sdc.UserID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if userRequest == nil && len(sdc.Username) == 0 {
|
if userRequest == nil && len(sdc.Username) == 0 {
|
||||||
// Make sure that Username or UserID are provided
|
|
||||||
return nil, fmt.Errorf("username and userid is empty")
|
return nil, fmt.Errorf("username and userid is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if userRequest == nil && len(sdc.DomainID) > 0 {
|
if userRequest == nil && len(sdc.DomainID) > 0 {
|
||||||
userRequest = &userReq{
|
userRequest = &userReq{
|
||||||
Name: &sdc.Username,
|
Name: &sdc.Username,
|
||||||
Domain: &domainReq{ID: &sdc.DomainID},
|
Domain: &domainReq{ID: &sdc.DomainID},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if userRequest == nil && len(sdc.DomainName) > 0 {
|
if userRequest == nil && len(sdc.DomainName) > 0 {
|
||||||
userRequest = &userReq{
|
userRequest = &userReq{
|
||||||
Name: &sdc.Username,
|
Name: &sdc.Username,
|
||||||
Domain: &domainReq{Name: &sdc.DomainName},
|
Domain: &domainReq{Name: &sdc.DomainName},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that domain_id or domain_name are provided among username
|
|
||||||
if userRequest == nil {
|
if userRequest == nil {
|
||||||
return nil, fmt.Errorf("domain_id and domain_name is empty for application_credential_name auth")
|
return nil, fmt.Errorf("domain_id and domain_name cannot be empty for application_credential_name auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Auth.Identity.Methods = []string{"application_credential"}
|
req.Auth.Identity.Methods = []string{"application_credential"}
|
||||||
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
|
||||||
Name: &sdc.ApplicationCredentialName,
|
Name: &sdc.ApplicationCredentialName,
|
||||||
User: userRequest,
|
User: userRequest,
|
||||||
Secret: &sdc.ApplicationCredentialSecret,
|
Secret: &sdc.ApplicationCredentialSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
// fast path unscoped
|
|
||||||
return json.Marshal(req)
|
return json.Marshal(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password authentication.
|
// Password authentication.
|
||||||
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password")
|
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password")
|
||||||
|
|
||||||
// At least one of Username and UserID must be specified.
|
|
||||||
if len(sdc.Username) == 0 && len(sdc.UserID) == 0 {
|
if len(sdc.Username) == 0 && len(sdc.UserID) == 0 {
|
||||||
return nil, fmt.Errorf("username and userid is empty for username/password auth")
|
return nil, fmt.Errorf("username and userid is empty for username/password auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.Username) > 0 {
|
if len(sdc.Username) > 0 {
|
||||||
// If Username is provided, UserID may not be provided.
|
|
||||||
if len(sdc.UserID) > 0 {
|
if len(sdc.UserID) > 0 {
|
||||||
return nil, fmt.Errorf("both username and userid is present")
|
return nil, fmt.Errorf("both username and userid is present")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Either DomainID or DomainName must also be specified.
|
|
||||||
if len(sdc.DomainID) == 0 && len(sdc.DomainName) == 0 {
|
if len(sdc.DomainID) == 0 && len(sdc.DomainName) == 0 {
|
||||||
return nil, fmt.Errorf(" domain_id or domain_name is missing for username/password auth: %s", sdc.Username)
|
return nil, fmt.Errorf(" domain_id or domain_name is missing for username/password auth: %s", sdc.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.DomainID) > 0 {
|
if len(sdc.DomainID) > 0 {
|
||||||
if sdc.DomainName != "" {
|
if sdc.DomainName != "" {
|
||||||
return nil, fmt.Errorf("both domain_id and domain_name is present")
|
return nil, fmt.Errorf("both domain_id and domain_name is present")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the request for Username and Password authentication with a DomainID.
|
// Configure the request for Username and Password authentication with a DomainID.
|
||||||
if len(sdc.Password) > 0 {
|
if len(sdc.Password) > 0 {
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
|
@ -207,7 +178,6 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.DomainName) > 0 {
|
if len(sdc.DomainName) > 0 {
|
||||||
// Configure the request for Username and Password authentication with a DomainName.
|
// Configure the request for Username and Password authentication with a DomainName.
|
||||||
if len(sdc.Password) > 0 {
|
if len(sdc.Password) > 0 {
|
||||||
|
@ -221,16 +191,13 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.UserID) > 0 {
|
if len(sdc.UserID) > 0 {
|
||||||
// If UserID is specified, neither DomainID nor DomainName may be.
|
|
||||||
if len(sdc.DomainID) > 0 {
|
if len(sdc.DomainID) > 0 {
|
||||||
return nil, fmt.Errorf("both user_id and domain_id is present")
|
return nil, fmt.Errorf("both user_id and domain_id is present")
|
||||||
}
|
}
|
||||||
if len(sdc.DomainName) > 0 {
|
if len(sdc.DomainName) > 0 {
|
||||||
return nil, fmt.Errorf("both user_id and domain_name is present")
|
return nil, fmt.Errorf("both user_id and domain_name is present")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the request for UserID and Password authentication.
|
// Configure the request for UserID and Password authentication.
|
||||||
if len(sdc.Password) > 0 {
|
if len(sdc.Password) > 0 {
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
req.Auth.Identity.Password = &passwordReq{
|
||||||
|
@ -251,19 +218,16 @@ func buildAuthRequestBody(sdc *SDConfig) ([]byte, error) {
|
||||||
if len(scope) > 0 {
|
if len(scope) > 0 {
|
||||||
req.Auth.Scope = scope
|
req.Auth.Scope = scope
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(req)
|
return json.Marshal(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildScope - adds scope information into auth request
|
// buildScope adds scope information into auth request
|
||||||
// https://docs.openstack.org/api-ref/identity/v3/?expanded=password-authentication-with-scoped-authorization-detail#password-authentication-with-unscoped-authorization
|
//
|
||||||
|
// See https://docs.openstack.org/api-ref/identity/v3/#password-authentication-with-unscoped-authorization
|
||||||
func buildScope(sdc *SDConfig) (map[string]interface{}, error) {
|
func buildScope(sdc *SDConfig) (map[string]interface{}, error) {
|
||||||
|
|
||||||
// fast path
|
|
||||||
if len(sdc.ProjectName) == 0 && len(sdc.ProjectID) == 0 && len(sdc.DomainID) == 0 && len(sdc.DomainName) == 0 {
|
if len(sdc.ProjectName) == 0 && len(sdc.ProjectID) == 0 && len(sdc.DomainID) == 0 && len(sdc.DomainName) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.ProjectName) > 0 {
|
if len(sdc.ProjectName) > 0 {
|
||||||
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
||||||
// ProjectID may not be supplied.
|
// ProjectID may not be supplied.
|
||||||
|
@ -273,10 +237,7 @@ func buildScope(sdc *SDConfig) (map[string]interface{}, error) {
|
||||||
if len(sdc.ProjectID) > 0 {
|
if len(sdc.ProjectID) > 0 {
|
||||||
return nil, fmt.Errorf("both domain_id and domain_name present")
|
return nil, fmt.Errorf("both domain_id and domain_name present")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.DomainID) > 0 {
|
if len(sdc.DomainID) > 0 {
|
||||||
|
|
||||||
// ProjectName + DomainID
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"project": map[string]interface{}{
|
"project": map[string]interface{}{
|
||||||
"name": &sdc.ProjectName,
|
"name": &sdc.ProjectName,
|
||||||
|
@ -284,10 +245,7 @@ func buildScope(sdc *SDConfig) (map[string]interface{}, error) {
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sdc.DomainName) > 0 {
|
if len(sdc.DomainName) > 0 {
|
||||||
|
|
||||||
// ProjectName + DomainName
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"project": map[string]interface{}{
|
"project": map[string]interface{}{
|
||||||
"name": &sdc.ProjectName,
|
"name": &sdc.ProjectName,
|
||||||
|
@ -303,39 +261,31 @@ func buildScope(sdc *SDConfig) (map[string]interface{}, error) {
|
||||||
if len(sdc.DomainName) > 0 {
|
if len(sdc.DomainName) > 0 {
|
||||||
return nil, fmt.Errorf("both project_id and domain_name present")
|
return nil, fmt.Errorf("both project_id and domain_name present")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProjectID
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"project": map[string]interface{}{
|
"project": map[string]interface{}{
|
||||||
"id": &sdc.ProjectID,
|
"id": &sdc.ProjectID,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
} else if len(sdc.DomainID) > 0 {
|
} else if len(sdc.DomainID) > 0 {
|
||||||
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
|
|
||||||
if len(sdc.DomainName) > 0 {
|
if len(sdc.DomainName) > 0 {
|
||||||
return nil, fmt.Errorf("both domain_id and domain_name present")
|
return nil, fmt.Errorf("both domain_id and domain_name present")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DomainID
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"domain": map[string]interface{}{
|
"domain": map[string]interface{}{
|
||||||
"id": &sdc.DomainID,
|
"id": &sdc.DomainID,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
} else if len(sdc.DomainName) > 0 {
|
} else if len(sdc.DomainName) > 0 {
|
||||||
|
|
||||||
// DomainName
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"domain": map[string]interface{}{
|
"domain": map[string]interface{}{
|
||||||
"name": &sdc.DomainName,
|
"name": &sdc.DomainName,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readCredentialsFromEnv - obtains serviceDiscoveryConfig from env variables for openstack
|
// readCredentialsFromEnv obtains serviceDiscoveryConfig from env variables for openstack
|
||||||
func readCredentialsFromEnv() SDConfig {
|
func readCredentialsFromEnv() SDConfig {
|
||||||
authURL := os.Getenv("OS_AUTH_URL")
|
authURL := os.Getenv("OS_AUTH_URL")
|
||||||
username := os.Getenv("OS_USERNAME")
|
username := os.Getenv("OS_USERNAME")
|
||||||
|
@ -352,7 +302,6 @@ func readCredentialsFromEnv() SDConfig {
|
||||||
if v := os.Getenv("OS_PROJECT_ID"); v != "" {
|
if v := os.Getenv("OS_PROJECT_ID"); v != "" {
|
||||||
tenantID = v
|
tenantID = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// If OS_PROJECT_NAME is set, overwrite tenantName with the value.
|
// If OS_PROJECT_NAME is set, overwrite tenantName with the value.
|
||||||
if v := os.Getenv("OS_PROJECT_NAME"); v != "" {
|
if v := os.Getenv("OS_PROJECT_NAME"); v != "" {
|
||||||
tenantName = v
|
tenantName = v
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detailed-detail#list-hypervisors-details
|
// See https://docs.openstack.org/api-ref/compute/#list-hypervisors-details
|
||||||
type hypervisorDetail struct {
|
type hypervisorDetail struct {
|
||||||
Hypervisors []hypervisor `json:"hypervisors"`
|
Hypervisors []hypervisor `json:"hypervisors"`
|
||||||
Links []struct {
|
Links []struct {
|
||||||
|
@ -32,12 +32,15 @@ func parseHypervisorDetail(data []byte) (*hypervisorDetail, error) {
|
||||||
if err := json.Unmarshal(data, &hvsd); err != nil {
|
if err := json.Unmarshal(data, &hvsd); err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse hypervisorDetail: %w", err)
|
return nil, fmt.Errorf("cannot parse hypervisorDetail: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &hvsd, nil
|
return &hvsd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *apiConfig) getHypervisors() ([]hypervisor, error) {
|
func (cfg *apiConfig) getHypervisors() ([]hypervisor, error) {
|
||||||
computeURL := *cfg.creds.computeURL
|
creds, err := cfg.getFreshAPICredentials()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
computeURL := *creds.computeURL
|
||||||
computeURL.Path = path.Join(computeURL.Path, "os-hypervisors", "detail")
|
computeURL.Path = path.Join(computeURL.Path, "os-hypervisors", "detail")
|
||||||
nextLink := computeURL.String()
|
nextLink := computeURL.String()
|
||||||
var hvs []hypervisor
|
var hvs []hypervisor
|
||||||
|
@ -46,19 +49,15 @@ func (cfg *apiConfig) getHypervisors() ([]hypervisor, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
detail, err := parseHypervisorDetail(resp)
|
detail, err := parseHypervisorDetail(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hvs = append(hvs, detail.Hypervisors...)
|
hvs = append(hvs, detail.Hypervisors...)
|
||||||
|
if len(detail.Links) == 0 {
|
||||||
if len(detail.Links) > 0 {
|
return hvs, nil
|
||||||
nextLink = detail.Links[0].HREF
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
nextLink = detail.Links[0].HREF
|
||||||
return hvs, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +76,6 @@ func addHypervisorLabels(hvs []hypervisor, port int) []map[string]string {
|
||||||
}
|
}
|
||||||
ms = append(ms, m)
|
ms = append(ms, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ms
|
return ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +84,5 @@ func getHypervisorLabels(cfg *apiConfig) ([]map[string]string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot get hypervisors: %w", err)
|
return nil, fmt.Errorf("cannot get hypervisors: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return addHypervisorLabels(hvs, cfg.port), nil
|
return addHypervisorLabels(hvs, cfg.port), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detailed-detail#list-servers
|
// See https://docs.openstack.org/api-ref/compute/#list-servers
|
||||||
type serversDetail struct {
|
type serversDetail struct {
|
||||||
Servers []server `json:"servers"`
|
Servers []server `json:"servers"`
|
||||||
Links []struct {
|
Links []struct {
|
||||||
|
@ -40,7 +41,6 @@ func parseServersDetail(data []byte) (*serversDetail, error) {
|
||||||
if err := json.Unmarshal(data, &srvd); err != nil {
|
if err := json.Unmarshal(data, &srvd); err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse serversDetail: %w", err)
|
return nil, fmt.Errorf("cannot parse serversDetail: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &srvd, nil
|
return &srvd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,13 +55,20 @@ func addInstanceLabels(servers []server, port int) []map[string]string {
|
||||||
"__meta_openstack_user_id": server.UserID,
|
"__meta_openstack_user_id": server.UserID,
|
||||||
"__meta_openstack_instance_flavor": server.Flavor.ID,
|
"__meta_openstack_instance_flavor": server.Flavor.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range server.Metadata {
|
for k, v := range server.Metadata {
|
||||||
m["__meta_openstack_tag_"+discoveryutils.SanitizeLabelName(k)] = v
|
m["__meta_openstack_tag_"+discoveryutils.SanitizeLabelName(k)] = v
|
||||||
}
|
}
|
||||||
for pool, addresses := range server.Addresses {
|
// Traverse server.Addresses in alphabetical order of pool name
|
||||||
|
// in order to return targets in deterministic order.
|
||||||
|
sortedPools := make([]string, 0, len(server.Addresses))
|
||||||
|
for pool := range server.Addresses {
|
||||||
|
sortedPools = append(sortedPools, pool)
|
||||||
|
}
|
||||||
|
sort.Strings(sortedPools)
|
||||||
|
for _, pool := range sortedPools {
|
||||||
|
addresses := server.Addresses[pool]
|
||||||
if len(addresses) == 0 {
|
if len(addresses) == 0 {
|
||||||
// pool with zero addresses skip it
|
// skip pool with zero addresses
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var publicIP string
|
var publicIP string
|
||||||
|
@ -90,7 +97,6 @@ func addInstanceLabels(servers []server, port int) []map[string]string {
|
||||||
}
|
}
|
||||||
lbls["__address__"] = discoveryutils.JoinHostPort(ip.Address, port)
|
lbls["__address__"] = discoveryutils.JoinHostPort(ip.Address, port)
|
||||||
ms = append(ms, lbls)
|
ms = append(ms, lbls)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +104,11 @@ func addInstanceLabels(servers []server, port int) []map[string]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *apiConfig) getServers() ([]server, error) {
|
func (cfg *apiConfig) getServers() ([]server, error) {
|
||||||
computeURL := *cfg.creds.computeURL
|
creds, err := cfg.getFreshAPICredentials()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
computeURL := *creds.computeURL
|
||||||
computeURL.Path = path.Join(computeURL.Path, "servers", "detail")
|
computeURL.Path = path.Join(computeURL.Path, "servers", "detail")
|
||||||
// by default, query fetches data from all tenants
|
// by default, query fetches data from all tenants
|
||||||
if !cfg.allTenants {
|
if !cfg.allTenants {
|
||||||
|
@ -106,28 +116,22 @@ func (cfg *apiConfig) getServers() ([]server, error) {
|
||||||
q.Set("all_tenants", "false")
|
q.Set("all_tenants", "false")
|
||||||
computeURL.RawQuery = q.Encode()
|
computeURL.RawQuery = q.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
nextLink := computeURL.String()
|
nextLink := computeURL.String()
|
||||||
|
|
||||||
var servers []server
|
var servers []server
|
||||||
for {
|
for {
|
||||||
resp, err := getAPIResponse(nextLink, cfg)
|
resp, err := getAPIResponse(nextLink, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
serversDetail, err := parseServersDetail(resp)
|
serversDetail, err := parseServersDetail(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
servers = append(servers, serversDetail.Servers...)
|
servers = append(servers, serversDetail.Servers...)
|
||||||
|
if len(serversDetail.Links) == 0 {
|
||||||
if len(serversDetail.Links) > 0 {
|
return servers, nil
|
||||||
nextLink = serversDetail.Links[0].HREF
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
nextLink = serversDetail.Links[0].HREF
|
||||||
return servers, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,5 +141,4 @@ func getInstancesLabels(cfg *apiConfig) ([]map[string]string, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return addInstanceLabels(srv, cfg.port), nil
|
return addInstanceLabels(srv, cfg.port), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,13 @@ func Test_addInstanceLabels(t *testing.T) {
|
||||||
want [][]prompbmarshal.Label
|
want [][]prompbmarshal.Label
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty response",
|
name: "empty_response",
|
||||||
args: args{
|
args: args{
|
||||||
port: 9100,
|
port: 9100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "1 server",
|
name: "one_server",
|
||||||
args: args{
|
args: args{
|
||||||
port: 9100,
|
port: 9100,
|
||||||
servers: []server{
|
servers: []server{
|
||||||
|
@ -70,7 +70,7 @@ func Test_addInstanceLabels(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "with public ip",
|
name: "with_public_ip",
|
||||||
args: args{
|
args: args{
|
||||||
port: 9100,
|
port: 9100,
|
||||||
servers: []server{
|
servers: []server{
|
||||||
|
@ -113,6 +113,17 @@ func Test_addInstanceLabels(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: [][]prompbmarshal.Label{
|
want: [][]prompbmarshal.Label{
|
||||||
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
|
"__address__": "10.10.0.1:9100",
|
||||||
|
"__meta_openstack_address_pool": "internal",
|
||||||
|
"__meta_openstack_instance_flavor": "5",
|
||||||
|
"__meta_openstack_instance_id": "10",
|
||||||
|
"__meta_openstack_instance_name": "server-2",
|
||||||
|
"__meta_openstack_instance_status": "enabled",
|
||||||
|
"__meta_openstack_private_ip": "10.10.0.1",
|
||||||
|
"__meta_openstack_project_id": "some-tenant-id",
|
||||||
|
"__meta_openstack_user_id": "some-user-id",
|
||||||
|
}),
|
||||||
discoveryutils.GetSortedLabels(map[string]string{
|
discoveryutils.GetSortedLabels(map[string]string{
|
||||||
"__address__": "192.168.0.1:9100",
|
"__address__": "192.168.0.1:9100",
|
||||||
"__meta_openstack_address_pool": "test",
|
"__meta_openstack_address_pool": "test",
|
||||||
|
@ -125,24 +136,12 @@ func Test_addInstanceLabels(t *testing.T) {
|
||||||
"__meta_openstack_project_id": "some-tenant-id",
|
"__meta_openstack_project_id": "some-tenant-id",
|
||||||
"__meta_openstack_user_id": "some-user-id",
|
"__meta_openstack_user_id": "some-user-id",
|
||||||
}),
|
}),
|
||||||
discoveryutils.GetSortedLabels(map[string]string{
|
|
||||||
"__address__": "10.10.0.1:9100",
|
|
||||||
"__meta_openstack_address_pool": "internal",
|
|
||||||
"__meta_openstack_instance_flavor": "5",
|
|
||||||
"__meta_openstack_instance_id": "10",
|
|
||||||
"__meta_openstack_instance_name": "server-2",
|
|
||||||
"__meta_openstack_instance_status": "enabled",
|
|
||||||
"__meta_openstack_private_ip": "10.10.0.1",
|
|
||||||
"__meta_openstack_project_id": "some-tenant-id",
|
|
||||||
"__meta_openstack_user_id": "some-user-id",
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := addInstanceLabels(tt.args.servers, tt.args.port)
|
got := addInstanceLabels(tt.args.servers, tt.args.port)
|
||||||
|
|
||||||
var sortedLabelss [][]prompbmarshal.Label
|
var sortedLabelss [][]prompbmarshal.Label
|
||||||
for _, labels := range got {
|
for _, labels := range got {
|
||||||
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
sortedLabelss = append(sortedLabelss, discoveryutils.GetSortedLabels(labels))
|
||||||
|
|
|
@ -7,24 +7,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SDConfig is the configuration for OpenStack based service discovery.
|
// SDConfig is the configuration for OpenStack based service discovery.
|
||||||
|
//
|
||||||
|
// See https://prometheus.io/docs/prometheus/latest/configuration/configuration/#openstack_sd_config
|
||||||
type SDConfig struct {
|
type SDConfig struct {
|
||||||
IdentityEndpoint string `yaml:"identity_endpoint"`
|
IdentityEndpoint string `yaml:"identity_endpoint"`
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
UserID string `yaml:"userid"`
|
UserID string `yaml:"userid"`
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
ProjectName string `yaml:"project_name"`
|
ProjectName string `yaml:"project_name"`
|
||||||
ProjectID string `yaml:"project_id"`
|
ProjectID string `yaml:"project_id"`
|
||||||
DomainName string `yaml:"domain_name"`
|
DomainName string `yaml:"domain_name"`
|
||||||
DomainID string `yaml:"domain_id"`
|
DomainID string `yaml:"domain_id"`
|
||||||
ApplicationCredentialName string `yaml:"application_credential_name"`
|
ApplicationCredentialName string `yaml:"application_credential_name"`
|
||||||
ApplicationCredentialID string `yaml:"application_credential_id"`
|
ApplicationCredentialID string `yaml:"application_credential_id"`
|
||||||
ApplicationCredentialSecret string `yaml:"application_credential_secret"`
|
ApplicationCredentialSecret string `yaml:"application_credential_secret"`
|
||||||
Role string `yaml:"role"`
|
Role string `yaml:"role"`
|
||||||
Region string `yaml:"region"`
|
Region string `yaml:"region"`
|
||||||
Port int `yaml:"port"`
|
// RefreshInterval time.Duration `yaml:"refresh_interval"`
|
||||||
AllTenants bool `yaml:"all_tenants"`
|
// refresh_interval is obtained from `-promscrape.openstackSDCheckInterval` command-line option.
|
||||||
TLSConfig *promauth.TLSConfig `yaml:"tls_config"`
|
Port int `yaml:"port"`
|
||||||
Availability string `yaml:"availability"`
|
AllTenants bool `yaml:"all_tenants"`
|
||||||
|
TLSConfig *promauth.TLSConfig `yaml:"tls_config"`
|
||||||
|
Availability string `yaml:"availability"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLabels returns gce labels according to sdc.
|
// GetLabels returns gce labels according to sdc.
|
||||||
|
|
Loading…
Reference in a new issue