run_xds_tests.py 103 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425
  1. #!/usr/bin/env python
  2. # Copyright 2020 gRPC authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Run xDS integration tests on GCP using Traffic Director."""
  16. import argparse
  17. import googleapiclient.discovery
  18. import grpc
  19. import json
  20. import logging
  21. import os
  22. import random
  23. import shlex
  24. import socket
  25. import subprocess
  26. import sys
  27. import tempfile
  28. import time
  29. from oauth2client.client import GoogleCredentials
  30. import python_utils.jobset as jobset
  31. import python_utils.report_utils as report_utils
  32. from src.proto.grpc.health.v1 import health_pb2
  33. from src.proto.grpc.health.v1 import health_pb2_grpc
  34. from src.proto.grpc.testing import empty_pb2
  35. from src.proto.grpc.testing import messages_pb2
  36. from src.proto.grpc.testing import test_pb2_grpc
  37. logger = logging.getLogger()
  38. console_handler = logging.StreamHandler()
  39. formatter = logging.Formatter(fmt='%(asctime)s: %(levelname)-8s %(message)s')
  40. console_handler.setFormatter(formatter)
  41. logger.handlers = []
  42. logger.addHandler(console_handler)
  43. logger.setLevel(logging.WARNING)
  44. _TEST_CASES = [
  45. 'backends_restart',
  46. 'change_backend_service',
  47. 'gentle_failover',
  48. 'load_report_based_failover',
  49. 'ping_pong',
  50. 'remove_instance_group',
  51. 'round_robin',
  52. 'secondary_locality_gets_no_requests_on_partial_primary_failure',
  53. 'secondary_locality_gets_requests_on_primary_failure',
  54. 'traffic_splitting',
  55. ]
  56. # Valid test cases, but not in all. So the tests can only run manually, and
  57. # aren't enabled automatically for all languages.
  58. #
  59. # TODO: Move them into _TEST_CASES when support is ready in all languages.
  60. _ADDITIONAL_TEST_CASES = [
  61. 'path_matching',
  62. 'header_matching',
  63. 'circuit_breaking',
  64. ]
  65. def parse_test_cases(arg):
  66. if arg == '':
  67. return []
  68. arg_split = arg.split(',')
  69. test_cases = set()
  70. all_test_cases = _TEST_CASES + _ADDITIONAL_TEST_CASES
  71. for arg in arg_split:
  72. if arg == "all":
  73. test_cases = test_cases.union(_TEST_CASES)
  74. else:
  75. test_cases = test_cases.union([arg])
  76. if not all([test_case in all_test_cases for test_case in test_cases]):
  77. raise Exception('Failed to parse test cases %s' % arg)
  78. # Perserve order.
  79. return [x for x in all_test_cases if x in test_cases]
  80. def parse_port_range(port_arg):
  81. try:
  82. port = int(port_arg)
  83. return range(port, port + 1)
  84. except:
  85. port_min, port_max = port_arg.split(':')
  86. return range(int(port_min), int(port_max) + 1)
  87. argp = argparse.ArgumentParser(description='Run xDS interop tests on GCP')
  88. argp.add_argument('--project_id', help='GCP project id')
  89. argp.add_argument(
  90. '--gcp_suffix',
  91. default='',
  92. help='Optional suffix for all generated GCP resource names. Useful to '
  93. 'ensure distinct names across test runs.')
  94. argp.add_argument(
  95. '--test_case',
  96. default='ping_pong',
  97. type=parse_test_cases,
  98. help='Comma-separated list of test cases to run. Available tests: %s, '
  99. '(or \'all\' to run every test). '
  100. 'Alternative tests not included in \'all\': %s' %
  101. (','.join(_TEST_CASES), ','.join(_ADDITIONAL_TEST_CASES)))
  102. argp.add_argument(
  103. '--bootstrap_file',
  104. default='',
  105. help='File to reference via GRPC_XDS_BOOTSTRAP. Disables built-in '
  106. 'bootstrap generation')
  107. argp.add_argument(
  108. '--xds_v3_support',
  109. default=False,
  110. action='store_true',
  111. help='Support xDS v3 via GRPC_XDS_EXPERIMENTAL_V3_SUPPORT. '
  112. 'If a pre-created bootstrap file is provided via the --bootstrap_file '
  113. 'parameter, it should include xds_v3 in its server_features field.')
  114. argp.add_argument(
  115. '--client_cmd',
  116. default=None,
  117. help='Command to launch xDS test client. {server_uri}, {stats_port} and '
  118. '{qps} references will be replaced using str.format(). GRPC_XDS_BOOTSTRAP '
  119. 'will be set for the command')
  120. argp.add_argument(
  121. '--client_hosts',
  122. default=None,
  123. help='Comma-separated list of hosts running client processes. If set, '
  124. '--client_cmd is ignored and client processes are assumed to be running on '
  125. 'the specified hosts.')
  126. argp.add_argument('--zone', default='us-central1-a')
  127. argp.add_argument('--secondary_zone',
  128. default='us-west1-b',
  129. help='Zone to use for secondary TD locality tests')
  130. argp.add_argument('--qps', default=100, type=int, help='Client QPS')
  131. argp.add_argument(
  132. '--wait_for_backend_sec',
  133. default=1200,
  134. type=int,
  135. help='Time limit for waiting for created backend services to report '
  136. 'healthy when launching or updated GCP resources')
  137. argp.add_argument(
  138. '--use_existing_gcp_resources',
  139. default=False,
  140. action='store_true',
  141. help=
  142. 'If set, find and use already created GCP resources instead of creating new'
  143. ' ones.')
  144. argp.add_argument(
  145. '--keep_gcp_resources',
  146. default=False,
  147. action='store_true',
  148. help=
  149. 'Leave GCP VMs and configuration running after test. Default behavior is '
  150. 'to delete when tests complete.')
  151. argp.add_argument(
  152. '--compute_discovery_document',
  153. default=None,
  154. type=str,
  155. help=
  156. 'If provided, uses this file instead of retrieving via the GCP discovery '
  157. 'API')
  158. argp.add_argument(
  159. '--alpha_compute_discovery_document',
  160. default=None,
  161. type=str,
  162. help='If provided, uses this file instead of retrieving via the alpha GCP '
  163. 'discovery API')
  164. argp.add_argument('--network',
  165. default='global/networks/default',
  166. help='GCP network to use')
  167. argp.add_argument('--service_port_range',
  168. default='8080:8110',
  169. type=parse_port_range,
  170. help='Listening port for created gRPC backends. Specified as '
  171. 'either a single int or as a range in the format min:max, in '
  172. 'which case an available port p will be chosen s.t. min <= p '
  173. '<= max')
  174. argp.add_argument(
  175. '--stats_port',
  176. default=8079,
  177. type=int,
  178. help='Local port for the client process to expose the LB stats service')
  179. argp.add_argument('--xds_server',
  180. default='trafficdirector.googleapis.com:443',
  181. help='xDS server')
  182. argp.add_argument('--source_image',
  183. default='projects/debian-cloud/global/images/family/debian-9',
  184. help='Source image for VMs created during the test')
  185. argp.add_argument('--path_to_server_binary',
  186. default=None,
  187. type=str,
  188. help='If set, the server binary must already be pre-built on '
  189. 'the specified source image')
  190. argp.add_argument('--machine_type',
  191. default='e2-standard-2',
  192. help='Machine type for VMs created during the test')
  193. argp.add_argument(
  194. '--instance_group_size',
  195. default=2,
  196. type=int,
  197. help='Number of VMs to create per instance group. Certain test cases (e.g., '
  198. 'round_robin) may not give meaningful results if this is set to a value '
  199. 'less than 2.')
  200. argp.add_argument('--verbose',
  201. help='verbose log output',
  202. default=False,
  203. action='store_true')
  204. # TODO(ericgribkoff) Remove this param once the sponge-formatted log files are
  205. # visible in all test environments.
  206. argp.add_argument('--log_client_output',
  207. help='Log captured client output',
  208. default=False,
  209. action='store_true')
  210. # TODO(ericgribkoff) Remove this flag once all test environments are verified to
  211. # have access to the alpha compute APIs.
  212. argp.add_argument('--only_stable_gcp_apis',
  213. help='Do not use alpha compute APIs. Some tests may be '
  214. 'incompatible with this option (gRPC health checks are '
  215. 'currently alpha and required for simulating server failure',
  216. default=False,
  217. action='store_true')
  218. args = argp.parse_args()
  219. if args.verbose:
  220. logger.setLevel(logging.DEBUG)
  221. CLIENT_HOSTS = []
  222. if args.client_hosts:
  223. CLIENT_HOSTS = args.client_hosts.split(',')
  224. _DEFAULT_SERVICE_PORT = 80
  225. _WAIT_FOR_BACKEND_SEC = args.wait_for_backend_sec
  226. _WAIT_FOR_OPERATION_SEC = 1200
  227. _INSTANCE_GROUP_SIZE = args.instance_group_size
  228. _NUM_TEST_RPCS = 10 * args.qps
  229. _WAIT_FOR_STATS_SEC = 360
  230. _WAIT_FOR_VALID_CONFIG_SEC = 60
  231. _WAIT_FOR_URL_MAP_PATCH_SEC = 300
  232. _CONNECTION_TIMEOUT_SEC = 60
  233. _GCP_API_RETRIES = 5
  234. _BOOTSTRAP_TEMPLATE = """
  235. {{
  236. "node": {{
  237. "id": "{node_id}",
  238. "metadata": {{
  239. "TRAFFICDIRECTOR_NETWORK_NAME": "%s"
  240. }},
  241. "locality": {{
  242. "zone": "%s"
  243. }}
  244. }},
  245. "xds_servers": [{{
  246. "server_uri": "%s",
  247. "channel_creds": [
  248. {{
  249. "type": "google_default",
  250. "config": {{}}
  251. }}
  252. ],
  253. "server_features": {server_features}
  254. }}]
  255. }}""" % (args.network.split('/')[-1], args.zone, args.xds_server)
  256. # TODO(ericgribkoff) Add change_backend_service to this list once TD no longer
  257. # sends an update with no localities when adding the MIG to the backend service
  258. # can race with the URL map patch.
  259. _TESTS_TO_FAIL_ON_RPC_FAILURE = ['ping_pong', 'round_robin']
  260. # Tests that run UnaryCall and EmptyCall.
  261. _TESTS_TO_RUN_MULTIPLE_RPCS = ['path_matching', 'header_matching']
  262. # Tests that make UnaryCall with test metadata.
  263. _TESTS_TO_SEND_METADATA = ['header_matching']
  264. _TEST_METADATA_KEY = 'xds_md'
  265. _TEST_METADATA_VALUE_UNARY = 'unary_yranu'
  266. _TEST_METADATA_VALUE_EMPTY = 'empty_ytpme'
  267. # Extra RPC metadata whose value is a number, sent with UnaryCall only.
  268. _TEST_METADATA_NUMERIC_KEY = 'xds_md_numeric'
  269. _TEST_METADATA_NUMERIC_VALUE = '159'
  270. _PATH_MATCHER_NAME = 'path-matcher'
  271. _BASE_TEMPLATE_NAME = 'test-template'
  272. _BASE_INSTANCE_GROUP_NAME = 'test-ig'
  273. _BASE_HEALTH_CHECK_NAME = 'test-hc'
  274. _BASE_FIREWALL_RULE_NAME = 'test-fw-rule'
  275. _BASE_BACKEND_SERVICE_NAME = 'test-backend-service'
  276. _BASE_URL_MAP_NAME = 'test-map'
  277. _BASE_SERVICE_HOST = 'grpc-test'
  278. _BASE_TARGET_PROXY_NAME = 'test-target-proxy'
  279. _BASE_FORWARDING_RULE_NAME = 'test-forwarding-rule'
  280. _TEST_LOG_BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
  281. '../../reports')
  282. _SPONGE_LOG_NAME = 'sponge_log.log'
  283. _SPONGE_XML_NAME = 'sponge_log.xml'
  284. def get_client_stats(num_rpcs, timeout_sec):
  285. if CLIENT_HOSTS:
  286. hosts = CLIENT_HOSTS
  287. else:
  288. hosts = ['localhost']
  289. for host in hosts:
  290. with grpc.insecure_channel('%s:%d' %
  291. (host, args.stats_port)) as channel:
  292. stub = test_pb2_grpc.LoadBalancerStatsServiceStub(channel)
  293. request = messages_pb2.LoadBalancerStatsRequest()
  294. request.num_rpcs = num_rpcs
  295. request.timeout_sec = timeout_sec
  296. rpc_timeout = timeout_sec + _CONNECTION_TIMEOUT_SEC
  297. logger.debug('Invoking GetClientStats RPC to %s:%d:', host,
  298. args.stats_port)
  299. response = stub.GetClientStats(request,
  300. wait_for_ready=True,
  301. timeout=rpc_timeout)
  302. logger.debug('Invoked GetClientStats RPC to %s: %s', host, response)
  303. return response
  304. def get_client_accumulated_stats():
  305. if CLIENT_HOSTS:
  306. hosts = CLIENT_HOSTS
  307. else:
  308. hosts = ['localhost']
  309. for host in hosts:
  310. with grpc.insecure_channel('%s:%d' %
  311. (host, args.stats_port)) as channel:
  312. stub = test_pb2_grpc.LoadBalancerStatsServiceStub(channel)
  313. request = messages_pb2.LoadBalancerAccumulatedStatsRequest()
  314. logger.debug('Invoking GetClientAccumulatedStats RPC to %s:%d:',
  315. host, args.stats_port)
  316. response = stub.GetClientAccumulatedStats(
  317. request, wait_for_ready=True, timeout=_CONNECTION_TIMEOUT_SEC)
  318. logger.debug('Invoked GetClientAccumulatedStats RPC to %s: %s',
  319. host, response)
  320. return response
  321. def configure_client(rpc_types, metadata):
  322. if CLIENT_HOSTS:
  323. hosts = CLIENT_HOSTS
  324. else:
  325. hosts = ['localhost']
  326. for host in hosts:
  327. with grpc.insecure_channel('%s:%d' %
  328. (host, args.stats_port)) as channel:
  329. stub = test_pb2_grpc.XdsUpdateClientConfigureServiceStub(channel)
  330. request = messages_pb2.ClientConfigureRequest()
  331. request.types.extend(rpc_types)
  332. for rpc_type, md_key, md_value in metadata:
  333. md = request.metadata.add()
  334. md.type = rpc_type
  335. md.key = md_key
  336. md.value = md_value
  337. logger.debug(
  338. 'Invoking XdsUpdateClientConfigureService RPC to %s:%d: %s',
  339. host, args.stats_port, request)
  340. stub.Configure(request,
  341. wait_for_ready=True,
  342. timeout=_CONNECTION_TIMEOUT_SEC)
  343. logger.debug('Invoked XdsUpdateClientConfigureService RPC to %s',
  344. host)
  345. class RpcDistributionError(Exception):
  346. pass
  347. def _verify_rpcs_to_given_backends(backends, timeout_sec, num_rpcs,
  348. allow_failures):
  349. start_time = time.time()
  350. error_msg = None
  351. logger.debug('Waiting for %d sec until backends %s receive load' %
  352. (timeout_sec, backends))
  353. while time.time() - start_time <= timeout_sec:
  354. error_msg = None
  355. stats = get_client_stats(num_rpcs, timeout_sec)
  356. rpcs_by_peer = stats.rpcs_by_peer
  357. for backend in backends:
  358. if backend not in rpcs_by_peer:
  359. error_msg = 'Backend %s did not receive load' % backend
  360. break
  361. if not error_msg and len(rpcs_by_peer) > len(backends):
  362. error_msg = 'Unexpected backend received load: %s' % rpcs_by_peer
  363. if not allow_failures and stats.num_failures > 0:
  364. error_msg = '%d RPCs failed' % stats.num_failures
  365. if not error_msg:
  366. return
  367. raise RpcDistributionError(error_msg)
  368. def wait_until_all_rpcs_go_to_given_backends_or_fail(backends,
  369. timeout_sec,
  370. num_rpcs=_NUM_TEST_RPCS):
  371. _verify_rpcs_to_given_backends(backends,
  372. timeout_sec,
  373. num_rpcs,
  374. allow_failures=True)
  375. def wait_until_all_rpcs_go_to_given_backends(backends,
  376. timeout_sec,
  377. num_rpcs=_NUM_TEST_RPCS):
  378. _verify_rpcs_to_given_backends(backends,
  379. timeout_sec,
  380. num_rpcs,
  381. allow_failures=False)
  382. def wait_until_rpcs_in_flight(rpc_type, timeout_sec, num_rpcs, threshold):
  383. '''Block until the test client reaches the state with the given number
  384. of RPCs being outstanding stably.
  385. Args:
  386. rpc_type: A string indicating the RPC method to check for. Either
  387. 'UnaryCall' or 'EmptyCall'.
  388. timeout_sec: Maximum number of seconds to wait until the desired state
  389. is reached.
  390. num_rpcs: Expected number of RPCs to be in-flight.
  391. threshold: Number within [0,100], the tolerable percentage by which
  392. the actual number of RPCs in-flight can differ from the expected number.
  393. '''
  394. if threshold < 0 or threshold > 100:
  395. raise ValueError('Value error: Threshold should be between 0 to 100')
  396. threshold_fraction = threshold / 100.0
  397. start_time = time.time()
  398. error_msg = None
  399. logger.debug(
  400. 'Waiting for %d sec until %d %s RPCs (with %d%% tolerance) in-flight' %
  401. (timeout_sec, num_rpcs, rpc_type, threshold))
  402. while time.time() - start_time <= timeout_sec:
  403. error_msg = _check_rpcs_in_flight(rpc_type, num_rpcs, threshold,
  404. threshold_fraction)
  405. if error_msg:
  406. logger.debug('Progress: %s', error_msg)
  407. time.sleep(2)
  408. else:
  409. break
  410. # Ensure the number of outstanding RPCs is stable.
  411. if not error_msg:
  412. time.sleep(5)
  413. error_msg = _check_rpcs_in_flight(rpc_type, num_rpcs, threshold,
  414. threshold_fraction)
  415. if error_msg:
  416. raise Exception("Wrong number of %s RPCs in-flight: %s" %
  417. (rpc_type, error_msg))
  418. def _check_rpcs_in_flight(rpc_type, num_rpcs, threshold, threshold_fraction):
  419. error_msg = None
  420. stats = get_client_accumulated_stats()
  421. rpcs_started = stats.num_rpcs_started_by_method[rpc_type]
  422. rpcs_succeeded = stats.num_rpcs_succeeded_by_method[rpc_type]
  423. rpcs_failed = stats.num_rpcs_failed_by_method[rpc_type]
  424. rpcs_in_flight = rpcs_started - rpcs_succeeded - rpcs_failed
  425. if rpcs_in_flight < (num_rpcs * (1 - threshold_fraction)):
  426. error_msg = ('actual(%d) < expected(%d - %d%%)' %
  427. (rpcs_in_flight, num_rpcs, threshold))
  428. elif rpcs_in_flight > (num_rpcs * (1 + threshold_fraction)):
  429. error_msg = ('actual(%d) > expected(%d + %d%%)' %
  430. (rpcs_in_flight, num_rpcs, threshold))
  431. return error_msg
  432. def compare_distributions(actual_distribution, expected_distribution,
  433. threshold):
  434. """Compare if two distributions are similar.
  435. Args:
  436. actual_distribution: A list of floats, contains the actual distribution.
  437. expected_distribution: A list of floats, contains the expected distribution.
  438. threshold: Number within [0,100], the threshold percentage by which the
  439. actual distribution can differ from the expected distribution.
  440. Returns:
  441. The similarity between the distributions as a boolean. Returns true if the
  442. actual distribution lies within the threshold of the expected
  443. distribution, false otherwise.
  444. Raises:
  445. ValueError: if threshold is not with in [0,100].
  446. Exception: containing detailed error messages.
  447. """
  448. if len(expected_distribution) != len(actual_distribution):
  449. raise Exception(
  450. 'Error: expected and actual distributions have different size (%d vs %d)'
  451. % (len(expected_distribution), len(actual_distribution)))
  452. if threshold < 0 or threshold > 100:
  453. raise ValueError('Value error: Threshold should be between 0 to 100')
  454. threshold_fraction = threshold / 100.0
  455. for expected, actual in zip(expected_distribution, actual_distribution):
  456. if actual < (expected * (1 - threshold_fraction)):
  457. raise Exception("actual(%f) < expected(%f-%d%%)" %
  458. (actual, expected, threshold))
  459. if actual > (expected * (1 + threshold_fraction)):
  460. raise Exception("actual(%f) > expected(%f+%d%%)" %
  461. (actual, expected, threshold))
  462. return True
  463. def compare_expected_instances(stats, expected_instances):
  464. """Compare if stats have expected instances for each type of RPC.
  465. Args:
  466. stats: LoadBalancerStatsResponse reported by interop client.
  467. expected_instances: a dict with key as the RPC type (string), value as
  468. the expected backend instances (list of strings).
  469. Returns:
  470. Returns true if the instances are expected. False if not.
  471. """
  472. for rpc_type, expected_peers in expected_instances.items():
  473. rpcs_by_peer_for_type = stats.rpcs_by_method[rpc_type]
  474. rpcs_by_peer = rpcs_by_peer_for_type.rpcs_by_peer if rpcs_by_peer_for_type else None
  475. logger.debug('rpc: %s, by_peer: %s', rpc_type, rpcs_by_peer)
  476. peers = list(rpcs_by_peer.keys())
  477. if set(peers) != set(expected_peers):
  478. logger.info('unexpected peers for %s, got %s, want %s', rpc_type,
  479. peers, expected_peers)
  480. return False
  481. return True
  482. def test_backends_restart(gcp, backend_service, instance_group):
  483. logger.info('Running test_backends_restart')
  484. instance_names = get_instance_names(gcp, instance_group)
  485. num_instances = len(instance_names)
  486. start_time = time.time()
  487. wait_until_all_rpcs_go_to_given_backends(instance_names,
  488. _WAIT_FOR_STATS_SEC)
  489. try:
  490. resize_instance_group(gcp, instance_group, 0)
  491. wait_until_all_rpcs_go_to_given_backends_or_fail([],
  492. _WAIT_FOR_BACKEND_SEC)
  493. finally:
  494. resize_instance_group(gcp, instance_group, num_instances)
  495. wait_for_healthy_backends(gcp, backend_service, instance_group)
  496. new_instance_names = get_instance_names(gcp, instance_group)
  497. wait_until_all_rpcs_go_to_given_backends(new_instance_names,
  498. _WAIT_FOR_BACKEND_SEC)
  499. def test_change_backend_service(gcp, original_backend_service, instance_group,
  500. alternate_backend_service,
  501. same_zone_instance_group):
  502. logger.info('Running test_change_backend_service')
  503. original_backend_instances = get_instance_names(gcp, instance_group)
  504. alternate_backend_instances = get_instance_names(gcp,
  505. same_zone_instance_group)
  506. patch_backend_service(gcp, alternate_backend_service,
  507. [same_zone_instance_group])
  508. wait_for_healthy_backends(gcp, original_backend_service, instance_group)
  509. wait_for_healthy_backends(gcp, alternate_backend_service,
  510. same_zone_instance_group)
  511. wait_until_all_rpcs_go_to_given_backends(original_backend_instances,
  512. _WAIT_FOR_STATS_SEC)
  513. try:
  514. patch_url_map_backend_service(gcp, alternate_backend_service)
  515. wait_until_all_rpcs_go_to_given_backends(alternate_backend_instances,
  516. _WAIT_FOR_URL_MAP_PATCH_SEC)
  517. finally:
  518. patch_url_map_backend_service(gcp, original_backend_service)
  519. patch_backend_service(gcp, alternate_backend_service, [])
  520. def test_gentle_failover(gcp,
  521. backend_service,
  522. primary_instance_group,
  523. secondary_instance_group,
  524. swapped_primary_and_secondary=False):
  525. logger.info('Running test_gentle_failover')
  526. num_primary_instances = len(get_instance_names(gcp, primary_instance_group))
  527. min_instances_for_gentle_failover = 3 # Need >50% failure to start failover
  528. try:
  529. if num_primary_instances < min_instances_for_gentle_failover:
  530. resize_instance_group(gcp, primary_instance_group,
  531. min_instances_for_gentle_failover)
  532. patch_backend_service(
  533. gcp, backend_service,
  534. [primary_instance_group, secondary_instance_group])
  535. primary_instance_names = get_instance_names(gcp, primary_instance_group)
  536. secondary_instance_names = get_instance_names(gcp,
  537. secondary_instance_group)
  538. wait_for_healthy_backends(gcp, backend_service, primary_instance_group)
  539. wait_for_healthy_backends(gcp, backend_service,
  540. secondary_instance_group)
  541. wait_until_all_rpcs_go_to_given_backends(primary_instance_names,
  542. _WAIT_FOR_STATS_SEC)
  543. instances_to_stop = primary_instance_names[:-1]
  544. remaining_instances = primary_instance_names[-1:]
  545. try:
  546. set_serving_status(instances_to_stop,
  547. gcp.service_port,
  548. serving=False)
  549. wait_until_all_rpcs_go_to_given_backends(
  550. remaining_instances + secondary_instance_names,
  551. _WAIT_FOR_BACKEND_SEC)
  552. finally:
  553. set_serving_status(primary_instance_names,
  554. gcp.service_port,
  555. serving=True)
  556. except RpcDistributionError as e:
  557. if not swapped_primary_and_secondary and is_primary_instance_group(
  558. gcp, secondary_instance_group):
  559. # Swap expectation of primary and secondary instance groups.
  560. test_gentle_failover(gcp,
  561. backend_service,
  562. secondary_instance_group,
  563. primary_instance_group,
  564. swapped_primary_and_secondary=True)
  565. else:
  566. raise e
  567. finally:
  568. patch_backend_service(gcp, backend_service, [primary_instance_group])
  569. resize_instance_group(gcp, primary_instance_group,
  570. num_primary_instances)
  571. instance_names = get_instance_names(gcp, primary_instance_group)
  572. wait_until_all_rpcs_go_to_given_backends(instance_names,
  573. _WAIT_FOR_BACKEND_SEC)
  574. def test_load_report_based_failover(gcp, backend_service,
  575. primary_instance_group,
  576. secondary_instance_group):
  577. logger.info('Running test_load_report_based_failover')
  578. try:
  579. patch_backend_service(
  580. gcp, backend_service,
  581. [primary_instance_group, secondary_instance_group])
  582. primary_instance_names = get_instance_names(gcp, primary_instance_group)
  583. secondary_instance_names = get_instance_names(gcp,
  584. secondary_instance_group)
  585. wait_for_healthy_backends(gcp, backend_service, primary_instance_group)
  586. wait_for_healthy_backends(gcp, backend_service,
  587. secondary_instance_group)
  588. wait_until_all_rpcs_go_to_given_backends(primary_instance_names,
  589. _WAIT_FOR_STATS_SEC)
  590. # Set primary locality's balance mode to RATE, and RPS to 20% of the
  591. # client's QPS. The secondary locality will be used.
  592. max_rate = int(args.qps * 1 / 5)
  593. logger.info('Patching backend service to RATE with %d max_rate',
  594. max_rate)
  595. patch_backend_service(
  596. gcp,
  597. backend_service, [primary_instance_group, secondary_instance_group],
  598. balancing_mode='RATE',
  599. max_rate=max_rate)
  600. wait_until_all_rpcs_go_to_given_backends(
  601. primary_instance_names + secondary_instance_names,
  602. _WAIT_FOR_BACKEND_SEC)
  603. # Set primary locality's balance mode to RATE, and RPS to 120% of the
  604. # client's QPS. Only the primary locality will be used.
  605. max_rate = int(args.qps * 6 / 5)
  606. logger.info('Patching backend service to RATE with %d max_rate',
  607. max_rate)
  608. patch_backend_service(
  609. gcp,
  610. backend_service, [primary_instance_group, secondary_instance_group],
  611. balancing_mode='RATE',
  612. max_rate=max_rate)
  613. wait_until_all_rpcs_go_to_given_backends(primary_instance_names,
  614. _WAIT_FOR_BACKEND_SEC)
  615. logger.info("success")
  616. finally:
  617. patch_backend_service(gcp, backend_service, [primary_instance_group])
  618. instance_names = get_instance_names(gcp, primary_instance_group)
  619. wait_until_all_rpcs_go_to_given_backends(instance_names,
  620. _WAIT_FOR_BACKEND_SEC)
  621. def test_ping_pong(gcp, backend_service, instance_group):
  622. logger.info('Running test_ping_pong')
  623. wait_for_healthy_backends(gcp, backend_service, instance_group)
  624. instance_names = get_instance_names(gcp, instance_group)
  625. wait_until_all_rpcs_go_to_given_backends(instance_names,
  626. _WAIT_FOR_STATS_SEC)
  627. def test_remove_instance_group(gcp, backend_service, instance_group,
  628. same_zone_instance_group):
  629. logger.info('Running test_remove_instance_group')
  630. try:
  631. patch_backend_service(gcp,
  632. backend_service,
  633. [instance_group, same_zone_instance_group],
  634. balancing_mode='RATE')
  635. wait_for_healthy_backends(gcp, backend_service, instance_group)
  636. wait_for_healthy_backends(gcp, backend_service,
  637. same_zone_instance_group)
  638. instance_names = get_instance_names(gcp, instance_group)
  639. same_zone_instance_names = get_instance_names(gcp,
  640. same_zone_instance_group)
  641. try:
  642. wait_until_all_rpcs_go_to_given_backends(
  643. instance_names + same_zone_instance_names,
  644. _WAIT_FOR_OPERATION_SEC)
  645. remaining_instance_group = same_zone_instance_group
  646. remaining_instance_names = same_zone_instance_names
  647. except RpcDistributionError as e:
  648. # If connected to TD in a different zone, we may route traffic to
  649. # only one instance group. Determine which group that is to continue
  650. # with the remainder of the test case.
  651. try:
  652. wait_until_all_rpcs_go_to_given_backends(
  653. instance_names, _WAIT_FOR_STATS_SEC)
  654. remaining_instance_group = same_zone_instance_group
  655. remaining_instance_names = same_zone_instance_names
  656. except RpcDistributionError as e:
  657. wait_until_all_rpcs_go_to_given_backends(
  658. same_zone_instance_names, _WAIT_FOR_STATS_SEC)
  659. remaining_instance_group = instance_group
  660. remaining_instance_names = instance_names
  661. patch_backend_service(gcp,
  662. backend_service, [remaining_instance_group],
  663. balancing_mode='RATE')
  664. wait_until_all_rpcs_go_to_given_backends(remaining_instance_names,
  665. _WAIT_FOR_BACKEND_SEC)
  666. finally:
  667. patch_backend_service(gcp, backend_service, [instance_group])
  668. wait_until_all_rpcs_go_to_given_backends(instance_names,
  669. _WAIT_FOR_BACKEND_SEC)
  670. def test_round_robin(gcp, backend_service, instance_group):
  671. logger.info('Running test_round_robin')
  672. wait_for_healthy_backends(gcp, backend_service, instance_group)
  673. instance_names = get_instance_names(gcp, instance_group)
  674. threshold = 1
  675. wait_until_all_rpcs_go_to_given_backends(instance_names,
  676. _WAIT_FOR_STATS_SEC)
  677. # TODO(ericgribkoff) Delayed config propagation from earlier tests
  678. # may result in briefly receiving an empty EDS update, resulting in failed
  679. # RPCs. Retry distribution validation if this occurs; long-term fix is
  680. # creating new backend resources for each individual test case.
  681. # Each attempt takes 10 seconds. Config propagation can take several
  682. # minutes.
  683. max_attempts = 40
  684. for i in range(max_attempts):
  685. stats = get_client_stats(_NUM_TEST_RPCS, _WAIT_FOR_STATS_SEC)
  686. requests_received = [stats.rpcs_by_peer[x] for x in stats.rpcs_by_peer]
  687. total_requests_received = sum(requests_received)
  688. if total_requests_received != _NUM_TEST_RPCS:
  689. logger.info('Unexpected RPC failures, retrying: %s', stats)
  690. continue
  691. expected_requests = total_requests_received / len(instance_names)
  692. for instance in instance_names:
  693. if abs(stats.rpcs_by_peer[instance] -
  694. expected_requests) > threshold:
  695. raise Exception(
  696. 'RPC peer distribution differs from expected by more than %d '
  697. 'for instance %s (%s)' % (threshold, instance, stats))
  698. return
  699. raise Exception('RPC failures persisted through %d retries' % max_attempts)
  700. def test_secondary_locality_gets_no_requests_on_partial_primary_failure(
  701. gcp,
  702. backend_service,
  703. primary_instance_group,
  704. secondary_instance_group,
  705. swapped_primary_and_secondary=False):
  706. logger.info(
  707. 'Running secondary_locality_gets_no_requests_on_partial_primary_failure'
  708. )
  709. try:
  710. patch_backend_service(
  711. gcp, backend_service,
  712. [primary_instance_group, secondary_instance_group])
  713. wait_for_healthy_backends(gcp, backend_service, primary_instance_group)
  714. wait_for_healthy_backends(gcp, backend_service,
  715. secondary_instance_group)
  716. primary_instance_names = get_instance_names(gcp, primary_instance_group)
  717. wait_until_all_rpcs_go_to_given_backends(primary_instance_names,
  718. _WAIT_FOR_STATS_SEC)
  719. instances_to_stop = primary_instance_names[:1]
  720. remaining_instances = primary_instance_names[1:]
  721. try:
  722. set_serving_status(instances_to_stop,
  723. gcp.service_port,
  724. serving=False)
  725. wait_until_all_rpcs_go_to_given_backends(remaining_instances,
  726. _WAIT_FOR_BACKEND_SEC)
  727. finally:
  728. set_serving_status(primary_instance_names,
  729. gcp.service_port,
  730. serving=True)
  731. except RpcDistributionError as e:
  732. if not swapped_primary_and_secondary and is_primary_instance_group(
  733. gcp, secondary_instance_group):
  734. # Swap expectation of primary and secondary instance groups.
  735. test_secondary_locality_gets_no_requests_on_partial_primary_failure(
  736. gcp,
  737. backend_service,
  738. secondary_instance_group,
  739. primary_instance_group,
  740. swapped_primary_and_secondary=True)
  741. else:
  742. raise e
  743. finally:
  744. patch_backend_service(gcp, backend_service, [primary_instance_group])
  745. def test_secondary_locality_gets_requests_on_primary_failure(
  746. gcp,
  747. backend_service,
  748. primary_instance_group,
  749. secondary_instance_group,
  750. swapped_primary_and_secondary=False):
  751. logger.info('Running secondary_locality_gets_requests_on_primary_failure')
  752. try:
  753. patch_backend_service(
  754. gcp, backend_service,
  755. [primary_instance_group, secondary_instance_group])
  756. wait_for_healthy_backends(gcp, backend_service, primary_instance_group)
  757. wait_for_healthy_backends(gcp, backend_service,
  758. secondary_instance_group)
  759. primary_instance_names = get_instance_names(gcp, primary_instance_group)
  760. secondary_instance_names = get_instance_names(gcp,
  761. secondary_instance_group)
  762. wait_until_all_rpcs_go_to_given_backends(primary_instance_names,
  763. _WAIT_FOR_STATS_SEC)
  764. try:
  765. set_serving_status(primary_instance_names,
  766. gcp.service_port,
  767. serving=False)
  768. wait_until_all_rpcs_go_to_given_backends(secondary_instance_names,
  769. _WAIT_FOR_BACKEND_SEC)
  770. finally:
  771. set_serving_status(primary_instance_names,
  772. gcp.service_port,
  773. serving=True)
  774. except RpcDistributionError as e:
  775. if not swapped_primary_and_secondary and is_primary_instance_group(
  776. gcp, secondary_instance_group):
  777. # Swap expectation of primary and secondary instance groups.
  778. test_secondary_locality_gets_requests_on_primary_failure(
  779. gcp,
  780. backend_service,
  781. secondary_instance_group,
  782. primary_instance_group,
  783. swapped_primary_and_secondary=True)
  784. else:
  785. raise e
  786. finally:
  787. patch_backend_service(gcp, backend_service, [primary_instance_group])
  788. def prepare_services_for_urlmap_tests(gcp, original_backend_service,
  789. instance_group, alternate_backend_service,
  790. same_zone_instance_group):
  791. '''
  792. This function prepares the services to be ready for tests that modifies
  793. urlmaps.
  794. Returns:
  795. Returns original and alternate backend names as lists of strings.
  796. '''
  797. logger.info('waiting for original backends to become healthy')
  798. wait_for_healthy_backends(gcp, original_backend_service, instance_group)
  799. patch_backend_service(gcp, alternate_backend_service,
  800. [same_zone_instance_group])
  801. logger.info('waiting for alternate to become healthy')
  802. wait_for_healthy_backends(gcp, alternate_backend_service,
  803. same_zone_instance_group)
  804. original_backend_instances = get_instance_names(gcp, instance_group)
  805. logger.info('original backends instances: %s', original_backend_instances)
  806. alternate_backend_instances = get_instance_names(gcp,
  807. same_zone_instance_group)
  808. logger.info('alternate backends instances: %s', alternate_backend_instances)
  809. # Start with all traffic going to original_backend_service.
  810. logger.info('waiting for traffic to all go to original backends')
  811. wait_until_all_rpcs_go_to_given_backends(original_backend_instances,
  812. _WAIT_FOR_STATS_SEC)
  813. return original_backend_instances, alternate_backend_instances
  814. def test_traffic_splitting(gcp, original_backend_service, instance_group,
  815. alternate_backend_service, same_zone_instance_group):
  816. # This test start with all traffic going to original_backend_service. Then
  817. # it updates URL-map to set default action to traffic splitting between
  818. # original and alternate. It waits for all backends in both services to
  819. # receive traffic, then verifies that weights are expected.
  820. logger.info('Running test_traffic_splitting')
  821. original_backend_instances, alternate_backend_instances = prepare_services_for_urlmap_tests(
  822. gcp, original_backend_service, instance_group,
  823. alternate_backend_service, same_zone_instance_group)
  824. try:
  825. # Patch urlmap, change route action to traffic splitting between
  826. # original and alternate.
  827. logger.info('patching url map with traffic splitting')
  828. original_service_percentage, alternate_service_percentage = 20, 80
  829. patch_url_map_backend_service(
  830. gcp,
  831. services_with_weights={
  832. original_backend_service: original_service_percentage,
  833. alternate_backend_service: alternate_service_percentage,
  834. })
  835. # Split percentage between instances: [20,80] -> [10,10,40,40].
  836. expected_instance_percentage = [
  837. original_service_percentage * 1.0 / len(original_backend_instances)
  838. ] * len(original_backend_instances) + [
  839. alternate_service_percentage * 1.0 /
  840. len(alternate_backend_instances)
  841. ] * len(alternate_backend_instances)
  842. # Wait for traffic to go to both services.
  843. logger.info(
  844. 'waiting for traffic to go to all backends (including alternate)')
  845. wait_until_all_rpcs_go_to_given_backends(
  846. original_backend_instances + alternate_backend_instances,
  847. _WAIT_FOR_STATS_SEC)
  848. # Verify that weights between two services are expected.
  849. retry_count = 10
  850. # Each attempt takes about 10 seconds, 10 retries is equivalent to 100
  851. # seconds timeout.
  852. for i in range(retry_count):
  853. stats = get_client_stats(_NUM_TEST_RPCS, _WAIT_FOR_STATS_SEC)
  854. got_instance_count = [
  855. stats.rpcs_by_peer[i] for i in original_backend_instances
  856. ] + [stats.rpcs_by_peer[i] for i in alternate_backend_instances]
  857. total_count = sum(got_instance_count)
  858. got_instance_percentage = [
  859. x * 100.0 / total_count for x in got_instance_count
  860. ]
  861. try:
  862. compare_distributions(got_instance_percentage,
  863. expected_instance_percentage, 5)
  864. except Exception as e:
  865. logger.info('attempt %d', i)
  866. logger.info('got percentage: %s', got_instance_percentage)
  867. logger.info('expected percentage: %s',
  868. expected_instance_percentage)
  869. logger.info(e)
  870. if i == retry_count - 1:
  871. raise Exception(
  872. 'RPC distribution (%s) differs from expected (%s)' %
  873. (got_instance_percentage, expected_instance_percentage))
  874. else:
  875. logger.info("success")
  876. break
  877. finally:
  878. patch_url_map_backend_service(gcp, original_backend_service)
  879. patch_backend_service(gcp, alternate_backend_service, [])
  880. def test_path_matching(gcp, original_backend_service, instance_group,
  881. alternate_backend_service, same_zone_instance_group):
  882. # This test start with all traffic (UnaryCall and EmptyCall) going to
  883. # original_backend_service.
  884. #
  885. # Then it updates URL-map to add routes, to make UnaryCall and EmptyCall to
  886. # go different backends. It waits for all backends in both services to
  887. # receive traffic, then verifies that traffic goes to the expected
  888. # backends.
  889. logger.info('Running test_path_matching')
  890. original_backend_instances, alternate_backend_instances = prepare_services_for_urlmap_tests(
  891. gcp, original_backend_service, instance_group,
  892. alternate_backend_service, same_zone_instance_group)
  893. try:
  894. # A list of tuples (route_rules, expected_instances).
  895. test_cases = [
  896. (
  897. [{
  898. 'priority': 0,
  899. # FullPath EmptyCall -> alternate_backend_service.
  900. 'matchRules': [{
  901. 'fullPathMatch': '/grpc.testing.TestService/EmptyCall'
  902. }],
  903. 'service': alternate_backend_service.url
  904. }],
  905. {
  906. "EmptyCall": alternate_backend_instances,
  907. "UnaryCall": original_backend_instances
  908. }),
  909. (
  910. [{
  911. 'priority': 0,
  912. # Prefix UnaryCall -> alternate_backend_service.
  913. 'matchRules': [{
  914. 'prefixMatch': '/grpc.testing.TestService/Unary'
  915. }],
  916. 'service': alternate_backend_service.url
  917. }],
  918. {
  919. "UnaryCall": alternate_backend_instances,
  920. "EmptyCall": original_backend_instances
  921. }),
  922. (
  923. # This test case is similar to the one above (but with route
  924. # services swapped). This test has two routes (full_path and
  925. # the default) to match EmptyCall, and both routes set
  926. # alternative_backend_service as the action. This forces the
  927. # client to handle duplicate Clusters in the RDS response.
  928. [
  929. {
  930. 'priority': 0,
  931. # Prefix UnaryCall -> original_backend_service.
  932. 'matchRules': [{
  933. 'prefixMatch': '/grpc.testing.TestService/Unary'
  934. }],
  935. 'service': original_backend_service.url
  936. },
  937. {
  938. 'priority': 1,
  939. # FullPath EmptyCall -> alternate_backend_service.
  940. 'matchRules': [{
  941. 'fullPathMatch':
  942. '/grpc.testing.TestService/EmptyCall'
  943. }],
  944. 'service': alternate_backend_service.url
  945. }
  946. ],
  947. {
  948. "UnaryCall": original_backend_instances,
  949. "EmptyCall": alternate_backend_instances
  950. }),
  951. (
  952. [{
  953. 'priority': 0,
  954. # Regex UnaryCall -> alternate_backend_service.
  955. 'matchRules': [{
  956. 'regexMatch':
  957. '^\/.*\/UnaryCall$' # Unary methods with any services.
  958. }],
  959. 'service': alternate_backend_service.url
  960. }],
  961. {
  962. "UnaryCall": alternate_backend_instances,
  963. "EmptyCall": original_backend_instances
  964. }),
  965. (
  966. [{
  967. 'priority': 0,
  968. # ignoreCase EmptyCall -> alternate_backend_service.
  969. 'matchRules': [{
  970. # Case insensitive matching.
  971. 'fullPathMatch': '/gRpC.tEsTinG.tEstseRvice/empTycaLl',
  972. 'ignoreCase': True,
  973. }],
  974. 'service': alternate_backend_service.url
  975. }],
  976. {
  977. "UnaryCall": original_backend_instances,
  978. "EmptyCall": alternate_backend_instances
  979. }),
  980. ]
  981. for (route_rules, expected_instances) in test_cases:
  982. logger.info('patching url map with %s', route_rules)
  983. patch_url_map_backend_service(gcp,
  984. original_backend_service,
  985. route_rules=route_rules)
  986. # Wait for traffic to go to both services.
  987. logger.info(
  988. 'waiting for traffic to go to all backends (including alternate)'
  989. )
  990. wait_until_all_rpcs_go_to_given_backends(
  991. original_backend_instances + alternate_backend_instances,
  992. _WAIT_FOR_STATS_SEC)
  993. retry_count = 80
  994. # Each attempt takes about 5 seconds, 80 retries is equivalent to 400
  995. # seconds timeout.
  996. for i in range(retry_count):
  997. stats = get_client_stats(_NUM_TEST_RPCS, _WAIT_FOR_STATS_SEC)
  998. if not stats.rpcs_by_method:
  999. raise ValueError(
  1000. 'stats.rpcs_by_method is None, the interop client stats service does not support this test case'
  1001. )
  1002. logger.info('attempt %d', i)
  1003. if compare_expected_instances(stats, expected_instances):
  1004. logger.info("success")
  1005. break
  1006. elif i == retry_count - 1:
  1007. raise Exception(
  1008. 'timeout waiting for RPCs to the expected instances: %s'
  1009. % expected_instances)
  1010. finally:
  1011. patch_url_map_backend_service(gcp, original_backend_service)
  1012. patch_backend_service(gcp, alternate_backend_service, [])
  1013. def test_header_matching(gcp, original_backend_service, instance_group,
  1014. alternate_backend_service, same_zone_instance_group):
  1015. # This test start with all traffic (UnaryCall and EmptyCall) going to
  1016. # original_backend_service.
  1017. #
  1018. # Then it updates URL-map to add routes, to make RPCs with test headers to
  1019. # go to different backends. It waits for all backends in both services to
  1020. # receive traffic, then verifies that traffic goes to the expected
  1021. # backends.
  1022. logger.info('Running test_header_matching')
  1023. original_backend_instances, alternate_backend_instances = prepare_services_for_urlmap_tests(
  1024. gcp, original_backend_service, instance_group,
  1025. alternate_backend_service, same_zone_instance_group)
  1026. try:
  1027. # A list of tuples (route_rules, expected_instances).
  1028. test_cases = [
  1029. (
  1030. [{
  1031. 'priority': 0,
  1032. # Header ExactMatch -> alternate_backend_service.
  1033. # EmptyCall is sent with the metadata.
  1034. 'matchRules': [{
  1035. 'prefixMatch':
  1036. '/',
  1037. 'headerMatches': [{
  1038. 'headerName': _TEST_METADATA_KEY,
  1039. 'exactMatch': _TEST_METADATA_VALUE_EMPTY
  1040. }]
  1041. }],
  1042. 'service': alternate_backend_service.url
  1043. }],
  1044. {
  1045. "EmptyCall": alternate_backend_instances,
  1046. "UnaryCall": original_backend_instances
  1047. }),
  1048. (
  1049. [{
  1050. 'priority': 0,
  1051. # Header PrefixMatch -> alternate_backend_service.
  1052. # UnaryCall is sent with the metadata.
  1053. 'matchRules': [{
  1054. 'prefixMatch':
  1055. '/',
  1056. 'headerMatches': [{
  1057. 'headerName': _TEST_METADATA_KEY,
  1058. 'prefixMatch': _TEST_METADATA_VALUE_UNARY[:2]
  1059. }]
  1060. }],
  1061. 'service': alternate_backend_service.url
  1062. }],
  1063. {
  1064. "EmptyCall": original_backend_instances,
  1065. "UnaryCall": alternate_backend_instances
  1066. }),
  1067. (
  1068. [{
  1069. 'priority': 0,
  1070. # Header SuffixMatch -> alternate_backend_service.
  1071. # EmptyCall is sent with the metadata.
  1072. 'matchRules': [{
  1073. 'prefixMatch':
  1074. '/',
  1075. 'headerMatches': [{
  1076. 'headerName': _TEST_METADATA_KEY,
  1077. 'suffixMatch': _TEST_METADATA_VALUE_EMPTY[-2:]
  1078. }]
  1079. }],
  1080. 'service': alternate_backend_service.url
  1081. }],
  1082. {
  1083. "EmptyCall": alternate_backend_instances,
  1084. "UnaryCall": original_backend_instances
  1085. }),
  1086. (
  1087. [{
  1088. 'priority': 0,
  1089. # Header 'xds_md_numeric' present -> alternate_backend_service.
  1090. # UnaryCall is sent with the metadata, so will be sent to alternative.
  1091. 'matchRules': [{
  1092. 'prefixMatch':
  1093. '/',
  1094. 'headerMatches': [{
  1095. 'headerName': _TEST_METADATA_NUMERIC_KEY,
  1096. 'presentMatch': True
  1097. }]
  1098. }],
  1099. 'service': alternate_backend_service.url
  1100. }],
  1101. {
  1102. "EmptyCall": original_backend_instances,
  1103. "UnaryCall": alternate_backend_instances
  1104. }),
  1105. (
  1106. [{
  1107. 'priority': 0,
  1108. # Header invert ExactMatch -> alternate_backend_service.
  1109. # UnaryCall is sent with the metadata, so will be sent to
  1110. # original. EmptyCall will be sent to alternative.
  1111. 'matchRules': [{
  1112. 'prefixMatch':
  1113. '/',
  1114. 'headerMatches': [{
  1115. 'headerName': _TEST_METADATA_KEY,
  1116. 'exactMatch': _TEST_METADATA_VALUE_UNARY,
  1117. 'invertMatch': True
  1118. }]
  1119. }],
  1120. 'service': alternate_backend_service.url
  1121. }],
  1122. {
  1123. "EmptyCall": alternate_backend_instances,
  1124. "UnaryCall": original_backend_instances
  1125. }),
  1126. (
  1127. [{
  1128. 'priority': 0,
  1129. # Header 'xds_md_numeric' range [100,200] -> alternate_backend_service.
  1130. # UnaryCall is sent with the metadata in range.
  1131. 'matchRules': [{
  1132. 'prefixMatch':
  1133. '/',
  1134. 'headerMatches': [{
  1135. 'headerName': _TEST_METADATA_NUMERIC_KEY,
  1136. 'rangeMatch': {
  1137. 'rangeStart': '100',
  1138. 'rangeEnd': '200'
  1139. }
  1140. }]
  1141. }],
  1142. 'service': alternate_backend_service.url
  1143. }],
  1144. {
  1145. "EmptyCall": original_backend_instances,
  1146. "UnaryCall": alternate_backend_instances
  1147. }),
  1148. (
  1149. [{
  1150. 'priority': 0,
  1151. # Header RegexMatch -> alternate_backend_service.
  1152. # EmptyCall is sent with the metadata.
  1153. 'matchRules': [{
  1154. 'prefixMatch':
  1155. '/',
  1156. 'headerMatches': [{
  1157. 'headerName':
  1158. _TEST_METADATA_KEY,
  1159. 'regexMatch':
  1160. "^%s.*%s$" % (_TEST_METADATA_VALUE_EMPTY[:2],
  1161. _TEST_METADATA_VALUE_EMPTY[-2:])
  1162. }]
  1163. }],
  1164. 'service': alternate_backend_service.url
  1165. }],
  1166. {
  1167. "EmptyCall": alternate_backend_instances,
  1168. "UnaryCall": original_backend_instances
  1169. }),
  1170. ]
  1171. for (route_rules, expected_instances) in test_cases:
  1172. logger.info('patching url map with %s -> alternative',
  1173. route_rules[0]['matchRules'])
  1174. patch_url_map_backend_service(gcp,
  1175. original_backend_service,
  1176. route_rules=route_rules)
  1177. # Wait for traffic to go to both services.
  1178. logger.info(
  1179. 'waiting for traffic to go to all backends (including alternate)'
  1180. )
  1181. wait_until_all_rpcs_go_to_given_backends(
  1182. original_backend_instances + alternate_backend_instances,
  1183. _WAIT_FOR_STATS_SEC)
  1184. retry_count = 80
  1185. # Each attempt takes about 5 seconds, 80 retries is equivalent to 400
  1186. # seconds timeout.
  1187. for i in range(retry_count):
  1188. stats = get_client_stats(_NUM_TEST_RPCS, _WAIT_FOR_STATS_SEC)
  1189. if not stats.rpcs_by_method:
  1190. raise ValueError(
  1191. 'stats.rpcs_by_method is None, the interop client stats service does not support this test case'
  1192. )
  1193. logger.info('attempt %d', i)
  1194. if compare_expected_instances(stats, expected_instances):
  1195. logger.info("success")
  1196. break
  1197. elif i == retry_count - 1:
  1198. raise Exception(
  1199. 'timeout waiting for RPCs to the expected instances: %s'
  1200. % expected_instances)
  1201. finally:
  1202. patch_url_map_backend_service(gcp, original_backend_service)
  1203. patch_backend_service(gcp, alternate_backend_service, [])
  1204. def test_circuit_breaking(gcp, original_backend_service, instance_group,
  1205. same_zone_instance_group):
  1206. '''
  1207. Since backend service circuit_breakers configuration cannot be unset,
  1208. which causes trouble for restoring validate_for_proxy flag in target
  1209. proxy/global forwarding rule. This test uses dedicated backend sevices.
  1210. The url_map and backend services undergoes the following state changes:
  1211. Before test:
  1212. original_backend_service -> [instance_group]
  1213. extra_backend_service -> []
  1214. more_extra_backend_service -> []
  1215. url_map -> [original_backend_service]
  1216. In test:
  1217. extra_backend_service (with circuit_breakers) -> [instance_group]
  1218. more_extra_backend_service (with circuit_breakers) -> [same_zone_instance_group]
  1219. url_map -> [extra_backend_service, more_extra_backend_service]
  1220. After test:
  1221. original_backend_service -> [instance_group]
  1222. extra_backend_service (with circuit_breakers) -> []
  1223. more_extra_backend_service (with circuit_breakers) -> []
  1224. url_map -> [original_backend_service]
  1225. '''
  1226. logger.info('Running test_circuit_breaking')
  1227. additional_backend_services = []
  1228. try:
  1229. # TODO(chengyuanzhang): Dedicated backend services created for circuit
  1230. # breaking test. Once the issue for unsetting backend service circuit
  1231. # breakers is resolved or configuring backend service circuit breakers is
  1232. # enabled for config validation, these dedicated backend services can be
  1233. # eliminated.
  1234. extra_backend_service_name = _BASE_BACKEND_SERVICE_NAME + '-extra' + gcp_suffix
  1235. more_extra_backend_service_name = _BASE_BACKEND_SERVICE_NAME + '-more-extra' + gcp_suffix
  1236. extra_backend_service = add_backend_service(gcp,
  1237. extra_backend_service_name)
  1238. additional_backend_services.append(extra_backend_service)
  1239. more_extra_backend_service = add_backend_service(
  1240. gcp, more_extra_backend_service_name)
  1241. additional_backend_services.append(more_extra_backend_service)
  1242. # The config validation for proxyless doesn't allow setting
  1243. # circuit_breakers. Disable validate validate_for_proxyless
  1244. # for this test. This can be removed when validation
  1245. # accepts circuit_breakers.
  1246. logger.info('disabling validate_for_proxyless in target proxy')
  1247. set_validate_for_proxyless(gcp, False)
  1248. extra_backend_service_max_requests = 500
  1249. more_extra_backend_service_max_requests = 1000
  1250. patch_backend_service(gcp,
  1251. extra_backend_service, [instance_group],
  1252. circuit_breakers={
  1253. 'maxRequests':
  1254. extra_backend_service_max_requests
  1255. })
  1256. logger.info('Waiting for extra backends to become healthy')
  1257. wait_for_healthy_backends(gcp, extra_backend_service, instance_group)
  1258. patch_backend_service(gcp,
  1259. more_extra_backend_service,
  1260. [same_zone_instance_group],
  1261. circuit_breakers={
  1262. 'maxRequests':
  1263. more_extra_backend_service_max_requests
  1264. })
  1265. logger.info('Waiting for more extra backend to become healthy')
  1266. wait_for_healthy_backends(gcp, more_extra_backend_service,
  1267. same_zone_instance_group)
  1268. extra_backend_instances = get_instance_names(gcp, instance_group)
  1269. more_extra_backend_instances = get_instance_names(
  1270. gcp, same_zone_instance_group)
  1271. route_rules = [
  1272. {
  1273. 'priority': 0,
  1274. # UnaryCall -> extra_backend_service
  1275. 'matchRules': [{
  1276. 'fullPathMatch': '/grpc.testing.TestService/UnaryCall'
  1277. }],
  1278. 'service': extra_backend_service.url
  1279. },
  1280. {
  1281. 'priority': 1,
  1282. # EmptyCall -> more_extra_backend_service
  1283. 'matchRules': [{
  1284. 'fullPathMatch': '/grpc.testing.TestService/EmptyCall'
  1285. }],
  1286. 'service': more_extra_backend_service.url
  1287. },
  1288. ]
  1289. # Make client send UNARY_CALL and EMPTY_CALL.
  1290. configure_client([
  1291. messages_pb2.ClientConfigureRequest.RpcType.UNARY_CALL,
  1292. messages_pb2.ClientConfigureRequest.RpcType.EMPTY_CALL
  1293. ], [])
  1294. logger.info('Patching url map with %s', route_rules)
  1295. patch_url_map_backend_service(gcp,
  1296. extra_backend_service,
  1297. route_rules=route_rules)
  1298. logger.info('Waiting for traffic to go to all backends')
  1299. wait_until_all_rpcs_go_to_given_backends(
  1300. extra_backend_instances + more_extra_backend_instances,
  1301. _WAIT_FOR_STATS_SEC)
  1302. # Make all calls keep-open.
  1303. configure_client([
  1304. messages_pb2.ClientConfigureRequest.RpcType.UNARY_CALL,
  1305. messages_pb2.ClientConfigureRequest.RpcType.EMPTY_CALL
  1306. ], [(messages_pb2.ClientConfigureRequest.RpcType.UNARY_CALL,
  1307. 'rpc-behavior', 'keep-open'),
  1308. (messages_pb2.ClientConfigureRequest.RpcType.EMPTY_CALL,
  1309. 'rpc-behavior', 'keep-open')])
  1310. wait_until_rpcs_in_flight(
  1311. 'UNARY_CALL', (_WAIT_FOR_BACKEND_SEC +
  1312. int(extra_backend_service_max_requests / args.qps)),
  1313. extra_backend_service_max_requests, 1)
  1314. logger.info('UNARY_CALL reached stable state (%d)',
  1315. extra_backend_service_max_requests)
  1316. wait_until_rpcs_in_flight(
  1317. 'EMPTY_CALL',
  1318. (_WAIT_FOR_BACKEND_SEC +
  1319. int(more_extra_backend_service_max_requests / args.qps)),
  1320. more_extra_backend_service_max_requests, 1)
  1321. logger.info('EMPTY_CALL reached stable state (%d)',
  1322. more_extra_backend_service_max_requests)
  1323. # Increment circuit breakers max_requests threshold.
  1324. extra_backend_service_max_requests = 800
  1325. patch_backend_service(gcp,
  1326. extra_backend_service, [instance_group],
  1327. circuit_breakers={
  1328. 'maxRequests':
  1329. extra_backend_service_max_requests
  1330. })
  1331. wait_until_rpcs_in_flight(
  1332. 'UNARY_CALL', (_WAIT_FOR_BACKEND_SEC +
  1333. int(extra_backend_service_max_requests / args.qps)),
  1334. extra_backend_service_max_requests, 1)
  1335. logger.info('UNARY_CALL reached stable state after increase (%d)',
  1336. extra_backend_service_max_requests)
  1337. logger.info('success')
  1338. # Avoid new RPCs being outstanding (some test clients create threads
  1339. # for sending RPCs) after restoring backend services.
  1340. configure_client(
  1341. [messages_pb2.ClientConfigureRequest.RpcType.UNARY_CALL], [])
  1342. finally:
  1343. patch_url_map_backend_service(gcp, original_backend_service)
  1344. patch_backend_service(gcp, original_backend_service, [instance_group])
  1345. for backend_service in additional_backend_services:
  1346. delete_backend_service(gcp, backend_service)
  1347. set_validate_for_proxyless(gcp, True)
  1348. def set_validate_for_proxyless(gcp, validate_for_proxyless):
  1349. if not gcp.alpha_compute:
  1350. logger.debug(
  1351. 'Not setting validateForProxy because alpha is not enabled')
  1352. return
  1353. # This function deletes global_forwarding_rule and target_proxy, then
  1354. # recreate target_proxy with validateForProxyless=False. This is necessary
  1355. # because patching target_grpc_proxy isn't supported.
  1356. delete_global_forwarding_rule(gcp)
  1357. delete_target_proxy(gcp)
  1358. create_target_proxy(gcp, gcp.target_proxy.name, validate_for_proxyless)
  1359. create_global_forwarding_rule(gcp, gcp.global_forwarding_rule.name,
  1360. [gcp.service_port])
  1361. def get_serving_status(instance, service_port):
  1362. with grpc.insecure_channel('%s:%d' % (instance, service_port)) as channel:
  1363. health_stub = health_pb2_grpc.HealthStub(channel)
  1364. return health_stub.Check(health_pb2.HealthCheckRequest())
  1365. def set_serving_status(instances, service_port, serving):
  1366. logger.info('setting %s serving status to %s', instances, serving)
  1367. for instance in instances:
  1368. with grpc.insecure_channel('%s:%d' %
  1369. (instance, service_port)) as channel:
  1370. logger.info('setting %s serving status to %s', instance, serving)
  1371. stub = test_pb2_grpc.XdsUpdateHealthServiceStub(channel)
  1372. retry_count = 5
  1373. for i in range(5):
  1374. if serving:
  1375. stub.SetServing(empty_pb2.Empty())
  1376. else:
  1377. stub.SetNotServing(empty_pb2.Empty())
  1378. serving_status = get_serving_status(instance, service_port)
  1379. logger.info('got instance service status %s', serving_status)
  1380. want_status = health_pb2.HealthCheckResponse.SERVING if serving else health_pb2.HealthCheckResponse.NOT_SERVING
  1381. if serving_status.status == want_status:
  1382. break
  1383. if i == retry_count - 1:
  1384. raise Exception(
  1385. 'failed to set instance service status after %d retries'
  1386. % retry_count)
  1387. def is_primary_instance_group(gcp, instance_group):
  1388. # Clients may connect to a TD instance in a different region than the
  1389. # client, in which case primary/secondary assignments may not be based on
  1390. # the client's actual locality.
  1391. instance_names = get_instance_names(gcp, instance_group)
  1392. stats = get_client_stats(_NUM_TEST_RPCS, _WAIT_FOR_STATS_SEC)
  1393. return all(peer in instance_names for peer in stats.rpcs_by_peer.keys())
  1394. def get_startup_script(path_to_server_binary, service_port):
  1395. if path_to_server_binary:
  1396. return "nohup %s --port=%d 1>/dev/null &" % (path_to_server_binary,
  1397. service_port)
  1398. else:
  1399. return """#!/bin/bash
  1400. sudo apt update
  1401. sudo apt install -y git default-jdk
  1402. mkdir java_server
  1403. pushd java_server
  1404. git clone https://github.com/grpc/grpc-java.git
  1405. pushd grpc-java
  1406. pushd interop-testing
  1407. ../gradlew installDist -x test -PskipCodegen=true -PskipAndroid=true
  1408. nohup build/install/grpc-interop-testing/bin/xds-test-server \
  1409. --port=%d 1>/dev/null &""" % service_port
  1410. def create_instance_template(gcp, name, network, source_image, machine_type,
  1411. startup_script):
  1412. config = {
  1413. 'name': name,
  1414. 'properties': {
  1415. 'tags': {
  1416. 'items': ['allow-health-checks']
  1417. },
  1418. 'machineType': machine_type,
  1419. 'serviceAccounts': [{
  1420. 'email': 'default',
  1421. 'scopes': ['https://www.googleapis.com/auth/cloud-platform',]
  1422. }],
  1423. 'networkInterfaces': [{
  1424. 'accessConfigs': [{
  1425. 'type': 'ONE_TO_ONE_NAT'
  1426. }],
  1427. 'network': network
  1428. }],
  1429. 'disks': [{
  1430. 'boot': True,
  1431. 'initializeParams': {
  1432. 'sourceImage': source_image
  1433. }
  1434. }],
  1435. 'metadata': {
  1436. 'items': [{
  1437. 'key': 'startup-script',
  1438. 'value': startup_script
  1439. }]
  1440. }
  1441. }
  1442. }
  1443. logger.debug('Sending GCP request with body=%s', config)
  1444. result = gcp.compute.instanceTemplates().insert(
  1445. project=gcp.project, body=config).execute(num_retries=_GCP_API_RETRIES)
  1446. wait_for_global_operation(gcp, result['name'])
  1447. gcp.instance_template = GcpResource(config['name'], result['targetLink'])
  1448. def add_instance_group(gcp, zone, name, size):
  1449. config = {
  1450. 'name': name,
  1451. 'instanceTemplate': gcp.instance_template.url,
  1452. 'targetSize': size,
  1453. 'namedPorts': [{
  1454. 'name': 'grpc',
  1455. 'port': gcp.service_port
  1456. }]
  1457. }
  1458. logger.debug('Sending GCP request with body=%s', config)
  1459. result = gcp.compute.instanceGroupManagers().insert(
  1460. project=gcp.project, zone=zone,
  1461. body=config).execute(num_retries=_GCP_API_RETRIES)
  1462. wait_for_zone_operation(gcp, zone, result['name'])
  1463. result = gcp.compute.instanceGroupManagers().get(
  1464. project=gcp.project, zone=zone,
  1465. instanceGroupManager=config['name']).execute(
  1466. num_retries=_GCP_API_RETRIES)
  1467. instance_group = InstanceGroup(config['name'], result['instanceGroup'],
  1468. zone)
  1469. gcp.instance_groups.append(instance_group)
  1470. wait_for_instance_group_to_reach_expected_size(gcp, instance_group, size,
  1471. _WAIT_FOR_OPERATION_SEC)
  1472. return instance_group
  1473. def create_health_check(gcp, name):
  1474. if gcp.alpha_compute:
  1475. config = {
  1476. 'name': name,
  1477. 'type': 'GRPC',
  1478. 'grpcHealthCheck': {
  1479. 'portSpecification': 'USE_SERVING_PORT'
  1480. }
  1481. }
  1482. compute_to_use = gcp.alpha_compute
  1483. else:
  1484. config = {
  1485. 'name': name,
  1486. 'type': 'TCP',
  1487. 'tcpHealthCheck': {
  1488. 'portName': 'grpc'
  1489. }
  1490. }
  1491. compute_to_use = gcp.compute
  1492. logger.debug('Sending GCP request with body=%s', config)
  1493. result = compute_to_use.healthChecks().insert(
  1494. project=gcp.project, body=config).execute(num_retries=_GCP_API_RETRIES)
  1495. wait_for_global_operation(gcp, result['name'])
  1496. gcp.health_check = GcpResource(config['name'], result['targetLink'])
  1497. def create_health_check_firewall_rule(gcp, name):
  1498. config = {
  1499. 'name': name,
  1500. 'direction': 'INGRESS',
  1501. 'allowed': [{
  1502. 'IPProtocol': 'tcp'
  1503. }],
  1504. 'sourceRanges': ['35.191.0.0/16', '130.211.0.0/22'],
  1505. 'targetTags': ['allow-health-checks'],
  1506. }
  1507. logger.debug('Sending GCP request with body=%s', config)
  1508. result = gcp.compute.firewalls().insert(
  1509. project=gcp.project, body=config).execute(num_retries=_GCP_API_RETRIES)
  1510. wait_for_global_operation(gcp, result['name'])
  1511. gcp.health_check_firewall_rule = GcpResource(config['name'],
  1512. result['targetLink'])
  1513. def add_backend_service(gcp, name):
  1514. if gcp.alpha_compute:
  1515. protocol = 'GRPC'
  1516. compute_to_use = gcp.alpha_compute
  1517. else:
  1518. protocol = 'HTTP2'
  1519. compute_to_use = gcp.compute
  1520. config = {
  1521. 'name': name,
  1522. 'loadBalancingScheme': 'INTERNAL_SELF_MANAGED',
  1523. 'healthChecks': [gcp.health_check.url],
  1524. 'portName': 'grpc',
  1525. 'protocol': protocol
  1526. }
  1527. logger.debug('Sending GCP request with body=%s', config)
  1528. result = compute_to_use.backendServices().insert(
  1529. project=gcp.project, body=config).execute(num_retries=_GCP_API_RETRIES)
  1530. wait_for_global_operation(gcp, result['name'])
  1531. backend_service = GcpResource(config['name'], result['targetLink'])
  1532. gcp.backend_services.append(backend_service)
  1533. return backend_service
  1534. def create_url_map(gcp, name, backend_service, host_name):
  1535. config = {
  1536. 'name': name,
  1537. 'defaultService': backend_service.url,
  1538. 'pathMatchers': [{
  1539. 'name': _PATH_MATCHER_NAME,
  1540. 'defaultService': backend_service.url,
  1541. }],
  1542. 'hostRules': [{
  1543. 'hosts': [host_name],
  1544. 'pathMatcher': _PATH_MATCHER_NAME
  1545. }]
  1546. }
  1547. logger.debug('Sending GCP request with body=%s', config)
  1548. result = gcp.compute.urlMaps().insert(
  1549. project=gcp.project, body=config).execute(num_retries=_GCP_API_RETRIES)
  1550. wait_for_global_operation(gcp, result['name'])
  1551. gcp.url_map = GcpResource(config['name'], result['targetLink'])
  1552. def patch_url_map_host_rule_with_port(gcp, name, backend_service, host_name):
  1553. config = {
  1554. 'hostRules': [{
  1555. 'hosts': ['%s:%d' % (host_name, gcp.service_port)],
  1556. 'pathMatcher': _PATH_MATCHER_NAME
  1557. }]
  1558. }
  1559. logger.debug('Sending GCP request with body=%s', config)
  1560. result = gcp.compute.urlMaps().patch(
  1561. project=gcp.project, urlMap=name,
  1562. body=config).execute(num_retries=_GCP_API_RETRIES)
  1563. wait_for_global_operation(gcp, result['name'])
  1564. def create_target_proxy(gcp, name, validate_for_proxyless=True):
  1565. if gcp.alpha_compute:
  1566. config = {
  1567. 'name': name,
  1568. 'url_map': gcp.url_map.url,
  1569. 'validate_for_proxyless': validate_for_proxyless
  1570. }
  1571. logger.debug('Sending GCP request with body=%s', config)
  1572. result = gcp.alpha_compute.targetGrpcProxies().insert(
  1573. project=gcp.project,
  1574. body=config).execute(num_retries=_GCP_API_RETRIES)
  1575. else:
  1576. config = {
  1577. 'name': name,
  1578. 'url_map': gcp.url_map.url,
  1579. }
  1580. logger.debug('Sending GCP request with body=%s', config)
  1581. result = gcp.compute.targetHttpProxies().insert(
  1582. project=gcp.project,
  1583. body=config).execute(num_retries=_GCP_API_RETRIES)
  1584. wait_for_global_operation(gcp, result['name'])
  1585. gcp.target_proxy = GcpResource(config['name'], result['targetLink'])
  1586. def create_global_forwarding_rule(gcp, name, potential_ports):
  1587. if gcp.alpha_compute:
  1588. compute_to_use = gcp.alpha_compute
  1589. else:
  1590. compute_to_use = gcp.compute
  1591. for port in potential_ports:
  1592. try:
  1593. config = {
  1594. 'name': name,
  1595. 'loadBalancingScheme': 'INTERNAL_SELF_MANAGED',
  1596. 'portRange': str(port),
  1597. 'IPAddress': '0.0.0.0',
  1598. 'network': args.network,
  1599. 'target': gcp.target_proxy.url,
  1600. }
  1601. logger.debug('Sending GCP request with body=%s', config)
  1602. result = compute_to_use.globalForwardingRules().insert(
  1603. project=gcp.project,
  1604. body=config).execute(num_retries=_GCP_API_RETRIES)
  1605. wait_for_global_operation(gcp, result['name'])
  1606. gcp.global_forwarding_rule = GcpResource(config['name'],
  1607. result['targetLink'])
  1608. gcp.service_port = port
  1609. return
  1610. except googleapiclient.errors.HttpError as http_error:
  1611. logger.warning(
  1612. 'Got error %s when attempting to create forwarding rule to '
  1613. '0.0.0.0:%d. Retrying with another port.' % (http_error, port))
  1614. def get_health_check(gcp, health_check_name):
  1615. result = gcp.compute.healthChecks().get(
  1616. project=gcp.project, healthCheck=health_check_name).execute()
  1617. gcp.health_check = GcpResource(health_check_name, result['selfLink'])
  1618. def get_health_check_firewall_rule(gcp, firewall_name):
  1619. result = gcp.compute.firewalls().get(project=gcp.project,
  1620. firewall=firewall_name).execute()
  1621. gcp.health_check_firewall_rule = GcpResource(firewall_name,
  1622. result['selfLink'])
  1623. def get_backend_service(gcp, backend_service_name):
  1624. result = gcp.compute.backendServices().get(
  1625. project=gcp.project, backendService=backend_service_name).execute()
  1626. backend_service = GcpResource(backend_service_name, result['selfLink'])
  1627. gcp.backend_services.append(backend_service)
  1628. return backend_service
  1629. def get_url_map(gcp, url_map_name):
  1630. result = gcp.compute.urlMaps().get(project=gcp.project,
  1631. urlMap=url_map_name).execute()
  1632. gcp.url_map = GcpResource(url_map_name, result['selfLink'])
  1633. def get_target_proxy(gcp, target_proxy_name):
  1634. if gcp.alpha_compute:
  1635. result = gcp.alpha_compute.targetGrpcProxies().get(
  1636. project=gcp.project, targetGrpcProxy=target_proxy_name).execute()
  1637. else:
  1638. result = gcp.compute.targetHttpProxies().get(
  1639. project=gcp.project, targetHttpProxy=target_proxy_name).execute()
  1640. gcp.target_proxy = GcpResource(target_proxy_name, result['selfLink'])
  1641. def get_global_forwarding_rule(gcp, forwarding_rule_name):
  1642. result = gcp.compute.globalForwardingRules().get(
  1643. project=gcp.project, forwardingRule=forwarding_rule_name).execute()
  1644. gcp.global_forwarding_rule = GcpResource(forwarding_rule_name,
  1645. result['selfLink'])
  1646. def get_instance_template(gcp, template_name):
  1647. result = gcp.compute.instanceTemplates().get(
  1648. project=gcp.project, instanceTemplate=template_name).execute()
  1649. gcp.instance_template = GcpResource(template_name, result['selfLink'])
  1650. def get_instance_group(gcp, zone, instance_group_name):
  1651. result = gcp.compute.instanceGroups().get(
  1652. project=gcp.project, zone=zone,
  1653. instanceGroup=instance_group_name).execute()
  1654. gcp.service_port = result['namedPorts'][0]['port']
  1655. instance_group = InstanceGroup(instance_group_name, result['selfLink'],
  1656. zone)
  1657. gcp.instance_groups.append(instance_group)
  1658. return instance_group
  1659. def delete_global_forwarding_rule(gcp):
  1660. try:
  1661. result = gcp.compute.globalForwardingRules().delete(
  1662. project=gcp.project,
  1663. forwardingRule=gcp.global_forwarding_rule.name).execute(
  1664. num_retries=_GCP_API_RETRIES)
  1665. wait_for_global_operation(gcp, result['name'])
  1666. except googleapiclient.errors.HttpError as http_error:
  1667. logger.info('Delete failed: %s', http_error)
  1668. def delete_target_proxy(gcp):
  1669. try:
  1670. if gcp.alpha_compute:
  1671. result = gcp.alpha_compute.targetGrpcProxies().delete(
  1672. project=gcp.project,
  1673. targetGrpcProxy=gcp.target_proxy.name).execute(
  1674. num_retries=_GCP_API_RETRIES)
  1675. else:
  1676. result = gcp.compute.targetHttpProxies().delete(
  1677. project=gcp.project,
  1678. targetHttpProxy=gcp.target_proxy.name).execute(
  1679. num_retries=_GCP_API_RETRIES)
  1680. wait_for_global_operation(gcp, result['name'])
  1681. except googleapiclient.errors.HttpError as http_error:
  1682. logger.info('Delete failed: %s', http_error)
  1683. def delete_url_map(gcp):
  1684. try:
  1685. result = gcp.compute.urlMaps().delete(
  1686. project=gcp.project,
  1687. urlMap=gcp.url_map.name).execute(num_retries=_GCP_API_RETRIES)
  1688. wait_for_global_operation(gcp, result['name'])
  1689. except googleapiclient.errors.HttpError as http_error:
  1690. logger.info('Delete failed: %s', http_error)
  1691. def delete_backend_service(gcp, backend_service):
  1692. try:
  1693. result = gcp.compute.backendServices().delete(
  1694. project=gcp.project, backendService=backend_service.name).execute(
  1695. num_retries=_GCP_API_RETRIES)
  1696. wait_for_global_operation(gcp, result['name'])
  1697. except googleapiclient.errors.HttpError as http_error:
  1698. logger.info('Delete failed: %s', http_error)
  1699. def delete_backend_services(gcp):
  1700. for backend_service in gcp.backend_services:
  1701. delete_backend_service(gcp, backend_service)
  1702. def delete_firewall(gcp):
  1703. try:
  1704. result = gcp.compute.firewalls().delete(
  1705. project=gcp.project,
  1706. firewall=gcp.health_check_firewall_rule.name).execute(
  1707. num_retries=_GCP_API_RETRIES)
  1708. wait_for_global_operation(gcp, result['name'])
  1709. except googleapiclient.errors.HttpError as http_error:
  1710. logger.info('Delete failed: %s', http_error)
  1711. def delete_health_check(gcp):
  1712. try:
  1713. result = gcp.compute.healthChecks().delete(
  1714. project=gcp.project, healthCheck=gcp.health_check.name).execute(
  1715. num_retries=_GCP_API_RETRIES)
  1716. wait_for_global_operation(gcp, result['name'])
  1717. except googleapiclient.errors.HttpError as http_error:
  1718. logger.info('Delete failed: %s', http_error)
  1719. def delete_instance_groups(gcp):
  1720. for instance_group in gcp.instance_groups:
  1721. try:
  1722. result = gcp.compute.instanceGroupManagers().delete(
  1723. project=gcp.project,
  1724. zone=instance_group.zone,
  1725. instanceGroupManager=instance_group.name).execute(
  1726. num_retries=_GCP_API_RETRIES)
  1727. wait_for_zone_operation(gcp,
  1728. instance_group.zone,
  1729. result['name'],
  1730. timeout_sec=_WAIT_FOR_BACKEND_SEC)
  1731. except googleapiclient.errors.HttpError as http_error:
  1732. logger.info('Delete failed: %s', http_error)
  1733. def delete_instance_template(gcp):
  1734. try:
  1735. result = gcp.compute.instanceTemplates().delete(
  1736. project=gcp.project,
  1737. instanceTemplate=gcp.instance_template.name).execute(
  1738. num_retries=_GCP_API_RETRIES)
  1739. wait_for_global_operation(gcp, result['name'])
  1740. except googleapiclient.errors.HttpError as http_error:
  1741. logger.info('Delete failed: %s', http_error)
  1742. def patch_backend_service(gcp,
  1743. backend_service,
  1744. instance_groups,
  1745. balancing_mode='UTILIZATION',
  1746. max_rate=1,
  1747. circuit_breakers=None):
  1748. if gcp.alpha_compute:
  1749. compute_to_use = gcp.alpha_compute
  1750. else:
  1751. compute_to_use = gcp.compute
  1752. config = {
  1753. 'backends': [{
  1754. 'group': instance_group.url,
  1755. 'balancingMode': balancing_mode,
  1756. 'maxRate': max_rate if balancing_mode == 'RATE' else None
  1757. } for instance_group in instance_groups],
  1758. 'circuitBreakers': circuit_breakers,
  1759. }
  1760. logger.debug('Sending GCP request with body=%s', config)
  1761. result = compute_to_use.backendServices().patch(
  1762. project=gcp.project, backendService=backend_service.name,
  1763. body=config).execute(num_retries=_GCP_API_RETRIES)
  1764. wait_for_global_operation(gcp,
  1765. result['name'],
  1766. timeout_sec=_WAIT_FOR_BACKEND_SEC)
  1767. def resize_instance_group(gcp,
  1768. instance_group,
  1769. new_size,
  1770. timeout_sec=_WAIT_FOR_OPERATION_SEC):
  1771. result = gcp.compute.instanceGroupManagers().resize(
  1772. project=gcp.project,
  1773. zone=instance_group.zone,
  1774. instanceGroupManager=instance_group.name,
  1775. size=new_size).execute(num_retries=_GCP_API_RETRIES)
  1776. wait_for_zone_operation(gcp,
  1777. instance_group.zone,
  1778. result['name'],
  1779. timeout_sec=360)
  1780. wait_for_instance_group_to_reach_expected_size(gcp, instance_group,
  1781. new_size, timeout_sec)
  1782. def patch_url_map_backend_service(gcp,
  1783. backend_service=None,
  1784. services_with_weights=None,
  1785. route_rules=None):
  1786. '''change url_map's backend service
  1787. Only one of backend_service and service_with_weights can be not None.
  1788. '''
  1789. if backend_service and services_with_weights:
  1790. raise ValueError(
  1791. 'both backend_service and service_with_weights are not None.')
  1792. default_service = backend_service.url if backend_service else None
  1793. default_route_action = {
  1794. 'weightedBackendServices': [{
  1795. 'backendService': service.url,
  1796. 'weight': w,
  1797. } for service, w in services_with_weights.items()]
  1798. } if services_with_weights else None
  1799. config = {
  1800. 'pathMatchers': [{
  1801. 'name': _PATH_MATCHER_NAME,
  1802. 'defaultService': default_service,
  1803. 'defaultRouteAction': default_route_action,
  1804. 'routeRules': route_rules,
  1805. }]
  1806. }
  1807. logger.debug('Sending GCP request with body=%s', config)
  1808. result = gcp.compute.urlMaps().patch(
  1809. project=gcp.project, urlMap=gcp.url_map.name,
  1810. body=config).execute(num_retries=_GCP_API_RETRIES)
  1811. wait_for_global_operation(gcp, result['name'])
  1812. def wait_for_instance_group_to_reach_expected_size(gcp, instance_group,
  1813. expected_size, timeout_sec):
  1814. start_time = time.time()
  1815. while True:
  1816. current_size = len(get_instance_names(gcp, instance_group))
  1817. if current_size == expected_size:
  1818. break
  1819. if time.time() - start_time > timeout_sec:
  1820. raise Exception(
  1821. 'Instance group had expected size %d but actual size %d' %
  1822. (expected_size, current_size))
  1823. time.sleep(2)
  1824. def wait_for_global_operation(gcp,
  1825. operation,
  1826. timeout_sec=_WAIT_FOR_OPERATION_SEC):
  1827. start_time = time.time()
  1828. while time.time() - start_time <= timeout_sec:
  1829. result = gcp.compute.globalOperations().get(
  1830. project=gcp.project,
  1831. operation=operation).execute(num_retries=_GCP_API_RETRIES)
  1832. if result['status'] == 'DONE':
  1833. if 'error' in result:
  1834. raise Exception(result['error'])
  1835. return
  1836. time.sleep(2)
  1837. raise Exception('Operation %s did not complete within %d' %
  1838. (operation, timeout_sec))
  1839. def wait_for_zone_operation(gcp,
  1840. zone,
  1841. operation,
  1842. timeout_sec=_WAIT_FOR_OPERATION_SEC):
  1843. start_time = time.time()
  1844. while time.time() - start_time <= timeout_sec:
  1845. result = gcp.compute.zoneOperations().get(
  1846. project=gcp.project, zone=zone,
  1847. operation=operation).execute(num_retries=_GCP_API_RETRIES)
  1848. if result['status'] == 'DONE':
  1849. if 'error' in result:
  1850. raise Exception(result['error'])
  1851. return
  1852. time.sleep(2)
  1853. raise Exception('Operation %s did not complete within %d' %
  1854. (operation, timeout_sec))
  1855. def wait_for_healthy_backends(gcp,
  1856. backend_service,
  1857. instance_group,
  1858. timeout_sec=_WAIT_FOR_BACKEND_SEC):
  1859. start_time = time.time()
  1860. config = {'group': instance_group.url}
  1861. instance_names = get_instance_names(gcp, instance_group)
  1862. expected_size = len(instance_names)
  1863. while time.time() - start_time <= timeout_sec:
  1864. for instance_name in instance_names:
  1865. try:
  1866. status = get_serving_status(instance_name, gcp.service_port)
  1867. logger.info('serving status response from %s: %s',
  1868. instance_name, status)
  1869. except grpc.RpcError as rpc_error:
  1870. logger.info('checking serving status of %s failed: %s',
  1871. instance_name, rpc_error)
  1872. result = gcp.compute.backendServices().getHealth(
  1873. project=gcp.project,
  1874. backendService=backend_service.name,
  1875. body=config).execute(num_retries=_GCP_API_RETRIES)
  1876. if 'healthStatus' in result:
  1877. logger.info('received GCP healthStatus: %s', result['healthStatus'])
  1878. healthy = True
  1879. for instance in result['healthStatus']:
  1880. if instance['healthState'] != 'HEALTHY':
  1881. healthy = False
  1882. break
  1883. if healthy and expected_size == len(result['healthStatus']):
  1884. return
  1885. else:
  1886. logger.info('no healthStatus received from GCP')
  1887. time.sleep(5)
  1888. raise Exception('Not all backends became healthy within %d seconds: %s' %
  1889. (timeout_sec, result))
  1890. def get_instance_names(gcp, instance_group):
  1891. instance_names = []
  1892. result = gcp.compute.instanceGroups().listInstances(
  1893. project=gcp.project,
  1894. zone=instance_group.zone,
  1895. instanceGroup=instance_group.name,
  1896. body={
  1897. 'instanceState': 'ALL'
  1898. }).execute(num_retries=_GCP_API_RETRIES)
  1899. if 'items' not in result:
  1900. return []
  1901. for item in result['items']:
  1902. # listInstances() returns the full URL of the instance, which ends with
  1903. # the instance name. compute.instances().get() requires using the
  1904. # instance name (not the full URL) to look up instance details, so we
  1905. # just extract the name manually.
  1906. instance_name = item['instance'].split('/')[-1]
  1907. instance_names.append(instance_name)
  1908. logger.info('retrieved instance names: %s', instance_names)
  1909. return instance_names
  1910. def clean_up(gcp):
  1911. if gcp.global_forwarding_rule:
  1912. delete_global_forwarding_rule(gcp)
  1913. if gcp.target_proxy:
  1914. delete_target_proxy(gcp)
  1915. if gcp.url_map:
  1916. delete_url_map(gcp)
  1917. delete_backend_services(gcp)
  1918. if gcp.health_check_firewall_rule:
  1919. delete_firewall(gcp)
  1920. if gcp.health_check:
  1921. delete_health_check(gcp)
  1922. delete_instance_groups(gcp)
  1923. if gcp.instance_template:
  1924. delete_instance_template(gcp)
  1925. class InstanceGroup(object):
  1926. def __init__(self, name, url, zone):
  1927. self.name = name
  1928. self.url = url
  1929. self.zone = zone
  1930. class GcpResource(object):
  1931. def __init__(self, name, url):
  1932. self.name = name
  1933. self.url = url
  1934. class GcpState(object):
  1935. def __init__(self, compute, alpha_compute, project):
  1936. self.compute = compute
  1937. self.alpha_compute = alpha_compute
  1938. self.project = project
  1939. self.health_check = None
  1940. self.health_check_firewall_rule = None
  1941. self.backend_services = []
  1942. self.url_map = None
  1943. self.target_proxy = None
  1944. self.global_forwarding_rule = None
  1945. self.service_port = None
  1946. self.instance_template = None
  1947. self.instance_groups = []
  1948. alpha_compute = None
  1949. if args.compute_discovery_document:
  1950. with open(args.compute_discovery_document, 'r') as discovery_doc:
  1951. compute = googleapiclient.discovery.build_from_document(
  1952. discovery_doc.read())
  1953. if not args.only_stable_gcp_apis and args.alpha_compute_discovery_document:
  1954. with open(args.alpha_compute_discovery_document, 'r') as discovery_doc:
  1955. alpha_compute = googleapiclient.discovery.build_from_document(
  1956. discovery_doc.read())
  1957. else:
  1958. compute = googleapiclient.discovery.build('compute', 'v1')
  1959. if not args.only_stable_gcp_apis:
  1960. alpha_compute = googleapiclient.discovery.build('compute', 'alpha')
  1961. try:
  1962. gcp = GcpState(compute, alpha_compute, args.project_id)
  1963. gcp_suffix = args.gcp_suffix
  1964. health_check_name = _BASE_HEALTH_CHECK_NAME + gcp_suffix
  1965. if not args.use_existing_gcp_resources:
  1966. if args.keep_gcp_resources:
  1967. # Auto-generating a unique suffix in case of conflict should not be
  1968. # combined with --keep_gcp_resources, as the suffix actually used
  1969. # for GCP resources will not match the provided --gcp_suffix value.
  1970. num_attempts = 1
  1971. else:
  1972. num_attempts = 5
  1973. for i in range(num_attempts):
  1974. try:
  1975. logger.info('Using GCP suffix %s', gcp_suffix)
  1976. create_health_check(gcp, health_check_name)
  1977. break
  1978. except googleapiclient.errors.HttpError as http_error:
  1979. gcp_suffix = '%s-%04d' % (gcp_suffix, random.randint(0, 9999))
  1980. health_check_name = _BASE_HEALTH_CHECK_NAME + gcp_suffix
  1981. logger.exception('HttpError when creating health check')
  1982. if gcp.health_check is None:
  1983. raise Exception('Failed to create health check name after %d '
  1984. 'attempts' % num_attempts)
  1985. firewall_name = _BASE_FIREWALL_RULE_NAME + gcp_suffix
  1986. backend_service_name = _BASE_BACKEND_SERVICE_NAME + gcp_suffix
  1987. alternate_backend_service_name = _BASE_BACKEND_SERVICE_NAME + '-alternate' + gcp_suffix
  1988. url_map_name = _BASE_URL_MAP_NAME + gcp_suffix
  1989. service_host_name = _BASE_SERVICE_HOST + gcp_suffix
  1990. target_proxy_name = _BASE_TARGET_PROXY_NAME + gcp_suffix
  1991. forwarding_rule_name = _BASE_FORWARDING_RULE_NAME + gcp_suffix
  1992. template_name = _BASE_TEMPLATE_NAME + gcp_suffix
  1993. instance_group_name = _BASE_INSTANCE_GROUP_NAME + gcp_suffix
  1994. same_zone_instance_group_name = _BASE_INSTANCE_GROUP_NAME + '-same-zone' + gcp_suffix
  1995. secondary_zone_instance_group_name = _BASE_INSTANCE_GROUP_NAME + '-secondary-zone' + gcp_suffix
  1996. if args.use_existing_gcp_resources:
  1997. logger.info('Reusing existing GCP resources')
  1998. get_health_check(gcp, health_check_name)
  1999. try:
  2000. get_health_check_firewall_rule(gcp, firewall_name)
  2001. except googleapiclient.errors.HttpError as http_error:
  2002. # Firewall rule may be auto-deleted periodically depending on GCP
  2003. # project settings.
  2004. logger.exception('Failed to find firewall rule, recreating')
  2005. create_health_check_firewall_rule(gcp, firewall_name)
  2006. backend_service = get_backend_service(gcp, backend_service_name)
  2007. alternate_backend_service = get_backend_service(
  2008. gcp, alternate_backend_service_name)
  2009. get_url_map(gcp, url_map_name)
  2010. get_target_proxy(gcp, target_proxy_name)
  2011. get_global_forwarding_rule(gcp, forwarding_rule_name)
  2012. get_instance_template(gcp, template_name)
  2013. instance_group = get_instance_group(gcp, args.zone, instance_group_name)
  2014. same_zone_instance_group = get_instance_group(
  2015. gcp, args.zone, same_zone_instance_group_name)
  2016. secondary_zone_instance_group = get_instance_group(
  2017. gcp, args.secondary_zone, secondary_zone_instance_group_name)
  2018. else:
  2019. create_health_check_firewall_rule(gcp, firewall_name)
  2020. backend_service = add_backend_service(gcp, backend_service_name)
  2021. alternate_backend_service = add_backend_service(
  2022. gcp, alternate_backend_service_name)
  2023. create_url_map(gcp, url_map_name, backend_service, service_host_name)
  2024. create_target_proxy(gcp, target_proxy_name)
  2025. potential_service_ports = list(args.service_port_range)
  2026. random.shuffle(potential_service_ports)
  2027. create_global_forwarding_rule(gcp, forwarding_rule_name,
  2028. potential_service_ports)
  2029. if not gcp.service_port:
  2030. raise Exception(
  2031. 'Failed to find a valid ip:port for the forwarding rule')
  2032. if gcp.service_port != _DEFAULT_SERVICE_PORT:
  2033. patch_url_map_host_rule_with_port(gcp, url_map_name,
  2034. backend_service,
  2035. service_host_name)
  2036. startup_script = get_startup_script(args.path_to_server_binary,
  2037. gcp.service_port)
  2038. create_instance_template(gcp, template_name, args.network,
  2039. args.source_image, args.machine_type,
  2040. startup_script)
  2041. instance_group = add_instance_group(gcp, args.zone, instance_group_name,
  2042. _INSTANCE_GROUP_SIZE)
  2043. patch_backend_service(gcp, backend_service, [instance_group])
  2044. same_zone_instance_group = add_instance_group(
  2045. gcp, args.zone, same_zone_instance_group_name, _INSTANCE_GROUP_SIZE)
  2046. secondary_zone_instance_group = add_instance_group(
  2047. gcp, args.secondary_zone, secondary_zone_instance_group_name,
  2048. _INSTANCE_GROUP_SIZE)
  2049. wait_for_healthy_backends(gcp, backend_service, instance_group)
  2050. if args.test_case:
  2051. client_env = dict(os.environ)
  2052. bootstrap_server_features = []
  2053. if gcp.service_port == _DEFAULT_SERVICE_PORT:
  2054. server_uri = service_host_name
  2055. else:
  2056. server_uri = service_host_name + ':' + str(gcp.service_port)
  2057. if args.xds_v3_support:
  2058. client_env['GRPC_XDS_EXPERIMENTAL_V3_SUPPORT'] = 'true'
  2059. bootstrap_server_features.append('xds_v3')
  2060. if args.bootstrap_file:
  2061. bootstrap_path = os.path.abspath(args.bootstrap_file)
  2062. else:
  2063. with tempfile.NamedTemporaryFile(delete=False) as bootstrap_file:
  2064. bootstrap_file.write(
  2065. _BOOTSTRAP_TEMPLATE.format(
  2066. node_id=socket.gethostname(),
  2067. server_features=json.dumps(
  2068. bootstrap_server_features)).encode('utf-8'))
  2069. bootstrap_path = bootstrap_file.name
  2070. client_env['GRPC_XDS_BOOTSTRAP'] = bootstrap_path
  2071. client_env['GRPC_XDS_EXPERIMENTAL_CIRCUIT_BREAKING'] = 'true'
  2072. test_results = {}
  2073. failed_tests = []
  2074. for test_case in args.test_case:
  2075. result = jobset.JobResult()
  2076. log_dir = os.path.join(_TEST_LOG_BASE_DIR, test_case)
  2077. if not os.path.exists(log_dir):
  2078. os.makedirs(log_dir)
  2079. test_log_filename = os.path.join(log_dir, _SPONGE_LOG_NAME)
  2080. test_log_file = open(test_log_filename, 'w+')
  2081. client_process = None
  2082. if test_case in _TESTS_TO_RUN_MULTIPLE_RPCS:
  2083. rpcs_to_send = '--rpc="UnaryCall,EmptyCall"'
  2084. else:
  2085. rpcs_to_send = '--rpc="UnaryCall"'
  2086. if test_case in _TESTS_TO_SEND_METADATA:
  2087. metadata_to_send = '--metadata="EmptyCall:{keyE}:{valueE},UnaryCall:{keyU}:{valueU},UnaryCall:{keyNU}:{valueNU}"'.format(
  2088. keyE=_TEST_METADATA_KEY,
  2089. valueE=_TEST_METADATA_VALUE_EMPTY,
  2090. keyU=_TEST_METADATA_KEY,
  2091. valueU=_TEST_METADATA_VALUE_UNARY,
  2092. keyNU=_TEST_METADATA_NUMERIC_KEY,
  2093. valueNU=_TEST_METADATA_NUMERIC_VALUE)
  2094. else:
  2095. # Setting the arg explicitly to empty with '--metadata=""'
  2096. # makes C# client fail
  2097. # (see https://github.com/commandlineparser/commandline/issues/412),
  2098. # so instead we just rely on clients using the default when
  2099. # metadata arg is not specified.
  2100. metadata_to_send = ''
  2101. # TODO(ericgribkoff) Temporarily disable fail_on_failed_rpc checks
  2102. # in the client. This means we will ignore intermittent RPC
  2103. # failures (but this framework still checks that the final result
  2104. # is as expected).
  2105. #
  2106. # Reason for disabling this is, the resources are shared by
  2107. # multiple tests, and a change in previous test could be delayed
  2108. # until the second test starts. The second test may see
  2109. # intermittent failures because of that.
  2110. #
  2111. # A fix is to not share resources between tests (though that does
  2112. # mean the tests will be significantly slower due to creating new
  2113. # resources).
  2114. fail_on_failed_rpc = ''
  2115. try:
  2116. if not CLIENT_HOSTS:
  2117. client_cmd_formatted = args.client_cmd.format(
  2118. server_uri=server_uri,
  2119. stats_port=args.stats_port,
  2120. qps=args.qps,
  2121. fail_on_failed_rpc=fail_on_failed_rpc,
  2122. rpcs_to_send=rpcs_to_send,
  2123. metadata_to_send=metadata_to_send)
  2124. logger.debug('running client: %s', client_cmd_formatted)
  2125. client_cmd = shlex.split(client_cmd_formatted)
  2126. client_process = subprocess.Popen(client_cmd,
  2127. env=client_env,
  2128. stderr=subprocess.STDOUT,
  2129. stdout=test_log_file)
  2130. if test_case == 'backends_restart':
  2131. test_backends_restart(gcp, backend_service, instance_group)
  2132. elif test_case == 'change_backend_service':
  2133. test_change_backend_service(gcp, backend_service,
  2134. instance_group,
  2135. alternate_backend_service,
  2136. same_zone_instance_group)
  2137. elif test_case == 'gentle_failover':
  2138. test_gentle_failover(gcp, backend_service, instance_group,
  2139. secondary_zone_instance_group)
  2140. elif test_case == 'load_report_based_failover':
  2141. test_load_report_based_failover(
  2142. gcp, backend_service, instance_group,
  2143. secondary_zone_instance_group)
  2144. elif test_case == 'ping_pong':
  2145. test_ping_pong(gcp, backend_service, instance_group)
  2146. elif test_case == 'remove_instance_group':
  2147. test_remove_instance_group(gcp, backend_service,
  2148. instance_group,
  2149. same_zone_instance_group)
  2150. elif test_case == 'round_robin':
  2151. test_round_robin(gcp, backend_service, instance_group)
  2152. elif test_case == 'secondary_locality_gets_no_requests_on_partial_primary_failure':
  2153. test_secondary_locality_gets_no_requests_on_partial_primary_failure(
  2154. gcp, backend_service, instance_group,
  2155. secondary_zone_instance_group)
  2156. elif test_case == 'secondary_locality_gets_requests_on_primary_failure':
  2157. test_secondary_locality_gets_requests_on_primary_failure(
  2158. gcp, backend_service, instance_group,
  2159. secondary_zone_instance_group)
  2160. elif test_case == 'traffic_splitting':
  2161. test_traffic_splitting(gcp, backend_service, instance_group,
  2162. alternate_backend_service,
  2163. same_zone_instance_group)
  2164. elif test_case == 'path_matching':
  2165. test_path_matching(gcp, backend_service, instance_group,
  2166. alternate_backend_service,
  2167. same_zone_instance_group)
  2168. elif test_case == 'header_matching':
  2169. test_header_matching(gcp, backend_service, instance_group,
  2170. alternate_backend_service,
  2171. same_zone_instance_group)
  2172. elif test_case == 'circuit_breaking':
  2173. test_circuit_breaking(gcp, backend_service, instance_group,
  2174. same_zone_instance_group)
  2175. else:
  2176. logger.error('Unknown test case: %s', test_case)
  2177. sys.exit(1)
  2178. if client_process and client_process.poll() is not None:
  2179. raise Exception(
  2180. 'Client process exited prematurely with exit code %d' %
  2181. client_process.returncode)
  2182. result.state = 'PASSED'
  2183. result.returncode = 0
  2184. except Exception as e:
  2185. logger.exception('Test case %s failed', test_case)
  2186. failed_tests.append(test_case)
  2187. result.state = 'FAILED'
  2188. result.message = str(e)
  2189. finally:
  2190. if client_process:
  2191. if client_process.returncode:
  2192. logger.info('Client exited with code %d' %
  2193. client_process.returncode)
  2194. else:
  2195. client_process.terminate()
  2196. test_log_file.close()
  2197. # Workaround for Python 3, as report_utils will invoke decode() on
  2198. # result.message, which has a default value of ''.
  2199. result.message = result.message.encode('UTF-8')
  2200. test_results[test_case] = [result]
  2201. if args.log_client_output:
  2202. logger.info('Client output:')
  2203. with open(test_log_filename, 'r') as client_output:
  2204. logger.info(client_output.read())
  2205. if not os.path.exists(_TEST_LOG_BASE_DIR):
  2206. os.makedirs(_TEST_LOG_BASE_DIR)
  2207. report_utils.render_junit_xml_report(test_results,
  2208. os.path.join(
  2209. _TEST_LOG_BASE_DIR,
  2210. _SPONGE_XML_NAME),
  2211. suite_name='xds_tests',
  2212. multi_target=True)
  2213. if failed_tests:
  2214. logger.error('Test case(s) %s failed', failed_tests)
  2215. sys.exit(1)
  2216. finally:
  2217. if not args.keep_gcp_resources:
  2218. logger.info('Cleaning up GCP resources. This may take some time.')
  2219. clean_up(gcp)