OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import os | 5 import os |
6 | 6 |
7 from telemetry.internal import forwarders | 7 from telemetry.internal import forwarders |
8 from telemetry.internal.util import webpagereplay | 8 from telemetry.internal.util import webpagereplay |
9 from telemetry.util import wpr_modes | 9 from telemetry.util import wpr_modes |
10 | 10 |
11 class ArchiveDoesNotExistError(Exception): | 11 class ArchiveDoesNotExistError(Exception): |
12 """Raised when the archive path does not exist for replay mode.""" | 12 """Raised when the archive path does not exist for replay mode.""" |
13 pass | 13 pass |
14 | 14 |
15 | 15 |
16 class ReplayAndBrowserPortsError(Exception): | 16 class ReplayAndBrowserPortsError(Exception): |
17 """Raised an existing browser would get different remote replay ports.""" | 17 """Raised an existing browser would get different remote replay ports.""" |
18 pass | 18 pass |
19 | 19 |
20 | 20 |
21 class NetworkControllerBackend(object): | 21 class NetworkControllerBackend(object): |
22 """Control network settings and servers to simulate the Web. | 22 """Control network settings and servers to simulate the Web. |
23 | 23 |
24 Network changes include forwarding device ports to host platform ports. | 24 Network changes include forwarding device ports to host platform ports. |
25 Web Page Replay is used to record and replay HTTP/HTTPS responses. | 25 Web Page Replay is used to record and replay HTTP/HTTPS responses. |
26 """ | 26 """ |
27 | 27 |
28 def __init__(self, platform_backend): | 28 def __init__(self, platform_backend): |
29 self._platform_backend = platform_backend | 29 self._platform_backend = platform_backend |
30 self._browser_backend = None | 30 self._is_android_platform = self._platform_backend.GetOSName() == 'android' |
31 self._active_replay_args = {} | 31 self._is_cros_platform = self._platform_backend.GetOSName() == 'chromeos' |
32 self._pending_replay_args = {} | 32 self._wpr_mode = None |
| 33 self._netsim = None |
| 34 self._extra_wpr_args = None |
| 35 self._wpr_port_pairs = None |
| 36 self._archive_path = None |
| 37 self._make_javascript_deterministic = None |
33 self._forwarder = None | 38 self._forwarder = None |
34 self._wpr_server = None | 39 self._wpr_server = None |
35 | 40 |
36 def SetReplayArgs(self, archive_path, wpr_mode, netsim, extra_wpr_args, | 41 @property |
37 make_javascript_deterministic=False): | 42 def wpr_mode(self): |
38 """Save the arguments needed for replay. | 43 return self._wpr_mode |
39 | 44 |
40 To make the settings effective, this call must be followed by a call | 45 @property |
41 to UpdateReplay. | 46 def wpr_device_ports(self): |
| 47 try: |
| 48 return self._forwarder.port_pairs.remote_ports |
| 49 except AttributeError: |
| 50 return None |
| 51 |
| 52 @property |
| 53 def host_ip(self): |
| 54 return self._platform_backend.forwarder_factory.host_ip |
| 55 |
| 56 def Open(self, wpr_mode, netsim, extra_wpr_args): |
| 57 assert self._wpr_port_pairs is None, 'Network controller is already open' |
| 58 self._wpr_mode = wpr_mode |
| 59 self._netsim = netsim |
| 60 self._extra_wpr_args = list(extra_wpr_args) # Keep a copy. |
| 61 |
| 62 if self._is_android_platform: |
| 63 self._platform_backend.InstallTestCa() |
| 64 |
| 65 # Determine adequate WPR ports to use for the specific paltform and |
| 66 # selected configuration. |
| 67 if self._netsim: |
| 68 if self._is_android_platform: |
| 69 assert self._platform_backend.use_rndis_forwarder, ( |
| 70 'Netsim requires RNDIS forwarding.') |
| 71 self._wpr_port_pairs = forwarders.PortPairs.Make( |
| 72 http=(0, 80), https=(0, 443), dns=(0, 53)) |
| 73 else: |
| 74 self._wpr_port_pairs = forwarders.PortPairs.Make( |
| 75 http=(80, 80), https=(443, 443), dns=(53, 53)) |
| 76 else: |
| 77 self._wpr_port_pairs = forwarders.PortPairs.Make( |
| 78 http=(0, 0), https=(0, 0)) |
| 79 |
| 80 if self._is_cros_platform: |
| 81 local_ports = self._wpr_port_pairs.local_ports |
| 82 # TODO(perezju): Is this correct? local https pairs with remote http? |
| 83 # Dns is never paired? |
| 84 self._wpr_port_pairs = forwarders.PortPairs.Make( |
| 85 http=(local_ports.http, |
| 86 self._platform_backend.GetRemotePort(local_ports.http)), |
| 87 https=(local_ports.https, |
| 88 self._platform_backend.GetRemotePort(local_ports.http))) |
| 89 |
| 90 def Close(self): |
| 91 if self._forwarder: |
| 92 self._forwarder.Close() |
| 93 self._forwarder = None |
| 94 self.StopReplay() |
| 95 |
| 96 if self._is_android_platform: |
| 97 self._platform_backend.RemoveTestCa() |
| 98 |
| 99 self._wpr_mode = None |
| 100 self._netsim = None |
| 101 self._extra_wpr_args = None |
| 102 self._wpr_port_pairs = None |
| 103 self._archive_path = None |
| 104 self._make_javascript_deterministic = None |
| 105 |
| 106 def StartReplay(self, archive_path, make_javascript_deterministic=False): |
| 107 """Start (or reuse) the web page replay server. |
42 | 108 |
43 Args: | 109 Args: |
44 archive_path: a path to a specific WPR archive. | 110 archive_path: a path to a specific WPR archive. |
45 wpr_mode: one of wpr_modes.WPR_OFF, wpr_modes.WPR_APPEND, | |
46 wpr_modes.WPR_REPLAY, or wpr_modes.WPR_RECORD. | |
47 netsim: a net_config string ('dialup', '3g', 'dsl', 'cable', or 'fios'). | |
48 extra_wpr_args: a list of additional replay args (or an empty list). | |
49 make_javascript_deterministic: True if replay should inject a script | 111 make_javascript_deterministic: True if replay should inject a script |
50 to make JavaScript behave deterministically (e.g., override Date()). | 112 to make JavaScript behave deterministically (e.g., override Date()). |
51 """ | 113 """ |
| 114 if self._wpr_mode == wpr_modes.WPR_OFF: |
| 115 return |
52 if not archive_path: | 116 if not archive_path: |
53 # TODO(slamm, tonyg): Ideally, replay mode should be stopped when there is | 117 # TODO(slamm, tonyg): Ideally, replay mode should be stopped when there is |
54 # no archive path. However, if the replay server already started, and | 118 # no archive path. However, if the replay server already started, and |
55 # a file URL is tested with the | 119 # a file URL is tested with the |
56 # telemetry.core.local_server.LocalServerController, then the | 120 # telemetry.core.local_server.LocalServerController, then the |
57 # replay server forwards requests to it. (Chrome is configured to use | 121 # replay server forwards requests to it. (Chrome is configured to use |
58 # fixed ports fo all HTTP/HTTPS requests.) | 122 # fixed ports fo all HTTP/HTTPS requests.) |
59 return | 123 return |
60 self._pending_replay_args = dict( | 124 if (self._wpr_mode == wpr_modes.WPR_REPLAY |
61 archive_path=archive_path, | 125 and not os.path.exists(archive_path)): |
62 wpr_mode=wpr_mode, | |
63 netsim=netsim, | |
64 extra_wpr_args=extra_wpr_args, | |
65 make_javascript_deterministic=make_javascript_deterministic) | |
66 # TODO(slamm): Update replay here when the browser_backend dependencies | |
67 # are moved to the platform. https://crbug.com/423962 | |
68 # |self._pending_replay_args| can be removed at that time. | |
69 | |
70 def UpdateReplay(self, browser_backend=None): | |
71 """Start or reuse Web Page Replay. | |
72 | |
73 UpdateReplay must be called after every call to SetReplayArgs. | |
74 | |
75 TODO(slamm): Update replay in SetReplayArgs once the browser_backend | |
76 dependencies move to platform. https://crbug.com/423962 | |
77 browser_backend properties used: | |
78 - Input: wpr_port_pairs | |
79 - Output: wpr_port_pairs (browser uses for --testing-fixed-* flags). | |
80 Args: | |
81 browser_backend: instance of telemetry.core.backends.browser_backend | |
82 """ | |
83 if not self._pending_replay_args: | |
84 # In some cases (e.g., unit tests), the browser is used without replay. | |
85 return | |
86 if self._pending_replay_args == self._active_replay_args: | |
87 return | |
88 | |
89 pending_archive_path = self._pending_replay_args['archive_path'] | |
90 | |
91 | |
92 | |
93 pending_wpr_mode = self._pending_replay_args['wpr_mode'] | |
94 if pending_wpr_mode == wpr_modes.WPR_OFF: | |
95 return | |
96 if (pending_wpr_mode == wpr_modes.WPR_REPLAY and | |
97 not os.path.exists(pending_archive_path)): | |
98 raise ArchiveDoesNotExistError( | |
99 'Archive path does not exist: %s' % pending_archive_path) | |
100 if browser_backend: | |
101 self._browser_backend = browser_backend | |
102 self.StopReplay() # stop any forwarder too | |
103 wpr_port_pairs = self._browser_backend.wpr_port_pairs | |
104 else: | |
105 # If no browser_backend, then this is an update for an existing browser. | |
106 assert self._browser_backend | |
107 self._StopReplayOnly() # leave existing forwarder in place | |
108 wpr_port_pairs = self._forwarder.port_pairs | |
109 wpr_http_port = wpr_port_pairs.http.local_port | |
110 wpr_https_port = wpr_port_pairs.https.local_port | |
111 wpr_dns_port = (wpr_port_pairs.dns.local_port | |
112 if wpr_port_pairs.dns else None) | |
113 | |
114 archive_path = self._pending_replay_args['archive_path'] | |
115 wpr_mode = self._pending_replay_args['wpr_mode'] | |
116 netsim = self._pending_replay_args['netsim'] | |
117 extra_wpr_args = self._pending_replay_args['extra_wpr_args'] | |
118 make_javascript_deterministic = self._pending_replay_args[ | |
119 'make_javascript_deterministic'] | |
120 | |
121 if wpr_mode == wpr_modes.WPR_OFF: | |
122 return | |
123 if (wpr_mode == wpr_modes.WPR_REPLAY and | |
124 not os.path.exists(archive_path)): | |
125 raise ArchiveDoesNotExistError( | 126 raise ArchiveDoesNotExistError( |
126 'Archive path does not exist: %s' % archive_path) | 127 'Archive path does not exist: %s' % archive_path) |
| 128 if (self._wpr_server is not None and |
| 129 self._archive_path == archive_path and |
| 130 self._make_javascript_deterministic == make_javascript_deterministic): |
| 131 return # We may reuse the existing server |
127 | 132 |
128 wpr_args = _ReplayCommandLineArgs( | 133 self._archive_path = archive_path |
129 wpr_mode, netsim, extra_wpr_args, make_javascript_deterministic, | 134 self._make_javascript_deterministic = make_javascript_deterministic |
130 self._platform_backend.wpr_ca_cert_path) | 135 self.StopReplay() # Stop if it was already running. |
131 self._wpr_server = self._ReplayServer( | 136 started_ports = self._StartReplayServer() |
132 archive_path, self._platform_backend.forwarder_factory.host_ip, | 137 self._StartForwarderIfNeeded(started_ports) |
133 wpr_http_port, wpr_https_port, wpr_dns_port, wpr_args) | |
134 started_ports = self._wpr_server.StartServer() | |
135 | |
136 if not self._forwarder: | |
137 self._forwarder = self._platform_backend.forwarder_factory.Create( | |
138 _ForwarderPortPairs(started_ports, wpr_port_pairs)) | |
139 | |
140 self._active_replay_args = self._pending_replay_args | |
141 self._pending_replay_args = None | |
142 | |
143 def _ReplayServer( | |
144 self, archive_path, host_ip, http_port, https_port, dns_port, wpr_args): | |
145 return webpagereplay.ReplayServer( | |
146 archive_path, host_ip, http_port, https_port, dns_port, wpr_args) | |
147 | 138 |
148 def StopReplay(self): | 139 def StopReplay(self): |
149 if self._forwarder: | |
150 self._forwarder.Close() | |
151 self._forwarder = None | |
152 self._StopReplayOnly() | |
153 | |
154 def _StopReplayOnly(self): | |
155 if self._wpr_server: | 140 if self._wpr_server: |
156 self._wpr_server.StopServer() | 141 self._wpr_server.StopServer() |
157 self._wpr_server = None | 142 self._wpr_server = None |
158 self._active_replay_args = {} | |
159 | 143 |
160 @property | 144 def _StartReplayServer(self): |
161 def wpr_http_device_port(self): | 145 assert self._wpr_server is None |
162 if not self._forwarder or not self._forwarder.port_pairs.http: | 146 local_ports = self._wpr_port_pairs.local_ports |
163 return None | 147 self._wpr_server = webpagereplay.ReplayServer( |
164 return self._forwarder.port_pairs.http.remote_port | 148 self._archive_path, |
| 149 self.host_ip, |
| 150 local_ports.http, |
| 151 local_ports.https, |
| 152 local_ports.dns, |
| 153 self._ReplayCommandLineArgs()) |
| 154 return self._wpr_server.StartServer() |
165 | 155 |
166 @property | 156 def _ReplayCommandLineArgs(self): |
167 def wpr_https_device_port(self): | 157 wpr_args = list(self._extra_wpr_args) |
168 if not self._forwarder or not self._forwarder.port_pairs.https: | 158 if self._netsim: |
169 return None | 159 wpr_args.append('--net=%s' % self._netsim) |
170 return self._forwarder.port_pairs.https.remote_port | 160 if self._wpr_mode == wpr_modes.WPR_APPEND: |
| 161 wpr_args.append('--append') |
| 162 elif self._wpr_mode == wpr_modes.WPR_RECORD: |
| 163 wpr_args.append('--record') |
| 164 if not self._make_javascript_deterministic: |
| 165 wpr_args.append('--inject_scripts=') |
| 166 if self._platform_backend.wpr_ca_cert_path: |
| 167 wpr_args.extend([ |
| 168 '--should_generate_certs', |
| 169 '--https_root_ca_cert_path=%s' |
| 170 % self._platform_backend.wpr_ca_cert_path]) |
| 171 return wpr_args |
171 | 172 |
| 173 def _StartForwarderIfNeeded(self, started_ports): |
| 174 """Start a forwarder from started local ports to requested remote ports. |
172 | 175 |
173 def _ReplayCommandLineArgs(wpr_mode, netsim, extra_wpr_args, | 176 The local host is where Telemetry is run. The remote is host where |
174 make_javascript_deterministic, wpr_ca_cert_path): | 177 the target application is run. The local and remote hosts may be |
175 wpr_args = list(extra_wpr_args) | 178 the same (e.g., testing a desktop browser) or different (e.g., testing |
176 if netsim: | 179 an android browser). |
177 wpr_args.append('--net=%s' % netsim) | |
178 if wpr_mode == wpr_modes.WPR_APPEND: | |
179 wpr_args.append('--append') | |
180 elif wpr_mode == wpr_modes.WPR_RECORD: | |
181 wpr_args.append('--record') | |
182 if not make_javascript_deterministic: | |
183 wpr_args.append('--inject_scripts=') | |
184 if wpr_ca_cert_path: | |
185 wpr_args.extend([ | |
186 '--should_generate_certs', | |
187 '--https_root_ca_cert_path=%s' % wpr_ca_cert_path, | |
188 ]) | |
189 return wpr_args | |
190 | 180 |
| 181 The remote ports may be zero. In that case, the forwarder determines |
| 182 the remote ports. |
191 | 183 |
192 def _ForwarderPortPairs(started_ports, wpr_port_pairs): | 184 An existing forwarder for the same started ports may be reused. |
193 """Return PortPairs with started local ports and requested remote ports. | |
194 | 185 |
195 The local host is where Telemetry is run. The remote is host where | 186 Args: |
196 the target application is run. The local and remote hosts may be | 187 started_ports: a PortSet tuple of integer ports from which to forward. |
197 the same (e.g., testing a desktop browser) or different (e.g., testing | 188 """ |
198 an android browser). | 189 if self._forwarder is not None: |
199 | 190 if started_ports == self._forwarder.port_pairs.local_ports: |
200 The remote ports may be zero. In that case, the forwarder determines | 191 return # Safe to reuse existing forwarder. |
201 the remote ports. | 192 self._forwarder.Close() |
202 | 193 remote_ports = self._wpr_port_pairs.remote_ports |
203 Args: | 194 self._forwarder = self._platform_backend.forwarder_factory.Create( |
204 started_ports: a tuple of of integer ports from which to forward: | 195 forwarders.PortPairs.Make( |
205 (HTTP_PORT, HTTPS_PORT, DNS_PORT) # DNS_PORT may be None | 196 http=(started_ports.http, remote_ports.http), |
206 wpr_port_pairs: a forwarders.PortPairs instance where the remote ports, | 197 https=(started_ports.https, remote_ports.https), |
207 if set, are used. | 198 dns=((started_ports.dns, remote_ports.dns) |
208 Returns: | 199 if remote_ports.dns is not None else None) |
209 a forwarders.PortPairs instance used to create the forwarder. | 200 )) |
210 """ | |
211 local_http_port, local_https_port, local_dns_port = started_ports | |
212 return forwarders.PortPairs( | |
213 forwarders.PortPair(local_http_port, wpr_port_pairs.http.remote_port), | |
214 forwarders.PortPair(local_https_port, wpr_port_pairs.https.remote_port), | |
215 (forwarders.PortPair(local_dns_port, wpr_port_pairs.dns.remote_port) | |
216 if wpr_port_pairs.dns is not None else None)) | |
OLD | NEW |