mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-11 22:14:02 +01:00
orchestrator/clickhouse: use SubnetMap for parsing networks
This commit is contained in:
@@ -33,6 +33,19 @@ func (sm *SubnetMap[V]) Lookup(ip net.IP) (V, bool) {
|
||||
return value, ok
|
||||
}
|
||||
|
||||
// ToMap return a map of the tree.
|
||||
func (sm *SubnetMap[V]) ToMap() map[string]V {
|
||||
output := map[string]V{}
|
||||
if sm == nil || sm.tree == nil {
|
||||
return output
|
||||
}
|
||||
iter := sm.tree.Iterate()
|
||||
for iter.Next() {
|
||||
output[iter.Address().String()] = iter.Tags()[0]
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// NewSubnetMap creates a subnetmap from a map. Unlike user-provided
|
||||
// configuration, this function is stricter and require everything to
|
||||
// be IPv6 subnets.
|
||||
@@ -137,7 +150,7 @@ func SubnetMapUnmarshallerHook[V any]() mapstructure.DecodeHookFunc {
|
||||
if err := intermediateDecoder.Decode(output); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode %q: %w", reflect.TypeOf(zero).Name(), err)
|
||||
}
|
||||
trie, err := NewSubnetMap[V](intermediate)
|
||||
trie, err := NewSubnetMap(intermediate)
|
||||
if err != nil {
|
||||
// Should not happen
|
||||
return nil, err
|
||||
@@ -148,21 +161,10 @@ func SubnetMapUnmarshallerHook[V any]() mapstructure.DecodeHookFunc {
|
||||
}
|
||||
|
||||
func (sm SubnetMap[V]) MarshalYAML() (interface{}, error) {
|
||||
output := map[string]V{}
|
||||
if sm.tree == nil {
|
||||
return output, nil
|
||||
}
|
||||
iter := sm.tree.Iterate()
|
||||
for iter.Next() {
|
||||
output[iter.Address().String()] = iter.Tags()[0]
|
||||
}
|
||||
return output, nil
|
||||
return sm.ToMap(), nil
|
||||
}
|
||||
|
||||
func (sm SubnetMap[V]) String() string {
|
||||
out, err := sm.MarshalYAML()
|
||||
if err != nil {
|
||||
return "SubnetMap???"
|
||||
}
|
||||
out := sm.ToMap()
|
||||
return fmt.Sprintf("%v", out)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
package clickhouse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
@@ -32,7 +30,7 @@ type Configuration struct {
|
||||
ASNs map[uint32]string
|
||||
// Networks is a mapping from IP networks to attributes. It is used
|
||||
// to instantiate the SrcNet* and DstNet* columns.
|
||||
Networks NetworkMap
|
||||
Networks *helpers.SubnetMap[NetworkAttributes]
|
||||
// OrchestratorURL allows one to override URL to reach
|
||||
// orchestrator from Clickhouse
|
||||
OrchestratorURL string `validate:"isdefault|url"`
|
||||
@@ -73,9 +71,6 @@ func DefaultConfiguration() Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkMap is a mapping from a network to a attributes.
|
||||
type NetworkMap map[string]NetworkAttributes
|
||||
|
||||
// NetworkAttributes is a set of attributes attached to a network
|
||||
type NetworkAttributes struct {
|
||||
// Name is a name attached to the network. May be unique or not.
|
||||
@@ -90,56 +85,28 @@ type NetworkAttributes struct {
|
||||
Tenant string
|
||||
}
|
||||
|
||||
// NetworkMapUnmarshallerHook decodes NetworkMap mapping and notably
|
||||
// check that valid networks are provided as key. It also accepts a
|
||||
// string instead of attributes for backward compatibility.
|
||||
func NetworkMapUnmarshallerHook() mapstructure.DecodeHookFunc {
|
||||
return func(from, to reflect.Type, data interface{}) (interface{}, error) {
|
||||
if from.Kind() != reflect.Map || to != reflect.TypeOf(NetworkMap{}) {
|
||||
return data, nil
|
||||
// NetworkAttributesUnmarshallerHook decodes network attributes. It
|
||||
// also accepts a string instead of attributes for backward
|
||||
// compatibility.
|
||||
func NetworkAttributesUnmarshallerHook() mapstructure.DecodeHookFunc {
|
||||
return func(from, to reflect.Value) (interface{}, error) {
|
||||
if to.Kind() == reflect.Interface {
|
||||
to = to.Elem()
|
||||
}
|
||||
output := map[string]interface{}{}
|
||||
iter := reflect.ValueOf(data).MapRange()
|
||||
for i := 0; iter.Next(); i++ {
|
||||
k := iter.Key()
|
||||
v := iter.Value()
|
||||
if k.Kind() == reflect.Interface {
|
||||
k = k.Elem()
|
||||
}
|
||||
if k.Kind() != reflect.String {
|
||||
return nil, fmt.Errorf("key %d is not a string (%s)", i, k.Kind())
|
||||
}
|
||||
// Parse key
|
||||
_, ipNet, err := net.ParseCIDR(k.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert key to IPv6
|
||||
ones, bits := ipNet.Mask.Size()
|
||||
if bits != 32 && bits != 128 {
|
||||
return nil, fmt.Errorf("key %d has an invalid netmask", i)
|
||||
}
|
||||
var key string
|
||||
if bits == 32 {
|
||||
key = fmt.Sprintf("::ffff:%s/%d", ipNet.IP.String(), ones+96)
|
||||
} else {
|
||||
key = ipNet.String()
|
||||
}
|
||||
|
||||
// Parse value (partially)
|
||||
if v.Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() == reflect.String {
|
||||
output[key] = NetworkAttributes{Name: v.String()}
|
||||
} else {
|
||||
output[key] = v.Interface()
|
||||
}
|
||||
if from.Kind() == reflect.Interface {
|
||||
from = from.Elem()
|
||||
}
|
||||
return output, nil
|
||||
if to.Type() != reflect.TypeOf(NetworkAttributes{}) {
|
||||
return from.Interface(), nil
|
||||
}
|
||||
if from.Kind() == reflect.String {
|
||||
return NetworkAttributes{Name: from.String()}, nil
|
||||
}
|
||||
return from.Interface(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
helpers.AddMapstructureUnmarshallerHook(NetworkMapUnmarshallerHook())
|
||||
helpers.AddMapstructureUnmarshallerHook(helpers.SubnetMapUnmarshallerHook[NetworkAttributes]())
|
||||
helpers.AddMapstructureUnmarshallerHook(NetworkAttributesUnmarshallerHook())
|
||||
}
|
||||
|
||||
@@ -16,33 +16,39 @@ func TestNetworkNamesUnmarshalHook(t *testing.T) {
|
||||
cases := []struct {
|
||||
Description string
|
||||
Input map[string]interface{}
|
||||
Output NetworkMap
|
||||
Output map[string]NetworkAttributes
|
||||
Error bool
|
||||
}{
|
||||
{
|
||||
Description: "nil",
|
||||
Input: nil,
|
||||
Output: NetworkMap{},
|
||||
}, {
|
||||
Description: "empty",
|
||||
Input: gin.H{},
|
||||
Output: NetworkMap{},
|
||||
}, {
|
||||
Description: "IPv4",
|
||||
Input: gin.H{"203.0.113.0/24": gin.H{"name": "customer"}},
|
||||
Output: NetworkMap{"::ffff:203.0.113.0/120": NetworkAttributes{Name: "customer"}},
|
||||
Output: map[string]NetworkAttributes{
|
||||
"203.0.113.0/24": {Name: "customer"},
|
||||
},
|
||||
}, {
|
||||
Description: "IPv6",
|
||||
Input: gin.H{"2001:db8:1::/64": gin.H{"name": "customer"}},
|
||||
Output: NetworkMap{"2001:db8:1::/64": NetworkAttributes{Name: "customer"}},
|
||||
Output: map[string]NetworkAttributes{
|
||||
"2001:db8:1::/64": {Name: "customer"},
|
||||
},
|
||||
}, {
|
||||
Description: "IPv4 subnet (compatibility)",
|
||||
Input: gin.H{"203.0.113.0/24": "customer"},
|
||||
Output: NetworkMap{"::ffff:203.0.113.0/120": NetworkAttributes{Name: "customer"}},
|
||||
Output: map[string]NetworkAttributes{
|
||||
"203.0.113.0/24": {Name: "customer"},
|
||||
},
|
||||
}, {
|
||||
Description: "IPv6 subnet (compatibility)",
|
||||
Input: gin.H{"2001:db8:1::/64": "customer"},
|
||||
Output: NetworkMap{"2001:db8:1::/64": NetworkAttributes{Name: "customer"}},
|
||||
Output: map[string]NetworkAttributes{
|
||||
"2001:db8:1::/64": {Name: "customer"},
|
||||
},
|
||||
}, {
|
||||
Description: "all attributes",
|
||||
Input: gin.H{"203.0.113.0/24": gin.H{
|
||||
@@ -52,7 +58,7 @@ func TestNetworkNamesUnmarshalHook(t *testing.T) {
|
||||
"region": "france",
|
||||
"tenant": "mobile",
|
||||
}},
|
||||
Output: NetworkMap{"::ffff:203.0.113.0/120": NetworkAttributes{
|
||||
Output: map[string]NetworkAttributes{"203.0.113.0/24": {
|
||||
Name: "customer1",
|
||||
Role: "customer",
|
||||
Site: "paris",
|
||||
@@ -71,13 +77,8 @@ func TestNetworkNamesUnmarshalHook(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.Description, func(t *testing.T) {
|
||||
var got NetworkMap
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
Result: &got,
|
||||
ErrorUnused: true,
|
||||
Metadata: nil,
|
||||
DecodeHook: NetworkMapUnmarshallerHook(),
|
||||
})
|
||||
var got helpers.SubnetMap[NetworkAttributes]
|
||||
decoder, err := mapstructure.NewDecoder(helpers.GetMapStructureDecoderConfig(&got))
|
||||
if err != nil {
|
||||
t.Fatalf("NewDecoder() error:\n%+v", err)
|
||||
}
|
||||
@@ -86,7 +87,7 @@ func TestNetworkNamesUnmarshalHook(t *testing.T) {
|
||||
t.Fatalf("Decode() error:\n%+v", err)
|
||||
} else if err == nil && tc.Error {
|
||||
t.Fatal("Decode() did not return an error")
|
||||
} else if diff := helpers.Diff(got, tc.Output); diff != "" {
|
||||
} else if diff := helpers.Diff(got.ToMap(), tc.Output); diff != "" {
|
||||
t.Fatalf("Decode() (-got, +want):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -60,7 +60,7 @@ func (c *Component) registerHTTPHandlers() error {
|
||||
wr := csv.NewWriter(w)
|
||||
wr.Write([]string{"network", "name", "role", "site", "region", "tenant"})
|
||||
if c.config.Networks != nil {
|
||||
for k, v := range c.config.Networks {
|
||||
for k, v := range c.config.Networks.ToMap() {
|
||||
wr.Write([]string{k, v.Name, v.Role, v.Site, v.Region, v.Tenant})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
func TestHTTPEndpoints(t *testing.T) {
|
||||
r := reporter.NewMock(t)
|
||||
config := DefaultConfiguration()
|
||||
config.Networks = NetworkMap{
|
||||
"::ffff:192.0.2.0/24": NetworkAttributes{Name: "infra"},
|
||||
}
|
||||
config.Networks = helpers.MustNewSubnetMap(map[string]NetworkAttributes{
|
||||
"::ffff:192.0.2.0/120": {Name: "infra"},
|
||||
})
|
||||
c, err := New(r, config, Dependencies{
|
||||
Daemon: daemon.NewMock(t),
|
||||
HTTP: http.NewMock(t, r),
|
||||
@@ -47,7 +47,7 @@ func TestHTTPEndpoints(t *testing.T) {
|
||||
ContentType: "text/csv; charset=utf-8",
|
||||
FirstLines: []string{
|
||||
`network,name,role,site,region,tenant`,
|
||||
`::ffff:192.0.2.0/24,infra,,,,`,
|
||||
`192.0.2.0/24,infra,,,,`,
|
||||
},
|
||||
}, {
|
||||
URL: "/api/v0/orchestrator/clickhouse/init.sh",
|
||||
|
||||
Reference in New Issue
Block a user