From a2a78d0a09f13f9adc74ae8620fe9611f639e4b6 Mon Sep 17 00:00:00 2001 From: Simon Pasquier Date: Tue, 9 Oct 2018 17:17:08 +0200 Subject: [PATCH] discovery/openstack: discover all interfaces (#4649) * discovery/openstack: discover all interfaces * Add address pool label Signed-off-by: Simon Pasquier --- discovery/openstack/instance.go | 94 +++++++++++++++++----------- discovery/openstack/instance_test.go | 67 +++++++++++++++----- discovery/openstack/mock_test.go | 33 ++++++++-- docs/configuration/configuration.md | 6 +- 4 files changed, 140 insertions(+), 60 deletions(-) diff --git a/discovery/openstack/instance.go b/discovery/openstack/instance.go index a5a54c3c5..8af90f6cb 100644 --- a/discovery/openstack/instance.go +++ b/discovery/openstack/instance.go @@ -34,6 +34,7 @@ import ( const ( openstackLabelPrefix = model.MetaLabelPrefix + "openstack_" + openstackLabelAddressPool = openstackLabelPrefix + "address_pool" openstackLabelInstanceID = openstackLabelPrefix + "instance_id" openstackLabelInstanceName = openstackLabelPrefix + "instance_name" 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) { var err error t0 := time.Now() @@ -124,7 +130,8 @@ func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) { // OpenStack API reference // https://developer.openstack.org/api-ref/compute/#list-floating-ips 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) { result, err := floatingips.ExtractFloatingIPs(page) if err != nil { @@ -132,9 +139,11 @@ func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) { } for _, ip := range result { // Skip not associated ips - if ip.InstanceID != "" { - floatingIPList[ip.InstanceID] = append(floatingIPList[ip.InstanceID], ip.IP) + if ip.InstanceID == "" || ip.FixedIP == "" { + continue } + floatingIPList[floatingIPKey{id: ip.InstanceID, fixed: ip.FixedIP}] = ip.IP + floatingIPPresent[ip.IP] = struct{}{} } return true, nil }) @@ -156,14 +165,28 @@ func (i *InstanceDiscovery) refresh() (*targetgroup.Group, error) { } for _, s := range instanceList { - labels := model.LabelSet{ - openstackLabelInstanceID: model.LabelValue(s.ID), - } if len(s.Addresses) == 0 { level.Info(i.logger).Log("msg", "Got no IP address", "instance", s.ID) 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{}) if !ok { 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) continue } - md1, ok := md[0].(map[string]interface{}) - if !ok { - level.Warn(i.logger).Log("msg", "Invalid type for address, expected dict") - continue + for _, address := range md { + md1, ok := address.(map[string]interface{}) + if !ok { + 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 }) diff --git a/discovery/openstack/instance_test.go b/discovery/openstack/instance_test.go index 74b94145c..8ee870a19 100644 --- a/discovery/openstack/instance_test.go +++ b/discovery/openstack/instance_test.go @@ -14,6 +14,7 @@ package openstack import ( + "fmt" "testing" "github.com/prometheus/common/model" @@ -56,28 +57,62 @@ func TestOpenstackSDInstanceRefresh(t *testing.T) { mock := &OpenstackSDInstanceTestSuite{} mock.SetupTest(t) - instance, _ := mock.openstackAuthSuccess() + instance, err := mock.openstackAuthSuccess() + testutil.Ok(t, err) + tg, err := instance.refresh() testutil.Ok(t, err) testutil.Assert(t, tg != 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")) - testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_flavor"], model.LabelValue("1")) - testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_id"], model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5")) - testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_name"], model.LabelValue("herp")) - testutil.Equals(t, tg.Targets[0]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE")) - testutil.Equals(t, tg.Targets[0]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.32")) - testutil.Equals(t, tg.Targets[0]["__meta_openstack_public_ip"], model.LabelValue("10.10.10.2")) - - testutil.Equals(t, tg.Targets[1]["__address__"], model.LabelValue("10.0.0.31:0")) - testutil.Equals(t, tg.Targets[1]["__meta_openstack_instance_flavor"], model.LabelValue("1")) - 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")) - testutil.Equals(t, tg.Targets[1]["__meta_openstack_instance_status"], model.LabelValue("ACTIVE")) - testutil.Equals(t, tg.Targets[1]["__meta_openstack_private_ip"], model.LabelValue("10.0.0.31")) + for i, lbls := range []model.LabelSet{ + model.LabelSet{ + "__address__": model.LabelValue("10.0.0.32:0"), + "__meta_openstack_instance_flavor": model.LabelValue("1"), + "__meta_openstack_instance_id": model.LabelValue("ef079b0c-e610-4dfb-b1aa-b49f07ac48e5"), + "__meta_openstack_instance_status": model.LabelValue("ACTIVE"), + "__meta_openstack_instance_name": model.LabelValue("herp"), + "__meta_openstack_private_ip": model.LabelValue("10.0.0.32"), + "__meta_openstack_public_ip": model.LabelValue("10.10.10.2"), + "__meta_openstack_address_pool": model.LabelValue("private"), + }, + model.LabelSet{ + "__address__": model.LabelValue("10.0.0.31:0"), + "__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() } diff --git a/discovery/openstack/mock_test.go b/discovery/openstack/mock_test.go index 0cd975a9e..67b14f6f6 100644 --- a/discovery/openstack/mock_test.go +++ b/discovery/openstack/mock_test.go @@ -327,6 +327,11 @@ const serverListBody = ` "version": 4, "addr": "10.0.0.32", "OS-EXT-IPS:type": "fixed" + }, + { + "version": 4, + "addr": "10.10.10.2", + "OS-EXT-IPS:type": "floating" } ] }, @@ -463,10 +468,19 @@ const serverListBody = ` "addresses": { "private": [ { - "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be", "version": 4, - "addr": "10.0.0.31", + "addr": "10.0.0.33", "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-EXT-SRV-ATTR:hypervisor_hostname": "devstack", "flavor": { - "id": "1", + "id": "4", "links": [ { "href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1", @@ -515,7 +529,9 @@ const serverListBody = ` "progress": 0, "OS-EXT-STS:power_state": 1, "config_drive": "", - "metadata": {} + "metadata": { + "env": "prod" + } } ] } @@ -543,11 +559,18 @@ const listOutput = ` "pool": "nova" }, { - "fixed_ip": "166.78.185.201", + "fixed_ip": "10.0.0.32", "id": "2", "instance_id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5", "ip": "10.10.10.2", "pool": "nova" + }, + { + "fixed_ip": "10.0.0.34", + "id": "3", + "instance_id": "9e5476bd-a4ec-4653-93d6-72c93aa682bb", + "ip": "10.10.10.4", + "pool": "nova" } ] } diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 96e76e160..1c7c6ece5 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -477,8 +477,9 @@ The following meta labels are available on targets during [relabeling](#relabel_ #### `instance` -The `instance` role discovers one target per Nova instance. The target -address defaults to the first private IP address of the instance. +The `instance` role discovers one target per network interface of Nova +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): @@ -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_public_ip`: the public 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_`: each tag value of the instance. See below for the configuration options for OpenStack discovery: