allow passing bgp community filters in query text

This commit is contained in:
Annika Hannig 2024-01-18 13:21:38 +01:00
parent bd78a05f37
commit f72c4a3518
4 changed files with 132 additions and 33 deletions

View File

@ -174,7 +174,7 @@ func filterValueAsString(value interface{}) string {
case ExtCommunity:
return v.String()
}
panic("unexpected filter value")
panic("unexpected filter value: " + fmt.Sprintf("%v", value))
}
// GetFilterByValue retrieves a filter by matching
@ -563,7 +563,7 @@ func parseCommunityFilterText(text string) (string, *SearchFilter, error) {
filter, err := parseCommunityValue(text)
if err != nil {
return "", nil, err
return "", nil, fmt.Errorf("BGP community incomplete")
}
if len(tokens) == 2 {
@ -573,19 +573,17 @@ func parseCommunityFilterText(text string) (string, *SearchFilter, error) {
return SearchKeyLargeCommunities, filter, nil
}
// FiltersFromQueryText parses the passed list of filters
// FiltersFromTokens parses the passed list of filters
// extracted from the query string and creates the filter.
func FiltersFromQueryText(filters []string) (*SearchFilters, error) {
func FiltersFromTokens(tokens []string) (*SearchFilters, error) {
queryFilters := NewSearchFilters()
for _, filter := range filters {
if strings.HasPrefix(filter, "#") { // Community query
key, value, err := parseCommunityFilterText(filter[1:])
for _, value := range tokens {
if strings.HasPrefix(value, "#") { // Community query
key, filter, err := parseCommunityFilterText(value[1:])
if err != nil {
return nil, err
}
queryFilters.GetGroupByKey(key).AddFilter(&SearchFilter{
Value: value,
})
queryFilters.GetGroupByKey(key).AddFilter(filter)
}
}
@ -623,6 +621,31 @@ func (s *SearchFilters) MatchRoute(r Filterable) bool {
return true
}
// Combine two search filters
func (s *SearchFilters) Combine(other *SearchFilters) *SearchFilters {
result := make(SearchFilters, len(*s))
for id, group := range *s {
otherGroup := (*other)[id]
combined := &SearchFilterGroup{
Key: group.Key,
Filters: []*SearchFilter{},
}
for _, f := range group.Filters {
combined.Filters = append(combined.Filters, f)
}
for _, f := range otherGroup.Filters {
if combined.Contains(f) {
continue
}
combined.Filters = append(combined.Filters, f)
}
combined.rebuildIndex()
result[id] = combined
}
return &result
}
// Sub makes a diff of two search filters
func (s *SearchFilters) Sub(other *SearchFilters) *SearchFilters {
result := make(SearchFilters, len(*s))

View File

@ -738,3 +738,54 @@ func TestParseExtCommunityFilterText(t *testing.T) {
t.Error("Expected community to be ro:12345:23 but got:", v)
}
}
func TestFiltersFromTokens(t *testing.T) {
tokens := []string{"#23:42", "#ro:23:42", "#1000:23:42"}
filters, err := FiltersFromTokens(tokens)
if err != nil {
t.Fatal(err)
}
// Check communities
communities := filters.GetGroupByKey(SearchKeyCommunities).Filters
if len(communities) != 1 {
t.Error("There should be 1 community filter")
}
v0 := communities[0].Value.(Community)
if v0[0] != 23 && v0[1] != 42 {
t.Error("Expected community to be 23:42 but got:", v0)
}
// Check ext. communities
extCommunities := filters.GetGroupByKey(SearchKeyExtCommunities).Filters
if len(extCommunities) != 1 {
t.Error("There should be 1 ext. community filter")
}
v1 := extCommunities[0].Value.(ExtCommunity)
if v1[0] != "ro" && v1[1] != "23" && v1[2] != "42" {
t.Error("Expected community to be ro:23:42 but got:", v1)
}
// Check large communities
largeCommunities := filters.GetGroupByKey(SearchKeyLargeCommunities).Filters
if len(largeCommunities) != 1 {
t.Error("There should be 1 large community filter")
}
v2 := largeCommunities[0].Value.(Community)
if v2[0] != 1000 && v2[1] != 23 && v2[2] != 42 {
t.Error("Expected community to be 1000:23:42 but got:", v2)
}
}
func TestFiltersFromTokensInvalid(t *testing.T) {
tokens := []string{"#"}
_, err := FiltersFromTokens(tokens)
if err == nil {
t.Error("Expected error for invalid filter")
}
t.Log(err)
}

View File

@ -26,20 +26,20 @@ func (s *Server) apiLookupPrefixGlobal(
// Get prefix to query
q, err := validateQueryString(req, "q")
/*
if err != nil {
return nil, err
if err != nil {
return nil, err
}
q, filterTokens := QueryString(q).ExtractFilters()
// Get filters from query string
queryFilters, err := api.FiltersFromTokens(filterTokens)
if err != nil {
return nil, &ErrValidationFailed{
Param: "q",
Reason: err.Error(),
}
*/
// Check what we want to query
// Prefix -> fetch prefix
// _ -> fetch neighbors and routes
lookupPrefix := decoders.MaybePrefix(q)
lookupPrefix = true
// Measure response time
t0 := time.Now()
}
// Get additional filter criteria
filtersApplied, err := api.FiltersFromQuery(req.URL.Query())
@ -47,20 +47,24 @@ func (s *Server) apiLookupPrefixGlobal(
return nil, err
}
filtersApplied.GetGroupByKey(api.SearchKeyCommunities).AddFilter(&api.SearchFilter{
Name: "65104:150",
Value: api.Community{65104, 150},
})
// Merge query filters into applied filters
filtersApplied = filtersApplied.Combine(queryFilters)
// Check what we want to query
// Prefix -> fetch prefix
// _ -> fetch neighbors and routes
lookupPrefix := decoders.MaybePrefix(q)
// Measure response time
t0 := time.Now()
// Perform query
var routes api.LookupRoutes
if lookupPrefix {
/*
q, err = validatePrefixQuery(q)
if err != nil {
return nil, err
}
*/
q, err = validatePrefixQuery(q)
if err != nil {
return nil, err
}
routes, err = s.routesStore.LookupPrefix(ctx, q, filtersApplied)
if err != nil {
return nil, err

View File

@ -57,3 +57,24 @@ func apiQueryFilterNextHopGateway(
return results
}
// QueryString wraps the q parameter from the query.
// Extract the value and additional filters from the string
type QueryString string
// ExtractFilters separates query and filters from string.
func (q QueryString) ExtractFilters() (string, []string) {
tokens := strings.Split(string(q), " ")
query := []string{}
filters := []string{}
for _, t := range tokens {
if strings.HasPrefix(t, "#") {
filters = append(filters, t)
} else {
query = append(query, t)
}
}
return strings.Join(query, " "), filters
}