Repository: threefoldtech/zos
Pull request #593 (2020-03-19 14:20:41)

Adds reporting of macaddress interfaces to bcdb

codecov[bot]: # [Codecov](https://codecov.io/gh/threefoldtech/zos/pull/593?src=pr&el=h1) Report
> Merging [#593](https://codecov.io/gh/threefoldtech/zos/pull/593?src=pr&el=desc) into [master](https://codecov.io/gh/threefoldtech/zos/commit/c47cd8bb1dc08e7f164cb937fb116a8d38de4e06&el=desc) will **not change** coverage by `%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/threefoldtech/zos/pull/593/graphs/tree.svg?width=650&height=150&src=pr&token=f7Vmq1OAEm)](https://codecov.io/gh/threefoldtech/zos/pull/593?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #593   +/-   ##
=======================================
  Coverage   36.00%   36.00%           
=======================================
  Files          71       71           
  Lines        5255     5255           
=======================================
  Hits         1892     1892           
  Misses       3093     3093           
  Partials      270      270           
```


| [Impacted Files](https://codecov.io/gh/threefoldtech/zos/pull/593?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/gedis/commands\_identity.go](https://codecov.io/gh/threefoldtech/zos/pull/593/diff?src=pr&el=tree#diff-cGtnL2dlZGlzL2NvbW1hbmRzX2lkZW50aXR5Lmdv) | `62.22% <ø> (ø)` | |
| [pkg/gedis/commands\_network.go](https://codecov.io/gh/threefoldtech/zos/pull/593/diff?src=pr&el=tree#diff-cGtnL2dlZGlzL2NvbW1hbmRzX25ldHdvcmsuZ28=) | `66.66% <ø> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/threefoldtech/zos/pull/593?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/threefoldtech/zos/pull/593?src=pr&el=footer). Last update [c47cd8b...2e1d3c5](https://codecov.io/gh/threefoldtech/zos/pull/593?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

DylanVerstraete: @zaibon @muhamadazmy is there anything missing to finish this?
DylanVerstraete: Don't merge yet, bcdb mock is throwing an error. Need to investigate

Patch:
diff --git a/cmds/networkd/addr_watcher.go b/cmds/networkd/addr_watcher.go
index d0020c15..bc0e3675 100644
--- a/cmds/networkd/addr_watcher.go
+++ b/cmds/networkd/addr_watcher.go
@@ -13,6 +13,7 @@ import (
 	"github.com/threefoldtech/zos/pkg/network"
 	"github.com/threefoldtech/zos/pkg/network/ifaceutil"
 	"github.com/threefoldtech/zos/pkg/network/types"
+	"github.com/threefoldtech/zos/pkg/schema"
 	"github.com/vishvananda/netlink"
 )
 
@@ -121,8 +122,9 @@ func getLocalInterfaces() ([]types.IfaceInfo, error) {
 		}
 
 		info := types.IfaceInfo{
-			Name:  link.Attrs().Name,
-			Addrs: make([]types.IPNet, len(addrs)),
+			Name:       link.Attrs().Name,
+			Addrs:      make([]types.IPNet, len(addrs)),
+			MacAddress: schema.MacAddress{link.Attrs().HardwareAddr},
 		}
 		for i, addr := range addrs {
 			info.Addrs[i] = types.NewIPNet(addr.IPNet)
diff --git a/pkg/gedis/commands_identity.go b/pkg/gedis/commands_identity.go
index d21af3e0..4c41cb8d 100644
--- a/pkg/gedis/commands_identity.go
+++ b/pkg/gedis/commands_identity.go
@@ -149,6 +149,7 @@ func infFromSchema(inf directory.TfgridNodeIface1) types.IfaceInfo {
 			}
 			return r
 		}(),
+		MacAddress: inf.MacAddress,
 	}
 }
 
diff --git a/pkg/gedis/commands_network.go b/pkg/gedis/commands_network.go
index 95291df0..ded60c50 100644
--- a/pkg/gedis/commands_network.go
+++ b/pkg/gedis/commands_network.go
@@ -21,7 +21,8 @@ func (g *Gedis) getLocalInterfaces(ifaces []types.IfaceInfo) []directory.TfgridN
 					}
 					return o
 				}(),
