Merge pull request #15539 from paulojmdias/openstack-loadbalancer-discovery

discovery(openstack): add load balancer discovery
This commit is contained in:
Jan Fajerski 2025-01-28 14:10:06 +01:00 committed by GitHub
commit ffea9f005b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 904 additions and 4 deletions

View file

@ -0,0 +1,201 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package openstack
import (
"context"
"fmt"
"log/slog"
"net"
"strconv"
"strings"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
"github.com/prometheus/common/model"
"github.com/prometheus/common/promslog"
"github.com/prometheus/prometheus/discovery/targetgroup"
)
const (
openstackLabelLoadBalancerID = openstackLabelPrefix + "loadbalancer_id"
openstackLabelLoadBalancerName = openstackLabelPrefix + "loadbalancer_name"
openstackLabelLoadBalancerOperatingStatus = openstackLabelPrefix + "loadbalancer_operating_status"
openstackLabelLoadBalancerProvisioningStatus = openstackLabelPrefix + "loadbalancer_provisioning_status"
openstackLabelLoadBalancerAvailabilityZone = openstackLabelPrefix + "loadbalancer_availability_zone"
openstackLabelLoadBalancerFloatingIP = openstackLabelPrefix + "loadbalancer_floating_ip"
openstackLabelLoadBalancerVIP = openstackLabelPrefix + "loadbalancer_vip"
openstackLabelLoadBalancerProvider = openstackLabelPrefix + "loadbalancer_provider"
openstackLabelLoadBalancerTags = openstackLabelPrefix + "loadbalancer_tags"
)
// LoadBalancerDiscovery discovers OpenStack load balancers.
type LoadBalancerDiscovery struct {
provider *gophercloud.ProviderClient
authOpts *gophercloud.AuthOptions
region string
logger *slog.Logger
availability gophercloud.Availability
}
// NewLoadBalancerDiscovery returns a new loadbalancer discovery.
func newLoadBalancerDiscovery(provider *gophercloud.ProviderClient, opts *gophercloud.AuthOptions,
region string, availability gophercloud.Availability, l *slog.Logger,
) *LoadBalancerDiscovery {
if l == nil {
l = promslog.NewNopLogger()
}
return &LoadBalancerDiscovery{
provider: provider, authOpts: opts,
region: region, availability: availability, logger: l,
}
}
func (i *LoadBalancerDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
i.provider.Context = ctx
err := openstack.Authenticate(i.provider, *i.authOpts)
if err != nil {
return nil, fmt.Errorf("could not authenticate to OpenStack: %w", err)
}
client, err := openstack.NewLoadBalancerV2(i.provider, gophercloud.EndpointOpts{
Region: i.region, Availability: i.availability,
})
if err != nil {
return nil, fmt.Errorf("could not create OpenStack load balancer session: %w", err)
}
networkClient, err := openstack.NewNetworkV2(i.provider, gophercloud.EndpointOpts{
Region: i.region, Availability: i.availability,
})
if err != nil {
return nil, fmt.Errorf("could not create OpenStack network session: %w", err)
}
allPages, err := loadbalancers.List(client, loadbalancers.ListOpts{}).AllPages()
if err != nil {
return nil, fmt.Errorf("failed to list load balancers: %w", err)
}
allLBs, err := loadbalancers.ExtractLoadBalancers(allPages)
if err != nil {
return nil, fmt.Errorf("failed to extract load balancers: %w", err)
}
// Fetch all listeners in one API call
listenerPages, err := listeners.List(client, listeners.ListOpts{}).AllPages()
if err != nil {
return nil, fmt.Errorf("failed to list all listeners: %w", err)
}
allListeners, err := listeners.ExtractListeners(listenerPages)
if err != nil {
return nil, fmt.Errorf("failed to extract all listeners: %w", err)
}
// Create a map to group listeners by Load Balancer ID
listenerMap := make(map[string][]listeners.Listener)
for _, listener := range allListeners {
// Iterate through each associated Load Balancer ID in the Loadbalancers array
for _, lb := range listener.Loadbalancers {
listenerMap[lb.ID] = append(listenerMap[lb.ID], listener)
}
}
// Fetch all floating IPs
fipPages, err := floatingips.List(networkClient, floatingips.ListOpts{}).AllPages()
if err != nil {
return nil, fmt.Errorf("failed to list all fips: %w", err)
}
if err != nil {
return nil, fmt.Errorf("failed to list floating IPs: %w", err)
}
allFIPs, err := floatingips.ExtractFloatingIPs(fipPages)
if err != nil {
return nil, fmt.Errorf("failed to extract floating IPs: %w", err)
}
// Create a map to associate floating IPs with their resource IDs
fipMap := make(map[string]string) // Key: LoadBalancerID/PortID, Value: Floating IP
for _, fip := range allFIPs {
if fip.PortID != "" {
fipMap[fip.PortID] = fip.FloatingIP
}
}
tg := &targetgroup.Group{
Source: "OS_" + i.region,
}
for _, lb := range allLBs {
// Retrieve listeners for this load balancer from the map
lbListeners, exists := listenerMap[lb.ID]
if !exists || len(lbListeners) == 0 {
i.logger.Debug("Got no listener", "loadbalancer", lb.ID)
continue
}
// Variable to store the port of the first PROMETHEUS listener
var listenerPort int
hasPrometheusListener := false
// Check if any listener has the PROMETHEUS protocol
for _, listener := range lbListeners {
if listener.Protocol == "PROMETHEUS" {
hasPrometheusListener = true
listenerPort = listener.ProtocolPort
break
}
}
// Skip LBs without PROMETHEUS listener protocol
if !hasPrometheusListener {
i.logger.Debug("Got no PROMETHEUS listener", "loadbalancer", lb.ID)
continue
}
labels := model.LabelSet{}
addr := net.JoinHostPort(lb.VipAddress, strconv.Itoa(listenerPort))
labels[model.AddressLabel] = model.LabelValue(addr)
labels[openstackLabelLoadBalancerID] = model.LabelValue(lb.ID)
labels[openstackLabelLoadBalancerName] = model.LabelValue(lb.Name)
labels[openstackLabelLoadBalancerOperatingStatus] = model.LabelValue(lb.OperatingStatus)
labels[openstackLabelLoadBalancerProvisioningStatus] = model.LabelValue(lb.ProvisioningStatus)
labels[openstackLabelLoadBalancerAvailabilityZone] = model.LabelValue(lb.AvailabilityZone)
labels[openstackLabelLoadBalancerVIP] = model.LabelValue(lb.VipAddress)
labels[openstackLabelLoadBalancerProvider] = model.LabelValue(lb.Provider)
labels[openstackLabelProjectID] = model.LabelValue(lb.ProjectID)
if len(lb.Tags) > 0 {
labels[openstackLabelLoadBalancerTags] = model.LabelValue(strings.Join(lb.Tags, ","))
}
if floatingIP, exists := fipMap[lb.VipPortID]; exists {
labels[openstackLabelLoadBalancerFloatingIP] = model.LabelValue(floatingIP)
}
tg.Targets = append(tg.Targets, labels)
}
if err != nil {
return nil, err
}
return []*targetgroup.Group{tg}, nil
}

