diff --git a/cmd/internal/core/reconfigure-switch.go b/cmd/internal/core/reconfigure-switch.go index b93a4c2c..c6c307e7 100644 --- a/cmd/internal/core/reconfigure-switch.go +++ b/cmd/internal/core/reconfigure-switch.go @@ -120,6 +120,7 @@ func (c *Core) buildSwitcherConfig(s *models.V1SwitchResponse) (*types.Conf, err } continue } + // Firewall-Port if nic.Vrf == "default" { fw := &types.Firewall{ @@ -149,6 +150,8 @@ func (c *Core) buildSwitcherConfig(s *models.V1SwitchResponse) (*types.Conf, err p.Vrfs[nic.Vrf] = vrf } switcherConfig.Ports = p + + c.nos.SanitizeConfig(switcherConfig) switcherConfig.FillRouteMapsAndIPPrefixLists() m, err := vlan.ReadMapping() if err != nil { @@ -158,6 +161,7 @@ func (c *Core) buildSwitcherConfig(s *models.V1SwitchResponse) (*types.Conf, err if err != nil { return nil, err } + return switcherConfig, nil } diff --git a/cmd/internal/core/reconfigure-switch_test.go b/cmd/internal/core/reconfigure-switch_test.go index 4d2c5f6f..93ef682e 100644 --- a/cmd/internal/core/reconfigure-switch_test.go +++ b/cmd/internal/core/reconfigure-switch_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/metal-stack/metal-core/cmd/internal/switcher/cumulus" "github.com/metal-stack/metal-core/cmd/internal/switcher/types" "github.com/metal-stack/metal-go/api/models" ) @@ -18,6 +19,7 @@ func TestBuildSwitcherConfig(t *testing.T) { loopbackIP: "10.0.0.1", spineUplinks: []string{"swp31", "swp32"}, additionalBridgeVIDs: []string{"201-256", "301-356"}, + nos: &cumulus.Cumulus{}, } n1 := "swp1" @@ -71,7 +73,7 @@ func TestBuildSwitcherConfig(t *testing.T) { IPPrefixLists: []types.IPPrefixList{ { Name: "vrf104001-in-prefixes", - Spec: "permit 10.244.0.0/16 le 32", + Spec: "permit 10.240.0.0/12 le 32", }, }, RouteMaps: []types.RouteMap{ @@ -83,7 +85,7 @@ func TestBuildSwitcherConfig(t *testing.T) { }, }, }, - Cidrs: []string{"10.244.0.0/16"}, + Cidrs: []string{"10.240.0.0/12"}, }}, }, AdditionalBridgeVIDs: []string{"201-256", "301-356"}, diff --git a/cmd/internal/core/register-switch.go b/cmd/internal/core/register-switch.go index 922b6c99..cfca15c7 100644 --- a/cmd/internal/core/register-switch.go +++ b/cmd/internal/core/register-switch.go @@ -2,13 +2,11 @@ package core import ( "fmt" - "net" "os" "time" - "go.uber.org/zap" + "github.com/avast/retry-go/v4" - "github.com/metal-stack/metal-core/cmd/internal/switcher" sw "github.com/metal-stack/metal-go/api/client/switch_operations" "github.com/metal-stack/metal-go/api/models" ) @@ -24,7 +22,26 @@ func (c *Core) RegisterSwitch() error { managementUser string ) - if nics, err = getNics(c.log, c.nos, c.additionalBridgePorts); err != nil { + err = retry.Do( + func() error { + initialized, err := c.nos.IsInitialized() + if err != nil { + return err + } + if initialized { + return nil + } + return fmt.Errorf("switch is not yet initialized") + }, + retry.Attempts(120), + retry.Delay(1*time.Second), + retry.DelayType(retry.FixedDelay), + ) + if err != nil { + return fmt.Errorf("unable to register switch because it is not initialized: %w", err) + } + + if nics, err = c.nos.GetNics(c.log, c.additionalBridgePorts); err != nil { return fmt.Errorf("unable to get nics: %w", err) } @@ -52,6 +69,7 @@ func (c *Core) RegisterSwitch() error { ManagementUser: managementUser, } + // TODO could be done with retry-go for { _, _, err := c.driver.SwitchOperations().RegisterSwitch(params, nil) if err == nil { @@ -63,33 +81,3 @@ func (c *Core) RegisterSwitch() error { c.log.Infow("register switch completed") return nil } - -func getNics(log *zap.SugaredLogger, nos switcher.NOS, blacklist []string) ([]*models.V1SwitchNic, error) { - var nics []*models.V1SwitchNic - ifs, err := nos.GetSwitchPorts() - if err != nil { - return nil, fmt.Errorf("unable to get all ifs: %w", err) - } -links: - for _, iface := range ifs { - name := iface.Name - mac := iface.HardwareAddr.String() - for _, b := range blacklist { - if b == name { - log.Debugw("skip interface, because it is contained in the blacklist", "interface", name, "blacklist", blacklist) - continue links - } - } - _, err := net.ParseMAC(mac) - if err != nil { - log.Debugw("skip interface with invalid mac", "interface", name, "MAC", mac) - continue - } - nic := &models.V1SwitchNic{ - Mac: &mac, - Name: &name, - } - nics = append(nics, nic) - } - return nics, nil -} diff --git a/cmd/internal/switcher/cumulus/applier.go b/cmd/internal/switcher/cumulus/applier.go index fa251830..2e23e1ca 100644 --- a/cmd/internal/switcher/cumulus/applier.go +++ b/cmd/internal/switcher/cumulus/applier.go @@ -32,7 +32,7 @@ func NewFrrApplier(tplPath string) *templates.Applier { Dest: frr, Reloader: reloadFrr, Tmp: frrTmp, - Tpl: templates.FrrTemplate(tplPath), + Tpl: templates.CumulusFrrTemplate(tplPath), ValidationService: frrValidationService, } } diff --git a/cmd/internal/switcher/cumulus/cumulus.go b/cmd/internal/switcher/cumulus/cumulus.go index 2b76bc12..e856106a 100644 --- a/cmd/internal/switcher/cumulus/cumulus.go +++ b/cmd/internal/switcher/cumulus/cumulus.go @@ -7,6 +7,7 @@ import ( "strings" "go.uber.org/zap" + "golang.org/x/exp/slices" "github.com/metal-stack/metal-core/cmd/internal" "github.com/metal-stack/metal-core/cmd/internal/switcher/templates" @@ -37,6 +38,40 @@ func (c *Cumulus) Apply(cfg *types.Conf) error { return c.frrApplier.Apply(cfg) } +func (c *Cumulus) IsInitialized() (initialized bool, err error) { + // FIXME decide how we can detect initialization is complete. + return true, nil +} + +func (c *Cumulus) GetNics(log *zap.SugaredLogger, blacklist []string) (nics []*models.V1SwitchNic, err error) { + ifs, err := c.GetSwitchPorts() + if err != nil { + return nil, fmt.Errorf("unable to get all ifs: %w", err) + } + + for _, iface := range ifs { + name := iface.Name + mac := iface.HardwareAddr.String() + if slices.Contains(blacklist, name) { + log.Debugw("skip interface, because it is contained in the blacklist", "interface", name, "blacklist", blacklist) + continue + } + + if _, err := net.ParseMAC(mac); err != nil { + log.Debugw("skip interface with invalid mac", "interface", name, "MAC", mac) + continue + } + + nic := &models.V1SwitchNic{ + Mac: &mac, + Name: &name, + } + nics = append(nics, nic) + } + + return nics, nil +} + func (c *Cumulus) GetSwitchPorts() ([]*net.Interface, error) { ifs, err := net.Interfaces() if err != nil { @@ -47,10 +82,7 @@ func (c *Cumulus) GetSwitchPorts() ([]*net.Interface, error) { for i := range ifs { iface := &ifs[i] if !strings.HasPrefix(iface.Name, "swp") { - c.log.Debug("skip interface, because only swp* interface are front panels", - zap.String("interface", iface.Name), - zap.String("MAC", iface.HardwareAddr.String()), - ) + c.log.Debugw("skip interface, because only swp* interface are front panels", "interface", iface.Name) continue } switchPorts = append(switchPorts, iface) @@ -58,6 +90,10 @@ func (c *Cumulus) GetSwitchPorts() ([]*net.Interface, error) { return switchPorts, nil } +func (c *Cumulus) SanitizeConfig(cfg *types.Conf) { + // nothing required here +} + func (c *Cumulus) GetOS() (*models.V1SwitchOS, error) { version := "unknown" lsbReleaseBytes, err := os.ReadFile("/etc/lsb-release") diff --git a/cmd/internal/switcher/nos.go b/cmd/internal/switcher/nos.go index 62f5e96b..40be1f95 100644 --- a/cmd/internal/switcher/nos.go +++ b/cmd/internal/switcher/nos.go @@ -1,22 +1,37 @@ package switcher import ( + "fmt" "net" + "os" "go.uber.org/zap" "github.com/metal-stack/metal-core/cmd/internal/switcher/cumulus" + "github.com/metal-stack/metal-core/cmd/internal/switcher/sonic" "github.com/metal-stack/metal-core/cmd/internal/switcher/types" "github.com/metal-stack/metal-go/api/models" ) type NOS interface { + SanitizeConfig(cfg *types.Conf) Apply(cfg *types.Conf) error + IsInitialized() (initialized bool, err error) + GetNics(log *zap.SugaredLogger, blacklist []string) ([]*models.V1SwitchNic, error) GetSwitchPorts() ([]*net.Interface, error) GetOS() (*models.V1SwitchOS, error) GetManagement() (ip, user string, err error) } -func NewNOS(log *zap.SugaredLogger, frrTplFile, interfacesTplFile string) NOS { - return cumulus.New(log.Named("cumulus"), frrTplFile, interfacesTplFile) +func NewNOS(log *zap.SugaredLogger, frrTplFile, interfacesTplFile string) (NOS, error) { + if _, err := os.Stat(sonic.SonicVersionFile); err == nil { + log.Infow("create sonic NOS") + nos, err := sonic.New(log.Named("sonic"), frrTplFile) + if err != nil { + return nil, fmt.Errorf("failed to initialize SONiC NOS %w", err) + } + return nos, nil + } + log.Infow("create cumulus NOS") + return cumulus.New(log.Named("cumulus"), frrTplFile, interfacesTplFile), nil } diff --git a/cmd/internal/switcher/sonic/db/appldb.go b/cmd/internal/switcher/sonic/db/appldb.go new file mode 100644 index 00000000..e8d2ca2b --- /dev/null +++ b/cmd/internal/switcher/sonic/db/appldb.go @@ -0,0 +1,21 @@ +package db + +import ( + "context" +) + +type ApplDB struct { + c *Client +} + +func newApplDB(addr string, id int, sep string) *ApplDB { + return &ApplDB{ + c: NewClient(addr, id, sep), + } +} + +func (d *ApplDB) ExistPortInitDone(ctx context.Context) (bool, error) { + key := Key{"PORT_TABLE", "PortInitDone"} + + return d.c.Exists(ctx, key) +} diff --git a/cmd/internal/switcher/sonic/db/asicdb.go b/cmd/internal/switcher/sonic/db/asicdb.go new file mode 100644 index 00000000..d9fa1ca1 --- /dev/null +++ b/cmd/internal/switcher/sonic/db/asicdb.go @@ -0,0 +1,67 @@ +package db + +import ( + "context" + "errors" + + "github.com/redis/go-redis/v9" +) + +type AsicDB struct { + c *Client +} + +type OID string + +func newAsicDB(addr string, id int, sep string) *AsicDB { + return &AsicDB{ + c: NewClient(addr, id, sep), + } +} + +func (d *AsicDB) GetPortIdBridgePortMap(ctx context.Context) (map[OID]OID, error) { + t := d.c.GetTable(Key{"ASIC_STATE", "SAI_OBJECT_TYPE_BRIDGE_PORT"}) + + bridges, err := t.GetView(ctx) + if err != nil { + return nil, err + } + + m := make(map[OID]OID, len(bridges)) + for bridge := range bridges { + port, err := t.HGet(ctx, bridge, "SAI_BRIDGE_PORT_ATTR_PORT_ID") + if err != nil { + if errors.Is(err, redis.Nil) { + continue + } + return nil, err + } + if len(port) == 0 { + continue + } + m[OID(port)] = OID(bridge) + } + return m, nil +} + +func (d *AsicDB) ExistBridgePort(ctx context.Context, bridgePort OID) (bool, error) { + key := Key{"ASIC_STATE", "SAI_OBJECT_TYPE_BRIDGE_PORT", string(bridgePort)} + + return d.c.Exists(ctx, key) +} + +func (d *AsicDB) ExistRouterInterface(ctx context.Context, rif OID) (bool, error) { + key := Key{"ASIC_STATE", "SAI_OBJECT_TYPE_ROUTER_INTERFACE", string(rif)} + + return d.c.Exists(ctx, key) +} + +func (d *AsicDB) InFecModeRs(ctx context.Context, port OID) (bool, error) { + key := Key{"ASIC_STATE", "SAI_OBJECT_TYPE_PORT", string(port)} + + result, err := d.c.HGet(ctx, key, "SAI_PORT_ATTR_FEC_MODE") + if err != nil { + return false, err + } + return result == "SAI_PORT_FEC_MODE_RS", err +} diff --git a/cmd/internal/switcher/sonic/db/asicdb_test.go b/cmd/internal/switcher/sonic/db/asicdb_test.go new file mode 100644 index 00000000..41a1fd4c --- /dev/null +++ b/cmd/internal/switcher/sonic/db/asicdb_test.go @@ -0,0 +1,26 @@ +package db + +import ( + "context" + "reflect" + "testing" +) + +func TestAsicDB_GetPortIdBridgePortMap(t *testing.T) { + c, mock := NewClientMock(":") + asic := &AsicDB{c: c} + bridgePort := "ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT:oid:0x3a000000001a0a" + want := map[OID]OID{OID("oid:0x1000000001251"): "oid:0x3a000000001a0a"} + + mock.ExpectKeys("ASIC_STATE:SAI_OBJECT_TYPE_BRIDGE_PORT:*").SetVal([]string{bridgePort}) + mock.ExpectHGet(bridgePort, "SAI_BRIDGE_PORT_ATTR_PORT_ID").SetVal("oid:0x1000000001251") + + got, err := asic.GetPortIdBridgePortMap(context.TODO()) + if err != nil { + t.Errorf("GetPortIdBridgePortMap() error = %v, wantErr %v", err, nil) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("GetPortIdBridgePortMap() got = %v, want %v", got, want) + } +} diff --git a/cmd/internal/switcher/sonic/db/client.go b/cmd/internal/switcher/sonic/db/client.go new file mode 100644 index 00000000..619349ef --- /dev/null +++ b/cmd/internal/switcher/sonic/db/client.go @@ -0,0 +1,104 @@ +package db + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/redis/go-redis/v9" +) + +type Key []string +type Val map[string]string + +func (k *Key) toString(sep string) string { + return strings.Join(*k, sep) +} + +type Client struct { + rdb *redis.Client + sep string +} + +func NewClient(addr string, id int, sep string) *Client { + rdb := redis.NewClient(&redis.Options{ + Addr: addr, + DB: id, + PoolSize: 1, + }) + return &Client{ + rdb: rdb, + sep: sep, + } +} + +func (c *Client) Del(ctx context.Context, key Key) error { + return c.rdb.Del(ctx, key.toString(c.sep)).Err() +} + +func (c *Client) Exists(ctx context.Context, key Key) (bool, error) { + result, err := c.rdb.Exists(ctx, key.toString(c.sep)).Result() + if err != nil { + return false, err + } + return result != 0, nil +} + +func (c *Client) GetTable(table Key) *Table { + return &Table{ + client: c, + name: table.toString(c.sep), + } +} + +func (c *Client) GetView(ctx context.Context, table string) (View, error) { + pattern := table + c.sep + "*" + keys, err := c.rdb.Keys(ctx, pattern).Result() + if err != nil { + return nil, err + } + + view := NewView(len(keys)) + for _, key := range keys { + if len(key) < len(pattern) { + return nil, fmt.Errorf("key %s is slower than pattern %s", key, pattern) + } + item := key[len(table)+1:] + view.Add(item) + } + return view, nil +} + +func (c *Client) HGet(ctx context.Context, key Key, field string) (string, error) { + result, err := c.rdb.HGet(ctx, key.toString(c.sep), field).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return "", nil + } + return "", err + } + return result, nil +} + +func (c *Client) HGetAll(ctx context.Context, key Key) (Val, error) { + return c.rdb.HGetAll(ctx, key.toString(c.sep)).Result() +} + +func (c *Client) HSet(ctx context.Context, key Key, val Val) error { + return c.rdb.HSet(ctx, key.toString(c.sep), map[string]string(val)).Err() +} + +func (c *Client) Keys(ctx context.Context, pattern Key) ([]Key, error) { + result, err := c.rdb.Keys(ctx, pattern.toString(c.sep)).Result() + if err != nil { + return nil, err + } + + keys := make([]Key, 0, len(result)) + for _, item := range result { + key := strings.Split(item, c.sep) + keys = append(keys, key) + } + return keys, nil +} diff --git a/cmd/internal/switcher/sonic/db/client_test.go b/cmd/internal/switcher/sonic/db/client_test.go new file mode 100644 index 00000000..0583f1bb --- /dev/null +++ b/cmd/internal/switcher/sonic/db/client_test.go @@ -0,0 +1,152 @@ +package db + +import ( + "context" + "reflect" + "testing" + + "github.com/go-redis/redismock/v9" +) + +func NewClientMock(sep string) (*Client, redismock.ClientMock) { + db, mock := redismock.NewClientMock() + c := &Client{ + rdb: db, + sep: sep, + } + return c, mock +} + +func TestClient_Del(t *testing.T) { + c, mock := NewClientMock("|") + + mock.ExpectDel("table|entry").SetVal(1) + + if err := c.Del(context.Background(), Key{"table", "entry"}); err != nil { + t.Errorf("Del() error = %v, wantErr %v", err, false) + } +} + +func TestClient_Exists(t *testing.T) { + tests := []struct { + name string + val int64 + want bool + }{ + { + name: "Doesn't exist", + val: 0, + want: false, + }, { + name: "Exists", + val: 1, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, mock := NewClientMock("|") + + mock.ExpectExists("table|key").SetVal(tt.val) + + got, err := c.Exists(context.Background(), Key{"table", "key"}) + if err != nil { + t.Errorf("Exists() error = %v, wantErr %v", err, false) + return + } + if got != tt.want { + t.Errorf("Exists() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestClient_GetTable(t *testing.T) { + c, _ := NewClientMock("|") + want := &Table{ + client: c, + name: "table|sub", + } + + if got := c.GetTable(Key{"table", "sub"}); !reflect.DeepEqual(got, want) { + t.Errorf("GetTable() = %v, want %v", got, want) + } +} + +func TestClient_GetView(t *testing.T) { + c, mock := NewClientMock("|") + want := View{"key": {}, "key1|key2": {}} + + mock.ExpectKeys("table|*").SetVal([]string{"table|key", "table|key1|key2"}) + + got, err := c.GetView(context.Background(), "table") + if err != nil { + t.Errorf("GetView() error = %v, wantErr %v", err, false) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("GetView() got = %v, want %v", got, want) + } +} + +func TestClient_HGet(t *testing.T) { + c, mock := NewClientMock("|") + + mock.ExpectHGet("table|key", "field").RedisNil() + + got, err := c.HGet(context.Background(), Key{"table", "key"}, "field") + if err != nil { + t.Errorf("HGet() error = %v, wantErr %v", err, false) + return + } + if len(got) != 0 { + t.Errorf("HGet() got = %v, want %v", got, "") + } +} + +func TestClient_HGetAll(t *testing.T) { + c, mock := NewClientMock("|") + want := Val{"key": "test"} + + mock.ExpectHGetAll("table|key").SetVal(map[string]string{"key": "test"}) + + got, err := c.HGetAll(context.Background(), Key{"table", "key"}) + if err != nil { + t.Errorf("HGetAll() error = %v, wantErr %v", err, false) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("HGetAll() got = %v, want %v", got, want) + } +} + +func TestClient_HSet(t *testing.T) { + c, mock := NewClientMock("|") + + val := Val{"key": "test"} + mock.ExpectHSet("table|key", "key", "test").SetVal(1) + + err := c.HSet(context.Background(), Key{"table", "key"}, val) + if err != nil { + t.Errorf("HSet() error = %v, wantErr %v", err, false) + } +} + +func TestClient_Keys(t *testing.T) { + c, mock := NewClientMock("|") + want := []Key{ + {"table", "key"}, + {"table", "key1", "key2"}, + } + + mock.ExpectKeys("table|*").SetVal([]string{"table|key", "table|key1|key2"}) + + got, err := c.Keys(context.Background(), Key{"table", "*"}) + if err != nil { + t.Errorf("Keys() error = %v, wantErr %v", err, false) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Keys() got = %v, want %v", got, want) + } +} diff --git a/cmd/internal/switcher/sonic/db/configdb.go b/cmd/internal/switcher/sonic/db/configdb.go new file mode 100644 index 00000000..c77c53ea --- /dev/null +++ b/cmd/internal/switcher/sonic/db/configdb.go @@ -0,0 +1,201 @@ +package db + +import ( + "context" + "fmt" +) + +const ( + enable = "enable" + interfaceTable = "INTERFACE" + linkLocalOnly = "ipv6_use_link_local_only" + vlanMemberTable = "VLAN_MEMBER" + taggingMode = "tagging_mode" + untagged = "untagged" + vrfName = "vrf_name" + portTable = "PORT" + mtu = "mtu" + fec = "fec" + fecRS = "rs" + fecNone = "none" +) + +type ConfigDB struct { + c *Client +} + +type Port struct { + Mtu string + FecRs bool +} + +func newConfigDB(addr string, id int, sep string) *ConfigDB { + return &ConfigDB{ + c: NewClient(addr, id, sep), + } +} + +func (d *ConfigDB) ExistVlan(ctx context.Context, vid uint16) (bool, error) { + key := Key{"VLAN", fmt.Sprintf("Vlan%d", vid)} + + return d.c.Exists(ctx, key) +} + +func (d *ConfigDB) CreateVlan(ctx context.Context, vid uint16) error { + vlanId := fmt.Sprintf("%d", vid) + key := Key{"VLAN", "Vlan" + vlanId} + + return d.c.HSet(ctx, key, Val{"vlanid": vlanId}) +} + +func (d *ConfigDB) AreNeighborsSuppressed(ctx context.Context, vid uint16) (bool, error) { + key := Key{"SUPPRESS_VLAN_NEIGH", fmt.Sprintf("Vlan%d", vid)} + + suppress, err := d.c.HGet(ctx, key, "suppress") + if err != nil { + return false, err + } + return suppress == "on", nil +} + +func (d *ConfigDB) SuppressNeighbors(ctx context.Context, vid uint16) error { + key := Key{"SUPPRESS_VLAN_NEIGH", fmt.Sprintf("Vlan%d", vid)} + + return d.c.HSet(ctx, key, Val{"suppress": "on"}) +} + +func (d *ConfigDB) ExistVlanInterface(ctx context.Context, vid uint16) (bool, error) { + key := Key{"VLAN_INTERFACE", fmt.Sprintf("Vlan%d", vid)} + + return d.c.Exists(ctx, key) +} + +func (d *ConfigDB) CreateVlanInterface(ctx context.Context, vid uint16, vrf string) error { + key := Key{"VLAN_INTERFACE", "Vlan" + fmt.Sprintf("%d", vid)} + + return d.c.HSet(ctx, key, Val{vrfName: vrf}) +} + +func (d *ConfigDB) GetVlanMembership(ctx context.Context, interfaceName string) ([]string, error) { + pattern := Key{vlanMemberTable, "*", interfaceName} + + keys, err := d.c.Keys(ctx, pattern) + if err != nil { + return nil, err + } + + vlans := make([]string, 0, len(keys)) + for _, key := range keys { + if len(key) != 3 { + return nil, fmt.Errorf("could not parse key %v", key) + } + vlans = append(vlans, key[1]) + } + return vlans, nil +} + +func (d *ConfigDB) SetVlanMember(ctx context.Context, interfaceName, vlan string) error { + key := Key{vlanMemberTable, vlan, interfaceName} + + return d.c.HSet(ctx, key, Val{taggingMode: untagged}) +} + +func (d *ConfigDB) DeleteVlanMember(ctx context.Context, interfaceName, vlan string) error { + key := Key{vlanMemberTable, vlan, interfaceName} + + return d.c.Del(ctx, key) +} + +func (d *ConfigDB) ExistVrf(ctx context.Context, vrf string) (bool, error) { + key := Key{"VRF", vrf} + + return d.c.Exists(ctx, key) +} + +func (d *ConfigDB) CreateVrf(ctx context.Context, vrf string) error { + key := Key{"VRF", vrf} + + return d.c.HSet(ctx, key, Val{"NULL": "NULL"}) +} + +func (d *ConfigDB) SetVrfMember(ctx context.Context, interfaceName string, vrf string) error { + key := Key{interfaceTable, interfaceName} + + return d.c.HSet(ctx, key, Val{linkLocalOnly: enable, vrfName: vrf}) +} + +func (d *ConfigDB) GetVrfMembership(ctx context.Context, interfaceName string) (string, error) { + key := Key{interfaceTable, interfaceName} + + return d.c.HGet(ctx, key, vrfName) +} + +func (d *ConfigDB) ExistVxlanTunnelMap(ctx context.Context, vid uint16, vni uint32) (bool, error) { + key := Key{"VXLAN_TUNNEL_MAP", "vtep", fmt.Sprintf("map_%d_Vlan%d", vni, vid)} + + return d.c.Exists(ctx, key) +} + +func (d *ConfigDB) CreateVxlanTunnelMap(ctx context.Context, vid uint16, vni uint32) error { + key := Key{"VXLAN_TUNNEL_MAP", "vtep", fmt.Sprintf("map_%d_Vlan%d", vni, vid)} + val := Val{ + "vlan": fmt.Sprintf("Vlan%d", vid), + "vni": fmt.Sprintf("%d", vni), + } + return d.c.HSet(ctx, key, val) +} + +func (d *ConfigDB) DeleteInterfaceConfiguration(ctx context.Context, interfaceName string) error { + key := Key{interfaceTable, interfaceName} + + return d.c.Del(ctx, key) +} + +func (d *ConfigDB) IsLinkLocalOnly(ctx context.Context, interfaceName string) (bool, error) { + key := Key{interfaceTable, interfaceName} + + result, err := d.c.HGet(ctx, key, linkLocalOnly) + if err != nil { + return false, err + } + return result == enable, nil +} + +func (d *ConfigDB) EnableLinkLocalOnly(ctx context.Context, interfaceName string) error { + key := Key{interfaceTable, interfaceName} + + return d.c.HSet(ctx, key, Val{linkLocalOnly: enable}) +} + +func (d *ConfigDB) GetPort(ctx context.Context, interfaceName string) (*Port, error) { + key := Key{portTable, interfaceName} + + result, err := d.c.HGetAll(ctx, key) + if err != nil { + return nil, err + } + + return &Port{ + Mtu: result[mtu], + FecRs: result[fec] == fecRS, + }, nil +} + +func (d *ConfigDB) SetPortFecMode(ctx context.Context, interfaceName string, isFecRs bool) error { + key := Key{portTable, interfaceName} + + var mode string + if isFecRs { + mode = fecRS + } else { + mode = fecNone + } + + return d.c.HSet(ctx, key, Val{fec: mode}) +} + +func (d *ConfigDB) SetPortMtu(ctx context.Context, interfaceName string, val string) error { + key := Key{portTable, interfaceName} + + return d.c.HSet(ctx, key, Val{mtu: val}) +} diff --git a/cmd/internal/switcher/sonic/db/countersdb.go b/cmd/internal/switcher/sonic/db/countersdb.go new file mode 100644 index 00000000..c0b2d99a --- /dev/null +++ b/cmd/internal/switcher/sonic/db/countersdb.go @@ -0,0 +1,33 @@ +package db + +import ( + "context" +) + +type CountersDB struct { + c *Client +} + +func newCountersDB(addr string, id int, sep string) *CountersDB { + return &CountersDB{ + c: NewClient(addr, id, sep), + } +} + +func (d *CountersDB) GetPortNameMap(ctx context.Context) (map[string]OID, error) { + val, err := d.c.HGetAll(ctx, Key{"COUNTERS_PORT_NAME_MAP"}) + return toOIDMap(val), err +} + +func (d *CountersDB) GetRifNameMap(ctx context.Context) (map[string]OID, error) { + val, err := d.c.HGetAll(ctx, Key{"COUNTERS_RIF_NAME_MAP"}) + return toOIDMap(val), err +} + +func toOIDMap(val Val) map[string]OID { + m := make(map[string]OID) + for k, v := range val { + m[k] = OID(v) + } + return m +} diff --git a/cmd/internal/switcher/sonic/db/db.go b/cmd/internal/switcher/sonic/db/db.go new file mode 100644 index 00000000..a3177c13 --- /dev/null +++ b/cmd/internal/switcher/sonic/db/db.go @@ -0,0 +1,37 @@ +package db + +type Config struct { + Databases map[string]database `json:"DATABASES"` + Instances map[string]instance `json:"INSTANCES"` +} + +type database struct { + Id int `json:"id"` + Instance string `json:"instance"` + Separator string `json:"separator"` +} + +type instance struct { + Addr string `json:"unix_socket_path"` +} + +type DB struct { + Appl *ApplDB + Asic *AsicDB + Config *ConfigDB + Counters *CountersDB +} + +func New(cfg *Config) *DB { + applDB := cfg.Databases["APPL_DB"] + asicDB := cfg.Databases["ASIC_DB"] + configDB := cfg.Databases["CONFIG_DB"] + countersDB := cfg.Databases["COUNTERS_DB"] + + return &DB{ + Appl: newApplDB(cfg.Instances[applDB.Instance].Addr, applDB.Id, applDB.Separator), + Asic: newAsicDB(cfg.Instances[asicDB.Instance].Addr, asicDB.Id, asicDB.Separator), + Config: newConfigDB(cfg.Instances[configDB.Instance].Addr, configDB.Id, configDB.Separator), + Counters: newCountersDB(cfg.Instances[countersDB.Instance].Addr, countersDB.Id, countersDB.Separator), + } +} diff --git a/cmd/internal/switcher/sonic/db/table.go b/cmd/internal/switcher/sonic/db/table.go new file mode 100644 index 00000000..1aebffc3 --- /dev/null +++ b/cmd/internal/switcher/sonic/db/table.go @@ -0,0 +1,34 @@ +package db + +import ( + "context" +) + +type Table struct { + client *Client + name string +} + +func (t *Table) Del(ctx context.Context, item string) error { + return t.client.Del(ctx, Key{t.name, item}) +} + +func (t *Table) Exists(ctx context.Context, item string) (bool, error) { + return t.client.Exists(ctx, Key{t.name, item}) +} + +func (t *Table) GetView(ctx context.Context) (View, error) { + return t.client.GetView(ctx, t.name) +} + +func (t *Table) HGet(ctx context.Context, item string, field string) (string, error) { + return t.client.HGet(ctx, Key{t.name, item}, field) +} + +func (t *Table) HGetAll(ctx context.Context, item string) (Val, error) { + return t.client.HGetAll(ctx, Key{t.name, item}) +} + +func (t *Table) HSet(ctx context.Context, item string, val Val) error { + return t.client.HSet(ctx, Key{t.name, item}, val) +} diff --git a/cmd/internal/switcher/sonic/db/view.go b/cmd/internal/switcher/sonic/db/view.go new file mode 100644 index 00000000..dfb59305 --- /dev/null +++ b/cmd/internal/switcher/sonic/db/view.go @@ -0,0 +1,20 @@ +package db + +type View map[string]struct{} + +func NewView(size int) View { + return make(View, size) +} + +func (v View) Add(item string) { + v[item] = struct{}{} +} + +func (v View) Remove(item string) { + delete(v, item) +} + +func (v View) Has(item string) bool { + _, ok := v[item] + return ok +} diff --git a/cmd/internal/switcher/sonic/frr.go b/cmd/internal/switcher/sonic/frr.go new file mode 100644 index 00000000..5f33a6e3 --- /dev/null +++ b/cmd/internal/switcher/sonic/frr.go @@ -0,0 +1,27 @@ +package sonic + +import ( + "github.com/metal-stack/metal-core/cmd/internal/dbus" + "github.com/metal-stack/metal-core/cmd/internal/switcher/templates" +) + +const ( + frr = "/etc/sonic/frr/frr.conf" + frrTmp = "/etc/sonic/frr/frr.tmp" + frrReloadService = "frr-reload.service" + frrValidationService = "bgp-validation" +) + +func NewFrrApplier(tplPath string) *templates.Applier { + return &templates.Applier{ + Dest: frr, + Reloader: reloadFrr, + Tmp: frrTmp, + Tpl: templates.SonicFrrTemplate(tplPath), + ValidationService: frrValidationService, + } +} + +func reloadFrr() error { + return dbus.Start(frrReloadService) +} diff --git a/cmd/internal/switcher/sonic/redis/applier.go b/cmd/internal/switcher/sonic/redis/applier.go new file mode 100644 index 00000000..759a5ea2 --- /dev/null +++ b/cmd/internal/switcher/sonic/redis/applier.go @@ -0,0 +1,236 @@ +package redis + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/google/go-cmp/cmp" + "go.uber.org/zap" + + "github.com/metal-stack/metal-core/cmd/internal/switcher/sonic/db" + "github.com/metal-stack/metal-core/cmd/internal/switcher/types" +) + +type Applier struct { + db *db.DB + log *zap.SugaredLogger + previousCfg *types.Conf + + bridgePortOidMap map[string]db.OID + portOidMap map[string]db.OID + rifOidMap map[string]db.OID +} + +func NewApplier(log *zap.SugaredLogger, db *db.DB) *Applier { + return &Applier{ + db: db, + log: log, + } +} + +func (a *Applier) Apply(cfg *types.Conf) error { + var errs []error + + // only process if changes are detected + if a.previousCfg != nil { + diff := cmp.Diff(a.previousCfg, cfg) + if diff == "" { + a.log.Infow("no changes on interfaces detected, nothing to do") + return nil + } else { + a.log.Debugw("interface changes", "changes", diff) + } + } + + if err := a.refreshOidMaps(); err != nil { + return err + } + + for _, interfaceName := range cfg.Ports.Underlay { + if err := a.configureUnderlayPort(interfaceName); err != nil { + errs = append(errs, err) + } + } + + for _, interfaceName := range cfg.Ports.Unprovisioned { + if err := a.configureUnprovisionedPort(interfaceName); err != nil { + errs = append(errs, err) + } + } + + for interfaceName := range cfg.Ports.Firewalls { + if err := a.configureFirewallPort(interfaceName); err != nil { + errs = append(errs, err) + } + } + + for vrfName, vrf := range cfg.Ports.Vrfs { + if err := a.configureVrf(vrfName, vrf); err != nil { + errs = append(errs, err) + } + for _, interfaceName := range vrf.Neighbors { + if err := a.configureVrfNeighbor(interfaceName, vrfName); err != nil { + errs = append(errs, err) + } + } + } + + // config is only treated as applied if no errors are encountered + if len(errs) == 0 { + a.previousCfg = cfg + } + return errors.Join(errs...) +} + +func (a *Applier) refreshOidMaps() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + oidMap, err := a.db.Counters.GetPortNameMap(ctx) + if err != nil { + return fmt.Errorf("could not update port to oid map: %w", err) + } + a.portOidMap = oidMap + + oidMap, err = a.db.Counters.GetRifNameMap(ctx) + if err != nil { + return fmt.Errorf("could not update rif to oid ma: %w", err) + } + a.rifOidMap = oidMap + + bridgePortMap, err := a.db.Asic.GetPortIdBridgePortMap(ctx) + if err != nil { + return fmt.Errorf("could not update bridge port to oid map: %w", err) + } + oidMap = make(map[string]db.OID, len(bridgePortMap)) + for port, oid := range a.portOidMap { + if bridgePort, ok := bridgePortMap[oid]; ok { + oidMap[port] = bridgePort + } + } + a.bridgePortOidMap = oidMap + + return nil +} + +func (a *Applier) configureUnprovisionedPort(interfaceName string) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err := a.ensureNotRouted(ctx, interfaceName) + if err != nil { + return err + } + + if err := a.ensurePortConfiguration(ctx, interfaceName, "9000", true); err != nil { + return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err) + } + + return a.ensureInterfaceIsVlanMember(ctx, interfaceName, "Vlan4000") +} + +func (a *Applier) configureFirewallPort(interfaceName string) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err := a.ensureNotBridged(ctx, interfaceName) + if err != nil { + return err + } + + if err := a.ensurePortConfiguration(ctx, interfaceName, "9216", true); err != nil { + return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err) + } + + return a.ensureLinkLocalOnlyIsEnabled(ctx, interfaceName) +} + +func (a *Applier) configureUnderlayPort(interfaceName string) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := a.ensurePortConfiguration(ctx, interfaceName, "9216", false); err != nil { + return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err) + } + return a.ensureLinkLocalOnlyIsEnabled(ctx, interfaceName) +} + +func (a *Applier) configureVrfNeighbor(interfaceName, vrfName string) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err := a.ensureNotBridged(ctx, interfaceName) + if err != nil { + return err + } + + err = a.ensureInterfaceIsVrfMember(ctx, interfaceName, vrfName) + if err != nil { + return err + } + + if err := a.ensurePortConfiguration(ctx, interfaceName, "9000", true); err != nil { + return fmt.Errorf("failed to update Port info for interface %s: %w", interfaceName, err) + } + + return a.ensureLinkLocalOnlyIsEnabled(ctx, interfaceName) +} + +func (a *Applier) configureVrf(vrfName string, vrf *types.Vrf) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + exist, err := a.db.Config.ExistVrf(ctx, vrfName) + if err != nil { + return err + } + if !exist { + if err := a.db.Config.CreateVrf(ctx, vrfName); err != nil { + return fmt.Errorf("could not create vrf %s: %w", vrfName, err) + } + } + + exist, err = a.db.Config.ExistVlan(ctx, vrf.VLANID) + if err != nil { + return err + } + if !exist { + if err := a.db.Config.CreateVlan(ctx, vrf.VLANID); err != nil { + return fmt.Errorf("could not create vlan %d: %w", vrf.VLANID, err) + } + } + + exist, err = a.db.Config.AreNeighborsSuppressed(ctx, vrf.VLANID) + if err != nil { + return err + } + if !exist { + if err := a.db.Config.SuppressNeighbors(ctx, vrf.VLANID); err != nil { + return fmt.Errorf("could not suppress neighbors for vlan %d: %w", vrf.VLANID, err) + } + } + + exist, err = a.db.Config.ExistVlanInterface(ctx, vrf.VLANID) + if err != nil { + return err + } + if !exist { + if err := a.db.Config.CreateVlanInterface(ctx, vrf.VLANID, vrfName); err != nil { + return fmt.Errorf("could not create vlan interface for vlan %d: %w", vrf.VLANID, err) + } + } + + exist, err = a.db.Config.ExistVxlanTunnelMap(ctx, vrf.VLANID, vrf.VNI) + if err != nil { + return err + } + if !exist { + if err := a.db.Config.CreateVxlanTunnelMap(ctx, vrf.VLANID, vrf.VNI); err != nil { + return fmt.Errorf("could not create vxlan tunnel between vlan %d and vni %d: %w", vrf.VLANID, vrf.VNI, err) + } + } + + return nil +} diff --git a/cmd/internal/switcher/sonic/redis/interface.go b/cmd/internal/switcher/sonic/redis/interface.go new file mode 100644 index 00000000..c2f4c055 --- /dev/null +++ b/cmd/internal/switcher/sonic/redis/interface.go @@ -0,0 +1,42 @@ +package redis + +import ( + "context" + "fmt" + + "github.com/avast/retry-go/v4" +) + +func (a *Applier) ensureNotRouted(ctx context.Context, interfaceName string) error { + oid, ok := a.rifOidMap[interfaceName] + if !ok { + return nil + } + routed, err := a.db.Asic.ExistRouterInterface(ctx, oid) + if err != nil { + return fmt.Errorf("could not retrieve state data for interface %s: %w", interfaceName, err) + } + if !routed { + return nil + } + + a.log.Infof("remove routing configuration for interface %s", interfaceName) + err = a.db.Config.DeleteInterfaceConfiguration(ctx, interfaceName) + if err != nil { + return fmt.Errorf("could not remove configuration for interface %s: %w", interfaceName, err) + } + + return retry.Do( + func() error { + configured, err := a.db.Asic.ExistRouterInterface(ctx, oid) + if err != nil { + return err + } + if configured { + a.log.Debugf("interface %s is still routed", interfaceName) + return fmt.Errorf("interface %s is still routed", interfaceName) + } + return nil + }, + ) +} diff --git a/cmd/internal/switcher/sonic/redis/ipv6.go b/cmd/internal/switcher/sonic/redis/ipv6.go new file mode 100644 index 00000000..b0e91bcf --- /dev/null +++ b/cmd/internal/switcher/sonic/redis/ipv6.go @@ -0,0 +1,17 @@ +package redis + +import ( + "context" + "fmt" +) + +func (a *Applier) ensureLinkLocalOnlyIsEnabled(ctx context.Context, interfaceName string) error { + enabled, err := a.db.Config.IsLinkLocalOnly(ctx, interfaceName) + if err != nil { + return fmt.Errorf("could not retrieve interface status for %s: %w", interfaceName, err) + } + if enabled { + return nil + } + return a.db.Config.EnableLinkLocalOnly(ctx, interfaceName) +} diff --git a/cmd/internal/switcher/sonic/redis/port.go b/cmd/internal/switcher/sonic/redis/port.go new file mode 100644 index 00000000..b74f076f --- /dev/null +++ b/cmd/internal/switcher/sonic/redis/port.go @@ -0,0 +1,55 @@ +package redis + +import ( + "context" + "fmt" + + "github.com/avast/retry-go/v4" +) + +func (a *Applier) ensurePortConfiguration(ctx context.Context, portName, mtu string, isFecRs bool) error { + p, err := a.db.Config.GetPort(ctx, portName) + if err != nil { + return fmt.Errorf("could not retrieve port info for %s from redis: %w", portName, err) + } + + if p.FecRs != isFecRs { + a.log.Debugf("set port %s rs mode to %v", portName, isFecRs) + err = a.ensurePortFecMode(ctx, portName, isFecRs) + if err != nil { + return err + } + } + + if p.Mtu != mtu { + a.log.Debugf("set port %s mtu to %s", portName, mtu) + return a.db.Config.SetPortMtu(ctx, portName, mtu) + } + + return nil +} + +func (a *Applier) ensurePortFecMode(ctx context.Context, portName string, wantFecRs bool) error { + err := a.db.Config.SetPortFecMode(ctx, portName, wantFecRs) + if err != nil { + return fmt.Errorf("could not update Fec for port %s: %w", portName, err) + } + + oid, ok := a.portOidMap[portName] + if !ok { + return fmt.Errorf("no mapping of port %s to OID", portName) + } + + return retry.Do( + func() error { + isFecRs, err := a.db.Asic.InFecModeRs(ctx, oid) + if err != nil { + return err + } + if isFecRs != wantFecRs { + return fmt.Errorf("port %s still has rs mode = %v, but want %v", portName, isFecRs, wantFecRs) + } + return nil + }, + ) +} diff --git a/cmd/internal/switcher/sonic/redis/vlan.go b/cmd/internal/switcher/sonic/redis/vlan.go new file mode 100644 index 00000000..93d9a9b4 --- /dev/null +++ b/cmd/internal/switcher/sonic/redis/vlan.go @@ -0,0 +1,64 @@ +package redis + +import ( + "context" + "fmt" + + "github.com/avast/retry-go/v4" +) + +func (a *Applier) ensureNotBridged(ctx context.Context, interfaceName string) error { + oid, ok := a.bridgePortOidMap[interfaceName] + if !ok { + return nil + } + bridged, err := a.db.Asic.ExistBridgePort(ctx, oid) + if err != nil { + return fmt.Errorf("could not retrieve state data for interface %s: %w", interfaceName, err) + } + if !bridged { + return nil + } + + vlans, err := a.db.Config.GetVlanMembership(ctx, interfaceName) + if err != nil { + return fmt.Errorf("could not retrieve vlan membership for %s from Redis: %w", interfaceName, err) + } + + for _, vlan := range vlans { + a.log.Infof("remove interface %s from vlan %s", interfaceName, vlan) + err := a.db.Config.DeleteVlanMember(ctx, interfaceName, vlan) + if err != nil { + return fmt.Errorf("could not remove interface %s from vlan %s", interfaceName, vlan) + } + } + + return retry.Do( + func() error { + bridged, err := a.db.Asic.ExistBridgePort(ctx, oid) + if err != nil { + return err + } + if bridged { + return fmt.Errorf("interface %s still bridged", interfaceName) + } + return nil + }, + ) +} + +func (a *Applier) ensureInterfaceIsVlanMember(ctx context.Context, interfaceName, vlan string) error { + current, err := a.db.Config.GetVlanMembership(ctx, interfaceName) + if err != nil { + return fmt.Errorf("could not retrieve vlan current for %s from redis: %w", interfaceName, err) + } + + if len(current) == 1 && current[0] == vlan { + return nil + } else if len(current) != 0 { + return fmt.Errorf("interface %s already member of a different vlan %v", interfaceName, current) + } + + a.log.Infof("add interface %s to vlan %s", interfaceName, vlan) + return a.db.Config.SetVlanMember(ctx, interfaceName, vlan) +} diff --git a/cmd/internal/switcher/sonic/redis/vrf.go b/cmd/internal/switcher/sonic/redis/vrf.go new file mode 100644 index 00000000..0a76c844 --- /dev/null +++ b/cmd/internal/switcher/sonic/redis/vrf.go @@ -0,0 +1,22 @@ +package redis + +import ( + "context" + "fmt" +) + +func (a *Applier) ensureInterfaceIsVrfMember(ctx context.Context, interfaceName, vrfName string) error { + current, err := a.db.Config.GetVrfMembership(ctx, interfaceName) + if err != nil { + return fmt.Errorf("could not retrieve vrfName membership for %s from redis: %w", interfaceName, err) + } + + if current == vrfName { + return nil + } else if len(current) != 0 { + return fmt.Errorf("interface %s already member of a different vrfName %v", interfaceName, current) + } + + a.log.Infof("add interface %s to vrfName %s", interfaceName, vrfName) + return a.db.Config.SetVrfMember(ctx, interfaceName, vrfName) +} diff --git a/cmd/internal/switcher/sonic/sonic.go b/cmd/internal/switcher/sonic/sonic.go new file mode 100644 index 00000000..a1e785f4 --- /dev/null +++ b/cmd/internal/switcher/sonic/sonic.go @@ -0,0 +1,188 @@ +package sonic + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "os" + "strings" + "time" + + "go.uber.org/zap" + "golang.org/x/exp/slices" + "gopkg.in/yaml.v3" + + "github.com/metal-stack/metal-core/cmd/internal" + "github.com/metal-stack/metal-core/cmd/internal/switcher/sonic/db" + "github.com/metal-stack/metal-core/cmd/internal/switcher/sonic/redis" + "github.com/metal-stack/metal-core/cmd/internal/switcher/templates" + "github.com/metal-stack/metal-core/cmd/internal/switcher/types" + "github.com/metal-stack/metal-go/api/models" +) + +const ( + sonicConfigDBPath = "/etc/sonic/config_db.json" + SonicVersionFile = "/etc/sonic/sonic_version.yml" + redisConfigFile = "/var/run/redis/sonic-db/database_config.json" +) + +type Sonic struct { + db *db.DB + frrApplier *templates.Applier + log *zap.SugaredLogger + redisApplier *redis.Applier +} + +type PortInfo struct { + Alias string +} + +func New(log *zap.SugaredLogger, frrTplFile string) (*Sonic, error) { + cfg, err := loadRedisConfig(redisConfigFile) + if err != nil { + return nil, fmt.Errorf("failed to load database config for SONiC: %w", err) + } + sonicDb := db.New(cfg) + + return &Sonic{ + db: sonicDb, + frrApplier: NewFrrApplier(frrTplFile), + log: log, + redisApplier: redis.NewApplier(log, sonicDb), + }, nil +} + +func loadRedisConfig(path string) (*db.Config, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + cfg := &db.Config{} + err = json.Unmarshal(data, cfg) + if err != nil { + return nil, err + } + return cfg, nil +} + +func (s *Sonic) Apply(cfg *types.Conf) error { + err := s.redisApplier.Apply(cfg) + if err != nil { + return err + } + + return s.frrApplier.Apply(cfg) +} + +func (s *Sonic) IsInitialized() (initialized bool, err error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + return s.db.Appl.ExistPortInitDone(ctx) +} + +func (s *Sonic) GetNics(log *zap.SugaredLogger, blacklist []string) (nics []*models.V1SwitchNic, err error) { + ifs, err := s.GetSwitchPorts() + if err != nil { + return nil, fmt.Errorf("unable to get all ifs: %w", err) + } + + portsConfig, err := getPortsConfig(sonicConfigDBPath) + if err != nil { + return nil, fmt.Errorf("failed to get ports config") + } + + for _, iface := range ifs { + name := iface.Name + if slices.Contains(blacklist, name) { + log.Debugw("skip interface, because it is contained in the blacklist", "interface", name, "blacklist", blacklist) + continue + } + + id, found := portsConfig[name] + if !found { + log.Debugw("skip interface as no info on it was found in config DB", "interface", name) + continue + } + + nic := &models.V1SwitchNic{ + Identifier: &id.Alias, + Name: &name, + } + nics = append(nics, nic) + } + + return nics, nil +} + +func (s *Sonic) SanitizeConfig(cfg *types.Conf) { + cfg.CapitalizeVrfName() +} + +func (s *Sonic) GetSwitchPorts() ([]*net.Interface, error) { + ifs, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("unable to get all interfaces: %w", err) + } + + switchPorts := make([]*net.Interface, 0, len(ifs)) + for i := range ifs { + iface := &ifs[i] + if !strings.HasPrefix(iface.Name, "Ethernet") { + s.log.Debugw("skip interface, because only Ethernet* interface are front panels", "interface", iface.Name) + continue + } + switchPorts = append(switchPorts, iface) + } + return switchPorts, nil +} + +func getPortsConfig(filepath string) (map[string]PortInfo, error) { + jsonFile, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer jsonFile.Close() + + byteValue, err := io.ReadAll(jsonFile) + if err != nil { + return nil, err + } + + config := struct { + Ports map[string]PortInfo `json:"PORT"` + }{} + err = json.Unmarshal(byteValue, &config) + + return config.Ports, err +} + +type sonic_version struct { + BuildVersion string `yaml:"build_version"` +} + +func (s *Sonic) GetOS() (*models.V1SwitchOS, error) { + versionBytes, err := os.ReadFile(SonicVersionFile) + if err != nil { + return nil, fmt.Errorf("unable to read sonic_version: %w", err) + } + + var sonicVersion sonic_version + err = yaml.Unmarshal(versionBytes, &sonicVersion) + if err != nil { + return nil, fmt.Errorf("unable to parse sonic_version: %w", err) + } + return &models.V1SwitchOS{ + Vendor: "SONiC", + Version: sonicVersion.BuildVersion, + }, nil +} +func (s *Sonic) GetManagement() (ip, user string, err error) { + ip, err = internal.GetManagementIP("eth0") + if err != nil { + return "", "", err + } + return ip, "admin", nil +} diff --git a/cmd/internal/switcher/sonic/sonic_test.go b/cmd/internal/switcher/sonic/sonic_test.go new file mode 100644 index 00000000..196ae682 --- /dev/null +++ b/cmd/internal/switcher/sonic/sonic_test.go @@ -0,0 +1,51 @@ +package sonic + +import ( + "log" + "os" + "path" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSonicGetPortsConfig(t *testing.T) { + tests := listTestCases() + for i := range tests { + tt := tests[i] + t.Run(tt, func(t *testing.T) { + ports, err := getPortsConfig(path.Join("test_data", tt, "portsdb.json")) + require.NoError(t, err, "Failed to get ports config") + + interfaceToAliasMap := map[string]string{ + "Ethernet0": "fortyGigE0/0", + "Ethernet4": "fortyGigE1/0", + "Ethernet8": "fortyGigE2/0", + "Ethernet12": "fortyGigE3/0", + } + require.Equal( + t, len(interfaceToAliasMap), len(ports), + "Expected ports config length: %d, Got: %d", len(interfaceToAliasMap), len(ports)) + + for i, a := range interfaceToAliasMap { + v := ports[i] + require.Equal(t, a, v.Alias, "Expected interface alias: %s, Got: %s", a, v.Alias) + } + }) + } +} + +func listTestCases() []string { + files, err := os.ReadDir("test_data") + if err != nil { + log.Fatal(err) + } + + r := []string{} + for _, f := range files { + if f.IsDir() { + r = append(r, f.Name()) + } + } + return r +} diff --git a/cmd/internal/switcher/sonic/test_data/dev/portsdb.json b/cmd/internal/switcher/sonic/test_data/dev/portsdb.json new file mode 100644 index 00000000..a28e1a2f --- /dev/null +++ b/cmd/internal/switcher/sonic/test_data/dev/portsdb.json @@ -0,0 +1,17 @@ +{ + "INTERFACE": {}, + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0" + }, + "Ethernet4": { + "alias": "fortyGigE1/0" + }, + "Ethernet8": { + "alias": "fortyGigE2/0" + }, + "Ethernet12": { + "alias": "fortyGigE3/0" + } + } +} \ No newline at end of file diff --git a/cmd/internal/switcher/sonic/test_data/lab/portsdb.json b/cmd/internal/switcher/sonic/test_data/lab/portsdb.json new file mode 100644 index 00000000..a28e1a2f --- /dev/null +++ b/cmd/internal/switcher/sonic/test_data/lab/portsdb.json @@ -0,0 +1,17 @@ +{ + "INTERFACE": {}, + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0" + }, + "Ethernet4": { + "alias": "fortyGigE1/0" + }, + "Ethernet8": { + "alias": "fortyGigE2/0" + }, + "Ethernet12": { + "alias": "fortyGigE3/0" + } + } +} \ No newline at end of file diff --git a/cmd/internal/switcher/sonic/test_data/notenants/portsdb.json b/cmd/internal/switcher/sonic/test_data/notenants/portsdb.json new file mode 100644 index 00000000..a28e1a2f --- /dev/null +++ b/cmd/internal/switcher/sonic/test_data/notenants/portsdb.json @@ -0,0 +1,17 @@ +{ + "INTERFACE": {}, + "PORT": { + "Ethernet0": { + "alias": "fortyGigE0/0" + }, + "Ethernet4": { + "alias": "fortyGigE1/0" + }, + "Ethernet8": { + "alias": "fortyGigE2/0" + }, + "Ethernet12": { + "alias": "fortyGigE3/0" + } + } +} \ No newline at end of file diff --git a/cmd/internal/switcher/templates/template.go b/cmd/internal/switcher/templates/template.go index 55e64a63..ffdce414 100644 --- a/cmd/internal/switcher/templates/template.go +++ b/cmd/internal/switcher/templates/template.go @@ -10,8 +10,9 @@ import ( var templates embed.FS const ( - interfaceTpl = "interfaces.tpl" - frrTpl = "frr.tpl" + interfaceTpl = "interfaces.tpl" + cumulusFrrTpl = "cumulus_frr.tpl" + sonicFrrTpl = "sonic_frr.tpl" ) func mustParseFile(path string) *template.Template { @@ -33,6 +34,10 @@ func InterfacesTemplate(customTplPath string) *template.Template { return mustParse(interfaceTpl, customTplPath) } -func FrrTemplate(customTplPath string) *template.Template { - return mustParse(frrTpl, customTplPath) +func CumulusFrrTemplate(customTplPath string) *template.Template { + return mustParse(cumulusFrrTpl, customTplPath) +} + +func SonicFrrTemplate(customTplPath string) *template.Template { + return mustParse(sonicFrrTpl, customTplPath) } diff --git a/cmd/internal/switcher/templates/template_test.go b/cmd/internal/switcher/templates/template_test.go index fead3282..887092e7 100644 --- a/cmd/internal/switcher/templates/template_test.go +++ b/cmd/internal/switcher/templates/template_test.go @@ -26,15 +26,29 @@ func TestInterfacesTemplate(t *testing.T) { } } -func TestFrrTemplate(t *testing.T) { +func TestCumulusFrrTemplate(t *testing.T) { tests := listTestCases() for i := range tests { tt := tests[i] t.Run(tt, func(t *testing.T) { c := readConf(t, path.Join("test_data", tt, "conf.yaml")) c.FillRouteMapsAndIPPrefixLists() - tpl := FrrTemplate("") - verifyTemplate(t, tpl, &c, path.Join("test_data", tt, "frr.conf")) + tpl := CumulusFrrTemplate("") + verifyTemplate(t, tpl, &c, path.Join("test_data", tt, "cumulus_frr.conf")) + }) + } +} + +func TestSonicFrrTpl(t *testing.T) { + tests := listTestCases() + for i := range tests { + tt := tests[i] + t.Run(tt, func(t *testing.T) { + c := readConf(t, path.Join("test_data", tt, "conf.yaml")) + c.CapitalizeVrfName() + c.FillRouteMapsAndIPPrefixLists() + tpl := SonicFrrTemplate("") + verifyTemplate(t, tpl, &c, path.Join("test_data", tt, "sonic_frr.conf")) }) } } @@ -45,10 +59,17 @@ func TestCustomInterfacesTemplate(t *testing.T) { verifyTemplate(t, tpl, &c, "test_data/dev/customtpl/interfaces") } -func TestCustomFrrTemplate(t *testing.T) { +func TestCustomCumulusFrrTemplate(t *testing.T) { + c := readConf(t, "test_data/dev/conf.yaml") + c.FillRouteMapsAndIPPrefixLists() + tpl := CumulusFrrTemplate("test_data/dev/customtpl/frr.tpl") + verifyTemplate(t, tpl, &c, "test_data/dev/customtpl/frr.conf") +} + +func TestCustomSonicFrrTemplate(t *testing.T) { c := readConf(t, "test_data/dev/conf.yaml") c.FillRouteMapsAndIPPrefixLists() - tpl := FrrTemplate("test_data/dev/customtpl/frr.tpl") + tpl := SonicFrrTemplate("test_data/dev/customtpl/frr.tpl") verifyTemplate(t, tpl, &c, "test_data/dev/customtpl/frr.conf") } diff --git a/cmd/internal/switcher/templates/test_data/dev/frr.conf b/cmd/internal/switcher/templates/test_data/dev/cumulus_frr.conf similarity index 97% rename from cmd/internal/switcher/templates/test_data/dev/frr.conf rename to cmd/internal/switcher/templates/test_data/dev/cumulus_frr.conf index aea9e8a8..06ef159d 100644 --- a/cmd/internal/switcher/templates/test_data/dev/frr.conf +++ b/cmd/internal/switcher/templates/test_data/dev/cumulus_frr.conf @@ -100,7 +100,7 @@ router bgp 4200000010 vrf vrf104001 # route-maps for vrf104001 ip prefix-list vrf104001-in-prefixes permit 100.127.131.0/24 le 32 ip prefix-list vrf104001-in-prefixes permit 212.17.234.17/32 le 32 -ip prefix-list vrf104001-in-prefixes permit 10.244.0.0/16 le 32 +ip prefix-list vrf104001-in-prefixes permit 10.240.0.0/12 le 32 route-map vrf104001-in permit 10 match ip address prefix-list vrf104001-in-prefixes ! diff --git a/cmd/internal/switcher/templates/test_data/dev/customtpl/frr.conf b/cmd/internal/switcher/templates/test_data/dev/customtpl/frr.conf index a6ffb1b4..f47d0f09 100644 --- a/cmd/internal/switcher/templates/test_data/dev/customtpl/frr.conf +++ b/cmd/internal/switcher/templates/test_data/dev/customtpl/frr.conf @@ -101,7 +101,7 @@ router bgp 4200000010 vrf vrf104001 # route-maps for vrf104001 ip prefix-list vrf104001-in-prefixes permit 100.127.131.0/24 le 32 ip prefix-list vrf104001-in-prefixes permit 212.17.234.17/32 le 32 -ip prefix-list vrf104001-in-prefixes permit 10.244.0.0/16 le 32 +ip prefix-list vrf104001-in-prefixes permit 10.240.0.0/12 le 32 route-map vrf104001-in permit 10 match ip address prefix-list vrf104001-in-prefixes ! diff --git a/cmd/internal/switcher/templates/test_data/dev/sonic_frr.conf b/cmd/internal/switcher/templates/test_data/dev/sonic_frr.conf new file mode 100644 index 00000000..fb40ebce --- /dev/null +++ b/cmd/internal/switcher/templates/test_data/dev/sonic_frr.conf @@ -0,0 +1,113 @@ +! The frr version is not rendered since it seems to be optional. +frr defaults datacenter +hostname leaf01 +password zebra +enable password zebra +! +log syslog warnings +log facility local4 +debug bgp updates +debug bgp nht +debug bgp update-groups +debug bgp zebra +debug zebra events +debug zebra nexthop detail +debug zebra rib detailed +debug zebra nht detailed +! +vrf Vrf104001 + vni 104001 + exit-vrf +! +interface swp31 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp32 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp3 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp1 vrf Vrf104001 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp2 vrf Vrf104001 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +router bgp 4200000010 + bgp router-id 10.0.0.10 + bgp bestpath as-path multipath-relax + neighbor FABRIC peer-group + neighbor FABRIC remote-as external + neighbor FABRIC timers 2 8 + neighbor swp31 interface peer-group FABRIC + neighbor swp32 interface peer-group FABRIC + neighbor FIREWALL peer-group + neighbor FIREWALL remote-as external + neighbor FIREWALL timers 2 8 + neighbor swp3 interface peer-group FIREWALL + ! + address-family ipv4 unicast + redistribute connected route-map LOOPBACKS + neighbor FIREWALL allowas-in 2 + neighbor swp3 route-map fw-swp3-in in + exit-address-family + ! + address-family l2vpn evpn + advertise-all-vni + neighbor FABRIC activate + neighbor FABRIC allowas-in 2 + neighbor FIREWALL activate + neighbor FIREWALL allowas-in 2 + neighbor swp3 route-map fw-swp3-vni out + exit-address-family +! +route-map LOOPBACKS permit 10 + match interface Loopback0 +! +# route-maps for firewall@swp3 +ip prefix-list fw-swp3-in-prefixes permit 10.0.2.1/32 le 32 +route-map fw-swp3-in permit 10 + match ip address prefix-list fw-swp3-in-prefixes +route-map fw-swp3-vni permit 10 + match evpn vni 104001 +route-map fw-swp3-vni permit 11 + match evpn vni 104009 +route-map fw-swp3-vni permit 12 + match evpn vni 104010 +! +ip route 0.0.0.0/0 192.168.101.1 nexthop-vrf mgmt +! +router bgp 4200000010 vrf Vrf104001 + bgp router-id 10.0.0.10 + bgp bestpath as-path multipath-relax + neighbor MACHINE peer-group + neighbor MACHINE remote-as external + neighbor MACHINE timers 2 8 + neighbor swp1 interface peer-group MACHINE + neighbor swp2 interface peer-group MACHINE + ! + address-family ipv4 unicast + redistribute connected + neighbor MACHINE maximum-prefix 24000 + neighbor MACHINE route-map Vrf104001-in in + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family +! +# route-maps for Vrf104001 +ip prefix-list Vrf104001-in-prefixes permit 100.127.131.0/24 le 32 +ip prefix-list Vrf104001-in-prefixes permit 212.17.234.17/32 le 32 +ip prefix-list Vrf104001-in-prefixes permit 10.240.0.0/12 le 32 +route-map Vrf104001-in permit 10 + match ip address prefix-list Vrf104001-in-prefixes +! +line vty +! diff --git a/cmd/internal/switcher/templates/test_data/lab/frr.conf b/cmd/internal/switcher/templates/test_data/lab/cumulus_frr.conf similarity index 97% rename from cmd/internal/switcher/templates/test_data/lab/frr.conf rename to cmd/internal/switcher/templates/test_data/lab/cumulus_frr.conf index 724b3ddd..614d7d77 100644 --- a/cmd/internal/switcher/templates/test_data/lab/frr.conf +++ b/cmd/internal/switcher/templates/test_data/lab/cumulus_frr.conf @@ -98,7 +98,7 @@ router bgp 4200000010 vrf vrf104001 exit-address-family ! # route-maps for vrf104001 -ip prefix-list vrf104001-in-prefixes permit 10.244.0.0/16 le 32 +ip prefix-list vrf104001-in-prefixes permit 10.240.0.0/12 le 32 route-map vrf104001-in permit 10 match ip address prefix-list vrf104001-in-prefixes ! diff --git a/cmd/internal/switcher/templates/test_data/lab/sonic_frr.conf b/cmd/internal/switcher/templates/test_data/lab/sonic_frr.conf new file mode 100644 index 00000000..249a096a --- /dev/null +++ b/cmd/internal/switcher/templates/test_data/lab/sonic_frr.conf @@ -0,0 +1,111 @@ +! The frr version is not rendered since it seems to be optional. +frr defaults datacenter +hostname leaf01 +password zebra +enable password zebra +! +log syslog debugging +log facility local4 +debug bgp updates +debug bgp nht +debug bgp update-groups +debug bgp zebra +debug zebra events +debug zebra nexthop detail +debug zebra rib detailed +debug zebra nht detailed +! +vrf Vrf104001 + vni 104001 + exit-vrf +! +interface swp31 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp32 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp3 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp1 vrf Vrf104001 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp2 vrf Vrf104001 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +router bgp 4200000010 + bgp router-id 10.0.0.10 + bgp bestpath as-path multipath-relax + neighbor FABRIC peer-group + neighbor FABRIC remote-as external + neighbor FABRIC timers 2 8 + neighbor swp31 interface peer-group FABRIC + neighbor swp32 interface peer-group FABRIC + neighbor FIREWALL peer-group + neighbor FIREWALL remote-as external + neighbor FIREWALL timers 2 8 + neighbor swp3 interface peer-group FIREWALL + ! + address-family ipv4 unicast + redistribute connected route-map LOOPBACKS + neighbor FIREWALL allowas-in 2 + neighbor swp3 route-map fw-swp3-in in + exit-address-family + ! + address-family l2vpn evpn + advertise-all-vni + neighbor FABRIC activate + neighbor FABRIC allowas-in 2 + neighbor FIREWALL activate + neighbor FIREWALL allowas-in 2 + neighbor swp3 route-map fw-swp3-vni out + exit-address-family +! +route-map LOOPBACKS permit 10 + match interface Loopback0 +! +# route-maps for firewall@swp3 +ip prefix-list fw-swp3-in-prefixes permit 10.0.2.1/32 le 32 +route-map fw-swp3-in permit 10 + match ip address prefix-list fw-swp3-in-prefixes +route-map fw-swp3-vni permit 10 + match evpn vni 104001 +route-map fw-swp3-vni permit 11 + match evpn vni 104009 +route-map fw-swp3-vni permit 12 + match evpn vni 104010 +! +ip route 0.0.0.0/0 192.168.0.254 nexthop-vrf mgmt +! +router bgp 4200000010 vrf Vrf104001 + bgp router-id 10.0.0.10 + bgp bestpath as-path multipath-relax + neighbor MACHINE peer-group + neighbor MACHINE remote-as external + neighbor MACHINE timers 2 8 + neighbor swp1 interface peer-group MACHINE + neighbor swp2 interface peer-group MACHINE + ! + address-family ipv4 unicast + redistribute connected + neighbor MACHINE maximum-prefix 24000 + neighbor MACHINE route-map Vrf104001-in in + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family +! +# route-maps for Vrf104001 +ip prefix-list Vrf104001-in-prefixes permit 10.240.0.0/12 le 32 +route-map Vrf104001-in permit 10 + match ip address prefix-list Vrf104001-in-prefixes +! +line vty +! diff --git a/cmd/internal/switcher/templates/test_data/notenants/conf.yaml b/cmd/internal/switcher/templates/test_data/notenants/conf.yaml index bb92228a..0f598160 100644 --- a/cmd/internal/switcher/templates/test_data/notenants/conf.yaml +++ b/cmd/internal/switcher/templates/test_data/notenants/conf.yaml @@ -13,4 +13,4 @@ ports: - swp32 unprovisioned: - swp1 - - swp2 + - swp2 \ No newline at end of file diff --git a/cmd/internal/switcher/templates/test_data/notenants/frr.conf b/cmd/internal/switcher/templates/test_data/notenants/cumulus_frr.conf similarity index 100% rename from cmd/internal/switcher/templates/test_data/notenants/frr.conf rename to cmd/internal/switcher/templates/test_data/notenants/cumulus_frr.conf diff --git a/cmd/internal/switcher/templates/test_data/notenants/sonic_frr.conf b/cmd/internal/switcher/templates/test_data/notenants/sonic_frr.conf new file mode 100644 index 00000000..a68bf2c3 --- /dev/null +++ b/cmd/internal/switcher/templates/test_data/notenants/sonic_frr.conf @@ -0,0 +1,57 @@ +! The frr version is not rendered since it seems to be optional. +frr defaults datacenter +hostname leaf01 +password zebra +enable password zebra +! +log syslog warnings +log facility local4 +debug bgp updates +debug bgp nht +debug bgp update-groups +debug bgp zebra +debug zebra events +debug zebra nexthop detail +debug zebra rib detailed +debug zebra nht detailed +! +interface swp31 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +interface swp32 + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +! +router bgp 4200000010 + bgp router-id 10.0.0.10 + bgp bestpath as-path multipath-relax + neighbor FABRIC peer-group + neighbor FABRIC remote-as external + neighbor FABRIC timers 2 8 + neighbor swp31 interface peer-group FABRIC + neighbor swp32 interface peer-group FABRIC + neighbor FIREWALL peer-group + neighbor FIREWALL remote-as external + neighbor FIREWALL timers 2 8 + ! + address-family ipv4 unicast + redistribute connected route-map LOOPBACKS + neighbor FIREWALL allowas-in 2 + exit-address-family + ! + address-family l2vpn evpn + advertise-all-vni + neighbor FABRIC activate + neighbor FABRIC allowas-in 2 + neighbor FIREWALL activate + neighbor FIREWALL allowas-in 2 + exit-address-family +! +route-map LOOPBACKS permit 10 + match interface Loopback0 +! +ip route 0.0.0.0/0 192.168.0.254 nexthop-vrf mgmt +! +line vty +! diff --git a/cmd/internal/switcher/templates/tpl/frr.tpl b/cmd/internal/switcher/templates/tpl/cumulus_frr.tpl similarity index 100% rename from cmd/internal/switcher/templates/tpl/frr.tpl rename to cmd/internal/switcher/templates/tpl/cumulus_frr.tpl diff --git a/cmd/internal/switcher/templates/tpl/sonic_frr.tpl b/cmd/internal/switcher/templates/tpl/sonic_frr.tpl new file mode 100644 index 00000000..79eff97d --- /dev/null +++ b/cmd/internal/switcher/templates/tpl/sonic_frr.tpl @@ -0,0 +1,135 @@ +{{- $ASN := .ASN -}}{{- $RouterId := .Loopback -}}! The frr version is not rendered since it seems to be optional. +frr defaults datacenter +hostname {{ .Name }} +password zebra +enable password zebra +! +log syslog {{ .LogLevel }} +log facility local4 +debug bgp updates +debug bgp nht +debug bgp update-groups +debug bgp zebra +debug zebra events +debug zebra nexthop detail +debug zebra rib detailed +debug zebra nht detailed +{{- range $vrf, $t := .Ports.Vrfs }} +! +vrf Vrf{{ $t.VNI }} + vni {{ $t.VNI }} + exit-vrf +{{- end }} +{{- range .Ports.Underlay }} +! +interface {{ . }} + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +{{- end }} +{{- range .Ports.Firewalls }} +! +interface {{ .Port }} + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +{{- end }} +{{- range $vrf, $t := .Ports.Vrfs }} +{{- range $t.Neighbors }} +! +interface {{ . }} vrf {{ $vrf }} + ipv6 nd ra-interval 6 + no ipv6 nd suppress-ra +{{- end }} +{{- end }} +! +router bgp {{ $ASN }} + bgp router-id {{ $RouterId }} + bgp bestpath as-path multipath-relax + neighbor FABRIC peer-group + neighbor FABRIC remote-as external + neighbor FABRIC timers 2 8 + {{- range .Ports.Underlay }} + neighbor {{ . }} interface peer-group FABRIC + {{- end }} + neighbor FIREWALL peer-group + neighbor FIREWALL remote-as external + neighbor FIREWALL timers 2 8 + {{- range .Ports.Firewalls }} + neighbor {{ .Port }} interface peer-group FIREWALL + {{- end }} + ! + address-family ipv4 unicast + redistribute connected route-map LOOPBACKS + neighbor FIREWALL allowas-in 2 + {{- range $k, $f := .Ports.Firewalls }} + neighbor {{ $f.Port }} route-map fw-{{ $k }}-in in + {{- end }} + exit-address-family + ! + address-family l2vpn evpn + advertise-all-vni + neighbor FABRIC activate + neighbor FABRIC allowas-in 2 + neighbor FIREWALL activate + neighbor FIREWALL allowas-in 2 + {{- range $k, $f := .Ports.Firewalls }} + neighbor {{ $f.Port }} route-map fw-{{ $k }}-vni out + {{- end }} + exit-address-family +! +route-map LOOPBACKS permit 10 + match interface Loopback0 +! +{{- range $k, $f := .Ports.Firewalls }} +# route-maps for firewall@{{ $k }} + {{- range $f.IPPrefixLists }} +ip prefix-list {{ .Name }} {{ .Spec }} + {{- end}} + {{- range $f.RouteMaps }} +route-map {{ .Name }} {{ .Policy }} {{ .Order }} + {{- range .Entries }} + {{ . }} + {{- end }} + {{- end }} +! +{{- end }} +{{- if .Ports.Eth0.Gateway }} +ip route 0.0.0.0/0 {{ .Ports.Eth0.Gateway }} nexthop-vrf mgmt +{{- end }} +! +{{- range $vrf, $t := .Ports.Vrfs }} +router bgp {{ $ASN }} vrf {{ $vrf }} + bgp router-id {{ $RouterId }} + bgp bestpath as-path multipath-relax + neighbor MACHINE peer-group + neighbor MACHINE remote-as external + neighbor MACHINE timers 2 8 + {{- range $t.Neighbors }} + neighbor {{ . }} interface peer-group MACHINE + {{- end }} + ! + address-family ipv4 unicast + redistribute connected + neighbor MACHINE maximum-prefix 24000 + {{- if gt (len $t.IPPrefixLists) 0 }} + neighbor MACHINE route-map {{ $vrf }}-in in + {{- end }} + exit-address-family + ! + address-family l2vpn evpn + advertise ipv4 unicast + exit-address-family +! +{{- if gt (len $t.IPPrefixLists) 0 }} +# route-maps for {{ $vrf }} + {{- range $t.IPPrefixLists }} +ip prefix-list {{ .Name }} {{ .Spec }} + {{- end}} + {{- range $t.RouteMaps }} +route-map {{ .Name }} {{ .Policy }} {{ .Order }} + {{- range .Entries }} + {{ . }} + {{- end }} + {{- end }} +!{{- end }}{{- end }} +line vty +! diff --git a/cmd/internal/switcher/types/conf.go b/cmd/internal/switcher/types/conf.go index 3d2068e7..5ceda8db 100644 --- a/cmd/internal/switcher/types/conf.go +++ b/cmd/internal/switcher/types/conf.go @@ -2,6 +2,8 @@ package types import ( "github.com/metal-stack/metal-core/cmd/internal/vlan" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) // FillVLANIDs fills the given configuration object with switch-local VLAN-IDs @@ -32,8 +34,21 @@ func (c *Conf) FillRouteMapsAndIPPrefixLists() { f.Assemble("fw-"+port, f.Vnis, f.Cidrs) } for vrf, t := range c.Ports.Vrfs { - podCidr := "10.244.0.0/16" + podCidr := "10.240.0.0/12" t.Cidrs = append(t.Cidrs, podCidr) t.Assemble(vrf, []string{}, t.Cidrs) } } + +// CapitalizeVrfName capitalizes VRF names, which is requirement for SONiC +func (c *Conf) CapitalizeVrfName() { + caser := cases.Title(language.English) + capitalizedVRFs := make(map[string]*Vrf) + for name, vrf := range c.Ports.Vrfs { + s := caser.String(name) + capitalizedVRFs[s] = vrf + } + + c.Ports.Vrfs = capitalizedVRFs + return +} diff --git a/cmd/server.go b/cmd/server.go index b17698bf..3f657a3c 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,3 +1,6 @@ +//go:build client +// +build client + package cmd import ( @@ -48,7 +51,6 @@ func Run() { fmt.Sprintf("%s://%s:%d%s", cfg.ApiProtocol, cfg.ApiIP, cfg.ApiPort, cfg.ApiBasePath), "", cfg.HMACKey, metalgo.AuthType("Metal-Edit"), ) - if err != nil { log.Fatalw("unable to create metal-api driver", "error", err) } @@ -71,7 +73,10 @@ func Run() { log.Fatalw("failed to create grpc client", "error", err) } - nos := switcher.NewNOS(log, cfg.FrrTplFile, cfg.InterfacesTplFile) + nos, err := switcher.NewNOS(log, cfg.FrrTplFile, cfg.InterfacesTplFile) + if err != nil { + log.Fatalw("failed to create NOS instance", "error", err) + } metrics := metrics.New() diff --git a/go.mod b/go.mod index a4c98caa..30777e05 100644 --- a/go.mod +++ b/go.mod @@ -3,52 +3,56 @@ module github.com/metal-stack/metal-core go 1.20 require ( + github.com/avast/retry-go/v4 v4.3.4 github.com/coreos/go-systemd/v22 v22.5.0 - github.com/go-openapi/errors v0.20.3 - github.com/go-openapi/runtime v0.25.0 - github.com/go-openapi/strfmt v0.21.3 - github.com/go-openapi/swag v0.22.3 - github.com/go-openapi/validate v0.22.1 + github.com/go-redis/redismock/v9 v9.0.3 + github.com/google/go-cmp v0.5.9 github.com/kelseyhightower/envconfig v1.4.0 github.com/metal-stack/go-lldpd v0.4.3 - github.com/metal-stack/metal-api v0.22.1 - github.com/metal-stack/metal-go v0.22.1 + github.com/metal-stack/metal-api v0.22.4 + github.com/metal-stack/metal-go v0.22.4 github.com/metal-stack/v v1.0.3 - github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_golang v1.15.1 + github.com/redis/go-redis/v9 v9.0.4 github.com/stretchr/testify v1.8.2 github.com/vishvananda/netlink v1.1.0 go.uber.org/zap v1.24.0 - golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 - google.golang.org/grpc v1.53.0 - google.golang.org/protobuf v1.28.1 + golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea + golang.org/x/text v0.9.0 + google.golang.org/grpc v1.55.0 + google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/coreos/go-oidc/v3 v3.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect + github.com/go-openapi/errors v0.20.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/loads v0.21.2 // indirect - github.com/go-openapi/spec v0.20.8 // indirect - github.com/goccy/go-json v0.10.0 // indirect + github.com/go-openapi/runtime v0.26.0 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/strfmt v0.21.7 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-openapi/validate v0.22.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect @@ -59,31 +63,28 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect github.com/mdlayher/lldp v0.0.0-20150915211757-afd9f83164c5 // indirect - github.com/metal-stack/metal-lib v0.11.3 // indirect + github.com/metal-stack/metal-lib v0.11.7 // indirect github.com/metal-stack/security v0.6.6 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.40.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.43.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect - go.mongodb.org/mongo-driver v1.11.2 // indirect - go.opentelemetry.io/otel v1.13.0 // indirect - go.opentelemetry.io/otel/trace v1.13.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.6.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + go.mongodb.org/mongo-driver v1.11.6 // indirect + go.opentelemetry.io/otel v1.15.1 // indirect + go.opentelemetry.io/otel/trace v1.15.1 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sys v0.8.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index c828b9fb..d00bdc7b 100644 --- a/go.sum +++ b/go.sum @@ -3,11 +3,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/avast/retry-go/v4 v4.3.4 h1:pHLkL7jvCvP317I8Ge+Km2Yhntv3SdkJm7uekkqbKhM= +github.com/avast/retry-go/v4 v4.3.4/go.mod h1:rv+Nla6Vk3/ilU0H51VHddWHiwimzX66yZ0JT6T+UvE= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw= @@ -20,14 +24,17 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= @@ -49,16 +56,17 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= -github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= -github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= +github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc= +github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= -github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= +github.com/go-openapi/strfmt v0.21.7 h1:rspiXgNWgeUzhjo1YU01do6qsahtJNByjLVbPLNHb8k= +github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -66,6 +74,8 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= +github.com/go-redis/redismock/v9 v9.0.3 h1:mtHQi2l51lCmXIbTRTqb1EiHYe9tL5Yk5oorlSJJqR0= +github.com/go-redis/redismock/v9 v9.0.3/go.mod h1:F6tJRfnU8R/NZ0E+Gjvoluk14MqMC5ueSZX6vVQypc0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= @@ -92,8 +102,8 @@ github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGt github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -101,10 +111,10 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -113,7 +123,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -127,7 +137,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= @@ -138,7 +147,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -175,12 +183,12 @@ github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jm github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= github.com/metal-stack/go-lldpd v0.4.3 h1:LIlqrdNMsTDke5MwphpbPOgmwJPcgJLnRTyJj/RIJ+Q= github.com/metal-stack/go-lldpd v0.4.3/go.mod h1:gtsAjN4CCDia+ZD3O53/HeDlxN2h2kMkvIbIREyBkCE= -github.com/metal-stack/metal-api v0.22.1 h1:Shi8hrPGk8rOzeaRkb6ZCcqIJlHg8Bh+tAgqy8NsqmQ= -github.com/metal-stack/metal-api v0.22.1/go.mod h1:BQmd6bt7GA9llGC7/BrPmpEe6pT17chC4bipmu/5iH0= -github.com/metal-stack/metal-go v0.22.1 h1:obKgLfd46VsznNZtBjQQUuRJ1fqcyitYgBGAg69S44Y= -github.com/metal-stack/metal-go v0.22.1/go.mod h1:IZ7qY6dUAi72ZTz7Ni5cwWzzUXJj2Or1t04c3u4AUzU= -github.com/metal-stack/metal-lib v0.11.3 h1:0VyfzyfwTYuaKn7ggok6ieU1MDY+RfbxOTL+Bji89x4= -github.com/metal-stack/metal-lib v0.11.3/go.mod h1:Ge1ypz6aOf2ab98kr8GYMXD8eZ7VfKFUfUE3p/8Sm3g= +github.com/metal-stack/metal-api v0.22.4 h1:CXbvjqEkoDkOazRGAeKkcnmusanqa9LaxEUwwVStiU0= +github.com/metal-stack/metal-api v0.22.4/go.mod h1:m9eWLH+YTYOhjIHQD5pxSL0bJ7HSzBXMe7If5TOKjxU= +github.com/metal-stack/metal-go v0.22.4 h1:RlyIqN59OD/YqnwbCFktnz4FrsIAhytIb9ej0XALEHU= +github.com/metal-stack/metal-go v0.22.4/go.mod h1:n0KALbtB6JGAICDmgSU5B/jekEFODuqcluTHEAXMPng= +github.com/metal-stack/metal-lib v0.11.7 h1:8RC1JrlgBUoIKI8rJIer2/JhLqEWAkZwlDPzlrml2PA= +github.com/metal-stack/metal-lib v0.11.7/go.mod h1:P5YbqdhQsNYMQmxthPljV+fZggUOayFs5YBO2ikpyUQ= github.com/metal-stack/security v0.6.6 h1:KSPNN8YZd2EJEjsJ0xCBcd5o53uU0iFupahHA9Twuh0= github.com/metal-stack/security v0.6.6/go.mod h1:WchPm3+2Xjj1h7AxM+DsnR9EWgLw+ktoGCl/0gcmgSA= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -189,38 +197,38 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= -github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.43.0 h1:iq+BVjvYLei5f27wiuNiB1DN6DYQkp1c8Bx0Vykh5us= +github.com/prometheus/common v0.43.0/go.mod h1:NCvr5cQIh3Y/gy73/RdVtC9r8xxrxwJnB+2lB3BxrFc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc= +github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -257,18 +265,18 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw= -go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= -go.opentelemetry.io/otel v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y= -go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= -go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= -go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY= -go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o= +go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= +go.opentelemetry.io/otel v1.15.1 h1:3Iwq3lfRByPaws0f6bU3naAqOR1n5IeDWd9390kWHa8= +go.opentelemetry.io/otel v1.15.1/go.mod h1:mHHGEHVDLal6YrKMmk9LqC4a3sF5g+fHfrttQIB1NTc= +go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= +go.opentelemetry.io/otel/trace v1.15.1 h1:uXLo6iHJEzDfrNC0L0mNjItIp06SyaBQxu5t3xMlngY= +go.opentelemetry.io/otel/trace v1.15.1/go.mod h1:IWdQG/5N1x7f6YUlmdLeJvH9yxtuJAfc4VW5Agv9r/8= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -280,10 +288,10 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= -golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -298,11 +306,11 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -327,8 +335,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= @@ -339,8 +347,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -354,15 +362,15 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -371,6 +379,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=