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