istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/route/route_internal_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package route
    16  
    17  import (
    18  	"reflect"
    19  	"testing"
    20  
    21  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    22  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    23  	xdsfault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/common/fault/v3"
    24  	cors "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/cors/v3"
    25  	xdshttpfault "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3"
    26  	matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
    27  	xdstype "github.com/envoyproxy/go-control-plane/envoy/type/v3"
    28  	"google.golang.org/protobuf/types/known/durationpb"
    29  	"google.golang.org/protobuf/types/known/wrapperspb"
    30  
    31  	networking "istio.io/api/networking/v1alpha3"
    32  	"istio.io/istio/pilot/pkg/model"
    33  	authzmatcher "istio.io/istio/pilot/pkg/security/authz/matcher"
    34  	authz "istio.io/istio/pilot/pkg/security/authz/model"
    35  	"istio.io/istio/pkg/config/labels"
    36  	"istio.io/istio/pkg/util/sets"
    37  )
    38  
    39  func TestIsCatchAllRoute(t *testing.T) {
    40  	cases := []struct {
    41  		name  string
    42  		route *route.Route
    43  		want  bool
    44  	}{
    45  		{
    46  			name: "catch all prefix",
    47  			route: &route.Route{
    48  				Name: "catch-all",
    49  				Match: &route.RouteMatch{
    50  					PathSpecifier: &route.RouteMatch_Prefix{
    51  						Prefix: "/",
    52  					},
    53  				},
    54  			},
    55  			want: true,
    56  		},
    57  		{
    58  			name: "catch all prefix >= 1.14",
    59  			route: &route.Route{
    60  				Name: "catch-all",
    61  				Match: &route.RouteMatch{
    62  					PathSpecifier: &route.RouteMatch_PathSeparatedPrefix{
    63  						PathSeparatedPrefix: "/",
    64  					},
    65  				},
    66  			},
    67  			want: true,
    68  		},
    69  		{
    70  			name: "catch all regex",
    71  			route: &route.Route{
    72  				Name: "catch-all",
    73  				Match: &route.RouteMatch{
    74  					PathSpecifier: &route.RouteMatch_SafeRegex{
    75  						SafeRegex: &matcher.RegexMatcher{
    76  							Regex: ".*",
    77  						},
    78  					},
    79  				},
    80  			},
    81  			want: true,
    82  		},
    83  		{
    84  			name: "catch all prefix with headers",
    85  			route: &route.Route{
    86  				Name: "catch-all",
    87  				Match: &route.RouteMatch{
    88  					PathSpecifier: &route.RouteMatch_Prefix{
    89  						Prefix: "/",
    90  					},
    91  					Headers: []*route.HeaderMatcher{
    92  						{
    93  							Name: "Authentication",
    94  							HeaderMatchSpecifier: &route.HeaderMatcher_ExactMatch{
    95  								ExactMatch: "test",
    96  							},
    97  						},
    98  					},
    99  				},
   100  			},
   101  			want: false,
   102  		},
   103  		{
   104  			name: "uri regex with headers",
   105  			route: &route.Route{
   106  				Name: "non-catch-all",
   107  				Match: &route.RouteMatch{
   108  					PathSpecifier: &route.RouteMatch_SafeRegex{
   109  						SafeRegex: &matcher.RegexMatcher{
   110  							// nolint: staticcheck
   111  							Regex: ".*",
   112  						},
   113  					},
   114  					Headers: []*route.HeaderMatcher{
   115  						{
   116  							Name: "Authentication",
   117  							HeaderMatchSpecifier: &route.HeaderMatcher_StringMatch{
   118  								StringMatch: &matcher.StringMatcher{
   119  									MatchPattern: &matcher.StringMatcher_SafeRegex{
   120  										SafeRegex: &matcher.RegexMatcher{
   121  											Regex: ".*",
   122  										},
   123  									},
   124  								},
   125  							},
   126  						},
   127  					},
   128  				},
   129  			},
   130  			want: false,
   131  		},
   132  		{
   133  			name: "uri regex with query params",
   134  			route: &route.Route{
   135  				Name: "non-catch-all",
   136  				Match: &route.RouteMatch{
   137  					PathSpecifier: &route.RouteMatch_SafeRegex{
   138  						SafeRegex: &matcher.RegexMatcher{
   139  							// nolint: staticcheck
   140  							Regex: ".*",
   141  						},
   142  					},
   143  					QueryParameters: []*route.QueryParameterMatcher{
   144  						{
   145  							Name: "Authentication",
   146  							QueryParameterMatchSpecifier: &route.QueryParameterMatcher_PresentMatch{
   147  								PresentMatch: true,
   148  							},
   149  						},
   150  					},
   151  				},
   152  			},
   153  			want: false,
   154  		},
   155  	}
   156  
   157  	for _, tt := range cases {
   158  		t.Run(tt.name, func(t *testing.T) {
   159  			catchall := IsCatchAllRoute(tt.route)
   160  			if catchall != tt.want {
   161  				t.Errorf("Unexpected catchAllMatch want %v, got %v", tt.want, catchall)
   162  			}
   163  		})
   164  	}
   165  }
   166  
   167  func TestTranslateCORSPolicyForwardNotMatchingPreflights(t *testing.T) {
   168  	node := &model.Proxy{
   169  		IstioVersion: &model.IstioVersion{
   170  			Major: 1,
   171  			Minor: 23,
   172  			Patch: 0,
   173  		},
   174  	}
   175  	corsPolicy := &networking.CorsPolicy{
   176  		AllowOrigins: []*networking.StringMatch{
   177  			{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   178  			{MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}},
   179  			{MatchType: &networking.StringMatch_Regex{Regex: "regex"}},
   180  		},
   181  		UnmatchedPreflights: networking.CorsPolicy_IGNORE,
   182  	}
   183  	expectedCorsPolicy := &cors.CorsPolicy{
   184  		ForwardNotMatchingPreflights: wrapperspb.Bool(false),
   185  		AllowOriginStringMatch: []*matcher.StringMatcher{
   186  			{MatchPattern: &matcher.StringMatcher_Exact{Exact: "exact"}},
   187  			{MatchPattern: &matcher.StringMatcher_Prefix{Prefix: "prefix"}},
   188  			{
   189  				MatchPattern: &matcher.StringMatcher_SafeRegex{
   190  					SafeRegex: &matcher.RegexMatcher{
   191  						Regex: "regex",
   192  					},
   193  				},
   194  			},
   195  		},
   196  		FilterEnabled: &core.RuntimeFractionalPercent{
   197  			DefaultValue: &xdstype.FractionalPercent{
   198  				Numerator:   100,
   199  				Denominator: xdstype.FractionalPercent_HUNDRED,
   200  			},
   201  		},
   202  	}
   203  	if got := TranslateCORSPolicy(node, corsPolicy); !reflect.DeepEqual(got, expectedCorsPolicy) {
   204  		t.Errorf("TranslateCORSPolicy() = \n%v, want \n%v", got, expectedCorsPolicy)
   205  	}
   206  }
   207  
   208  func TestTranslateCORSPolicy(t *testing.T) {
   209  	node := &model.Proxy{
   210  		IstioVersion: &model.IstioVersion{
   211  			Major: 1,
   212  			Minor: 21,
   213  			Patch: 0,
   214  		},
   215  	}
   216  	corsPolicy := &networking.CorsPolicy{
   217  		AllowOrigins: []*networking.StringMatch{
   218  			{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   219  			{MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}},
   220  			{MatchType: &networking.StringMatch_Regex{Regex: "regex"}},
   221  		},
   222  	}
   223  	expectedCorsPolicy := &cors.CorsPolicy{
   224  		AllowOriginStringMatch: []*matcher.StringMatcher{
   225  			{MatchPattern: &matcher.StringMatcher_Exact{Exact: "exact"}},
   226  			{MatchPattern: &matcher.StringMatcher_Prefix{Prefix: "prefix"}},
   227  			{
   228  				MatchPattern: &matcher.StringMatcher_SafeRegex{
   229  					SafeRegex: &matcher.RegexMatcher{
   230  						Regex: "regex",
   231  					},
   232  				},
   233  			},
   234  		},
   235  		FilterEnabled: &core.RuntimeFractionalPercent{
   236  			DefaultValue: &xdstype.FractionalPercent{
   237  				Numerator:   100,
   238  				Denominator: xdstype.FractionalPercent_HUNDRED,
   239  			},
   240  		},
   241  	}
   242  	if got := TranslateCORSPolicy(node, corsPolicy); !reflect.DeepEqual(got, expectedCorsPolicy) {
   243  		t.Errorf("TranslateCORSPolicy() = \n%v, want \n%v", got, expectedCorsPolicy)
   244  	}
   245  }
   246  
   247  func TestMirrorPercent(t *testing.T) {
   248  	cases := []struct {
   249  		name  string
   250  		route *networking.HTTPRoute
   251  		want  *core.RuntimeFractionalPercent
   252  	}{
   253  		{
   254  			name: "zero mirror percent",
   255  			route: &networking.HTTPRoute{
   256  				Mirror:        &networking.Destination{},
   257  				MirrorPercent: &wrapperspb.UInt32Value{Value: 0.0},
   258  			},
   259  			want: nil,
   260  		},
   261  		{
   262  			name: "mirror with no value given",
   263  			route: &networking.HTTPRoute{
   264  				Mirror: &networking.Destination{},
   265  			},
   266  			want: &core.RuntimeFractionalPercent{
   267  				DefaultValue: &xdstype.FractionalPercent{
   268  					Numerator:   100,
   269  					Denominator: xdstype.FractionalPercent_HUNDRED,
   270  				},
   271  			},
   272  		},
   273  		{
   274  			name: "mirror with actual percent",
   275  			route: &networking.HTTPRoute{
   276  				Mirror:        &networking.Destination{},
   277  				MirrorPercent: &wrapperspb.UInt32Value{Value: 50},
   278  			},
   279  			want: &core.RuntimeFractionalPercent{
   280  				DefaultValue: &xdstype.FractionalPercent{
   281  					Numerator:   50,
   282  					Denominator: xdstype.FractionalPercent_HUNDRED,
   283  				},
   284  			},
   285  		},
   286  		{
   287  			name: "zero mirror percentage",
   288  			route: &networking.HTTPRoute{
   289  				Mirror:           &networking.Destination{},
   290  				MirrorPercentage: &networking.Percent{Value: 0.0},
   291  			},
   292  			want: nil,
   293  		},
   294  		{
   295  			name: "mirrorpercentage with actual percent",
   296  			route: &networking.HTTPRoute{
   297  				Mirror:           &networking.Destination{},
   298  				MirrorPercentage: &networking.Percent{Value: 50.0},
   299  			},
   300  			want: &core.RuntimeFractionalPercent{
   301  				DefaultValue: &xdstype.FractionalPercent{
   302  					Numerator:   500000,
   303  					Denominator: xdstype.FractionalPercent_MILLION,
   304  				},
   305  			},
   306  		},
   307  		{
   308  			name: "mirrorpercentage takes precedence when both are given",
   309  			route: &networking.HTTPRoute{
   310  				Mirror:           &networking.Destination{},
   311  				MirrorPercent:    &wrapperspb.UInt32Value{Value: 40},
   312  				MirrorPercentage: &networking.Percent{Value: 50.0},
   313  			},
   314  			want: &core.RuntimeFractionalPercent{
   315  				DefaultValue: &xdstype.FractionalPercent{
   316  					Numerator:   500000,
   317  					Denominator: xdstype.FractionalPercent_MILLION,
   318  				},
   319  			},
   320  		},
   321  	}
   322  
   323  	for _, tt := range cases {
   324  		t.Run(tt.name, func(t *testing.T) {
   325  			mp := MirrorPercent(tt.route)
   326  			if !reflect.DeepEqual(mp, tt.want) {
   327  				t.Errorf("Unexpected mirror percent want %v, got %v", tt.want, mp)
   328  			}
   329  		})
   330  	}
   331  }
   332  
   333  func TestMirrorPercentByPolicy(t *testing.T) {
   334  	cases := []struct {
   335  		name   string
   336  		policy *networking.HTTPMirrorPolicy
   337  		want   *core.RuntimeFractionalPercent
   338  	}{
   339  		{
   340  			name: "mirror with no value given",
   341  			policy: &networking.HTTPMirrorPolicy{
   342  				Destination: &networking.Destination{},
   343  			},
   344  			want: &core.RuntimeFractionalPercent{
   345  				DefaultValue: &xdstype.FractionalPercent{
   346  					Numerator:   100,
   347  					Denominator: xdstype.FractionalPercent_HUNDRED,
   348  				},
   349  			},
   350  		},
   351  		{
   352  			name: "zero mirror percentage",
   353  			policy: &networking.HTTPMirrorPolicy{
   354  				Destination: &networking.Destination{},
   355  				Percentage:  &networking.Percent{Value: 0.0},
   356  			},
   357  			want: nil,
   358  		},
   359  		{
   360  			name: "mirrorpercentage with actual percent",
   361  			policy: &networking.HTTPMirrorPolicy{
   362  				Destination: &networking.Destination{},
   363  				Percentage:  &networking.Percent{Value: 50.0},
   364  			},
   365  			want: &core.RuntimeFractionalPercent{
   366  				DefaultValue: &xdstype.FractionalPercent{
   367  					Numerator:   500000,
   368  					Denominator: xdstype.FractionalPercent_MILLION,
   369  				},
   370  			},
   371  		},
   372  	}
   373  
   374  	for _, tt := range cases {
   375  		t.Run(tt.name, func(t *testing.T) {
   376  			mp := MirrorPercentByPolicy(tt.policy)
   377  			if !reflect.DeepEqual(mp, tt.want) {
   378  				t.Errorf("Unexpected mirror percent want %v, got %v", tt.want, mp)
   379  			}
   380  		})
   381  	}
   382  }
   383  
   384  func TestSourceMatchHTTP(t *testing.T) {
   385  	type args struct {
   386  		match          *networking.HTTPMatchRequest
   387  		proxyLabels    labels.Instance
   388  		gatewayNames   sets.String
   389  		proxyNamespace string
   390  	}
   391  	tests := []struct {
   392  		name string
   393  		args args
   394  		want bool
   395  	}{
   396  		{
   397  			"source namespace match",
   398  			args{
   399  				match: &networking.HTTPMatchRequest{
   400  					SourceNamespace: "foo",
   401  				},
   402  				proxyNamespace: "foo",
   403  			},
   404  			true,
   405  		},
   406  		{
   407  			"source namespace not match",
   408  			args{
   409  				match: &networking.HTTPMatchRequest{
   410  					SourceNamespace: "foo",
   411  				},
   412  				proxyNamespace: "bar",
   413  			},
   414  			false,
   415  		},
   416  		{
   417  			"source namespace not match when empty",
   418  			args{
   419  				match: &networking.HTTPMatchRequest{
   420  					SourceNamespace: "foo",
   421  				},
   422  				proxyNamespace: "",
   423  			},
   424  			false,
   425  		},
   426  		{
   427  			"source namespace any",
   428  			args{
   429  				match:          &networking.HTTPMatchRequest{},
   430  				proxyNamespace: "bar",
   431  			},
   432  			true,
   433  		},
   434  	}
   435  	for _, tt := range tests {
   436  		t.Run(tt.name, func(t *testing.T) {
   437  			if got := sourceMatchHTTP(tt.args.match, tt.args.proxyLabels, tt.args.gatewayNames, tt.args.proxyNamespace); got != tt.want {
   438  				t.Errorf("sourceMatchHTTP() = %v, want %v", got, tt.want)
   439  			}
   440  		})
   441  	}
   442  }
   443  
   444  func TestTranslateMetadataMatch(t *testing.T) {
   445  	cases := []struct {
   446  		name string
   447  		in   *networking.StringMatch
   448  		want *matcher.MetadataMatcher
   449  
   450  		useExtended bool
   451  	}{
   452  		{
   453  			name: "@request.auth.claims",
   454  		},
   455  		{
   456  			name: "@request.auth.claims-",
   457  		},
   458  		{
   459  			name: "request.auth.claims.",
   460  		},
   461  		{
   462  			name: "@request.auth.claims.",
   463  		},
   464  		{
   465  			name: "@request.auth.claims-abc",
   466  		},
   467  		{
   468  			name: "x-some-other-header",
   469  		},
   470  		{
   471  			name: "@request.auth.claims.key1",
   472  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   473  			want: authz.MetadataMatcherForJWTClaims([]string{"key1"}, authzmatcher.StringMatcher("exact"), false),
   474  		},
   475  		{
   476  			name: "@request.auth.claims.key1.KEY2",
   477  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   478  			want: authz.MetadataMatcherForJWTClaims([]string{"key1", "KEY2"}, authzmatcher.StringMatcher("exact"), false),
   479  		},
   480  		{
   481  			name: "@request.auth.claims.key1-key2",
   482  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   483  			want: authz.MetadataMatcherForJWTClaims([]string{"key1-key2"}, authzmatcher.StringMatcher("exact"), false),
   484  		},
   485  		{
   486  			name: "@request.auth.claims.prefix",
   487  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Prefix{Prefix: "prefix"}},
   488  			want: authz.MetadataMatcherForJWTClaims([]string{"prefix"}, authzmatcher.StringMatcher("prefix*"), false),
   489  		},
   490  		{
   491  			name: "@request.auth.claims.regex",
   492  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Regex{Regex: ".+?\\..+?\\..+?"}},
   493  			want: authz.MetadataMatcherForJWTClaims([]string{"regex"}, authzmatcher.StringMatcherRegex(".+?\\..+?\\..+?"), false),
   494  		},
   495  		{
   496  			name: "@request.auth.claims[key1",
   497  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   498  		},
   499  		{
   500  			name: "@request.auth.claims]key1",
   501  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   502  		},
   503  		{
   504  			// have `@request.auth.claims` prefix, but no separator
   505  			name: "@request.auth.claimskey1",
   506  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   507  		},
   508  		{
   509  			// if `.` exists, use `.` as separator
   510  			name: "@request.auth.claims.[key1]",
   511  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   512  			want: authz.MetadataMatcherForJWTClaims([]string{"[key1]"}, authzmatcher.StringMatcher("exact"), false),
   513  		},
   514  		{
   515  			name: "@request.auth.claims[key1]",
   516  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   517  			want: authz.MetadataMatcherForJWTClaims([]string{"key1"}, authzmatcher.StringMatcher("exact"), false),
   518  		},
   519  		{
   520  			name: "@request.auth.claims[key1][key2]",
   521  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   522  			want: authz.MetadataMatcherForJWTClaims([]string{"key1", "key2"}, authzmatcher.StringMatcher("exact"), false),
   523  		},
   524  		{
   525  			name: "@request.auth.claims[test-issuer-2@istio.io]",
   526  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   527  			want: authz.MetadataMatcherForJWTClaims([]string{"test-issuer-2@istio.io"}, authzmatcher.StringMatcher("exact"), false),
   528  		},
   529  		{
   530  			name: "@request.auth.claims[test-issuer-2@istio.io][key1]",
   531  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   532  			want: authz.MetadataMatcherForJWTClaims([]string{"test-issuer-2@istio.io", "key1"}, authzmatcher.StringMatcher("exact"), false),
   533  		},
   534  		{
   535  			name: "@request.auth.claims[test-issuer-2@istio.io][key1]",
   536  			in:   &networking.StringMatch{MatchType: &networking.StringMatch_Exact{Exact: "exact"}},
   537  			want: authz.MetadataMatcherForJWTClaims([]string{"test-issuer-2@istio.io", "key1"}, authzmatcher.StringMatcher("exact"), true),
   538  
   539  			useExtended: true,
   540  		},
   541  	}
   542  	for _, tc := range cases {
   543  		t.Run(tc.name, func(t *testing.T) {
   544  			got := translateMetadataMatch(tc.name, tc.in, tc.useExtended)
   545  			if !reflect.DeepEqual(got, tc.want) {
   546  				t.Errorf("Unexpected metadata matcher want %v, got %v", tc.want, got)
   547  			}
   548  		})
   549  	}
   550  }
   551  
   552  func TestTranslateFault(t *testing.T) {
   553  	cases := []struct {
   554  		name  string
   555  		fault *networking.HTTPFaultInjection
   556  		want  *xdshttpfault.HTTPFault
   557  	}{
   558  		{
   559  			name: "http delay",
   560  			fault: &networking.HTTPFaultInjection{
   561  				Delay: &networking.HTTPFaultInjection_Delay{
   562  					HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
   563  						FixedDelay: &durationpb.Duration{
   564  							Seconds: int64(3),
   565  						},
   566  					},
   567  					Percentage: &networking.Percent{
   568  						Value: float64(50),
   569  					},
   570  				},
   571  			},
   572  			want: &xdshttpfault.HTTPFault{
   573  				Delay: &xdsfault.FaultDelay{
   574  					Percentage: &xdstype.FractionalPercent{
   575  						Numerator:   uint32(50 * 10000),
   576  						Denominator: xdstype.FractionalPercent_MILLION,
   577  					},
   578  					FaultDelaySecifier: &xdsfault.FaultDelay_FixedDelay{
   579  						FixedDelay: &durationpb.Duration{
   580  							Seconds: int64(3),
   581  						},
   582  					},
   583  				},
   584  			},
   585  		},
   586  		{
   587  			name: "grpc abort",
   588  			fault: &networking.HTTPFaultInjection{
   589  				Abort: &networking.HTTPFaultInjection_Abort{
   590  					ErrorType: &networking.HTTPFaultInjection_Abort_GrpcStatus{
   591  						GrpcStatus: "DEADLINE_EXCEEDED",
   592  					},
   593  					Percentage: &networking.Percent{
   594  						Value: float64(50),
   595  					},
   596  				},
   597  			},
   598  			want: &xdshttpfault.HTTPFault{
   599  				Abort: &xdshttpfault.FaultAbort{
   600  					Percentage: &xdstype.FractionalPercent{
   601  						Numerator:   uint32(50 * 10000),
   602  						Denominator: xdstype.FractionalPercent_MILLION,
   603  					},
   604  					ErrorType: &xdshttpfault.FaultAbort_GrpcStatus{
   605  						GrpcStatus: uint32(4),
   606  					},
   607  				},
   608  			},
   609  		},
   610  		{
   611  			name: "both delay and abort",
   612  			fault: &networking.HTTPFaultInjection{
   613  				Delay: &networking.HTTPFaultInjection_Delay{
   614  					HttpDelayType: &networking.HTTPFaultInjection_Delay_FixedDelay{
   615  						FixedDelay: &durationpb.Duration{
   616  							Seconds: int64(3),
   617  						},
   618  					},
   619  					Percentage: &networking.Percent{
   620  						Value: float64(50),
   621  					},
   622  				},
   623  				Abort: &networking.HTTPFaultInjection_Abort{
   624  					ErrorType: &networking.HTTPFaultInjection_Abort_GrpcStatus{
   625  						GrpcStatus: "DEADLINE_EXCEEDED",
   626  					},
   627  					Percentage: &networking.Percent{
   628  						Value: float64(50),
   629  					},
   630  				},
   631  			},
   632  			want: &xdshttpfault.HTTPFault{
   633  				Delay: &xdsfault.FaultDelay{
   634  					Percentage: &xdstype.FractionalPercent{
   635  						Numerator:   uint32(50 * 10000),
   636  						Denominator: xdstype.FractionalPercent_MILLION,
   637  					},
   638  					FaultDelaySecifier: &xdsfault.FaultDelay_FixedDelay{
   639  						FixedDelay: &durationpb.Duration{
   640  							Seconds: int64(3),
   641  						},
   642  					},
   643  				},
   644  				Abort: &xdshttpfault.FaultAbort{
   645  					Percentage: &xdstype.FractionalPercent{
   646  						Numerator:   uint32(50 * 10000),
   647  						Denominator: xdstype.FractionalPercent_MILLION,
   648  					},
   649  					ErrorType: &xdshttpfault.FaultAbort_GrpcStatus{
   650  						GrpcStatus: uint32(4),
   651  					},
   652  				},
   653  			},
   654  		},
   655  	}
   656  
   657  	for _, tt := range cases {
   658  		t.Run(tt.name, func(t *testing.T) {
   659  			tf := TranslateFault(tt.fault)
   660  			if !reflect.DeepEqual(tf, tt.want) {
   661  				t.Errorf("Unexpected translate fault want %v, got %v", tt.want, tf)
   662  			}
   663  		})
   664  	}
   665  }