discovery/openstack: discover all interfaces (#4649)

* discovery/openstack: discover all interfaces
* Add address pool label

Signed-off-by: Simon Pasquier <spasquie@redhat.com>
This commit is contained in:
Simon Pasquier 2018-10-09 17:17:08 +02:00 committed by Brian Brazil
parent 33c97d73e4
commit a2a78d0a09
4 changed files with 140 additions and 60 deletions

View File

@ -34,6 +34,7 @@ import (
const ( const (
openstackLabelPrefix = model.MetaLabelPrefix + "openstack_" openstackLabelPrefix = model.MetaLabelPrefix + "openstack_"
openstackLabelAddressPool = openstackLabelPrefix + "address_pool"
openstackLabelInstanceID = openstackLabelPrefix + "instance_id" openstackLabelInstanceID = openstackLabelPrefix + "instance_id"
openstackLabelInstanceName = openstackLabelPrefix + "instance_name" openstackLabelInstanceName = openstackLabelPrefix + "instance_name"
openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status" openstackLabelInstanceStatus = openstackLabelPrefix + "instance_status"
@ -100,6 +101,11 @@ func (i *InstanceDiscovery) Run(ctx context.Context, ch chan<- []*targetgroup.Gr
} }
} }
type floatingIPKey struct {
id string
fixed string
}
func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) { func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) {
var err error var err error
t0 := time.Now() t0 := time.Now()
@ -124,7 +130,8 @@ func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) {
// OpenStack API reference // OpenStack API reference
// https://developer.openstack.org/api-ref/compute/#list-floating-ips // https://developer.openstack.org/api-ref/compute/#list-floating-ips
pagerFIP := floatingips.List(client) pagerFIP := floatingips.List(client)
floatingIPList := make(map[string][]string) floatingIPList := make(map[floatingIPKey]string)
floatingIPPresent := make(map[string]struct{})
err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) { err = pagerFIP.EachPage(func(page pagination.Page) (bool, error) {
result, err := floatingips.ExtractFloatingIPs(page) result, err := floatingips.ExtractFloatingIPs(page)
if err != nil { if err != nil {
@ -132,9 +139,11 @@ func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) {
} }
for _, ip := range result { for _, ip := range result {
// Skip not associated ips // Skip not associated ips
if ip.InstanceID != "" { if ip.InstanceID == "" || ip.FixedIP == "" {
floatingIPList[ip.InstanceID] = append(floatingIPList[ip.InstanceID], ip.IP) continue
} }
floatingIPList[floatingIPKey{id: ip.InstanceID, fixed: ip.FixedIP}] = ip.IP
floatingIPPresent[ip.IP] = struct{}{}
} }
return true, nil return true, nil
}) })
@ -156,14 +165,28 @@ func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) {
} }
for _, s := range instanceList { for _, s := range instanceList {
labels := model.LabelSet{
openstackLabelInstanceID: model.LabelValue(s.ID),
}
if len(s.Addresses) == 0 { if len(s.Addresses) == 0 {
level.Info(i.logger).Log("msg", "Got no IP address", "instance", s.ID) level.Info(i.logger).Log("msg", "Got no IP address", "instance", s.ID)
continue continue
} }
for _, address := range s.Addresses {
labels := model.LabelSet{
openstackLabelInstanceID: model.LabelValue(s.ID),
openstackLabelInstanceStatus: model.LabelValue(s.Status),
openstackLabelInstanceName: model.LabelValue(s.Name),
}
id, ok := s.Flavor["id"].(string)
if !ok {
level.Warn(i.logger).Log("msg", "Invalid type for flavor id, expected string")
continue
}
labels[openstackLabelInstanceFlavor] = model.LabelValue(id)
for k, v := range s.Metadata {
name := strutil.SanitizeLabelName(k)
labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v)
}
for pool, address := range s.Addresses {
md, ok := address.([]interface{}) md, ok := address.([]interface{})
if !ok { if !ok {
level.Warn(i.logger).Log("msg", "Invalid type for address, expected array") level.Warn(i.logger).Log("msg", "Invalid type for address, expected array")
@ -173,38 +196,35 @@ func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) {
level.Debug(i.logger).Log("msg", "Got no IP address", "instance", s.ID) level.Debug(i.logger).Log("msg", "Got no IP address", "instance", s.ID)
continue continue
} }
md1, ok := md[0].(map[string]interface{}) for _, address := range md {
if !ok { md1, ok := address.(map[string]interface{})
level.Warn(i.logger).Log("msg", "Invalid type for address, expected dict") if !ok {
continue level.Warn(i.logger).Log("msg", "Invalid type for address, expected dict")
continue
}
addr, ok := md1["addr"].(string)
if !ok {
level.Warn(i.logger).Log("msg", "Invalid type for address, expected string")
continue
}
if _, ok := floatingIPPresent[addr]; ok {
continue
}
lbls := make(model.LabelSet, len(labels))
for k, v := range labels {
lbls[k] = v
}
lbls[openstackLabelAddressPool] = model.LabelValue(pool)
lbls[openstackLabelPrivateIP] = model.LabelValue(addr)
if val, ok := floatingIPList[floatingIPKey{id: s.ID, fixed: addr}]; ok {
lbls[openstackLabelPublicIP] = model.LabelValue(val)
}
addr = net.JoinHostPort(addr, fmt.Sprintf("%d", i.port))
lbls[model.AddressLabel] = model.LabelValue(addr)
tg.Targets = append(tg.Targets, lbls)
} }
addr, ok := md1["addr"].(string)
if !ok {
level.Warn(i.logger).Log("msg", "Invalid type for address, expected string")
continue
}
labels[openstackLabelPrivateIP] = model.LabelValue(addr)
addr = net.JoinHostPort(addr, fmt.Sprintf("%d", i.port))
labels[model.AddressLabel] = model.LabelValue(addr)
// Only use first private IP
break
} }
if val, ok := floatingIPList[s.ID]; ok && len(val) > 0 {
labels[openstackLabelPublicIP] = model.LabelValue(val[0])
}
labels[openstackLabelInstanceStatus] = model.LabelValue(s.Status)
labels[openstackLabelInstanceName] = model.LabelValue(s.Name)
id, ok := s.Flavor["id"].(string)
if !ok {
level.Warn(i.logger).Log("msg", "Invalid type for instance id, excepted string")
continue
}
labels[openstackLabelInstanceFlavor] = model.LabelValue(id)
for k, v := range s.Metadata {
name := strutil.SanitizeLabelName(k)
labels[openstackLabelTagPrefix+model.LabelName(name)] = model.LabelValue(v)
}
tg.Targets = append(tg.Targets, labels)
} }
return true, nil return true, nil
}) })

