mirror of
https://github.com/osrg/gobgp.git
synced 2024-05-11 05:55:10 +00:00
send-max: respect configuration
This commit is contained in:
committed by
FUJITA Tomonori
parent
bbde806641
commit
9fbc03765a
+1211
-1200
File diff suppressed because it is too large
Load Diff
@@ -544,6 +544,7 @@ message Path {
|
||||
uint32 local_identifier = 19;
|
||||
bytes nlri_binary = 20;
|
||||
repeated bytes pattrs_binary = 21;
|
||||
bool send_max_filtered = 22;
|
||||
}
|
||||
|
||||
message Destination {
|
||||
|
||||
+21
-4
@@ -586,7 +586,7 @@ func getPathAttributeString(nlri bgp.AddrPrefixInterface, attrs []bgp.PathAttrib
|
||||
return fmt.Sprint(s)
|
||||
}
|
||||
|
||||
func makeShowRouteArgs(p *api.Path, idx int, now time.Time, showAge, showBest, showLabel, showMUP bool, showIdentifier bgp.BGPAddPathMode) []interface{} {
|
||||
func makeShowRouteArgs(p *api.Path, idx int, now time.Time, showAge, showBest, showLabel, showMUP, showSendMaxFiltered bool, showIdentifier bgp.BGPAddPathMode) []interface{} {
|
||||
nlri, _ := apiutil.GetNativeNlri(p)
|
||||
|
||||
// Path Symbols (e.g. "*>")
|
||||
@@ -650,17 +650,27 @@ func makeShowRouteArgs(p *api.Path, idx int, now time.Time, showAge, showBest, s
|
||||
pattrstr := getPathAttributeString(nlri, attrs)
|
||||
args = append(args, pattrstr)
|
||||
|
||||
if showSendMaxFiltered {
|
||||
if p.SendMaxFiltered {
|
||||
args = append(args, "send-max-filtered")
|
||||
} else if p.Filtered {
|
||||
args = append(args, "policy-filtered")
|
||||
} else {
|
||||
args = append(args, "not filtered")
|
||||
}
|
||||
}
|
||||
|
||||
updateColumnWidth(nlri.String(), nexthop, aspathstr, label, teid, qfi, endpoint)
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func showRoute(dsts []*api.Destination, showAge, showBest, showLabel, showMUP bool, showIdentifier bgp.BGPAddPathMode) {
|
||||
func showRoute(dsts []*api.Destination, showAge, showBest, showLabel, showMUP, showSendMaxFiltered bool, showIdentifier bgp.BGPAddPathMode) {
|
||||
pathStrs := make([][]interface{}, 0, len(dsts))
|
||||
now := time.Now()
|
||||
for _, dst := range dsts {
|
||||
for idx, p := range dst.Paths {
|
||||
pathStrs = append(pathStrs, makeShowRouteArgs(p, idx, now, showAge, showBest, showLabel, showMUP, showIdentifier))
|
||||
pathStrs = append(pathStrs, makeShowRouteArgs(p, idx, now, showAge, showBest, showLabel, showMUP, showSendMaxFiltered, showIdentifier))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,6 +701,11 @@ func showRoute(dsts []*api.Destination, showAge, showBest, showLabel, showMUP bo
|
||||
headers = append(headers, "Attrs")
|
||||
format += "%-s\n"
|
||||
|
||||
if showSendMaxFiltered {
|
||||
headers = append(headers, "Filtered")
|
||||
format += "%-s\n"
|
||||
}
|
||||
|
||||
fmt.Printf(format, headers...)
|
||||
for _, pathStr := range pathStrs {
|
||||
fmt.Printf(format, pathStr...)
|
||||
@@ -839,6 +854,7 @@ func showNeighborRib(r string, name string, args []string) error {
|
||||
showAge := true
|
||||
showLabel := false
|
||||
showMUP := false
|
||||
showSendMaxFiltered := false
|
||||
showIdentifier := bgp.BGP_ADD_PATH_NONE
|
||||
validationTarget := ""
|
||||
rd := ""
|
||||
@@ -852,6 +868,7 @@ func showNeighborRib(r string, name string, args []string) error {
|
||||
showBest = true
|
||||
case cmdAdjOut:
|
||||
showAge = false
|
||||
showSendMaxFiltered = true
|
||||
case cmdVRF:
|
||||
def = ipv4UC
|
||||
showBest = true
|
||||
@@ -1058,7 +1075,7 @@ func showNeighborRib(r string, name string, args []string) error {
|
||||
}
|
||||
}
|
||||
if len(dsts) > 0 {
|
||||
showRoute(dsts, showAge, showBest, showLabel, showMUP, showIdentifier)
|
||||
showRoute(dsts, showAge, showBest, showLabel, showMUP, showSendMaxFiltered, showIdentifier)
|
||||
} else {
|
||||
fmt.Println("Network not in table")
|
||||
}
|
||||
|
||||
@@ -185,10 +185,7 @@ func rsFilter(id string, as uint32, path *Path) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if id != GLOBAL_RIB_NAME && (path.GetSource().Address.String() == id || isASLoop(as, path)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return id != GLOBAL_RIB_NAME && (path.GetSource().Address.String() == id || isASLoop(as, path))
|
||||
}
|
||||
|
||||
func (dd *Destination) GetKnownPathList(id string, as uint32) []*Path {
|
||||
|
||||
@@ -142,6 +142,15 @@ type Path struct {
|
||||
IsNexthopInvalid bool
|
||||
IsWithdraw bool
|
||||
}
|
||||
type FilteredType uint8
|
||||
|
||||
const (
|
||||
NotFiltered FilteredType = 1 << iota
|
||||
PolicyFiltered
|
||||
SendMaxFiltered
|
||||
)
|
||||
|
||||
type PathLocalKey string
|
||||
|
||||
var localSource = &PeerInfo{}
|
||||
|
||||
@@ -566,7 +575,7 @@ func (path *Path) String() string {
|
||||
s.WriteString(fmt.Sprintf("{ %s EOR | src: %s }", path.GetRouteFamily(), path.GetSource()))
|
||||
return s.String()
|
||||
}
|
||||
s.WriteString(fmt.Sprintf("{ %s | ", path.getPrefix()))
|
||||
s.WriteString(fmt.Sprintf("{ %s | ", path.GetPrefix()))
|
||||
s.WriteString(fmt.Sprintf("src: %s", path.GetSource()))
|
||||
s.WriteString(fmt.Sprintf(", nh: %s", path.GetNexthop()))
|
||||
if path.IsNexthopInvalid {
|
||||
@@ -579,7 +588,13 @@ func (path *Path) String() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (path *Path) getPrefix() string {
|
||||
// GetLocalKey identifies the path in the local BGP server.
|
||||
func (path *Path) GetLocalKey() PathLocalKey {
|
||||
// return PathLocalKey(path.GetPrefix())
|
||||
return PathLocalKey(fmt.Sprintf("%s:%s:%d", path.GetRouteFamily(), path.GetNlri(), path.GetNlri().PathLocalIdentifier()))
|
||||
}
|
||||
|
||||
func (path *Path) GetPrefix() string {
|
||||
return path.GetNlri().String()
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestPathGetPrefix(t *testing.T) {
|
||||
peerP := PathCreatePeer()
|
||||
pathP := PathCreatePath(peerP)
|
||||
prefix := "10.10.10.0/24"
|
||||
r_prefix := pathP[0].getPrefix()
|
||||
r_prefix := pathP[0].GetPrefix()
|
||||
assert.Equal(t, r_prefix, prefix)
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ func (t *Table) validatePath(path *Path) {
|
||||
log.Fields{
|
||||
"Topic": "Table",
|
||||
"Key": t.routeFamily,
|
||||
"Prefix": path.GetNlri().String(),
|
||||
"Prefix": path.GetPrefix(),
|
||||
"ReceivedRf": path.GetRouteFamily().String()})
|
||||
}
|
||||
if attr := path.getPathAttr(bgp.BGP_ATTR_TYPE_AS_PATH); attr != nil {
|
||||
|
||||
@@ -118,7 +118,7 @@ func TestProcessBGPUpdate_0_select_onlypath_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.50.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -169,7 +169,7 @@ func TestProcessBGPUpdate_0_select_onlypath_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:50:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -250,7 +250,7 @@ func TestProcessBGPUpdate_1_select_high_localpref_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.50.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -331,7 +331,7 @@ func TestProcessBGPUpdate_1_select_high_localpref_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:100:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -414,7 +414,7 @@ func TestProcessBGPUpdate_2_select_local_origin_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "0.0.0.0"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -498,7 +498,7 @@ func TestProcessBGPUpdate_2_select_local_origin_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "::"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -554,7 +554,7 @@ func TestProcessBGPUpdate_3_select_aspath_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "20.20.20.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.100.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -610,7 +610,7 @@ func TestProcessBGPUpdate_3_select_aspath_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2002:223:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:100:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -691,7 +691,7 @@ func TestProcessBGPUpdate_4_select_low_origin_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.100.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -772,7 +772,7 @@ func TestProcessBGPUpdate_4_select_low_origin_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:100:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -853,7 +853,7 @@ func TestProcessBGPUpdate_5_select_low_med_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.100.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -934,7 +934,7 @@ func TestProcessBGPUpdate_5_select_low_med_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:100:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1015,7 +1015,7 @@ func TestProcessBGPUpdate_6_select_ebgp_path_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.100.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1096,7 +1096,7 @@ func TestProcessBGPUpdate_6_select_ebgp_path_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:100:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1180,7 +1180,7 @@ func TestProcessBGPUpdate_7_select_low_routerid_path_ipv4(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.100.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1261,7 +1261,7 @@ func TestProcessBGPUpdate_7_select_low_routerid_path_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:100:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1344,7 +1344,7 @@ func TestProcessBGPUpdate_8_withdraw_path_ipv4(t *testing.T) {
|
||||
checkPattr(bgpMessage2, path)
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.100.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1365,7 +1365,7 @@ func TestProcessBGPUpdate_8_withdraw_path_ipv4(t *testing.T) {
|
||||
checkPattr(bgpMessage1, path)
|
||||
// check destination
|
||||
expectedPrefix = "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop = "192.168.50.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1472,7 +1472,7 @@ func TestProcessBGPUpdate_8_mpunreach_path_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:100:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1492,7 +1492,7 @@ func TestProcessBGPUpdate_8_mpunreach_path_ipv6(t *testing.T) {
|
||||
checkPattr(bgpMessage1, path)
|
||||
// check destination
|
||||
expectedPrefix = "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop = "2001::192:168:50:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1567,7 +1567,7 @@ func TestProcessBGPUpdate_bestpath_lost_ipv4(t *testing.T) {
|
||||
checkPattr(bgpMessage1, path)
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
}
|
||||
|
||||
func TestProcessBGPUpdate_bestpath_lost_ipv6(t *testing.T) {
|
||||
@@ -1637,7 +1637,7 @@ func TestProcessBGPUpdate_bestpath_lost_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
}
|
||||
|
||||
// test: implicit withdrawal case
|
||||
@@ -1715,7 +1715,7 @@ func TestProcessBGPUpdate_implicit_withdrwal_ipv4(t *testing.T) {
|
||||
checkPattr(bgpMessage2, path)
|
||||
// check destination
|
||||
expectedPrefix := "10.10.10.0/24"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "192.168.50.1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1821,7 +1821,7 @@ func TestProcessBGPUpdate_implicit_withdrwal_ipv6(t *testing.T) {
|
||||
|
||||
// check destination
|
||||
expectedPrefix := "2001:123:123:1::/64"
|
||||
assert.Equal(t, expectedPrefix, path.getPrefix())
|
||||
assert.Equal(t, expectedPrefix, path.GetPrefix())
|
||||
// check nexthop
|
||||
expectedNexthop := "2001::192:168:50:1"
|
||||
assert.Equal(t, expectedNexthop, path.GetNexthop().String())
|
||||
@@ -1876,7 +1876,7 @@ func TestProcessBGPUpdate_multiple_nlri_ipv4(t *testing.T) {
|
||||
assert.Equal(t, p.GetRouteFamily(), rf)
|
||||
checkPattr(m, p)
|
||||
// check destination
|
||||
assert.Equal(t, prefix, p.getPrefix())
|
||||
assert.Equal(t, prefix, p.GetPrefix())
|
||||
// check nexthop
|
||||
assert.Equal(t, nexthop, p.GetNexthop().String())
|
||||
}
|
||||
@@ -2014,7 +2014,7 @@ func TestProcessBGPUpdate_multiple_nlri_ipv6(t *testing.T) {
|
||||
assert.Equal(t, p.GetRouteFamily(), rf)
|
||||
checkPattr(m, p)
|
||||
// check destination
|
||||
assert.Equal(t, prefix, p.getPrefix())
|
||||
assert.Equal(t, prefix, p.GetPrefix())
|
||||
// check nexthop
|
||||
assert.Equal(t, nexthop, p.GetNexthop().String())
|
||||
}
|
||||
|
||||
+19
-16
@@ -28,14 +28,16 @@ import (
|
||||
|
||||
// workaround. This for the json format compatibility. Once we update senario tests, we can remove this.
|
||||
type Path struct {
|
||||
Nlri bgp.AddrPrefixInterface `json:"nlri"`
|
||||
Age int64 `json:"age"`
|
||||
Best bool `json:"best"`
|
||||
Attrs []bgp.PathAttributeInterface `json:"attrs"`
|
||||
Stale bool `json:"stale"`
|
||||
Withdrawal bool `json:"withdrawal,omitempty"`
|
||||
SourceID net.IP `json:"source-id,omitempty"`
|
||||
NeighborIP net.IP `json:"neighbor-ip,omitempty"`
|
||||
Nlri bgp.AddrPrefixInterface `json:"nlri"`
|
||||
Age int64 `json:"age"`
|
||||
Best bool `json:"best"`
|
||||
Attrs []bgp.PathAttributeInterface `json:"attrs"`
|
||||
Stale bool `json:"stale"`
|
||||
// true if the path has been filtered out due to max path count reached
|
||||
SendMaxFiltered bool `json:"send-max-filtered,omitempty"`
|
||||
Withdrawal bool `json:"withdrawal,omitempty"`
|
||||
SourceID net.IP `json:"source-id,omitempty"`
|
||||
NeighborIP net.IP `json:"neighbor-ip,omitempty"`
|
||||
}
|
||||
|
||||
type Destination struct {
|
||||
@@ -52,14 +54,15 @@ func NewDestination(dst *api.Destination) *Destination {
|
||||
nlri, _ := GetNativeNlri(p)
|
||||
attrs, _ := GetNativePathAttributes(p)
|
||||
l = append(l, &Path{
|
||||
Nlri: nlri,
|
||||
Age: p.Age.AsTime().Unix(),
|
||||
Best: p.Best,
|
||||
Attrs: attrs,
|
||||
Stale: p.Stale,
|
||||
Withdrawal: p.IsWithdraw,
|
||||
SourceID: net.ParseIP(p.SourceId),
|
||||
NeighborIP: net.ParseIP(p.NeighborIp),
|
||||
Nlri: nlri,
|
||||
Age: p.Age.AsTime().Unix(),
|
||||
Best: p.Best,
|
||||
Attrs: attrs,
|
||||
Stale: p.Stale,
|
||||
SendMaxFiltered: p.SendMaxFiltered,
|
||||
Withdrawal: p.IsWithdraw,
|
||||
SourceID: net.ParseIP(p.SourceId),
|
||||
NeighborIP: net.ParseIP(p.NeighborIp),
|
||||
})
|
||||
}
|
||||
return &Destination{Paths: l}
|
||||
|
||||
+137
-12
@@ -98,21 +98,25 @@ func newDynamicPeer(g *oc.Global, neighborAddress string, pg *oc.PeerGroup, loc
|
||||
}
|
||||
|
||||
type peer struct {
|
||||
tableId string
|
||||
fsm *fsm
|
||||
adjRibIn *table.AdjRib
|
||||
policy *table.RoutingPolicy
|
||||
localRib *table.TableManager
|
||||
prefixLimitWarned map[bgp.RouteFamily]bool
|
||||
llgrEndChs []chan struct{}
|
||||
tableId string
|
||||
fsm *fsm
|
||||
adjRibIn *table.AdjRib
|
||||
policy *table.RoutingPolicy
|
||||
localRib *table.TableManager
|
||||
prefixLimitWarned map[bgp.RouteFamily]bool
|
||||
dstRoutesCount map[bgp.RouteFamily]map[string]uint8
|
||||
sendMaxPathFiltered map[table.PathLocalKey]struct{}
|
||||
llgrEndChs []chan struct{}
|
||||
}
|
||||
|
||||
func newPeer(g *oc.Global, conf *oc.Neighbor, loc *table.TableManager, policy *table.RoutingPolicy, logger log.Logger) *peer {
|
||||
peer := &peer{
|
||||
localRib: loc,
|
||||
policy: policy,
|
||||
fsm: newFSM(g, conf, logger),
|
||||
prefixLimitWarned: make(map[bgp.RouteFamily]bool),
|
||||
localRib: loc,
|
||||
policy: policy,
|
||||
fsm: newFSM(g, conf, logger),
|
||||
prefixLimitWarned: make(map[bgp.RouteFamily]bool),
|
||||
dstRoutesCount: make(map[bgp.RouteFamily]map[string]uint8),
|
||||
sendMaxPathFiltered: make(map[table.PathLocalKey]struct{}),
|
||||
}
|
||||
if peer.isRouteServerClient() {
|
||||
peer.tableId = conf.State.NeighborAddress
|
||||
@@ -200,6 +204,128 @@ func (peer *peer) isAddPathSendEnabled(family bgp.RouteFamily) bool {
|
||||
return (peer.getAddPathMode(family) & bgp.BGP_ADD_PATH_SEND) > 0
|
||||
}
|
||||
|
||||
func (peer *peer) getAddPathSendMax(family bgp.RouteFamily) uint8 {
|
||||
peer.fsm.lock.RLock()
|
||||
defer peer.fsm.lock.RUnlock()
|
||||
for _, a := range peer.fsm.pConf.AfiSafis {
|
||||
if a.State.Family == family {
|
||||
return a.AddPaths.Config.SendMax
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (peer *peer) getRoutesCount(dstPrefix string, family bgp.RouteFamily) uint8 {
|
||||
peer.fsm.lock.RLock()
|
||||
defer peer.fsm.lock.RUnlock()
|
||||
if _, ok := peer.dstRoutesCount[family]; ok {
|
||||
return peer.dstRoutesCount[family][dstPrefix]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (peer *peer) setRoutesCount(dstPrefix string, family bgp.RouteFamily, count uint8) {
|
||||
peer.fsm.lock.Lock()
|
||||
defer peer.fsm.lock.Unlock()
|
||||
if _, ok := peer.dstRoutesCount[family]; !ok {
|
||||
peer.dstRoutesCount[family] = make(map[string]uint8)
|
||||
}
|
||||
peer.dstRoutesCount[family][dstPrefix] = count
|
||||
}
|
||||
|
||||
func (peer *peer) incrementRoutesCount(dstPrefix string, family bgp.RouteFamily, inc uint8) {
|
||||
if inc == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
peer.fsm.lock.Lock()
|
||||
defer peer.fsm.lock.Unlock()
|
||||
if _, ok := peer.dstRoutesCount[family]; !ok {
|
||||
peer.dstRoutesCount[family] = make(map[string]uint8)
|
||||
}
|
||||
newCount := peer.dstRoutesCount[family][dstPrefix] + inc
|
||||
if newCount < peer.dstRoutesCount[family][dstPrefix] {
|
||||
newCount = 0xFF
|
||||
}
|
||||
peer.dstRoutesCount[family][dstPrefix] = newCount
|
||||
}
|
||||
|
||||
func (peer *peer) decrementRoutesCount(dstPrefix string, family bgp.RouteFamily, dec uint8) {
|
||||
if dec == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
peer.fsm.lock.Lock()
|
||||
defer peer.fsm.lock.Unlock()
|
||||
if _, ok := peer.dstRoutesCount[family]; !ok {
|
||||
peer.dstRoutesCount[family] = make(map[string]uint8)
|
||||
}
|
||||
newCount := peer.dstRoutesCount[family][dstPrefix] - dec
|
||||
if newCount > peer.dstRoutesCount[family][dstPrefix] {
|
||||
newCount = 0
|
||||
}
|
||||
peer.dstRoutesCount[family][dstPrefix] = newCount
|
||||
}
|
||||
|
||||
func (peer *peer) isPathSendMaxFiltered(path *table.Path) bool {
|
||||
if path == nil {
|
||||
return false
|
||||
}
|
||||
_, found := peer.sendMaxPathFiltered[path.GetLocalKey()]
|
||||
return found
|
||||
}
|
||||
|
||||
func (peer *peer) unsetPathSendMaxFiltered(path *table.Path) bool {
|
||||
if path == nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := peer.sendMaxPathFiltered[path.GetLocalKey()]; !ok {
|
||||
return false
|
||||
}
|
||||
delete(peer.sendMaxPathFiltered, path.GetLocalKey())
|
||||
return true
|
||||
}
|
||||
|
||||
func (peer *peer) getSendMaxFilteredPathList(dest *table.Destination, limit int) []*table.Path {
|
||||
knownPathList := dest.GetKnownPathList(peer.TableID(), peer.AS())
|
||||
list := make([]*table.Path, 0, len(knownPathList))
|
||||
for _, p := range knownPathList {
|
||||
if !peer.isPathSendMaxFiltered(p) {
|
||||
continue
|
||||
}
|
||||
list = append(list, p)
|
||||
if limit > 0 && len(list) == limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (peer *peer) canSendPathWithinLimit(path *table.Path) bool {
|
||||
if path == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
family := path.GetRouteFamily()
|
||||
dstPrefix := path.GetPrefix()
|
||||
sendMax := peer.getAddPathSendMax(family)
|
||||
dstRouteCount := peer.getRoutesCount(dstPrefix, family)
|
||||
|
||||
if dstRouteCount >= sendMax {
|
||||
peer.sendMaxPathFiltered[path.GetLocalKey()] = struct{}{}
|
||||
return false
|
||||
}
|
||||
|
||||
if dstRouteCount > 0 && path.IsWithdraw {
|
||||
peer.decrementRoutesCount(dstPrefix, family, 1)
|
||||
} else if dstRouteCount < sendMax && !path.IsWithdraw {
|
||||
peer.incrementRoutesCount(dstPrefix, family, 1)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (peer *peer) isDynamicNeighbor() bool {
|
||||
peer.fsm.lock.RLock()
|
||||
defer peer.fsm.lock.RUnlock()
|
||||
@@ -440,7 +566,6 @@ func (peer *peer) doPrefixLimit(k bgp.RouteFamily, c *oc.PrefixLimitConfig) *bgp
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (peer *peer) updatePrefixLimitConfig(c []oc.AfiSafi) error {
|
||||
|
||||
+72
-20
@@ -760,13 +760,14 @@ func (s *BgpServer) postFilterpath(peer *peer, path *table.Path) *table.Path {
|
||||
if path != nil && !peer.isIBGPPeer() && !peer.isRouteServerClient() {
|
||||
path.RemoveLocalPref()
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func (s *BgpServer) filterpath(peer *peer, path, old *table.Path) *table.Path {
|
||||
path, options, stop := s.prePolicyFilterpath(peer, path, old)
|
||||
if stop {
|
||||
return path
|
||||
return nil
|
||||
}
|
||||
options.Validate = s.roaTable.Validate
|
||||
path = peer.policy.ApplyPolicy(peer.TableID(), table.POLICY_DIRECTION_EXPORT, path, options)
|
||||
@@ -1138,7 +1139,6 @@ func (s *BgpServer) processOutgoingPaths(peer *peer, paths, olds []*table.Path)
|
||||
}
|
||||
|
||||
outgoing := make([]*table.Path, 0, len(paths))
|
||||
|
||||
for idx, path := range paths {
|
||||
var old *table.Path
|
||||
if olds != nil {
|
||||
@@ -1291,7 +1291,7 @@ func (s *BgpServer) propagateUpdate(peer *peer, pathList []*table.Path) {
|
||||
}
|
||||
|
||||
if dsts := rib.Update(path); len(dsts) > 0 {
|
||||
s.propagateUpdateToNeighbors(peer, path, dsts, true)
|
||||
s.propagateUpdateToNeighbors(rib, peer, path, dsts, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1312,7 +1312,7 @@ func dstsToPaths(id string, as uint32, dsts []*table.Update) ([]*table.Path, []*
|
||||
return bestList, oldList, mpathList
|
||||
}
|
||||
|
||||
func (s *BgpServer) propagateUpdateToNeighbors(source *peer, newPath *table.Path, dsts []*table.Update, needOld bool) {
|
||||
func (s *BgpServer) propagateUpdateToNeighbors(rib *table.TableManager, source *peer, newPath *table.Path, dsts []*table.Update, needOld bool) {
|
||||
if table.SelectionOptions.DisableBestPathSelection {
|
||||
return
|
||||
}
|
||||
@@ -1350,11 +1350,41 @@ func (s *BgpServer) propagateUpdateToNeighbors(source *peer, newPath *table.Path
|
||||
bestList = func() []*table.Path {
|
||||
l := make([]*table.Path, 0, len(dsts))
|
||||
for _, d := range dsts {
|
||||
l = append(l, d.GetWithdrawnPath()...)
|
||||
toDelete := d.GetWithdrawnPath()
|
||||
toActuallyDelete := make([]*table.Path, 0, len(toDelete))
|
||||
for _, p := range toDelete {
|
||||
// the path was never advertized to the peer
|
||||
if targetPeer.unsetPathSendMaxFiltered(p) {
|
||||
continue
|
||||
}
|
||||
toActuallyDelete = append(toActuallyDelete, p)
|
||||
}
|
||||
|
||||
if len(toActuallyDelete) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
destination := rib.GetDestination(toActuallyDelete[0])
|
||||
dstPrefix := toActuallyDelete[0].GetPrefix()
|
||||
targetPeer.decrementRoutesCount(dstPrefix, f, uint8(len(toActuallyDelete)))
|
||||
l = append(l, toActuallyDelete...)
|
||||
|
||||
// the destination has been removed from the table
|
||||
// e.g. no more paths to it
|
||||
if destination == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
toAdd := targetPeer.getSendMaxFilteredPathList(destination, len(toActuallyDelete))
|
||||
targetPeer.incrementRoutesCount(dstPrefix, f, uint8(len(toAdd)))
|
||||
for _, p := range toAdd {
|
||||
targetPeer.unsetPathSendMaxFiltered(p)
|
||||
}
|
||||
l = append(l, toAdd...)
|
||||
}
|
||||
return l
|
||||
}()
|
||||
} else {
|
||||
} else if targetPeer.canSendPathWithinLimit(newPath) {
|
||||
bestList = []*table.Path{newPath}
|
||||
if newPath.GetRouteFamily() == bgp.RF_RTC_UC {
|
||||
// we assumes that new "path" nlri was already sent before. This assumption avoids the
|
||||
@@ -1366,6 +1396,14 @@ func (s *BgpServer) propagateUpdateToNeighbors(source *peer, newPath *table.Path
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bestList = []*table.Path{}
|
||||
s.logger.Warn("exceeding max routes for prefix",
|
||||
log.Fields{
|
||||
"Topic": "Peer",
|
||||
"Key": targetPeer.ID(),
|
||||
"Prefix": newPath.GetPrefix(),
|
||||
})
|
||||
}
|
||||
oldList = nil
|
||||
} else if targetPeer.isRouteServerClient() {
|
||||
@@ -2161,7 +2199,7 @@ func (s *BgpServer) fixupApiPath(vrfId string, pathList []*table.Path) error {
|
||||
}
|
||||
|
||||
func pathTokey(path *table.Path) string {
|
||||
return fmt.Sprintf("%d:%s", path.GetNlri().PathIdentifier(), path.GetNlri().String())
|
||||
return fmt.Sprintf("%d:%s", path.GetNlri().PathIdentifier(), path.GetPrefix())
|
||||
}
|
||||
|
||||
func (s *BgpServer) addPathList(vrfId string, pathList []*table.Path) error {
|
||||
@@ -2631,7 +2669,7 @@ func (s *BgpServer) getVrfRib(name string, family bgp.RouteFamily, prefixes []*t
|
||||
return
|
||||
}
|
||||
|
||||
func (s *BgpServer) getAdjRib(addr string, family bgp.RouteFamily, in bool, enableFiltered bool, prefixes []*table.LookupPrefix) (rib *table.Table, filtered map[string]*table.Path, v map[*table.Path]*table.Validation, err error) {
|
||||
func (s *BgpServer) getAdjRib(addr string, family bgp.RouteFamily, in bool, enableFiltered bool, prefixes []*table.LookupPrefix) (rib *table.Table, filtered map[table.PathLocalKey]table.FilteredType, v map[*table.Path]*table.Validation, err error) {
|
||||
err = s.mgmtOperation(func() error {
|
||||
peer, ok := s.neighborMap[addr]
|
||||
if !ok {
|
||||
@@ -2641,23 +2679,26 @@ func (s *BgpServer) getAdjRib(addr string, family bgp.RouteFamily, in bool, enab
|
||||
as := peer.AS()
|
||||
|
||||
var adjRib *table.AdjRib
|
||||
filtered = make(map[string]*table.Path)
|
||||
var toUpdate []*table.Path
|
||||
filtered = make(map[table.PathLocalKey]table.FilteredType)
|
||||
if in {
|
||||
adjRib = peer.adjRibIn
|
||||
if enableFiltered {
|
||||
toUpdate = make([]*table.Path, 0)
|
||||
for _, path := range peer.adjRibIn.PathList([]bgp.RouteFamily{family}, true) {
|
||||
options := &table.PolicyOptions{
|
||||
Validate: s.roaTable.Validate,
|
||||
}
|
||||
if p := s.policy.ApplyPolicy(peer.TableID(), table.POLICY_DIRECTION_IMPORT, path, options); p == nil {
|
||||
filtered[path.GetNlri().String()] = path
|
||||
filtered[path.GetLocalKey()] = table.PolicyFiltered
|
||||
} else {
|
||||
adjRib.Update([]*table.Path{p})
|
||||
toUpdate = append(toUpdate, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
adjRib = table.NewAdjRib(s.logger, peer.configuredRFlist())
|
||||
pathList := make([]*table.Path, 0)
|
||||
if enableFiltered {
|
||||
for _, path := range s.getPossibleBest(peer, family) {
|
||||
path, options, stop := s.prePolicyFilterpath(peer, path, nil)
|
||||
@@ -2667,15 +2708,23 @@ func (s *BgpServer) getAdjRib(addr string, family bgp.RouteFamily, in bool, enab
|
||||
options.Validate = s.roaTable.Validate
|
||||
p := peer.policy.ApplyPolicy(peer.TableID(), table.POLICY_DIRECTION_EXPORT, path, options)
|
||||
if p == nil {
|
||||
filtered[path.GetNlri().String()] = path
|
||||
filtered[path.GetLocalKey()] = table.PolicyFiltered
|
||||
}
|
||||
adjRib.UpdateAdjRibOut([]*table.Path{path})
|
||||
pathList = append(pathList, path)
|
||||
}
|
||||
} else {
|
||||
accepted, _ := s.getBestFromLocal(peer, peer.configuredRFlist())
|
||||
adjRib.UpdateAdjRibOut(accepted)
|
||||
pathList, _ = s.getBestFromLocal(peer, peer.configuredRFlist())
|
||||
}
|
||||
toUpdate = make([]*table.Path, 0, len(pathList))
|
||||
for _, p := range pathList {
|
||||
pathLocalKey := p.GetLocalKey()
|
||||
if peer.isPathSendMaxFiltered(p) {
|
||||
filtered[pathLocalKey] = filtered[pathLocalKey] | table.SendMaxFiltered
|
||||
}
|
||||
toUpdate = append(toUpdate, p)
|
||||
}
|
||||
}
|
||||
adjRib.Update(toUpdate)
|
||||
rib, err = adjRib.Select(family, false, table.TableSelectOption{ID: id, AS: as, LookupPrefixes: prefixes})
|
||||
v = s.validateTable(rib)
|
||||
return err
|
||||
@@ -2689,7 +2738,7 @@ func (s *BgpServer) ListPath(ctx context.Context, r *api.ListPathRequest, fn fun
|
||||
}
|
||||
var tbl *table.Table
|
||||
var v map[*table.Path]*table.Validation
|
||||
var filtered map[string]*table.Path
|
||||
var filtered map[table.PathLocalKey]table.FilteredType
|
||||
|
||||
f := func() []*table.LookupPrefix {
|
||||
l := make([]*table.LookupPrefix, 0, len(r.Prefixes))
|
||||
@@ -2747,10 +2796,13 @@ func (s *BgpServer) ListPath(ctx context.Context, r *api.ListPathRequest, fn fun
|
||||
}
|
||||
}
|
||||
d.Paths = append(d.Paths, p)
|
||||
if r.EnableFiltered {
|
||||
if _, ok := filtered[path.GetNlri().String()]; ok {
|
||||
p.Filtered = true
|
||||
}
|
||||
if r.EnableFiltered && filtered[path.GetLocalKey()]&table.PolicyFiltered > 0 {
|
||||
p.Filtered = true
|
||||
}
|
||||
// we always want to know that some paths are filtered out
|
||||
// by send-max attribute
|
||||
if filtered[path.GetLocalKey()]&table.SendMaxFiltered > 0 {
|
||||
p.SendMaxFiltered = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ func newIPRouteBody(dst []*table.Path, vrfID uint32, z *zebraClient) (body *zebr
|
||||
}
|
||||
path := paths[0]
|
||||
|
||||
l := strings.SplitN(path.GetNlri().String(), "/", 2)
|
||||
l := strings.SplitN(path.GetPrefix(), "/", 2)
|
||||
var prefix net.IP
|
||||
var nexthop zebra.Nexthop
|
||||
nexthops := make([]zebra.Nexthop, 0, len(paths))
|
||||
|
||||
+33
-12
@@ -128,15 +128,14 @@ def wait_for_completion(f, timeout=120):
|
||||
|
||||
|
||||
def try_several_times(f, t=3, s=1):
|
||||
e = Exception
|
||||
for _ in range(t):
|
||||
try:
|
||||
r = f()
|
||||
except RuntimeError as e:
|
||||
except RuntimeError:
|
||||
time.sleep(s)
|
||||
else:
|
||||
return r
|
||||
raise e
|
||||
raise Exception
|
||||
|
||||
|
||||
def assert_several_times(f, t=30, s=1):
|
||||
@@ -144,8 +143,9 @@ def assert_several_times(f, t=30, s=1):
|
||||
for _ in range(t):
|
||||
try:
|
||||
f()
|
||||
except AssertionError as e:
|
||||
except AssertionError as ae:
|
||||
time.sleep(s)
|
||||
e = ae
|
||||
else:
|
||||
return
|
||||
raise e
|
||||
@@ -361,14 +361,35 @@ class BGPContainer(Container):
|
||||
raise Exception('peer not exists')
|
||||
self.add_peer(peer, **kwargs)
|
||||
|
||||
def add_peer(self, peer, passwd=None, vpn=False, is_rs_client=False,
|
||||
policies=None, passive=False,
|
||||
is_rr_client=False, cluster_id=None,
|
||||
flowspec=False, bridge='', reload_config=True, as2=False,
|
||||
graceful_restart=None, local_as=None, prefix_limit=None,
|
||||
v6=False, llgr=None, vrf='', interface='', allow_as_in=0,
|
||||
remove_private_as=None, replace_peer_as=False, addpath=False,
|
||||
treat_as_withdraw=False, remote_as=None, mup=False):
|
||||
def add_peer(
|
||||
self,
|
||||
peer,
|
||||
passwd=None,
|
||||
vpn=False,
|
||||
is_rs_client=False,
|
||||
policies=None,
|
||||
passive=False,
|
||||
is_rr_client=False,
|
||||
cluster_id=None,
|
||||
flowspec=False,
|
||||
bridge="",
|
||||
reload_config=True,
|
||||
as2=False,
|
||||
graceful_restart=None,
|
||||
local_as=None,
|
||||
prefix_limit=None,
|
||||
v6=False,
|
||||
llgr=None,
|
||||
vrf="",
|
||||
interface="",
|
||||
allow_as_in=0,
|
||||
remove_private_as=None,
|
||||
replace_peer_as=False,
|
||||
addpath=0,
|
||||
treat_as_withdraw=False,
|
||||
remote_as=None,
|
||||
mup=False,
|
||||
):
|
||||
neigh_addr = ''
|
||||
local_addr = ''
|
||||
it = itertools.product(self.ip_addrs, peer.ip_addrs)
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ class ExaBGPContainer(BGPContainer):
|
||||
caps = []
|
||||
if info['as2']:
|
||||
caps.append(' asn4 disable;')
|
||||
if info['addpath']:
|
||||
if info["addpath"] > 0:
|
||||
caps.append(' add-path send/receive;')
|
||||
if caps:
|
||||
cmd << ' capability {'
|
||||
|
||||
+10
-10
@@ -14,7 +14,6 @@
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
|
||||
import collections
|
||||
import json
|
||||
from itertools import chain
|
||||
@@ -484,9 +483,10 @@ class GoBGPContainer(BGPContainer):
|
||||
n['route-reflector'] = {'config': {'route-reflector-client': True,
|
||||
'route-reflector-cluster-id': cluster_id}}
|
||||
|
||||
if info['addpath']:
|
||||
n['add-paths'] = {'config': {'receive': True,
|
||||
'send-max': 16}}
|
||||
if info["addpath"] > 0:
|
||||
n["add-paths"] = {
|
||||
"config": {"receive": True, "send-max": info["addpath"]}
|
||||
}
|
||||
|
||||
if len(info.get('default-policy', [])) + len(info.get('policies', [])) > 0:
|
||||
n['apply-policy'] = {'config': {}}
|
||||
@@ -536,11 +536,11 @@ class GoBGPContainer(BGPContainer):
|
||||
|
||||
with open('{0}/gobgpd.conf'.format(self.config_dir), 'w') as f:
|
||||
print(yellow('[{0}\'s new gobgpd.conf]'.format(self.name)))
|
||||
if self.config_format is 'toml':
|
||||
if self.config_format == "toml":
|
||||
raw = toml.dumps(config)
|
||||
elif self.config_format is 'yaml':
|
||||
elif self.config_format == "yaml":
|
||||
raw = yaml.dump(config)
|
||||
elif self.config_format is 'json':
|
||||
elif self.config_format == "json":
|
||||
raw = json.dumps(config)
|
||||
else:
|
||||
raise Exception('invalid config_format {0}'.format(self.config_format))
|
||||
@@ -666,11 +666,11 @@ class GoBGPContainer(BGPContainer):
|
||||
class RawGoBGPContainer(GoBGPContainer):
|
||||
def __init__(self, name, config, ctn_image_name='osrg/gobgp',
|
||||
log_level='debug', zebra=False, config_format='yaml'):
|
||||
if config_format is 'toml':
|
||||
if config_format == "toml":
|
||||
d = toml.loads(config)
|
||||
elif config_format is 'yaml':
|
||||
elif config_format == "yaml":
|
||||
d = yaml.load(config)
|
||||
elif config_format is 'json':
|
||||
elif config_format == "json":
|
||||
d = json.loads(config)
|
||||
else:
|
||||
raise Exception('invalid config format {0}'.format(config_format))
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import unittest
|
||||
|
||||
@@ -34,6 +35,8 @@ from lib.noseplugin import OptionParser, parser_option
|
||||
|
||||
|
||||
class GoBGPTestBase(unittest.TestCase):
|
||||
SEND_MAX = 5
|
||||
INSTALLED_PATHS = SEND_MAX + 1
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -56,14 +59,14 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
|
||||
time.sleep(initial_wait_time)
|
||||
|
||||
g1.add_peer(e1, addpath=True)
|
||||
e1.add_peer(g1, addpath=True)
|
||||
g1.add_peer(e1, addpath=16)
|
||||
e1.add_peer(g1, addpath=16)
|
||||
|
||||
g1.add_peer(g2, addpath=False, is_rr_client=True)
|
||||
g2.add_peer(g1, addpath=False)
|
||||
g1.add_peer(g2, is_rr_client=True)
|
||||
g2.add_peer(g1)
|
||||
|
||||
g1.add_peer(g3, addpath=True, is_rr_client=True)
|
||||
g3.add_peer(g1, addpath=True)
|
||||
g1.add_peer(g3, addpath=cls.SEND_MAX, is_rr_client=True)
|
||||
g3.add_peer(g1, addpath=cls.SEND_MAX)
|
||||
|
||||
cls.g1 = g1
|
||||
cls.g2 = g2
|
||||
@@ -73,20 +76,26 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
# test each neighbor state is turned establish
|
||||
def test_00_neighbor_established(self):
|
||||
self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2)
|
||||
self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g3)
|
||||
self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.e1)
|
||||
|
||||
# prepare routes with path_id (no error check)
|
||||
def test_01_prepare_add_paths_routes(self):
|
||||
self.e1.add_route(route='192.168.100.0/24', identifier=10, aspath=[100, 200, 300])
|
||||
self.e1.add_route(route='192.168.100.0/24', identifier=20, aspath=[100, 200])
|
||||
self.e1.add_route(route='192.168.100.0/24', identifier=30, aspath=[100])
|
||||
aspath = []
|
||||
for i in range(self.INSTALLED_PATHS):
|
||||
aspath.append((i + 1) * 100)
|
||||
self.e1.add_route(
|
||||
route="192.168.100.0/24",
|
||||
identifier=(i + 1) * 10,
|
||||
aspath=aspath,
|
||||
)
|
||||
|
||||
# test three routes are installed to the rib due to add-path feature
|
||||
def test_02_check_g1_global_rib(self):
|
||||
def f():
|
||||
rib = self.g1.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 3)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
@@ -96,7 +105,7 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
rib = self.g2.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 1)
|
||||
self.assertEqual(rib[0]['paths'][0]['aspath'], [100])
|
||||
self.assertEqual(len(rib[0]["paths"][0]["aspath"]), 1)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
@@ -105,167 +114,179 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
def f():
|
||||
rib = self.g3.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 3)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.SEND_MAX)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
def test_05_check_g1_adj_out(self):
|
||||
adj_out = self.g1.get_adj_rib_out(self.g2, add_path_enabled=True)
|
||||
self.assertEqual(len(adj_out), 1)
|
||||
self.assertEqual(len(adj_out[0]["paths"]), 1)
|
||||
|
||||
adj_out = self.g1.get_adj_rib_out(self.g3, add_path_enabled=True)
|
||||
self.assertEqual(len(adj_out), 1)
|
||||
self.assertEqual(len(adj_out[0]["paths"]), self.INSTALLED_PATHS)
|
||||
# expect the last path to be filtered
|
||||
self.assertTrue(adj_out[0]["paths"][-1].get("send-max-filtered", False))
|
||||
|
||||
# withdraw a route with path_id (no error check)
|
||||
def test_05_withdraw_route_with_path_id(self):
|
||||
self.e1.del_route(route='192.168.100.0/24', identifier=30)
|
||||
def test_06_withdraw_route_with_path_id(self):
|
||||
self.e1.del_route(route="192.168.100.0/24", identifier=10)
|
||||
|
||||
# test the withdrawn route is removed from the rib
|
||||
def test_06_check_g1_global_rib(self):
|
||||
def test_07_check_g1_global_rib(self):
|
||||
def f():
|
||||
rib = self.g1.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 2)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.INSTALLED_PATHS - 1)
|
||||
# we deleted the highest priority path
|
||||
for path in rib[0]['paths']:
|
||||
self.assertIn(path['aspath'], ([100, 200, 300],
|
||||
[100, 200]))
|
||||
self.assertTrue(2 <= len(path["aspath"]) <= self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# test the best path is replaced due to the removal from g1 rib
|
||||
def test_07_check_g2_global_rib(self):
|
||||
def test_08_check_g2_global_rib(self):
|
||||
def f():
|
||||
rib = self.g2.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 1)
|
||||
self.assertEqual(rib[0]['paths'][0]['aspath'], [100, 200])
|
||||
self.assertEqual(len(rib[0]["paths"][0]["aspath"]), 2)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# test the withdrawn route is removed from the rib of g3
|
||||
def test_08_check_g3_global_rib(self):
|
||||
# and the filtered route is advertised to g3
|
||||
def test_09_check_g3_global_rib(self):
|
||||
def f():
|
||||
rib = self.g3.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 2)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.SEND_MAX)
|
||||
for path in rib[0]['paths']:
|
||||
self.assertIn(path['aspath'], ([100, 200, 300],
|
||||
[100, 200]))
|
||||
self.assertTrue(2 <= len(path["aspath"]) <= self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# install a route with path_id via GoBGP CLI (no error check)
|
||||
def test_09_install_add_paths_route_via_cli(self):
|
||||
def test_10_install_add_paths_route_via_cli(self):
|
||||
# identifier is duplicated with the identifier of the route from e1
|
||||
self.g1.add_route(route='192.168.100.0/24', identifier=10, local_pref=500)
|
||||
|
||||
# test the route from CLI is installed to the rib
|
||||
def test_10_check_g1_global_rib(self):
|
||||
def test_11_check_g1_global_rib(self):
|
||||
def f():
|
||||
rib = self.g1.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 3)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.INSTALLED_PATHS)
|
||||
for path in rib[0]['paths']:
|
||||
self.assertIn(path['aspath'], ([100, 200, 300],
|
||||
[100, 200],
|
||||
[]))
|
||||
if not path['aspath']: # path['aspath'] == []
|
||||
if not path["aspath"]:
|
||||
self.assertEqual(path['local-pref'], 500)
|
||||
else:
|
||||
self.assertTrue(2 <= len(path["aspath"]) <= self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
def test_11_check_g1_adj_out(self):
|
||||
def test_12_check_g1_adj_out(self):
|
||||
adj_out = self.g1.get_adj_rib_out(self.g2, add_path_enabled=True)
|
||||
self.assertEqual(len(adj_out), 1)
|
||||
self.assertEqual(len(adj_out[0]['paths']), 1)
|
||||
self.assertEqual(len(adj_out[0]["paths"]), 1)
|
||||
|
||||
adj_out = self.g1.get_adj_rib_out(self.g3, add_path_enabled=True)
|
||||
self.assertEqual(len(adj_out), 1)
|
||||
self.assertEqual(len(adj_out[0]['paths']), 3)
|
||||
self.assertEqual(len(adj_out[0]["paths"]), self.INSTALLED_PATHS)
|
||||
print(json.dumps(adj_out, indent=2))
|
||||
# the new best path shouldn't be advertised as it is added after
|
||||
# the limit is reached
|
||||
self.assertEqual(adj_out[0]["paths"][0]["local-pref"], 500)
|
||||
self.assertTrue(adj_out[0]["paths"][0].get("send-max-filtered", False))
|
||||
|
||||
# test the best path is replaced due to the CLI route from g1 rib
|
||||
def test_12_check_g2_global_rib(self):
|
||||
def test_13_check_g2_global_rib(self):
|
||||
def f():
|
||||
rib = self.g2.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 1)
|
||||
self.assertEqual(rib[0]['paths'][0]['aspath'], [])
|
||||
self.assertEqual(len(rib[0]["paths"][0]["aspath"]), 0)
|
||||
self.assertEqual(rib[0]["paths"][0]["local-pref"], 500)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# test the route from CLI is advertised from g1
|
||||
def test_13_check_g3_global_rib(self):
|
||||
def test_14_check_g3_global_rib(self):
|
||||
def f():
|
||||
rib = self.g3.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 3)
|
||||
print(json.dumps(rib, indent=2))
|
||||
self.assertEqual(len(rib[0]["paths"]), self.SEND_MAX)
|
||||
for path in rib[0]['paths']:
|
||||
self.assertIn(path['aspath'], ([100, 200, 300],
|
||||
[100, 200],
|
||||
[]))
|
||||
if not path['aspath']: # path['aspath'] == []
|
||||
self.assertEqual(path['local-pref'], 500)
|
||||
self.assertTrue(2 <= len(path["aspath"]) <= self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# remove non-existing route with path_id via GoBGP CLI (no error check)
|
||||
def test_14_remove_non_existing_add_paths_route_via_cli(self):
|
||||
def test_15_remove_non_existing_add_paths_route_via_cli(self):
|
||||
# specify locally non-existing identifier which has the same value
|
||||
# with the identifier of the route from e1
|
||||
self.g1.del_route(route='192.168.100.0/24', identifier=20)
|
||||
|
||||
# test none of route is removed by non-existing path_id via CLI
|
||||
def test_15_check_g1_global_rib(self):
|
||||
def test_16_check_g1_global_rib(self):
|
||||
def f():
|
||||
rib = self.g1.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 3)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.INSTALLED_PATHS)
|
||||
for path in rib[0]['paths']:
|
||||
self.assertIn(path['aspath'], ([100, 200, 300],
|
||||
[100, 200],
|
||||
[]))
|
||||
if not path['aspath']: # path['aspath'] == []
|
||||
if not path["aspath"]:
|
||||
self.assertEqual(path['local-pref'], 500)
|
||||
else:
|
||||
self.assertTrue(2 <= len(path["aspath"]) <= self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# remove route with path_id via GoBGP CLI (no error check)
|
||||
def test_16_remove_add_paths_route_via_cli(self):
|
||||
def test_17_remove_add_paths_route_via_cli(self):
|
||||
self.g1.del_route(route='192.168.100.0/24', identifier=10)
|
||||
|
||||
def test_17_check_g1_adj_out(self):
|
||||
def test_18_check_g1_adj_out(self):
|
||||
adj_out = self.g1.get_adj_rib_out(self.g2, add_path_enabled=True)
|
||||
self.assertEqual(len(adj_out), 1)
|
||||
self.assertEqual(len(adj_out[0]['paths']), 1)
|
||||
self.assertEqual(len(adj_out[0]["paths"]), 1)
|
||||
|
||||
adj_out = self.g1.get_adj_rib_out(self.g3, add_path_enabled=True)
|
||||
self.assertEqual(len(adj_out), 1)
|
||||
self.assertEqual(len(adj_out[0]['paths']), 2)
|
||||
self.assertEqual(len(adj_out[0]["paths"]), self.INSTALLED_PATHS - 1)
|
||||
|
||||
# test the route is removed from the rib via CLI
|
||||
def test_18_check_g1_global_rib(self):
|
||||
def test_19_check_g1_global_rib(self):
|
||||
def f():
|
||||
rib = self.g1.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 2)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.INSTALLED_PATHS - 1)
|
||||
for path in rib[0]['paths']:
|
||||
self.assertIn(path['aspath'], ([100, 200, 300],
|
||||
[100, 200]))
|
||||
if not path["aspath"]:
|
||||
self.assertTrue(2 <= len(path["aspath"]) <= self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# test the best path is replaced the removal from g1 rib
|
||||
def test_19_check_g2_global_rib(self):
|
||||
def test_20_check_g2_global_rib(self):
|
||||
def f():
|
||||
rib = self.g2.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 1)
|
||||
self.assertEqual(rib[0]['paths'][0]['aspath'], [100, 200])
|
||||
self.assertEqual(len(rib[0]["paths"][0]["aspath"]), 2)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
# test the removed route from CLI is withdrawn by g1
|
||||
def test_20_check_g3_global_rib(self):
|
||||
def test_21_check_g3_global_rib(self):
|
||||
def f():
|
||||
rib = self.g3.get_global_rib()
|
||||
self.assertEqual(len(rib), 1)
|
||||
self.assertEqual(len(rib[0]['paths']), 2)
|
||||
self.assertEqual(len(rib[0]["paths"]), self.SEND_MAX)
|
||||
for path in rib[0]['paths']:
|
||||
self.assertIn(path['aspath'], ([100, 200, 300],
|
||||
[100, 200]))
|
||||
if not path["aspath"]:
|
||||
self.assertTrue(2 <= len(path["aspath"]) <= self.INSTALLED_PATHS)
|
||||
|
||||
assert_several_times(f)
|
||||
|
||||
|
||||
@@ -480,7 +480,6 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
self.gobgp.add_peer(q6, passwd='password')
|
||||
q6.add_peer(self.gobgp, passwd='password')
|
||||
|
||||
|
||||
self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q6)
|
||||
|
||||
def test_25_add_quagga_md5_badpass(self):
|
||||
@@ -575,7 +574,6 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
# note that we are checking it on the Quagga side since GoBGP
|
||||
# config doesn't explicitly "know about" the peer
|
||||
q9.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g6, timeout=30)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -162,17 +162,45 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
|
||||
time.sleep(max(ctn.run() for ctn in [rr, acme1, acme2, tyrell1, tyrell2]))
|
||||
|
||||
rr.add_peer(acme1, vpn=True, addpath=True, graceful_restart=True, llgr=True, is_rr_client=True)
|
||||
acme1.add_peer(rr, vpn=True, addpath=True, graceful_restart=True, llgr=True)
|
||||
rr.add_peer(
|
||||
acme1,
|
||||
vpn=True,
|
||||
addpath=16,
|
||||
graceful_restart=True,
|
||||
llgr=True,
|
||||
is_rr_client=True,
|
||||
)
|
||||
acme1.add_peer(rr, vpn=True, addpath=16, graceful_restart=True, llgr=True)
|
||||
|
||||
rr.add_peer(acme2, vpn=True, addpath=True, graceful_restart=True, llgr=True, is_rr_client=True)
|
||||
acme2.add_peer(rr, vpn=True, addpath=True, graceful_restart=True, llgr=True)
|
||||
rr.add_peer(
|
||||
acme2,
|
||||
vpn=True,
|
||||
addpath=16,
|
||||
graceful_restart=True,
|
||||
llgr=True,
|
||||
is_rr_client=True,
|
||||
)
|
||||
acme2.add_peer(rr, vpn=True, addpath=16, graceful_restart=True, llgr=True)
|
||||
|
||||
rr.add_peer(tyrell1, vpn=True, addpath=True, graceful_restart=True, llgr=True, is_rr_client=True)
|
||||
tyrell1.add_peer(rr, vpn=True, addpath=True, graceful_restart=True, llgr=True)
|
||||
rr.add_peer(
|
||||
tyrell1,
|
||||
vpn=True,
|
||||
addpath=16,
|
||||
graceful_restart=True,
|
||||
llgr=True,
|
||||
is_rr_client=True,
|
||||
)
|
||||
tyrell1.add_peer(rr, vpn=True, addpath=16, graceful_restart=True, llgr=True)
|
||||
|
||||
rr.add_peer(tyrell2, vpn=True, addpath=True, graceful_restart=True, llgr=True, is_rr_client=True)
|
||||
tyrell2.add_peer(rr, vpn=True, addpath=True, graceful_restart=True, llgr=True)
|
||||
rr.add_peer(
|
||||
tyrell2,
|
||||
vpn=True,
|
||||
addpath=16,
|
||||
graceful_restart=True,
|
||||
llgr=True,
|
||||
is_rr_client=True,
|
||||
)
|
||||
tyrell2.add_peer(rr, vpn=True, addpath=16, graceful_restart=True, llgr=True)
|
||||
|
||||
self.__class__.rr = rr
|
||||
self.__class__.acme1 = acme1
|
||||
|
||||
@@ -91,7 +91,6 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
state = self.gobgp.get_neighbor_state(rs_client)
|
||||
self.assertEqual(state, BGP_FSM_ESTABLISHED)
|
||||
local_rib = self.gobgp.get_local_rib(rs_client)
|
||||
local_rib = [p['prefix'] for p in local_rib]
|
||||
if len(local_rib) < (len(self.quaggas) - 1):
|
||||
time.sleep(self.wait_per_retry)
|
||||
continue
|
||||
|
||||
@@ -87,7 +87,7 @@ class GoBGPIPv6Test(unittest.TestCase):
|
||||
state = self.gobgp.get_neighbor_state(rs_client)
|
||||
self.assertEqual(state, BGP_FSM_ESTABLISHED)
|
||||
local_rib = self.gobgp.get_local_rib(rs_client, rf=rf)
|
||||
local_rib = [p['prefix'] for p in local_rib]
|
||||
local_rib = [p["prefix"] for p in local_rib]
|
||||
if len(local_rib) < (len(ctns) - 1):
|
||||
time.sleep(self.wait_per_retry)
|
||||
continue
|
||||
|
||||
@@ -841,11 +841,11 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
# +-------------+ +-------------+
|
||||
g3, g4, g5 = self.g3, self.g4, self.g5
|
||||
|
||||
g3.update_peer(g4, vpn=True, addpath=True, is_rr_client=True)
|
||||
g4.update_peer(g3, vpn=True, addpath=True)
|
||||
g3.update_peer(g4, vpn=True, addpath=16, is_rr_client=True)
|
||||
g4.update_peer(g3, vpn=True, addpath=16)
|
||||
|
||||
g3.update_peer(g5, vpn=True, addpath=True, is_rr_client=True)
|
||||
g5.update_peer(g3, vpn=True, addpath=True)
|
||||
g3.update_peer(g5, vpn=True, addpath=16, is_rr_client=True)
|
||||
g5.update_peer(g3, vpn=True, addpath=16)
|
||||
|
||||
g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g4)
|
||||
g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g5)
|
||||
|
||||
@@ -55,10 +55,10 @@ class GoBGPTestBase(unittest.TestCase):
|
||||
|
||||
time.sleep(initial_wait_time)
|
||||
|
||||
g1.add_peer(g2, vpn=True, addpath=True)
|
||||
g2.add_peer(g1, vpn=True, addpath=True)
|
||||
g1.add_peer(g3, vpn=True, addpath=True)
|
||||
g3.add_peer(g1, vpn=True, addpath=True)
|
||||
g1.add_peer(g2, vpn=True, addpath=16)
|
||||
g2.add_peer(g1, vpn=True, addpath=16)
|
||||
g1.add_peer(g3, vpn=True, addpath=16)
|
||||
g3.add_peer(g1, vpn=True, addpath=16)
|
||||
|
||||
cls.g1 = g1
|
||||
cls.g2 = g2
|
||||
|
||||
Reference in New Issue
Block a user