config_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. *
  3. * Copyright 2020 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. package rls
  19. import (
  20. "encoding/json"
  21. "fmt"
  22. "strings"
  23. "testing"
  24. "time"
  25. _ "google.golang.org/grpc/balancer/grpclb" // grpclb for config parsing.
  26. _ "google.golang.org/grpc/internal/resolver/passthrough" // passthrough resolver.
  27. )
  28. // testEqual reports whether the lbCfgs a and b are equal. This is to be used
  29. // only from tests. This ignores the keyBuilderMap field because its internals
  30. // are not exported, and hence not possible to specify in the want section of
  31. // the test. This is fine because we already have tests to make sure that the
  32. // keyBuilder is parsed properly from the service config.
  33. func testEqual(a, b *lbConfig) bool {
  34. return a.lookupService == b.lookupService &&
  35. a.lookupServiceTimeout == b.lookupServiceTimeout &&
  36. a.maxAge == b.maxAge &&
  37. a.staleAge == b.staleAge &&
  38. a.cacheSizeBytes == b.cacheSizeBytes &&
  39. a.defaultTarget == b.defaultTarget &&
  40. a.controlChannelServiceConfig == b.controlChannelServiceConfig &&
  41. a.childPolicyName == b.childPolicyName &&
  42. a.childPolicyTargetField == b.childPolicyTargetField &&
  43. childPolicyConfigEqual(a.childPolicyConfig, b.childPolicyConfig)
  44. }
  45. // TestParseConfig verifies successful config parsing scenarios.
  46. func (s) TestParseConfig(t *testing.T) {
  47. childPolicyTargetFieldVal, _ := json.Marshal(dummyChildPolicyTarget)
  48. tests := []struct {
  49. desc string
  50. input []byte
  51. wantCfg *lbConfig
  52. }{
  53. {
  54. // This input validates a few cases:
  55. // - A top-level unknown field should not fail.
  56. // - An unknown field in routeLookupConfig proto should not fail.
  57. // - lookupServiceTimeout is set to its default value, since it is not specified in the input.
  58. // - maxAge is set to maxMaxAge since the value is too large in the input.
  59. // - staleAge is ignore because it is higher than maxAge in the input.
  60. // - cacheSizeBytes is greater than the hard upper limit of 5MB
  61. desc: "with transformations 1",
  62. input: []byte(`{
  63. "top-level-unknown-field": "unknown-value",
  64. "routeLookupConfig": {
  65. "unknown-field": "unknown-value",
  66. "grpcKeybuilders": [{
  67. "names": [{"service": "service", "method": "method"}],
  68. "headers": [{"key": "k1", "names": ["v1"]}]
  69. }],
  70. "lookupService": ":///target",
  71. "maxAge" : "500s",
  72. "staleAge": "600s",
  73. "cacheSizeBytes": 100000000,
  74. "defaultTarget": "passthrough:///default"
  75. },
  76. "childPolicy": [
  77. {"cds_experimental": {"Cluster": "my-fav-cluster"}},
  78. {"unknown-policy": {"unknown-field": "unknown-value"}},
  79. {"grpclb": {"childPolicy": [{"pickfirst": {}}]}}
  80. ],
  81. "childPolicyConfigTargetFieldName": "serviceName"
  82. }`),
  83. wantCfg: &lbConfig{
  84. lookupService: ":///target",
  85. lookupServiceTimeout: 10 * time.Second, // This is the default value.
  86. maxAge: 5 * time.Minute, // This is max maxAge.
  87. staleAge: time.Duration(0), // StaleAge is ignore because it was higher than maxAge.
  88. cacheSizeBytes: maxCacheSize,
  89. defaultTarget: "passthrough:///default",
  90. childPolicyName: "grpclb",
  91. childPolicyTargetField: "serviceName",
  92. childPolicyConfig: map[string]json.RawMessage{
  93. "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`),
  94. "serviceName": json.RawMessage(childPolicyTargetFieldVal),
  95. },
  96. },
  97. },
  98. {
  99. desc: "without transformations",
  100. input: []byte(`{
  101. "routeLookupConfig": {
  102. "grpcKeybuilders": [{
  103. "names": [{"service": "service", "method": "method"}],
  104. "headers": [{"key": "k1", "names": ["v1"]}]
  105. }],
  106. "lookupService": "target",
  107. "lookupServiceTimeout" : "100s",
  108. "maxAge": "60s",
  109. "staleAge" : "50s",
  110. "cacheSizeBytes": 1000,
  111. "defaultTarget": "passthrough:///default"
  112. },
  113. "routeLookupChannelServiceConfig": {"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]},
  114. "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
  115. "childPolicyConfigTargetFieldName": "serviceName"
  116. }`),
  117. wantCfg: &lbConfig{
  118. lookupService: "target",
  119. lookupServiceTimeout: 100 * time.Second,
  120. maxAge: 60 * time.Second,
  121. staleAge: 50 * time.Second,
  122. cacheSizeBytes: 1000,
  123. defaultTarget: "passthrough:///default",
  124. controlChannelServiceConfig: `{"loadBalancingConfig": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}]}`,
  125. childPolicyName: "grpclb",
  126. childPolicyTargetField: "serviceName",
  127. childPolicyConfig: map[string]json.RawMessage{
  128. "childPolicy": json.RawMessage(`[{"pickfirst": {}}]`),
  129. "serviceName": json.RawMessage(childPolicyTargetFieldVal),
  130. },
  131. },
  132. },
  133. }
  134. builder := rlsBB{}
  135. for _, test := range tests {
  136. t.Run(test.desc, func(t *testing.T) {
  137. lbCfg, err := builder.ParseConfig(test.input)
  138. if err != nil || !testEqual(lbCfg.(*lbConfig), test.wantCfg) {
  139. t.Errorf("ParseConfig(%s) = {%+v, %v}, want {%+v, nil}", string(test.input), lbCfg, err, test.wantCfg)
  140. }
  141. })
  142. }
  143. }
  144. // TestParseConfigErrors verifies config parsing failure scenarios.
  145. func (s) TestParseConfigErrors(t *testing.T) {
  146. tests := []struct {
  147. desc string
  148. input []byte
  149. wantErr string
  150. }{
  151. {
  152. desc: "empty input",
  153. input: nil,
  154. wantErr: "rls: json unmarshal failed for service config",
  155. },
  156. {
  157. desc: "bad json",
  158. input: []byte(`bad bad json`),
  159. wantErr: "rls: json unmarshal failed for service config",
  160. },
  161. {
  162. desc: "bad grpcKeyBuilder",
  163. input: []byte(`{
  164. "routeLookupConfig": {
  165. "grpcKeybuilders": [{
  166. "names": [{"service": "service", "method": "method"}],
  167. "headers": [{"key": "k1", "requiredMatch": true, "names": ["v1"]}]
  168. }]
  169. }
  170. }`),
  171. wantErr: "rls: GrpcKeyBuilder in RouteLookupConfig has required_match field set",
  172. },
  173. {
  174. desc: "empty lookup service",
  175. input: []byte(`{
  176. "routeLookupConfig": {
  177. "grpcKeybuilders": [{
  178. "names": [{"service": "service", "method": "method"}],
  179. "headers": [{"key": "k1", "names": ["v1"]}]
  180. }]
  181. }
  182. }`),
  183. wantErr: "rls: empty lookup_service in route lookup config",
  184. },
  185. {
  186. desc: "unregistered scheme in lookup service URI",
  187. input: []byte(`{
  188. "routeLookupConfig": {
  189. "grpcKeybuilders": [{
  190. "names": [{"service": "service", "method": "method"}],
  191. "headers": [{"key": "k1", "names": ["v1"]}]
  192. }],
  193. "lookupService": "badScheme:///target"
  194. }
  195. }`),
  196. wantErr: "rls: unregistered scheme in lookup_service",
  197. },
  198. {
  199. desc: "invalid lookup service timeout",
  200. input: []byte(`{
  201. "routeLookupConfig": {
  202. "grpcKeybuilders": [{
  203. "names": [{"service": "service", "method": "method"}],
  204. "headers": [{"key": "k1", "names": ["v1"]}]
  205. }],
  206. "lookupService": "passthrough:///target",
  207. "lookupServiceTimeout" : "315576000001s"
  208. }
  209. }`),
  210. wantErr: "google.protobuf.Duration value out of range",
  211. },
  212. {
  213. desc: "invalid max age",
  214. input: []byte(`{
  215. "routeLookupConfig": {
  216. "grpcKeybuilders": [{
  217. "names": [{"service": "service", "method": "method"}],
  218. "headers": [{"key": "k1", "names": ["v1"]}]
  219. }],
  220. "lookupService": "passthrough:///target",
  221. "lookupServiceTimeout" : "10s",
  222. "maxAge" : "315576000001s"
  223. }
  224. }`),
  225. wantErr: "google.protobuf.Duration value out of range",
  226. },
  227. {
  228. desc: "invalid stale age",
  229. input: []byte(`{
  230. "routeLookupConfig": {
  231. "grpcKeybuilders": [{
  232. "names": [{"service": "service", "method": "method"}],
  233. "headers": [{"key": "k1", "names": ["v1"]}]
  234. }],
  235. "lookupService": "passthrough:///target",
  236. "lookupServiceTimeout" : "10s",
  237. "maxAge" : "10s",
  238. "staleAge" : "315576000001s"
  239. }
  240. }`),
  241. wantErr: "google.protobuf.Duration value out of range",
  242. },
  243. {
  244. desc: "invalid max age stale age combo",
  245. input: []byte(`{
  246. "routeLookupConfig": {
  247. "grpcKeybuilders": [{
  248. "names": [{"service": "service", "method": "method"}],
  249. "headers": [{"key": "k1", "names": ["v1"]}]
  250. }],
  251. "lookupService": "passthrough:///target",
  252. "lookupServiceTimeout" : "10s",
  253. "staleAge" : "10s"
  254. }
  255. }`),
  256. wantErr: "rls: stale_age is set, but max_age is not in route lookup config",
  257. },
  258. {
  259. desc: "cache_size_bytes field is not set",
  260. input: []byte(`{
  261. "routeLookupConfig": {
  262. "grpcKeybuilders": [{
  263. "names": [{"service": "service", "method": "method"}],
  264. "headers": [{"key": "k1", "names": ["v1"]}]
  265. }],
  266. "lookupService": "passthrough:///target",
  267. "lookupServiceTimeout" : "10s",
  268. "maxAge": "30s",
  269. "staleAge" : "25s",
  270. "defaultTarget": "passthrough:///default"
  271. },
  272. "childPolicyConfigTargetFieldName": "serviceName"
  273. }`),
  274. wantErr: "rls: cache_size_bytes must be set to a non-zero value",
  275. },
  276. {
  277. desc: "routeLookupChannelServiceConfig is not in service config format",
  278. input: []byte(`{
  279. "routeLookupConfig": {
  280. "grpcKeybuilders": [{
  281. "names": [{"service": "service", "method": "method"}],
  282. "headers": [{"key": "k1", "names": ["v1"]}]
  283. }],
  284. "lookupService": "target",
  285. "lookupServiceTimeout" : "100s",
  286. "maxAge": "60s",
  287. "staleAge" : "50s",
  288. "cacheSizeBytes": 1000,
  289. "defaultTarget": "passthrough:///default"
  290. },
  291. "routeLookupChannelServiceConfig": "unknown",
  292. "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
  293. "childPolicyConfigTargetFieldName": "serviceName"
  294. }`),
  295. wantErr: "cannot unmarshal string into Go value of type grpc.jsonSC",
  296. },
  297. {
  298. desc: "routeLookupChannelServiceConfig contains unknown LB policy",
  299. input: []byte(`{
  300. "routeLookupConfig": {
  301. "grpcKeybuilders": [{
  302. "names": [{"service": "service", "method": "method"}],
  303. "headers": [{"key": "k1", "names": ["v1"]}]
  304. }],
  305. "lookupService": "target",
  306. "lookupServiceTimeout" : "100s",
  307. "maxAge": "60s",
  308. "staleAge" : "50s",
  309. "cacheSizeBytes": 1000,
  310. "defaultTarget": "passthrough:///default"
  311. },
  312. "routeLookupChannelServiceConfig": {
  313. "loadBalancingConfig": [{"not_a_balancer1": {} }, {"not_a_balancer2": {}}]
  314. },
  315. "childPolicy": [{"grpclb": {"childPolicy": [{"pickfirst": {}}]}}],
  316. "childPolicyConfigTargetFieldName": "serviceName"
  317. }`),
  318. wantErr: "invalid loadBalancingConfig: no supported policies found",
  319. },
  320. {
  321. desc: "no child policy",
  322. input: []byte(`{
  323. "routeLookupConfig": {
  324. "grpcKeybuilders": [{
  325. "names": [{"service": "service", "method": "method"}],
  326. "headers": [{"key": "k1", "names": ["v1"]}]
  327. }],
  328. "lookupService": "passthrough:///target",
  329. "lookupServiceTimeout" : "10s",
  330. "maxAge": "30s",
  331. "staleAge" : "25s",
  332. "cacheSizeBytes": 1000,
  333. "defaultTarget": "passthrough:///default"
  334. },
  335. "childPolicyConfigTargetFieldName": "serviceName"
  336. }`),
  337. wantErr: "rls: invalid childPolicy config: no supported policies found",
  338. },
  339. {
  340. desc: "no known child policy",
  341. input: []byte(`{
  342. "routeLookupConfig": {
  343. "grpcKeybuilders": [{
  344. "names": [{"service": "service", "method": "method"}],
  345. "headers": [{"key": "k1", "names": ["v1"]}]
  346. }],
  347. "lookupService": "passthrough:///target",
  348. "lookupServiceTimeout" : "10s",
  349. "maxAge": "30s",
  350. "staleAge" : "25s",
  351. "cacheSizeBytes": 1000,
  352. "defaultTarget": "passthrough:///default"
  353. },
  354. "childPolicy": [
  355. {"cds_experimental": {"Cluster": "my-fav-cluster"}},
  356. {"unknown-policy": {"unknown-field": "unknown-value"}}
  357. ],
  358. "childPolicyConfigTargetFieldName": "serviceName"
  359. }`),
  360. wantErr: "rls: invalid childPolicy config: no supported policies found",
  361. },
  362. {
  363. desc: "invalid child policy config - more than one entry in map",
  364. input: []byte(`{
  365. "routeLookupConfig": {
  366. "grpcKeybuilders": [{
  367. "names": [{"service": "service", "method": "method"}],
  368. "headers": [{"key": "k1", "names": ["v1"]}]
  369. }],
  370. "lookupService": "passthrough:///target",
  371. "lookupServiceTimeout" : "10s",
  372. "maxAge": "30s",
  373. "staleAge" : "25s",
  374. "cacheSizeBytes": 1000,
  375. "defaultTarget": "passthrough:///default"
  376. },
  377. "childPolicy": [
  378. {
  379. "cds_experimental": {"Cluster": "my-fav-cluster"},
  380. "unknown-policy": {"unknown-field": "unknown-value"}
  381. }
  382. ],
  383. "childPolicyConfigTargetFieldName": "serviceName"
  384. }`),
  385. wantErr: "does not contain exactly 1 policy/config pair",
  386. },
  387. {
  388. desc: "no childPolicyConfigTargetFieldName",
  389. input: []byte(`{
  390. "routeLookupConfig": {
  391. "grpcKeybuilders": [{
  392. "names": [{"service": "service", "method": "method"}],
  393. "headers": [{"key": "k1", "names": ["v1"]}]
  394. }],
  395. "lookupService": "passthrough:///target",
  396. "lookupServiceTimeout" : "10s",
  397. "maxAge": "30s",
  398. "staleAge" : "25s",
  399. "cacheSizeBytes": 1000,
  400. "defaultTarget": "passthrough:///default"
  401. },
  402. "childPolicy": [
  403. {"cds_experimental": {"Cluster": "my-fav-cluster"}},
  404. {"unknown-policy": {"unknown-field": "unknown-value"}},
  405. {"grpclb": {}}
  406. ]
  407. }`),
  408. wantErr: "rls: childPolicyConfigTargetFieldName field is not set in service config",
  409. },
  410. {
  411. desc: "child policy config validation failure",
  412. input: []byte(`{
  413. "routeLookupConfig": {
  414. "grpcKeybuilders": [{
  415. "names": [{"service": "service", "method": "method"}],
  416. "headers": [{"key": "k1", "names": ["v1"]}]
  417. }],
  418. "lookupService": "passthrough:///target",
  419. "lookupServiceTimeout" : "10s",
  420. "maxAge": "30s",
  421. "staleAge" : "25s",
  422. "cacheSizeBytes": 1000,
  423. "defaultTarget": "passthrough:///default"
  424. },
  425. "childPolicy": [
  426. {"cds_experimental": {"Cluster": "my-fav-cluster"}},
  427. {"unknown-policy": {"unknown-field": "unknown-value"}},
  428. {"grpclb": {"childPolicy": "not-an-array"}}
  429. ],
  430. "childPolicyConfigTargetFieldName": "serviceName"
  431. }`),
  432. wantErr: "rls: childPolicy config validation failed",
  433. },
  434. }
  435. builder := rlsBB{}
  436. for _, test := range tests {
  437. t.Run(test.desc, func(t *testing.T) {
  438. lbCfg, err := builder.ParseConfig(test.input)
  439. if lbCfg != nil || !strings.Contains(fmt.Sprint(err), test.wantErr) {
  440. t.Errorf("ParseConfig(%s) = {%+v, %v}, want {nil, %s}", string(test.input), lbCfg, err, test.wantErr)
  441. }
  442. })
  443. }
  444. }