View File

@ -14,6 +14,7 @@
package openstack package openstack
import ( import (
"fmt"
"testing" "testing"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -56,28 +57,62 @@ func TestOpenstackSDInstanceRefresh(t *testing.T) {
mock := &OpenstackSDInstanceTestSuite{} mock := &OpenstackSDInstanceTestSuite{}
mock.SetupTest(t) mock.SetupTest(t)
instance, _ := mock.openstackAuthSuccess() instance, err := mock.openstackAuthSuccess()
testutil.Ok(t, err)
tg, err := instance.refresh() tg, err := instance.refresh()
testutil.Ok(t, err) testutil.Ok(t, err)
testutil.Assert(t, tg != nil, "") testutil.Assert(t, tg != nil, "")
testutil.Assert(t, tg.Targets != nil, "") testutil.Assert(t, tg.Targets != nil, "")
testutil.Assert(t, len(tg.Targets) == 3, "") testutil.Equals(t, 4, len(tg.Targets))
testutil.Equals(t, tg.Targets[0]["__address__"], model.LabelValue("10.0.0.32:0")) for i, lbls := range []model.LabelSet{
testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_flavor"], model.LabelValue("1")) model.LabelSet{
testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_id"], model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5")) "__address__": model.LabelValue("10.0.0.32:0"),
testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_name"], model.LabelValue("herp")) "__meta_openstack_instance_flavor": model.LabelValue("1"),
testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE")) "__meta_openstack_instance_id": model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5"),
testutil.Equals(t, tg.Targets[0]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.32")) "__meta_openstack_instance_status": model.LabelValue("ACTIVE"),
testutil.Equals(t, tg.Targets[0]["__meta_openstack_public_ip"], model.LabelValue("10.10.10.2")) "__meta_openstack_instance_name": model.LabelValue("herp"),
"__meta_openstack_private_ip": model.LabelValue("10.0.0.32"),
testutil.Equals(t, tg.Targets[1]["__address__"], model.LabelValue("10.0.0.31:0")) "__meta_openstack_public_ip": model.LabelValue("10.10.10.2"),
testutil.Equals(t, tg.Targets[1]["__meta_openstack_instance_flavor"], model.LabelValue("1")) "__meta_openstack_address_pool": model.LabelValue("private"),
testutil.Equals(t, tg.Targets[1]["__meta_openstack_instance_id"], model.LabelValue("9e5476bd-a4ec-4653-93d6-72c93aa682ba")) },
testutil.Equals(t, tg.Targets[1]["__meta_openstack_instance_name"], model.LabelValue("derp")) model.LabelSet{
testutil.Equals(t, tg.Targets[1]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE")) "__address__": model.LabelValue("10.0.0.31:0"),
testutil.Equals(t, tg.Targets[1]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.31")) "__meta_openstack_instance_flavor": model.LabelValue("1"),
"__meta_openstack_instance_id": model.LabelValue("9e5476bd-a4ec-4653-93d6-72c93aa682ba"),
"__meta_openstack_instance_status": model.LabelValue("ACTIVE"),
"__meta_openstack_instance_name": model.LabelValue("derp"),
"__meta_openstack_private_ip": model.LabelValue("10.0.0.31"),
"__meta_openstack_address_pool": model.LabelValue("private"),
},
model.LabelSet{
"__address__": model.LabelValue("10.0.0.33:0"),
"__meta_openstack_instance_flavor": model.LabelValue("4"),
"__meta_openstack_instance_id": model.LabelValue("9e5476bd-a4ec-4653-93d6-72c93aa682bb"),
"__meta_openstack_instance_status": model.LabelValue("ACTIVE"),
"__meta_openstack_instance_name": model.LabelValue("merp"),
"__meta_openstack_private_ip": model.LabelValue("10.0.0.33"),
"__meta_openstack_address_pool": model.LabelValue("private"),
"__meta_openstack_tag_env": model.LabelValue("prod"),
},
model.LabelSet{
"__address__": model.LabelValue("10.0.0.34:0"),
"__meta_openstack_instance_flavor": model.LabelValue("4"),
"__meta_openstack_instance_id": model.LabelValue("9e5476bd-a4ec-4653-93d6-72c93aa682bb"),
"__meta_openstack_instance_status": model.LabelValue("ACTIVE"),
"__meta_openstack_instance_name": model.LabelValue("merp"),
"__meta_openstack_private_ip": model.LabelValue("10.0.0.34"),
"__meta_openstack_address_pool": model.LabelValue("private"),
"__meta_openstack_tag_env": model.LabelValue("prod"),
"__meta_openstack_public_ip": model.LabelValue("10.10.10.4"),
},
} {
t.Run(fmt.Sprintf("item %d", i), func(t *testing.T) {
testutil.Equals(t, lbls, tg.Targets[i])
})
}
mock.TearDownSuite() mock.TearDownSuite()
} }

