From d3089f2ce805f6296c004f39fcb4bb69a181143a Mon Sep 17 00:00:00 2001
From: Matt Layher <mdlayher@gmail.com>
Date: Thu, 12 Jan 2017 12:41:35 -0500
Subject: [PATCH] Make wifi collector fail gracefully if metrics not available

---
 collector/wifi_linux.go                       | 30 +++++++-----
 vendor/github.com/mdlayher/netlink/conn.go    | 47 +++++++++++++++----
 vendor/github.com/mdlayher/wifi/client.go     | 20 ++++++++
 .../github.com/mdlayher/wifi/client_linux.go  | 13 ++++-
 .../github.com/mdlayher/wifi/client_others.go |  5 ++
 vendor/vendor.json                            | 12 ++---
 6 files changed, 101 insertions(+), 26 deletions(-)

diff --git a/collector/wifi_linux.go b/collector/wifi_linux.go
index 4eb1b687..994f8080 100644
--- a/collector/wifi_linux.go
+++ b/collector/wifi_linux.go
@@ -18,10 +18,12 @@ import (
 	"flag"
 	"fmt"
 	"io/ioutil"
+	"os"
 	"path/filepath"
 
 	"github.com/mdlayher/wifi"
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/common/log"
 )
 
 type wifiCollector struct {
@@ -35,8 +37,6 @@ type wifiCollector struct {
 	StationTransmitRetriesTotal  *prometheus.Desc
 	StationTransmitFailedTotal   *prometheus.Desc
 	StationBeaconLossTotal       *prometheus.Desc
-
-	stat wifiStater
 }
 
 var (
@@ -51,16 +51,12 @@ var _ wifiStater = &wifi.Client{}
 
 // wifiStater is an interface used to swap out a *wifi.Client for end to end tests.
 type wifiStater interface {
+	Close() error
 	Interfaces() ([]*wifi.Interface, error)
 	StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error)
 }
 
 func NewWifiCollector() (Collector, error) {
-	stat, err := newWifiStater(*collectorWifi)
-	if err != nil {
-		return nil, fmt.Errorf("failed to access wifi data: %v", err)
-	}
-
 	const (
 		subsystem = "wifi"
 	)
@@ -132,13 +128,23 @@ func NewWifiCollector() (Collector, error) {
 			labels,
 			nil,
 		),
-
-		stat: stat,
 	}, nil
 }
 
 func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error {
-	ifis, err := c.stat.Interfaces()
+	stat, err := newWifiStater(*collectorWifi)
+	if err != nil {
+		// Cannot access wifi metrics, report no error
+		if os.IsNotExist(err) {
+			log.Debug("wifi collector metrics are not available for this system")
+			return nil
+		}
+
+		return fmt.Errorf("failed to access wifi data: %v", err)
+	}
+	defer stat.Close()
+
+	ifis, err := stat.Interfaces()
 	if err != nil {
 		return fmt.Errorf("failed to retrieve wifi interfaces: %v", err)
 	}
@@ -149,7 +155,7 @@ func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error {
 			continue
 		}
 
-		info, err := c.stat.StationInfo(ifi)
+		info, err := stat.StationInfo(ifi)
 		if err != nil {
 			return fmt.Errorf("failed to retrieve station info for device %s: %v",
 				ifi.Name, err)
@@ -260,6 +266,8 @@ func (s *mockWifiStater) unmarshalJSONFile(filename string, v interface{}) error
 	return json.Unmarshal(b, v)
 }
 
+func (s *mockWifiStater) Close() error { return nil }
+
 func (s *mockWifiStater) Interfaces() ([]*wifi.Interface, error) {
 	var ifis []*wifi.Interface
 	if err := s.unmarshalJSONFile("interfaces.json", &ifis); err != nil {
diff --git a/vendor/github.com/mdlayher/netlink/conn.go b/vendor/github.com/mdlayher/netlink/conn.go
index c658c771..4b4c7377 100644
--- a/vendor/github.com/mdlayher/netlink/conn.go
+++ b/vendor/github.com/mdlayher/netlink/conn.go
@@ -2,7 +2,7 @@ package netlink
 
 import (
 	"errors"
-	"os"
+	"sync"
 	"sync/atomic"
 )
 
@@ -23,6 +23,12 @@ type Conn struct {
 	// seq is an atomically incremented integer used to provide sequence
 	// numbers when Conn.Send is called.
 	seq *uint32
+
+	// pid is an atomically set/loaded integer which is set to the PID assigned
+	// by netlink, when netlink sends its first response message.  pidOnce performs
+	// the assignment exactl once.
+	pid     *uint32
+	pidOnce sync.Once
 }
 
 // An osConn is an operating-system specific implementation of netlink
@@ -53,6 +59,7 @@ func newConn(c osConn) *Conn {
 	return &Conn{
 		c:   c,
 		seq: new(uint32),
+		pid: new(uint32),
 	}
 }
 
@@ -96,8 +103,8 @@ func (c *Conn) Execute(m Message) ([]Message, error) {
 // If m.Header.Sequence is 0, it will be automatically populated using the
 // next sequence number for this connection.
 //
-// If m.Header.PID is 0, it will be automatically populated using the
-// process ID (PID) of this process.
+// If m.Header.PID is 0, it will be automatically populated using a PID
+// assigned by netlink.
 func (c *Conn) Send(m Message) (Message, error) {
 	ml := nlmsgLength(len(m.Data))
 
@@ -115,7 +122,7 @@ func (c *Conn) Send(m Message) (Message, error) {
 	}
 
 	if m.Header.PID == 0 {
-		m.Header.PID = uint32(os.Getpid())
+		m.Header.PID = atomic.LoadUint32(c.pid)
 	}
 
 	if err := c.c.Send(m); err != nil {
@@ -127,14 +134,29 @@ func (c *Conn) Send(m Message) (Message, error) {
 
 // Receive receives one or more messages from netlink.  Multi-part messages are
 // handled transparently and returned as a single slice of Messages, with the
-// final empty "multi-part done" message removed.  If any of the messages
-// indicate a netlink error, that error will be returned.
+// final empty "multi-part done" message removed.
+//
+// If a PID has not yet been assigned to this Conn by netlink, the PID will
+// be set from the first received message.  This PID will be used in all
+// subsequent communications with netlink.
+//
+// If any of the messages indicate a netlink error, that error will be returned.
 func (c *Conn) Receive() ([]Message, error) {
 	msgs, err := c.receive()
 	if err != nil {
 		return nil, err
 	}
 
+	if len(msgs) > 0 {
+		// netlink multicast messages from kernel have PID of 0, so don't
+		// assign 0 as the expected PID for next messages
+		if pid := msgs[0].Header.PID; pid != 0 {
+			c.pidOnce.Do(func() {
+				atomic.StoreUint32(c.pid, pid)
+			})
+		}
+	}
+
 	// Trim the final message with multi-part done indicator if
 	// present
 	if m := msgs[len(msgs)-1]; m.Header.Flags&HeaderFlagsMulti != 0 && m.Header.Type == HeaderTypeDone {
@@ -200,10 +222,19 @@ func (c *Conn) nextSequence() uint32 {
 // ensuring that they contain matching sequence numbers and PIDs.
 func Validate(request Message, replies []Message) error {
 	for _, m := range replies {
-		if m.Header.Sequence != request.Header.Sequence {
+		// Check for mismatched sequence, unless:
+		//   - request had no sequence, meaning we are probably validating
+		//     a multicast reply
+		if m.Header.Sequence != request.Header.Sequence && request.Header.Sequence != 0 {
 			return errMismatchedSequence
 		}
-		if m.Header.PID != request.Header.PID {
+
+		// Check for mismatched PID, unless:
+		//   - request had no PID, meaning we are either:
+		//     - validating a multicast reply
+		//     - netlink has not yet assigned us a PID
+		//   - response had no PID, meaning it's from the kernel as a multicast reply
+		if m.Header.PID != request.Header.PID && request.Header.PID != 0 && m.Header.PID != 0 {
 			return errMismatchedPID
 		}
 	}
diff --git a/vendor/github.com/mdlayher/wifi/client.go b/vendor/github.com/mdlayher/wifi/client.go
index cac93f7f..47fe6d50 100644
--- a/vendor/github.com/mdlayher/wifi/client.go
+++ b/vendor/github.com/mdlayher/wifi/client.go
@@ -1,5 +1,15 @@
 package wifi
 
+import (
+	"errors"
+)
+
+var (
+	// errNotStation is returned when attempting to query station info for
+	// an interface which is not a station.
+	errNotStation = errors.New("interface is not a station")
+)
+
 // A Client is a type which can access WiFi device actions and statistics
 // using operating system-specific operations.
 type Client struct {
@@ -18,6 +28,11 @@ func New() (*Client, error) {
 	}, nil
 }
 
+// Close releases resources used by a Client.
+func (c *Client) Close() error {
+	return c.c.Close()
+}
+
 // Interfaces returns a list of the system's WiFi network interfaces.
 func (c *Client) Interfaces() ([]*Interface, error) {
 	return c.c.Interfaces()
@@ -26,11 +41,16 @@ func (c *Client) Interfaces() ([]*Interface, error) {
 // StationInfo retrieves statistics about a WiFi interface operating in
 // station mode.
 func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) {
+	if ifi.Type != InterfaceTypeStation {
+		return nil, errNotStation
+	}
+
 	return c.c.StationInfo(ifi)
 }
 
 // An osClient is the operating system-specific implementation of Client.
 type osClient interface {
+	Close() error
 	Interfaces() ([]*Interface, error)
 	StationInfo(ifi *Interface) (*StationInfo, error)
 }
diff --git a/vendor/github.com/mdlayher/wifi/client_linux.go b/vendor/github.com/mdlayher/wifi/client_linux.go
index 09796512..1eb5398a 100644
--- a/vendor/github.com/mdlayher/wifi/client_linux.go
+++ b/vendor/github.com/mdlayher/wifi/client_linux.go
@@ -36,6 +36,7 @@ type client struct {
 // genl is an interface over generic netlink, so netlink interactions can
 // be stubbed in tests.
 type genl interface {
+	Close() error
 	GetFamily(name string) (genetlink.Family, error)
 	Execute(m genetlink.Message, family uint16, flags netlink.HeaderFlags) ([]genetlink.Message, error)
 }
@@ -66,6 +67,11 @@ func initClient(c genl) (*client, error) {
 	}, nil
 }
 
+// Close closes the client's generic netlink connection.
+func (c *client) Close() error {
+	return c.c.Close()
+}
+
 // Interfaces requests that nl80211 return a list of all WiFi interfaces present
 // on this system.
 func (c *client) Interfaces() ([]*Interface, error) {
@@ -117,7 +123,12 @@ func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) {
 		return nil, err
 	}
 
-	if len(msgs) > 1 {
+	switch len(msgs) {
+	case 0:
+		return nil, os.ErrNotExist
+	case 1:
+		break
+	default:
 		return nil, errMultipleMessages
 	}
 
diff --git a/vendor/github.com/mdlayher/wifi/client_others.go b/vendor/github.com/mdlayher/wifi/client_others.go
index 4d7b4f43..a694e7c8 100644
--- a/vendor/github.com/mdlayher/wifi/client_others.go
+++ b/vendor/github.com/mdlayher/wifi/client_others.go
@@ -24,6 +24,11 @@ func newClient() (*client, error) {
 	return nil, errUnimplemented
 }
 
+// Close always returns an error.
+func (c *client) Close() error {
+	return errUnimplemented
+}
+
 // Interfaces always returns an error.
 func (c *client) Interfaces() ([]*Interface, error) {
 	return nil, errUnimplemented
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 65c87f81..ef571fe6 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -51,10 +51,10 @@
 			"revisionTime": "2016-04-24T11:30:07Z"
 		},
 		{
-			"checksumSHA1": "87nUxyFGVJFXB6MQpGCGUHi5NY0=",
+			"checksumSHA1": "n31d2o+dY0HXZTDWE5Rc0+7NEjc=",
 			"path": "github.com/mdlayher/netlink",
-			"revision": "a65cbc3bb3f7a793b7d79ad7d19b16d471ddbd78",
-			"revisionTime": "2017-01-10T22:29:47Z"
+			"revision": "e5da4eb480835e5bce0dc5e526fe5f9a8002b54e",
+			"revisionTime": "2017-01-13T17:56:52Z"
 		},
 		{
 			"checksumSHA1": "+2roeIWCAjCC58tZcs12Vqgf1Io=",
@@ -69,10 +69,10 @@
 			"revisionTime": "2017-01-04T04:59:06Z"
 		},
 		{
-			"checksumSHA1": "dSy4F6HRYyadhiQbv3Bof/Tdxec=",
+			"checksumSHA1": "l8M/rZH5s/ZVtCCyeiUQXZ5FosA=",
 			"path": "github.com/mdlayher/wifi",
-			"revision": "88fd1c0ec178645c1b7d300090b5a9d4b226b8e1",
-			"revisionTime": "2017-01-07T15:17:58Z"
+			"revision": "eb8b29b956ba5ff2fdd2d2f1f0b988b57fd3d8a3",
+			"revisionTime": "2017-01-12T20:47:29Z"
 		},
 		{
 			"checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=",