View file

@ -0,0 +1,137 @@
// Copyright 2017 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package openstack
import (
"context"
"fmt"
"testing"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
type OpenstackSDLoadBalancerTestSuite struct {
Mock *SDMock
}
func (s *OpenstackSDLoadBalancerTestSuite) SetupTest(t *testing.T) {
s.Mock = NewSDMock(t)
s.Mock.Setup()
s.Mock.HandleLoadBalancerListSuccessfully()
s.Mock.HandleListenersListSuccessfully()
s.Mock.HandleFloatingIPListSuccessfully()
s.Mock.HandleVersionsSuccessfully()
s.Mock.HandleAuthSuccessfully()
}
func (s *OpenstackSDLoadBalancerTestSuite) openstackAuthSuccess() (refresher, error) {
conf := SDConfig{
IdentityEndpoint: s.Mock.Endpoint(),
Password: "test",
Username: "test",
DomainName: "12345",
Region: "RegionOne",
Role: "loadbalancer",
}
return newRefresher(&conf, nil)
}
func TestOpenstackSDLoadBalancerRefresh(t *testing.T) {
mock := &OpenstackSDLoadBalancerTestSuite{}
mock.SetupTest(t)
instance, err := mock.openstackAuthSuccess()
require.NoError(t, err)
ctx := context.Background()
tgs, err := instance.refresh(ctx)
require.NoError(t, err)
require.Len(t, tgs, 1)
tg := tgs[0]
require.NotNil(t, tg)
require.NotNil(t, tg.Targets)
require.Len(t, tg.Targets, 4)
for i, lbls := range []model.LabelSet{
{
"__address__": model.LabelValue("10.0.0.32:9273"),
"__meta_openstack_loadbalancer_id": model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5"),
"__meta_openstack_loadbalancer_name": model.LabelValue("lb1"),
"__meta_openstack_loadbalancer_operating_status": model.LabelValue("ONLINE"),
"__meta_openstack_loadbalancer_provisioning_status": model.LabelValue("ACTIVE"),
"__meta_openstack_loadbalancer_availability_zone": model.LabelValue("az1"),
"__meta_openstack_loadbalancer_floating_ip": model.LabelValue("192.168.1.2"),
"__meta_openstack_loadbalancer_vip": model.LabelValue("10.0.0.32"),
"__meta_openstack_loadbalancer_provider": model.LabelValue("amphora"),
"__meta_openstack_loadbalancer_tags": model.LabelValue("tag1,tag2"),
"__meta_openstack_project_id": model.LabelValue("fcad67a6189847c4aecfa3c81a05783b"),
},
{
"__address__": model.LabelValue("10.0.2.78:8080"),
"__meta_openstack_loadbalancer_id": model.LabelValue("d92c471e-8d3e-4b9f-b2b5-9c72a9e3ef54"),
"__meta_openstack_loadbalancer_name": model.LabelValue("lb3"),
"__meta_openstack_loadbalancer_operating_status": model.LabelValue("ONLINE"),
"__meta_openstack_loadbalancer_provisioning_status": model.LabelValue("ACTIVE"),
"__meta_openstack_loadbalancer_availability_zone": model.LabelValue("az3"),
"__meta_openstack_loadbalancer_floating_ip": model.LabelValue("192.168.3.4"),
"__meta_openstack_loadbalancer_vip": model.LabelValue("10.0.2.78"),
"__meta_openstack_loadbalancer_provider": model.LabelValue("amphora"),
"__meta_openstack_loadbalancer_tags": model.LabelValue("tag5,tag6"),
"__meta_openstack_project_id": model.LabelValue("ac57f03dba1a4fdebff3e67201bc7a85"),
},
{
"__address__": model.LabelValue("10.0.3.99:9090"),
"__meta_openstack_loadbalancer_id": model.LabelValue("f5c7e918-df38-4a5a-a7d4-d9c27ab2cf67"),
"__meta_openstack_loadbalancer_name": model.LabelValue("lb4"),
"__meta_openstack_loadbalancer_operating_status": model.LabelValue("ONLINE"),
"__meta_openstack_loadbalancer_provisioning_status": model.LabelValue("ACTIVE"),
"__meta_openstack_loadbalancer_availability_zone": model.LabelValue("az1"),
"__meta_openstack_loadbalancer_floating_ip": model.LabelValue("192.168.4.5"),
"__meta_openstack_loadbalancer_vip": model.LabelValue("10.0.3.99"),
"__meta_openstack_loadbalancer_provider": model.LabelValue("amphora"),
"__meta_openstack_project_id": model.LabelValue("fa8c372dfe4d4c92b0c4e3a2d9b3c9fa"),
},
{
"__address__": model.LabelValue("10.0.4.88:9876"),
"__meta_openstack_loadbalancer_id": model.LabelValue("e83a6d92-7a3e-4567-94b3-20c83b32a75e"),
"__meta_openstack_loadbalancer_name": model.LabelValue("lb5"),
"__meta_openstack_loadbalancer_operating_status": model.LabelValue("ONLINE"),
"__meta_openstack_loadbalancer_provisioning_status": model.LabelValue("ACTIVE"),
"__meta_openstack_loadbalancer_availability_zone": model.LabelValue("az4"),
"__meta_openstack_loadbalancer_vip": model.LabelValue("10.0.4.88"),
"__meta_openstack_loadbalancer_provider": model.LabelValue("amphora"),
"__meta_openstack_project_id": model.LabelValue("a5d3b2e1e6f34cd9a5f7c2f01a6b8e29"),
},
} {
t.Run(fmt.Sprintf("item %d", i), func(t *testing.T) {
require.Equal(t, lbls, tg.Targets[i])
})
}
}
func TestOpenstackSDLoadBalancerRefreshWithDoneContext(t *testing.T) {
mock := &OpenstackSDLoadBalancerTestSuite{}
mock.SetupTest(t)
loadbalancer, _ := mock.openstackAuthSuccess()
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := loadbalancer.refresh(ctx)
require.ErrorContains(t, err, context.Canceled.Error(), "%q doesn't contain %q", err, context.Canceled)
}

View file

@ -149,7 +149,20 @@ func (m *SDMock) HandleAuthSuccessfully() {
],
"id": "589f3d99a3d94f5f871e9f5cf206d2e8",
"type": "network"
}
},
{
"endpoints": [
{
"id": "39dc322ce86c1234b4f06c2eeae0841b",
"interface": "public",
"region": "RegionOne",
"region_id": "RegionOne",
"url": "%s"
}
],
"id": "26968f704a68417bbddd29508455ff90",
"type": "load-balancer"
}
],
"expires_at": "2013-02-27T18:30:59.999999Z",
"is_domain": false,
@ -186,7 +199,7 @@ func (m *SDMock) HandleAuthSuccessfully() {
}
}
}
`, m.Endpoint(), m.Endpoint())
`, m.Endpoint(), m.Endpoint(), m.Endpoint())
})
}
@ -711,6 +724,63 @@ const listOutput = `
"tags": [],
"created_at": "2024-01-24T13:30:50Z",
"updated_at": "2024-01-24T13:30:51Z"
},
{
"id": "fea7332d-9027-4cf9-bf62-c3c4c6ebaf84",
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
"floating_ip_address": "192.168.1.2",
"floating_network_id": "d02c4f18-d606-4864-b12a-1c9b39a46be2",
"router_id": "f03af93b-4e8f-4f55-adcf-a0317782ede2",
"port_id": "b47c39f5-238d-4b17-ae87-9b5d19af8a2e",
"fixed_ip_address": "10.0.0.32",
"status": "ACTIVE",
"description": "",
"dns_domain": "",
"dns_name": "",
"port_forwardings": [],
"tags": [],
"created_at": "2023-08-30T15:11:37Z",
"updated_at": "2023-08-30T15:11:38Z",
"revision_number": 1,
"project_id": "fcad67a6189847c4aecfa3c81a05783b"
},
{
"id": "febb9554-cf83-4f9b-94d9-1b3c34be357f",
"tenant_id": "ac57f03dba1a4fdebff3e67201bc7a85",
"floating_ip_address": "192.168.3.4",
"floating_network_id": "d02c4f18-d606-4864-b12a-1c9b39a46be2",
"router_id": "f03af93b-4e8f-4f55-adcf-a0317782ede2",
"port_id": "c83b6e12-4e5d-4673-a4b3-5bc72a7f3ef9",
"fixed_ip_address": "10.0.2.78",
"status": "ACTIVE",
"description": "",
"dns_domain": "",
"dns_name": "",
"port_forwardings": [],
"tags": [],
"created_at": "2023-08-30T15:11:37Z",
"updated_at": "2023-08-30T15:11:38Z",
"revision_number": 1,
"project_id": "ac57f03dba1a4fdebff3e67201bc7a85"
},
{
"id": "febb9554-cf83-4f9b-94d9-1b3c34be357f",
"tenant_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa",
"floating_ip_address": "192.168.4.5",
"floating_network_id": "d02c4f18-d606-4864-b12a-1c9b39a46be2",
"router_id": "f03af93b-4e8f-4f55-adcf-a0317782ede2",
"port_id": "f9e8b6e12-7e4d-4963-a5b3-6cd82a7f3ff6",
"fixed_ip_address": "10.0.3.99",
"status": "ACTIVE",
"description": "",
"dns_domain": "",
"dns_name": "",
"port_forwardings": [],
"tags": [],
"created_at": "2023-08-30T15:11:37Z",
"updated_at": "2023-08-30T15:11:38Z",
"revision_number": 1,
"project_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa"
}
]
}
@ -863,3 +933,471 @@ func (m *SDMock) HandlePortsListSuccessfully() {
fmt.Fprint(w, portsListBody)
})
}
const lbListBody = `
{
"loadbalancers": [
{
"id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
"name": "lb1",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"project_id": "fcad67a6189847c4aecfa3c81a05783b",
"created_at": "2024-12-01T10:00:00",
"updated_at": "2024-12-01T10:30:00",
"vip_address": "10.0.0.32",
"vip_port_id": "b47c39f5-238d-4b17-ae87-9b5d19af8a2e",
"vip_subnet_id": "14a4c6a5-fe71-4a94-9071-4cd12fb8337f",
"vip_network_id": "d02c4f18-d606-4864-b12a-1c9b39a46be2",
"tags": ["tag1", "tag2"],
"availability_zone": "az1",
"vip_vnic_type": "normal",
"provider": "amphora",
"listeners": [
{
"id": "c4146b54-febc-4caf-a53f-ed1cab6faba5"
},
{
"id": "a058d20e-82de-4eff-bb65-5c76a8554435"
}
],
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b"
},
{
"id": "d92c471e-8d3e-4b9f-b2b5-9c72a9e3ef54",
"name": "lb3",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"project_id": "ac57f03dba1a4fdebff3e67201bc7a85",
"created_at": "2024-12-01T12:00:00",
"updated_at": "2024-12-01T12:45:00",
"vip_address": "10.0.2.78",
"vip_port_id": "c83b6e12-4e5d-4673-a4b3-5bc72a7f3ef9",
"vip_subnet_id": "36c5e9f6-e7a2-4975-a8c6-3b8e4f93cf45",
"vip_network_id": "g03c6f27-e617-4975-c8f7-4c9f3f94cf68",
"tags": ["tag5", "tag6"],
"availability_zone": "az3",
"vip_vnic_type": "normal",
"provider": "amphora",
"listeners": [
{
"id": "5b9529a4-6cbf-48f8-a006-d99cbc717da0"
},
{
"id": "5d26333b-74d1-4b2a-90ab-2b2c0f5a8048"
}
],
"tenant_id": "ac57f03dba1a4fdebff3e67201bc7a85"
},
{
"id": "f5c7e918-df38-4a5a-a7d4-d9c27ab2cf67",
"name": "lb4",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"project_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa",
"created_at": "2024-12-01T13:00:00",
"updated_at": "2024-12-01T13:20:00",
"vip_address": "10.0.3.99",
"vip_port_id": "f9e8b6e12-7e4d-4963-a5b3-6cd82a7f3ff6",
"vip_subnet_id": "47d6f8f9-f7b2-4876-a9d8-4e8f4g95df79",
"vip_network_id": "h04d7f38-f718-4876-d9g8-5d8g5h95df89",
"tags": [],
"availability_zone": "az1",
"vip_vnic_type": "normal",
"provider": "amphora",
"listeners": [
{
"id": "84c87596-1ff0-4f6d-b151-0a78e1f407a3"
},
{
"id": "fe460a7c-16a9-4984-9fe6-f6e5153ebab1"
}
],
"tenant_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa"
},
{
"id": "e83a6d92-7a3e-4567-94b3-20c83b32a75e",
"name": "lb5",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"project_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29",
"created_at": "2024-12-01T11:00:00",
"updated_at": "2024-12-01T11:15:00",
"vip_address": "10.0.4.88",
"vip_port_id": "d83a6d92-7a3e-4567-94b3-20c83b32a75e",
"vip_subnet_id": "25b4d8e5-fe81-4a87-9071-4cc12fb8337f",
"vip_network_id": "f02c5e19-c507-4864-b16e-2b7a39e56be3",
"tags": [],
"availability_zone": "az4",
"vip_vnic_type": "normal",
"provider": "amphora",
"listeners": [
{
"id": "50902e62-34b8-46b2-9ed4-9053e7ad46dc"
},
{
"id": "98a867ad-ff07-4880-b05f-32088866a68a"
}
],
"tenant_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29"
}
]
}
`
// HandleLoadBalancerListSuccessfully mocks the load balancer list API.
func (m *SDMock) HandleLoadBalancerListSuccessfully() {
m.Mux.HandleFunc("/v2.0/lbaas/loadbalancers", func(w http.ResponseWriter, r *http.Request) {
testMethod(m.t, r, http.MethodGet)
testHeader(m.t, r, "X-Auth-Token", tokenID)
w.Header().Add("Content-Type", "application/json")
fmt.Fprint(w, lbListBody)
})
}
const listenerListBody = `
{
"listeners": [
{
"id": "c4146b54-febc-4caf-a53f-ed1cab6faba5",
"name": "stats-listener",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "PROMETHEUS",
"protocol_port": 9273,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "fcad67a6189847c4aecfa3c81a05783b",
"default_pool_id": null,
"l7policies": [],
"insert_headers": {},
"created_at": "2024-08-29T18:05:24",
"updated_at": "2024-12-04T21:21:10",
"loadbalancers": [
{
"id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b"
},
{
"id": "5b9529a4-6cbf-48f8-a006-d99cbc717da0",
"name": "stats-listener2",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "PROMETHEUS",
"protocol_port": 8080,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "ac57f03dba1a4fdebff3e67201bc7a85",
"default_pool_id": null,
"l7policies": [],
"insert_headers": {},
"created_at": "2024-08-29T18:05:24",
"updated_at": "2024-12-04T21:21:10",
"loadbalancers": [
{
"id": "d92c471e-8d3e-4b9f-b2b5-9c72a9e3ef54"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "ac57f03dba1a4fdebff3e67201bc7a85"
},
{
"id": "84c87596-1ff0-4f6d-b151-0a78e1f407a3",
"name": "stats-listener3",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "PROMETHEUS",
"protocol_port": 9090,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa",
"default_pool_id": null,
"l7policies": [],
"insert_headers": {},
"created_at": "2024-08-29T18:05:24",
"updated_at": "2024-12-04T21:21:10",
"loadbalancers": [
{
"id": "f5c7e918-df38-4a5a-a7d4-d9c27ab2cf67"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa"
},
{
"id": "50902e62-34b8-46b2-9ed4-9053e7ad46dc",
"name": "stats-listener4",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "PROMETHEUS",
"protocol_port": 9876,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29",
"default_pool_id": null,
"l7policies": [],
"insert_headers": {},
"created_at": "2024-08-29T18:05:24",
"updated_at": "2024-12-04T21:21:10",
"loadbalancers": [
{
"id": "e83a6d92-7a3e-4567-94b3-20c83b32a75e"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29"
},
{
"id": "a058d20e-82de-4eff-bb65-5c76a8554435",
"name": "port6443",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "TCP",
"protocol_port": 6443,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29",
"default_pool_id": "5643208b-b691-4b1f-a6b8-356f14903e56",
"l7policies": [],
"insert_headers": {},
"created_at": "2024-10-02T19:32:48",
"updated_at": "2024-12-04T21:44:34",
"loadbalancers": [
{
"id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29"
},
{
"id": "5d26333b-74d1-4b2a-90ab-2b2c0f5a8048",
"name": "port6444",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "TCP",
"protocol_port": 6444,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "ac57f03dba1a4fdebff3e67201bc7a85",
"default_pool_id": "5643208b-b691-4b1f-a6b8-356f14903e56",
"l7policies": [],
"insert_headers": {},
"created_at": "2024-10-02T19:32:48",
"updated_at": "2024-12-04T21:44:34",
"loadbalancers": [
{
"id": "d92c471e-8d3e-4b9f-b2b5-9c72a9e3ef54"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "ac57f03dba1a4fdebff3e67201bc7a85"
},
{
"id": "fe460a7c-16a9-4984-9fe6-f6e5153ebab1",
"name": "port6445",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "TCP",
"protocol_port": 6445,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa",
"default_pool_id": "5643208b-b691-4b1f-a6b8-356f14903e56",
"l7policies": [],
"insert_headers": {},
"created_at": "2024-10-02T19:32:48",
"updated_at": "2024-12-04T21:44:34",
"loadbalancers": [
{
"id": "f5c7e918-df38-4a5a-a7d4-d9c27ab2cf67"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "fa8c372dfe4d4c92b0c4e3a2d9b3c9fa"
},
{
"id": "98a867ad-ff07-4880-b05f-32088866a68a",
"name": "port6446",
"description": "",
"provisioning_status": "ACTIVE",
"operating_status": "ONLINE",
"admin_state_up": true,
"protocol": "TCP",
"protocol_port": 6446,
"connection_limit": -1,
"default_tls_container_ref": null,
"sni_container_refs": [],
"project_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29",
"default_pool_id": "5643208b-b691-4b1f-a6b8-356f14903e56",
"l7policies": [],
"insert_headers": {},
"created_at": "2024-10-02T19:32:48",
"updated_at": "2024-12-04T21:44:34",
"loadbalancers": [
{
"id": "e83a6d92-7a3e-4567-94b3-20c83b32a75e"
}
],
"timeout_client_data": 50000,
"timeout_member_connect": 5000,
"timeout_member_data": 50000,
"timeout_tcp_inspect": 0,
"tags": [],
"client_ca_tls_container_ref": null,
"client_authentication": "NONE",
"client_crl_container_ref": null,
"allowed_cidrs": null,
"tls_ciphers": null,
"tls_versions": null,
"alpn_protocols": null,
"hsts_max_age": null,
"hsts_include_subdomains": null,
"hsts_preload": null,
"tenant_id": "a5d3b2e1e6f34cd9a5f7c2f01a6b8e29"
}
]
}
`
// HandleListenersListSuccessfully mocks the listeners endpoint.
func (m *SDMock) HandleListenersListSuccessfully() {
m.Mux.HandleFunc("/v2.0/lbaas/listeners", func(w http.ResponseWriter, r *http.Request) {
testMethod(m.t, r, http.MethodGet)
testHeader(m.t, r, "X-Auth-Token", tokenID)
w.Header().Add("Content-Type", "application/json")
fmt.Fprint(w, listenerListBody)
})
}

View file

@ -97,6 +97,9 @@ const (
// OpenStack document reference
// https://docs.openstack.org/horizon/pike/user/launch-instances.html
OpenStackRoleInstance Role = "instance"
// Openstack document reference
// https://docs.openstack.org/openstacksdk/rocky/user/resources/load_balancer/index.html
OpenStackRoleLoadBalancer Role = "loadbalancer"
)
// UnmarshalYAML implements the yaml.Unmarshaler interface.
@ -105,7 +108,7 @@ func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
switch *c {
case OpenStackRoleHypervisor, OpenStackRoleInstance:
case OpenStackRoleHypervisor, OpenStackRoleInstance, OpenStackRoleLoadBalancer:
return nil
default:
return fmt.Errorf("unknown OpenStack SD role %q", *c)
@ -128,7 +131,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
if c.Role == "" {
return errors.New("role missing (one of: instance, hypervisor)")
return errors.New("role missing (one of: instance, hypervisor, loadbalancer)")
}
if c.Region == "" {
return errors.New("openstack SD configuration requires a region")
@ -211,6 +214,8 @@ func newRefresher(conf *SDConfig, l *slog.Logger) (refresher, error) {
return newHypervisorDiscovery(client, &opts, conf.Port, conf.Region, availability, l), nil
case OpenStackRoleInstance:
return newInstanceDiscovery(client, &opts, conf.Port, conf.Region, conf.AllTenants, availability, l), nil
case OpenStackRoleLoadBalancer:
return newLoadBalancerDiscovery(client, &opts, conf.Region, availability, l), nil
}
return nil, errors.New("unknown OpenStack discovery role")
}

View file

@ -1208,6 +1208,25 @@ The following meta labels are available on targets during [relabeling](#relabel_
* `__meta_openstack_tag_<key>`: each metadata item of the instance, with any unsupported characters converted to an underscore.
* `__meta_openstack_user_id`: the user account owning the tenant.
#### `loadbalancer`
The `loadbalancer` role discovers one target per Octavia loadbalancer with a
`PROMETHEUS` listener. The target address defaults to the VIP address
of the load balancer.
The following meta labels are available on targets during [relabeling](#relabel_config):
* `__meta_openstack_loadbalancer_availability_zone`: the availability zone of the OpenStack load balancer.
* `__meta_openstack_loadbalancer_floating_ip`: the floating IP of the OpenStack load balancer.
* `__meta_openstack_loadbalancer_id`: the OpenStack load balancer ID.
* `__meta_openstack_loadbalancer_name`: the OpenStack load balancer name.
* `__meta_openstack_loadbalancer_provider`: the Octavia provider of the OpenStack load balancer.
* `__meta_openstack_loadbalancer_operating_status`: the operating status of the OpenStack load balancer.
* `__meta_openstack_loadbalancer_provisioning_status`: the provisioning status of the OpenStack load balancer.
* `__meta_openstack_loadbalancer_tags`: comma separated list of the OpenStack load balancer.
* `__meta_openstack_loadbalancer_vip`: the VIP of the OpenStack load balancer.
* `__meta_openstack_project_id`: the project (tenant) owning this load balancer.
See below for the configuration options for OpenStack discovery:
```yaml