-				Gateway: iface.Gateway,
+				Gateway:    iface.Gateway,
+				MacAddress: iface.MacAddress,
 			},
 		)
 	}
diff --git a/pkg/gedis/types/directory/tfgrid_node_2.go b/pkg/gedis/types/directory/tfgrid_node_2.go
index f1c09f4b..26ab5bc3 100644
--- a/pkg/gedis/types/directory/tfgrid_node_2.go
+++ b/pkg/gedis/types/directory/tfgrid_node_2.go
@@ -30,9 +30,10 @@ type TfgridNode2 struct {
 
 // TfgridNodeIface1 jsx schema
 type TfgridNodeIface1 struct {
-	Name    string           `json:"name"`
-	Addrs   []schema.IPRange `json:"addrs"`
-	Gateway []net.IP         `json:"gateway"`
+	Name       string            `json:"name"`
+	Addrs      []schema.IPRange  `json:"addrs"`
+	Gateway    []net.IP          `json:"gateway"`
+	MacAddress schema.MacAddress `json:"macaddress"`
 }
 
 // TfgridNodePublicIface1 jsx schema
diff --git a/pkg/network/types/types.go b/pkg/network/types/types.go
index 0fb77ebf..5db8b078 100644
--- a/pkg/network/types/types.go
+++ b/pkg/network/types/types.go
@@ -22,9 +22,10 @@ const (
 // that the node will publish publicly
 // this is used to be able to configure public side of a node
 type IfaceInfo struct {
-	Name    string   `json:"name"`
-	Addrs   []IPNet  `json:"addrs"`
-	Gateway []net.IP `json:"gateway"`
+	Name       string            `json:"name"`
+	Addrs      []IPNet           `json:"addrs"`
+	Gateway    []net.IP          `json:"gateway"`
+	MacAddress schema.MacAddress `json:"macaddress"`
 }
 
 // DefaultIP return the IP address of the interface that has a default gateway configured
@@ -94,9 +95,10 @@ func NewNodeFromSchema(node directory.TfgridNode2) *Node {
 	if node.Ifaces != nil {
 		for i, iface := range node.Ifaces {
 			n.Ifaces[i] = &IfaceInfo{
-				Name:    iface.Name,
-				Addrs:   make([]IPNet, len(iface.Addrs)),
-				Gateway: iface.Gateway,
+				Name:       iface.Name,
+				Addrs:      make([]IPNet, len(iface.Addrs)),
+				Gateway:    iface.Gateway,
+				MacAddress: iface.MacAddress,
 			}
 
 			for y, addr := range iface.Addrs {
diff --git a/pkg/schema/types.go b/pkg/schema/types.go
index 0744823a..1f2cec19 100644
--- a/pkg/schema/types.go
+++ b/pkg/schema/types.go
@@ -228,3 +228,29 @@ func (e *Email) UnmarshalText(text []byte) error {
 
 // ID object id
 type ID int64
+
+// MacAddress type
+type MacAddress struct{ net.HardwareAddr }
+
+// MarshalText marshals MacAddress type to a string
+func (mac MacAddress) MarshalText() ([]byte, error) {
+	if mac.HardwareAddr == nil {
+		return nil, nil
+	} else if mac.HardwareAddr.String() == "" {
+		return nil, nil
+	}
+	return []byte(mac.HardwareAddr.String()), nil
+}
+
+// UnmarshalText loads a macaddress from a string
+func (mac *MacAddress) UnmarshalText(addr []byte) error {
+	if len(addr) == 0 {
+		return nil
+	}
+	addr, err := net.ParseMAC(string(addr))
+	if err != nil {
+		return err
+	}
+	mac.HardwareAddr = addr
+	return nil
+}
diff --git a/tools/bcdb_mock/models/generated/directory/directory.go b/tools/bcdb_mock/models/generated/directory/directory.go
index 6d83a350..2560c651 100644
--- a/tools/bcdb_mock/models/generated/directory/directory.go
+++ b/tools/bcdb_mock/models/generated/directory/directory.go
@@ -96,9 +96,10 @@ func NewTfgridDirectoryNode2() (TfgridDirectoryNode2, error) {
 }
 
 type TfgridDirectoryNodeIface1 struct {
-	Name    string           `bson:"name" json:"name"`
-	Addrs   []schema.IPRange `bson:"addrs" json:"addrs"`
-	Gateway []net.IP         `bson:"gateway" json:"gateway"`
+	Name       string            `bson:"name" json:"name"`
+	Addrs      []schema.IPRange  `bson:"addrs" json:"addrs"`
+	Gateway    []net.IP          `bson:"gateway" json:"gateway"`
+	MacAddress schema.MacAddress `bson:"macaddress" json:"macaddress"`
 }
 
 func NewTfgridDirectoryNodeIface1() (TfgridDirectoryNodeIface1, error) {
diff --git a/tools/bcdb_mock/models/schema/directory/tfgrid_directory_node_2.toml b/tools/bcdb_mock/models/schema/directory/tfgrid_directory_node_2.toml
index f812d07e..c80e10cd 100644
--- a/tools/bcdb_mock/models/schema/directory/tfgrid_directory_node_2.toml
+++ b/tools/bcdb_mock/models/schema/directory/tfgrid_directory_node_2.toml
@@ -28,6 +28,7 @@ wg_ports = (LI)
 name = (S)
 addrs = (Liprange)
 gateway = (Lipaddr)
+macaddress = (S)
 
 #famer configures this information so we know how to configure the ipaddress
 @url = tfgrid.directory.node.public_iface.1


Test patch:
diff --git a/pkg/schema/types_test.go b/pkg/schema/types_test.go
index d6d3786b..1c78152b 100644
--- a/pkg/schema/types_test.go
+++ b/pkg/schema/types_test.go
@@ -188,3 +188,78 @@ func TestDumpIPRange(t *testing.T) {
 		})
 	}
 }
+
+func TestParseMacAddress(t *testing.T) {
+	parser := func(t *testing.T, in string) MacAddress {
+		//note in is surrounded by "" because it's json
+		var str string
+		if err := json.Unmarshal([]byte(in), &str); err != nil {
+			t.Fatal(err)
+		}
+
+		if len(str) == 0 {
+			return MacAddress{}
+		}
+
+		mac, err := net.ParseMAC(str)
+		if err != nil {
+			t.Fatal(err)
+		}
+		return MacAddress{mac}
+	}
+
+	cases := []struct {
+		Input  string
+		Output func(*testing.T, string) MacAddress
+	}{
+		{`"54:45:46:f6:02:61"`, parser},
+		{`"FF:FF:FF:FF:FF:FF"`, parser},
+		{`""`, parser},
+	}
+
+	for _, c := range cases {
+		t.Run(c.Input, func(t *testing.T) {
+			var d MacAddress
+			err := json.Unmarshal([]byte(c.Input), &d)
+			if ok := assert.NoError(t, err); !ok {
+				t.Fatal()
+			}
+
+			if ok := assert.Equal(t, c.Output(t, c.Input), d); !ok {
+				t.Error()
+			}
+		})
+	}
+}
+
+func TestDumpMacaddress(t *testing.T) {
+	mustParse := func(in string) MacAddress {
+		mac, err := net.ParseMAC(in)
+		if err != nil {
+			require.NoError(t, err)
+		}
+		return MacAddress{mac}
+	}
+
+	cases := []struct {
+		Input  MacAddress
+		Output string
+	}{
+		{MacAddress{}, `""`},
+		{mustParse("54:45:46:f6:02:61"), `"54:45:46:f6:02:61"`},
+		{mustParse("FF:FF:FF:FF:FF:FF"), `"ff:ff:ff:ff:ff:ff"`},
+	}
+
+	for _, c := range cases {
+		t.Run(c.Output, func(t *testing.T) {
+			out, err := json.Marshal(c.Input)
+			if ok := assert.NoError(t, err); !ok {
+				t.Fatal()
+			}
+
+			if ok := assert.Equal(t, c.Output, string(out)); !ok {
+				t.Error()
+			}
+		})
+	}
+}