View File

@ -327,6 +327,11 @@ const serverListBody = `
"version": 4, "version": 4,
"addr": "10.0.0.32", "addr": "10.0.0.32",
"OS-EXT-IPS:type": "fixed" "OS-EXT-IPS:type": "fixed"
},
{
"version": 4,
"addr": "10.10.10.2",
"OS-EXT-IPS:type": "floating"
} }
] ]
}, },
@ -463,10 +468,19 @@ const serverListBody = `
"addresses": { "addresses": {
"private": [ "private": [
{ {
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
"version": 4, "version": 4,
"addr": "10.0.0.31", "addr": "10.0.0.33",
"OS-EXT-IPS:type": "fixed" "OS-EXT-IPS:type": "fixed"
},
{
"version": 4,
"addr": "10.0.0.34",
"OS-EXT-IPS:type": "fixed"
},
{
"version": 4,
"addr": "10.10.10.4",
"OS-EXT-IPS:type": "floating"
} }
] ]
}, },
@ -488,7 +502,7 @@ const serverListBody = `
"OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000", "OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack", "OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
"flavor": { "flavor": {
"id": "1", "id": "4",
"links": [ "links": [
{ {
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1", "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
@ -515,7 +529,9 @@ const serverListBody = `
"progress": 0, "progress": 0,
"OS-EXT-STS:power_state": 1, "OS-EXT-STS:power_state": 1,
"config_drive": "", "config_drive": "",
"metadata": {} "metadata": {
"env": "prod"
}
} }
] ]
} }
@ -543,11 +559,18 @@ const listOutput = `
"pool": "nova" "pool": "nova"
}, },
{ {
"fixed_ip": "166.78.185.201", "fixed_ip": "10.0.0.32",
"id": "2", "id": "2",
"instance_id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5", "instance_id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
"ip": "10.10.10.2", "ip": "10.10.10.2",
"pool": "nova" "pool": "nova"
},
{
"fixed_ip": "10.0.0.34",
"id": "3",
"instance_id": "9e5476bd-a4ec-4653-93d6-72c93aa682bb",
"ip": "10.10.10.4",
"pool": "nova"
} }
] ]
} }

View File

@ -477,8 +477,9 @@ The following meta labels are available on targets during [relabeling](#relabel_
#### `instance` #### `instance`
The `instance` role discovers one target per Nova instance. The target The `instance` role discovers one target per network interface of Nova
address defaults to the first private IP address of the instance. instance. The target address defaults to the private IP address of the network
interface.
The following meta labels are available on targets during [relabeling](#relabel_config): The following meta labels are available on targets during [relabeling](#relabel_config):
@ -488,6 +489,7 @@ The following meta labels are available on targets during [relabeling](#relabel_
* `__meta_openstack_instance_flavor`: the flavor of the OpenStack instance. * `__meta_openstack_instance_flavor`: the flavor of the OpenStack instance.
* `__meta_openstack_public_ip`: the public IP of the OpenStack instance. * `__meta_openstack_public_ip`: the public IP of the OpenStack instance.
* `__meta_openstack_private_ip`: the private IP of the OpenStack instance. * `__meta_openstack_private_ip`: the private IP of the OpenStack instance.
* `__meta_openstack_address_pool`: the pool of the private IP.
* `__meta_openstack_tag_<tagkey>`: each tag value of the instance. * `__meta_openstack_tag_<tagkey>`: each tag value of the instance.
See below for the configuration options for OpenStack discovery: See below for the configuration options for OpenStack discovery: