...

Source file src/github.com/datawire/ambassador/v2/cmd/entrypoint/testutil_rendering_test.go

Documentation: github.com/datawire/ambassador/v2/cmd/entrypoint

     1  package entrypoint_test
     2  
     3  import (
     4  	// standard library
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"sort"
     9  	"strings"
    10  	"testing"
    11  
    12  	// envoy api v3
    13  	apiv3_bootstrap "github.com/datawire/ambassador/v2/pkg/api/envoy/config/bootstrap/v3"
    14  	apiv3_httpman "github.com/datawire/ambassador/v2/pkg/api/envoy/extensions/filters/network/http_connection_manager/v3"
    15  
    16  	// envoy control plane
    17  	ecp_v3_resource "github.com/datawire/ambassador/v2/pkg/envoy-control-plane/resource/v3"
    18  	ecp_wellknown "github.com/datawire/ambassador/v2/pkg/envoy-control-plane/wellknown"
    19  
    20  	// first-party libraries
    21  	"github.com/datawire/ambassador/v2/pkg/api/getambassador.io/v3alpha1"
    22  	"github.com/datawire/ambassador/v2/pkg/kates"
    23  )
    24  
    25  func JSONify(obj interface{}) (string, error) {
    26  	bytes, err := json.MarshalIndent(obj, "", "  ")
    27  	if err != nil {
    28  		return "", err
    29  	}
    30  
    31  	return string(bytes), nil
    32  }
    33  
    34  func LoadYAML(path string) ([]kates.Object, error) {
    35  	content, err := ioutil.ReadFile(path)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	objs, err := kates.ParseManifests(string(content))
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	return objs, nil
    46  }
    47  
    48  type RenderedRoute struct {
    49  	Scheme         string `json:"scheme"`
    50  	Host           string `json:"host"`
    51  	Path           string `json:"path"`
    52  	Authority      string `json:"authority"`
    53  	AuthorityMatch string `json:"authorityMatch"`
    54  	Action         string `json:"action"`
    55  	ActionArg      string `json:"action_arg"`
    56  }
    57  
    58  func (rr *RenderedRoute) String() string {
    59  	s := fmt.Sprintf("%s%s: %s://%s%s", rr.Action, rr.ActionArg, rr.Scheme, rr.Host, rr.Path)
    60  
    61  	if rr.Authority != "" {
    62  		s += fmt.Sprintf(" (:authority %s %s)", rr.AuthorityMatch, rr.Authority)
    63  	}
    64  
    65  	return s
    66  }
    67  
    68  type RenderedVHost struct {
    69  	Name   string          `json:"name"`
    70  	Routes []RenderedRoute `json:"routes"`
    71  }
    72  
    73  func (rvh *RenderedVHost) AddRoute(rr RenderedRoute) {
    74  	rvh.Routes = append(rvh.Routes, rr)
    75  }
    76  
    77  func NewRenderedVHost(name string) RenderedVHost {
    78  	return RenderedVHost{
    79  		Name:   name,
    80  		Routes: []RenderedRoute{},
    81  	}
    82  }
    83  
    84  type RenderedChain struct {
    85  	ServerNames       []string                  `json:"server_names"`
    86  	TransportProtocol string                    `json:"transport_protocol"`
    87  	VHosts            map[string]*RenderedVHost `json:"-"`
    88  	VHostList         []*RenderedVHost          `json:"vhosts"`
    89  }
    90  
    91  func (rchain *RenderedChain) AddVHost(rvh *RenderedVHost) {
    92  	rchain.VHosts[rvh.Name] = rvh
    93  }
    94  
    95  func (rchain *RenderedChain) GetVHost(vhostname string) *RenderedVHost {
    96  	return rchain.VHosts[vhostname]
    97  }
    98  
    99  func NewRenderedChain(serverNames []string, transportProtocol string) RenderedChain {
   100  	chain := RenderedChain{
   101  		ServerNames:       nil,
   102  		TransportProtocol: transportProtocol,
   103  		VHosts:            map[string]*RenderedVHost{},
   104  		VHostList:         []*RenderedVHost{},
   105  	}
   106  
   107  	if len(serverNames) > 0 {
   108  		chain.ServerNames = []string{}
   109  
   110  		for _, name := range serverNames {
   111  			if (name != "") && (name != "*") {
   112  				chain.ServerNames = append(chain.ServerNames, name)
   113  			}
   114  		}
   115  	}
   116  
   117  	return chain
   118  }
   119  
   120  type RenderedListener struct {
   121  	Name      string                    `json:"name"`
   122  	Port      uint32                    `json:"port"`
   123  	Chains    map[string]*RenderedChain `json:"-"`
   124  	ChainList []*RenderedChain          `json:"chains"`
   125  }
   126  
   127  func (rl *RenderedListener) AddChain(rchain *RenderedChain) error {
   128  	hostname := "*"
   129  
   130  	if len(rchain.ServerNames) > 0 {
   131  		hostname = rchain.ServerNames[0]
   132  	}
   133  
   134  	xport := rchain.TransportProtocol
   135  
   136  	extant := rl.GetChain(hostname, xport)
   137  
   138  	if extant != nil {
   139  		return fmt.Errorf("chain for %s, %s already exists in %s", hostname, xport, rl.Name)
   140  	}
   141  
   142  	key := fmt.Sprintf("%s-%s", hostname, xport)
   143  
   144  	rl.Chains[key] = rchain
   145  	return nil
   146  }
   147  
   148  func (rl *RenderedListener) GetChain(hostname string, xport string) *RenderedChain {
   149  	key := fmt.Sprintf("%s-%s", hostname, xport)
   150  
   151  	return rl.Chains[key]
   152  }
   153  
   154  func NewRenderedListener(name string, port uint32) RenderedListener {
   155  	return RenderedListener{
   156  		Name:      name,
   157  		Port:      port,
   158  		Chains:    map[string]*RenderedChain{},
   159  		ChainList: []*RenderedChain{},
   160  	}
   161  }
   162  
   163  func NewListener(port uint32) RenderedListener {
   164  	return RenderedListener{
   165  		Name:   fmt.Sprintf("ambassador-listener-0.0.0.0-%d", port),
   166  		Port:   port,
   167  		Chains: map[string]*RenderedChain{},
   168  	}
   169  }
   170  
   171  func NewMapping(name string, pfx string) v3alpha1.Mapping {
   172  	return v3alpha1.Mapping{
   173  		TypeMeta:   kates.TypeMeta{Kind: "Mapping"},
   174  		ObjectMeta: kates.ObjectMeta{Namespace: "default", Name: name},
   175  		Spec: v3alpha1.MappingSpec{
   176  			Prefix:  pfx,
   177  			Service: "127.0.0.1:8877",
   178  		},
   179  	}
   180  }
   181  
   182  func JSONifyRenderedListeners(renderedListeners []RenderedListener) (string, error) {
   183  	// Why is this needed? JSONifying renderedListeners directly always
   184  	// shows empty listeners -- kinda feels like something's getting copied
   185  	// in a way I'm not awake enough to follow right now.
   186  	toDump := []RenderedListener{}
   187  
   188  	for _, l := range renderedListeners {
   189  		for _, c := range l.Chains {
   190  			for _, v := range c.VHosts {
   191  				if len(v.Routes) > 1 {
   192  					sort.SliceStable(v.Routes, func(i, j int) bool {
   193  						if v.Routes[i].Path != v.Routes[j].Path {
   194  							return v.Routes[i].Path < v.Routes[j].Path
   195  						}
   196  
   197  						if v.Routes[i].Host != v.Routes[j].Host {
   198  							return v.Routes[i].Host < v.Routes[j].Host
   199  						}
   200  
   201  						if v.Routes[i].Action != v.Routes[j].Action {
   202  							return v.Routes[i].Action < v.Routes[j].Action
   203  						}
   204  
   205  						return v.Routes[i].ActionArg < v.Routes[j].ActionArg
   206  					})
   207  				}
   208  
   209  				c.VHostList = append(c.VHostList, v)
   210  			}
   211  
   212  			if len(c.VHostList) > 1 {
   213  				sort.SliceStable(c.VHostList, func(i, j int) bool {
   214  					return c.VHostList[i].Name < c.VHostList[j].Name
   215  				})
   216  			}
   217  
   218  			l.ChainList = append(l.ChainList, c)
   219  		}
   220  
   221  		if len(l.ChainList) > 1 {
   222  			sort.SliceStable(l.ChainList, func(i, j int) bool {
   223  				sNamesI := l.ChainList[i].ServerNames
   224  				sNamesJ := l.ChainList[j].ServerNames
   225  
   226  				if (len(sNamesI) > 0) && (len(sNamesJ) > 0) {
   227  					if l.ChainList[i].ServerNames[0] != l.ChainList[j].ServerNames[0] {
   228  						return l.ChainList[i].ServerNames[0] < l.ChainList[j].ServerNames[0]
   229  					}
   230  				}
   231  
   232  				return l.ChainList[i].TransportProtocol < l.ChainList[j].TransportProtocol
   233  			})
   234  		}
   235  
   236  		toDump = append(toDump, l)
   237  	}
   238  
   239  	if len(toDump) > 1 {
   240  		sort.SliceStable(toDump, func(i, j int) bool {
   241  			return toDump[i].Port < toDump[j].Port
   242  		})
   243  	}
   244  
   245  	return JSONify(toDump)
   246  }
   247  
   248  type Candidate struct {
   249  	Scheme    string
   250  	Action    string
   251  	ActionArg string
   252  }
   253  
   254  func RenderEnvoyConfig(t *testing.T, envoyConfig *apiv3_bootstrap.Bootstrap) ([]RenderedListener, error) {
   255  	renderedListeners := make([]RenderedListener, 0, 2)
   256  
   257  	for _, l := range envoyConfig.StaticResources.Listeners {
   258  		port := l.Address.GetSocketAddress().GetPortValue()
   259  
   260  		t.Logf("LISTENER %s on port %d (chains %d)", l.Name, port, len(l.FilterChains))
   261  		rlistener := NewRenderedListener(l.Name, port)
   262  
   263  		for _, chain := range l.FilterChains {
   264  			t.Logf("  CHAIN %s", chain.FilterChainMatch)
   265  
   266  			rchain := NewRenderedChain(chain.FilterChainMatch.ServerNames, chain.FilterChainMatch.TransportProtocol)
   267  
   268  			for _, filter := range chain.Filters {
   269  				if filter.Name != ecp_wellknown.HTTPConnectionManager {
   270  					// We only know how to create an rds listener for HttpConnectionManager
   271  					// listeners. We must ignore all other listeners.
   272  					continue
   273  				}
   274  
   275  				// Note that the hcm configuration is stored in a protobuf any, so make
   276  				// sure that GetHTTPConnectionManager is actually returning an unmarshalled copy.
   277  				hcm := ecp_v3_resource.GetHTTPConnectionManager(filter)
   278  				if hcm == nil {
   279  					continue
   280  				}
   281  
   282  				// RouteSpecifier is a protobuf oneof that corresponds to the rds, route_config, and
   283  				// scoped_routes fields. Only one of those may be set at a time.
   284  				rs, ok := hcm.RouteSpecifier.(*apiv3_httpman.HttpConnectionManager_RouteConfig)
   285  				if !ok {
   286  					continue
   287  				}
   288  
   289  				rc := rs.RouteConfig
   290  
   291  				for _, vhost := range rc.VirtualHosts {
   292  					t.Logf("    VHost %s", vhost.Name)
   293  
   294  					rvh := NewRenderedVHost(vhost.Name)
   295  
   296  					for _, domain := range vhost.Domains {
   297  						for _, route := range vhost.Routes {
   298  							m := route.Match
   299  							pfx := m.GetPrefix()
   300  							hdrs := m.GetHeaders()
   301  							scheme := "implicit-http"
   302  
   303  							if !strings.HasPrefix(pfx, "/") {
   304  								pfx = "/" + pfx
   305  							}
   306  
   307  							authority := ""
   308  							authorityMatch := ""
   309  
   310  							for _, h := range hdrs {
   311  								hName := h.Name
   312  								prefixMatch := h.GetPrefixMatch()
   313  								suffixMatch := h.GetSuffixMatch()
   314  								exactMatch := h.GetExactMatch()
   315  
   316  								regexMatch := ""
   317  								srm := h.GetSafeRegexMatch()
   318  
   319  								if srm != nil {
   320  									regexMatch = srm.Regex
   321  									// } else {
   322  									// 	regexMatch = h.GetRegexMatch()
   323  								}
   324  
   325  								// summary := fmt.Sprintf("%#v", h)
   326  
   327  								if exactMatch != "" {
   328  									if hName == "x-forwarded-proto" {
   329  										scheme = exactMatch
   330  										continue
   331  									}
   332  
   333  									authority = exactMatch
   334  									authorityMatch = "=="
   335  								} else if prefixMatch != "" {
   336  									authority = prefixMatch + "*"
   337  									authorityMatch = "gl~"
   338  								} else if suffixMatch != "" {
   339  									authority = "*" + suffixMatch
   340  									authorityMatch = "gl~"
   341  								} else if regexMatch != "" {
   342  									authority = regexMatch
   343  									authorityMatch = "re~"
   344  								}
   345  							}
   346  
   347  							actionRoute := route.GetRoute()
   348  							actionRedirect := route.GetRedirect()
   349  
   350  							finalAction := "???"
   351  							finalActionArg := ""
   352  
   353  							if actionRoute != nil {
   354  								finalAction = "ROUTE"
   355  								finalActionArg = " " + actionRoute.GetCluster()
   356  							} else if actionRedirect != nil {
   357  								finalAction = "REDIRECT"
   358  
   359  								if actionRedirect.GetHttpsRedirect() {
   360  									finalActionArg = " HTTPS"
   361  								} else {
   362  									finalActionArg = fmt.Sprintf(" %#v", actionRedirect)
   363  								}
   364  							}
   365  
   366  							rroute := RenderedRoute{
   367  								Scheme:         scheme,
   368  								Host:           domain,
   369  								Path:           pfx,
   370  								Authority:      authority,
   371  								AuthorityMatch: authorityMatch,
   372  								Action:         finalAction,
   373  								ActionArg:      finalActionArg,
   374  							}
   375  
   376  							rvh.AddRoute(rroute)
   377  
   378  							t.Logf("      %s", rroute.String())
   379  
   380  							// if expectedAction != finalAction {
   381  							// 	t.Logf("    !! wanted %s", expectedAction)
   382  							// 	badRoutes++
   383  							// } else {
   384  							// 	goodRoutes++
   385  							// }
   386  							// require.Equal(t, expectedAction, finalAction)
   387  						}
   388  					}
   389  
   390  					rchain.AddVHost(&rvh)
   391  				}
   392  			}
   393  
   394  			if err := rlistener.AddChain(&rchain); err != nil {
   395  				return nil, err
   396  			}
   397  		}
   398  
   399  		renderedListeners = append(renderedListeners, rlistener)
   400  	}
   401  
   402  	return renderedListeners, nil
   403  }
   404  

View as plain text