1'''
2'''
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#      http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing, software
14#  distributed under the License is distributed on an "AS IS" BASIS,
15#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16#  See the License for the specific language governing permissions and
17#  limitations under the License.
18
19Test.Summary = '''
20Test next hop selection using strategies.yaml with consistent hashing, with fallover
21'''
22
23# Define and populate MicroServer.
24#
25server = Test.MakeOriginServer("server")
26response_header = {
27    "headers":
28        "HTTP/1.1 200 OK\r\n"
29        "Connection: close\r\n"
30        "Cache-control: max-age=85000\r\n"
31        "\r\n",
32    "timestamp": "1469733493.993",
33    "body": "This is the body.\n"
34}
35num_objects = 32
36for i in range(num_objects):
37    request_header = {
38        "headers":
39            f"GET /obj{i} HTTP/1.1\r\n"
40            "Host: does.not.matter\r\n"  # But cannot be omitted.
41            "\r\n",
42        "timestamp": "1469733493.993",
43        "body": ""
44    }
45    server.addResponse("sessionlog.json", request_header, response_header)
46
47dns = Test.MakeDNServer("dns")
48
49# Define next hop trafficserver instances.
50#
51num_nh = 8
52ts_nh = []
53for i in range(num_nh):
54    ts = Test.MakeATSProcess(f"ts_nh{i}")
55    ts.Disk.records_config.update({
56        'proxy.config.diags.debug.enabled': 1,
57        'proxy.config.diags.debug.tags': 'http|dns',
58        'proxy.config.dns.nameservers': f"127.0.0.1:{dns.Variables.Port}",
59        'proxy.config.dns.resolv_conf': "NULL",
60    })
61    ts.Disk.remap_config.AddLine(
62        f"map / http://127.0.0.1:{server.Variables.Port}"
63    )
64    ts_nh.append(ts)
65
66ts = Test.MakeATSProcess("ts", command="traffic_server 2> trace.log")
67
68ts.Disk.records_config.update({
69    'proxy.config.diags.debug.enabled': 1,
70    'proxy.config.diags.debug.tags': 'http|dns|parent|next_hop|host_statuses|hostdb',
71    'proxy.config.dns.nameservers': f"127.0.0.1:{dns.Variables.Port}",  # Only nameservers if resolv_conf NULL.
72    'proxy.config.dns.resolv_conf': "NULL",  # This defaults to /etc/resvolv.conf (OS namesevers) if not NULL.
73    'proxy.config.http.cache.http': 0,
74    'proxy.config.http.uncacheable_requests_bypass_parent': 0,
75    'proxy.config.http.no_dns_just_forward_to_parent': 1,
76    'proxy.config.http.parent_proxy.mark_down_hostdb': 1,
77    'proxy.config.http.parent_proxy.self_detect': 0,
78})
79
80ts.Disk.File(ts.Variables.CONFIGDIR + "/strategies.yaml", id="strategies", typename="ats:config")
81s = ts.Disk.strategies
82s.AddLine("groups:")
83s.AddLine("  - &g1")
84for i in range(num_nh):
85    dns.addRecords(records={f"next_hop{i}": ["127.0.0.1"]})
86    s.AddLine(f"    - host: next_hop{i}")
87    s.AddLine(f"      protocol:")
88    s.AddLine(f"        - scheme: http")
89    s.AddLine(f"          port: {ts_nh[i].Variables.port}")
90    # The health check URL does not seem to be used currently.
91    # s.AddLine(f"          health_check_url: http://next_hop{i}:{ts_nh[i].Variables.port}")
92    s.AddLine(f"      weight: 1.0")
93s.AddLines([
94    "strategies:",
95    "  - strategy: the-strategy",
96    "    policy: consistent_hash",
97    "    hash_key: path",
98    "    go_direct: false",
99    "    parent_is_proxy: true",
100    "    ignore_self_detect: true",
101    "    groups:",
102    "      - *g1",
103    "    scheme: http"
104])
105
106# Use default fallover config.
107#
108# s.AddLines([
109#     "    fallover:",
110#     "      max_simple_retries: 2",
111#     "      ring_mode: exhaust_ring",
112#     "      response_codes:",
113#     "        - 404",
114#     "      health_check:",
115#     "        - passive"
116# ])
117
118ts.Disk.remap_config.AddLine(
119    "map http://dummy.com http://not_used @strategy=the-strategy"
120)
121
122tr = Test.AddTestRun()
123tr.Processes.Default.StartBefore(server)
124tr.Processes.Default.StartBefore(dns)
125for i in range(num_nh):
126    if (i != 3) and (i != 6):
127        tr.Processes.Default.StartBefore(ts_nh[i])
128tr.Processes.Default.StartBefore(Test.Processes.ts)
129tr.Processes.Default.Command = 'echo start TS, HTTP server, DNS server and all but 2 next hop TSes'
130tr.Processes.Default.ReturnCode = 0
131
132for i in range(num_objects):
133    tr = Test.AddTestRun()
134    tr.Processes.Default.Command = (
135        f'curl --verbose --proxy 127.0.0.1:{ts.Variables.port} http://dummy.com/obj{i}'
136    )
137    tr.Processes.Default.Streams.stdout = "body.gold"
138    tr.Processes.Default.ReturnCode = 0
139
140tr = Test.AddTestRun()
141tr.Processes.Default.StartBefore(ts_nh[3])
142tr.Processes.Default.StartBefore(ts_nh[6])
143tr.Processes.Default.Command = 'echo start the 2 unstarted next hop TSes'
144tr.Processes.Default.ReturnCode = 0
145
146for i in range(num_objects):
147    tr = Test.AddTestRun()
148    tr.Processes.Default.Command = (
149        f'curl --verbose --proxy 127.0.0.1:{ts.Variables.port} http://dummy.com/obj{i}'
150    )
151    tr.Processes.Default.Streams.stdout = "body.gold"
152    tr.Processes.Default.ReturnCode = 0
153
154tr = Test.AddTestRun()
155tr.Processes.Default.Command = (
156    "grep -F PARENT_SPECIFIED trace.log | sed 's/^.*(next_hop) [^ ]* //' | sed 's/[.][0-9]*$$//'"
157)
158tr.Processes.Default.Streams.stdout = "trace.gold"
159tr.Processes.Default.ReturnCode = 0
160