Index: telemetry/telemetry/internal/platform/network_controller_backend.py |
diff --git a/telemetry/telemetry/internal/platform/network_controller_backend.py b/telemetry/telemetry/internal/platform/network_controller_backend.py |
index 57cc37d3679cb5f522fc8b86a85d668bc805eccd..5f4422e20b47e7dd731c5447688b8b7849dfd909 100644 |
--- a/telemetry/telemetry/internal/platform/network_controller_backend.py |
+++ b/telemetry/telemetry/internal/platform/network_controller_backend.py |
@@ -27,28 +27,118 @@ class NetworkControllerBackend(object): |
def __init__(self, platform_backend): |
self._platform_backend = platform_backend |
- self._browser_backend = None |
- self._active_replay_args = {} |
- self._pending_replay_args = {} |
+ self._wpr_mode = None |
+ self._netsim = None |
+ self._extra_wpr_args = None |
+ self._wpr_port_pairs = None |
+ self._archive_path = None |
+ self._make_javascript_deterministic = None |
self._forwarder = None |
self._wpr_server = None |
- def SetReplayArgs(self, archive_path, wpr_mode, netsim, extra_wpr_args, |
- make_javascript_deterministic=False): |
- """Save the arguments needed for replay. |
+ # TODO(perezju) Remove when SetReplayArgs is gone. |
+ self._pending_replay_args = None |
+ |
+ @property |
+ def is_open(self): |
+ return self._wpr_mode is not None |
+ |
+ @property |
+ def host_ip(self): |
+ return self._platform_backend.forwarder_factory.host_ip |
+ |
+ @property |
+ def wpr_mode(self): |
+ return self._wpr_mode |
- To make the settings effective, this call must be followed by a call |
- to UpdateReplay. |
+ @property |
+ def wpr_device_ports(self): |
+ try: |
+ return self._forwarder.port_pairs.remote_ports |
+ except AttributeError: |
+ return None |
+ |
+ @property |
+ def wpr_http_device_port(self): |
+ # TODO(perezju): Remove and switch clients to wpr_device_ports.http |
+ return self.wpr_device_ports.http |
+ |
+ @property |
+ def wpr_https_device_port(self): |
+ # TODO(perezju): Remove and switch clients to wpr_device_ports.https |
+ return self.wpr_device_ports.https |
+ |
+ def Open(self, wpr_mode, netsim, extra_wpr_args): |
+ """Configure and prepare target platform for network control. |
+ |
+ This may, e.g., install test certificates and perform any needed setup |
+ on the target platform. |
+ |
+ After network interactions are over, clients should call the Close method. |
+ |
+ Args: |
+ wpr_mode: a mode for web page replay; available modes are |
+ wpr_modes.WPR_OFF, wpr_modes.APPEND, wpr_modes.WPR_REPLAY, or |
+ wpr_modes.WPR_RECORD. |
+ netsim: a net_config string (e.g. 'dialup', '3g', 'dsl', 'cable', 'fios'), |
+ may be None. |
+ extra_wpr_args: an list of extra arguments for web page replay. |
+ """ |
+ assert not self.is_open, 'Network controller is already open' |
+ self._wpr_mode = wpr_mode |
+ self._netsim = netsim |
+ self._extra_wpr_args = extra_wpr_args |
+ |
+ # TODO(perezju): Determine correct ports for different platform backends, |
+ # and install test certificates if needed. |
+ self._wpr_port_pairs = forwarders.PortPairs( |
+ http=forwarders.PortPair(0, 0), |
+ https=forwarders.PortPair(0, 0), |
+ dns=forwarders.PortPair(0, 0)) |
+ |
+ def Close(self): |
+ """Undo changes in the target platform used for network control. |
+ |
+ Implicitly stops replay if currently active. |
+ """ |
+ # TODO(perezju): Uninstall test certificates if needed. |
+ self.StopReplay() |
+ self._make_javascript_deterministic = None |
+ self._archive_path = None |
+ self._wpr_port_pairs = None |
+ self._extra_wpr_args = None |
+ self._netsim = None |
+ self._wpr_mode = None |
+ |
+ def StartReplay(self, archive_path, make_javascript_deterministic=False): |
+ """Start web page replay from a given replay archive. |
+ |
+ Starts as needed, and reuses if possible, the replay server on the host and |
+ a forwarder from the host to the target platform. |
+ |
+ Implementation details |
+ ---------------------- |
+ |
+ The local host is where Telemetry is run. The remote is host where |
+ the target application is run. The local and remote hosts may be |
+ the same (e.g., testing a desktop browser) or different (e.g., testing |
+ an android browser). |
+ |
+ A replay server is started on the local host using the local ports, while |
+ a forwarder ties the local to the remote ports. |
+ |
+ Both local and remote ports may be zero. In that case they are determined |
+ by the replay server and the forwarder respectively. Setting dns to None |
+ disables DNS traffic. |
Args: |
archive_path: a path to a specific WPR archive. |
- wpr_mode: one of wpr_modes.WPR_OFF, wpr_modes.WPR_APPEND, |
- wpr_modes.WPR_REPLAY, or wpr_modes.WPR_RECORD. |
- netsim: a net_config string ('dialup', '3g', 'dsl', 'cable', or 'fios'). |
- extra_wpr_args: a list of additional replay args (or an empty list). |
make_javascript_deterministic: True if replay should inject a script |
to make JavaScript behave deterministically (e.g., override Date()). |
""" |
+ assert self.is_open, 'Network controller is not open' |
+ if self._wpr_mode == wpr_modes.WPR_OFF: |
+ return |
if not archive_path: |
# TODO(slamm, tonyg): Ideally, replay mode should be stopped when there is |
# no archive path. However, if the replay server already started, and |
@@ -57,160 +147,123 @@ class NetworkControllerBackend(object): |
# replay server forwards requests to it. (Chrome is configured to use |
# fixed ports fo all HTTP/HTTPS requests.) |
return |
- self._pending_replay_args = dict( |
- archive_path=archive_path, |
- wpr_mode=wpr_mode, |
- netsim=netsim, |
- extra_wpr_args=extra_wpr_args, |
- make_javascript_deterministic=make_javascript_deterministic) |
- # TODO(slamm): Update replay here when the browser_backend dependencies |
- # are moved to the platform. https://crbug.com/423962 |
- # |self._pending_replay_args| can be removed at that time. |
- |
- def UpdateReplay(self, browser_backend=None): |
- """Start or reuse Web Page Replay. |
- |
- UpdateReplay must be called after every call to SetReplayArgs. |
- |
- TODO(slamm): Update replay in SetReplayArgs once the browser_backend |
- dependencies move to platform. https://crbug.com/423962 |
- browser_backend properties used: |
- - Input: wpr_port_pairs |
- - Output: wpr_port_pairs (browser uses for --testing-fixed-* flags). |
- Args: |
- browser_backend: instance of telemetry.core.backends.browser_backend |
- """ |
- if not self._pending_replay_args: |
- # In some cases (e.g., unit tests), the browser is used without replay. |
- return |
- if self._pending_replay_args == self._active_replay_args: |
- return |
- |
- pending_archive_path = self._pending_replay_args['archive_path'] |
- |
- |
- |
- pending_wpr_mode = self._pending_replay_args['wpr_mode'] |
- if pending_wpr_mode == wpr_modes.WPR_OFF: |
- return |
- if (pending_wpr_mode == wpr_modes.WPR_REPLAY and |
- not os.path.exists(pending_archive_path)): |
- raise ArchiveDoesNotExistError( |
- 'Archive path does not exist: %s' % pending_archive_path) |
- if browser_backend: |
- self._browser_backend = browser_backend |
- self.StopReplay() # stop any forwarder too |
- wpr_port_pairs = self._browser_backend.wpr_port_pairs |
- else: |
- # If no browser_backend, then this is an update for an existing browser. |
- assert self._browser_backend |
- self._StopReplayOnly() # leave existing forwarder in place |
- wpr_port_pairs = self._forwarder.port_pairs |
- wpr_http_port = wpr_port_pairs.http.local_port |
- wpr_https_port = wpr_port_pairs.https.local_port |
- wpr_dns_port = (wpr_port_pairs.dns.local_port |
- if wpr_port_pairs.dns else None) |
- |
- archive_path = self._pending_replay_args['archive_path'] |
- wpr_mode = self._pending_replay_args['wpr_mode'] |
- netsim = self._pending_replay_args['netsim'] |
- extra_wpr_args = self._pending_replay_args['extra_wpr_args'] |
- make_javascript_deterministic = self._pending_replay_args[ |
- 'make_javascript_deterministic'] |
- |
- if wpr_mode == wpr_modes.WPR_OFF: |
- return |
- if (wpr_mode == wpr_modes.WPR_REPLAY and |
+ if (self._wpr_mode == wpr_modes.WPR_REPLAY and |
not os.path.exists(archive_path)): |
raise ArchiveDoesNotExistError( |
'Archive path does not exist: %s' % archive_path) |
+ if (self._wpr_server is not None and |
+ self._archive_path == archive_path and |
+ self._make_javascript_deterministic == make_javascript_deterministic): |
+ return # We may reuse the existing replay server. |
- wpr_args = _ReplayCommandLineArgs( |
- wpr_mode, netsim, extra_wpr_args, make_javascript_deterministic, |
- self._platform_backend.wpr_ca_cert_path) |
- self._wpr_server = self._ReplayServer( |
- archive_path, self._platform_backend.forwarder_factory.host_ip, |
- wpr_http_port, wpr_https_port, wpr_dns_port, wpr_args) |
- started_ports = self._wpr_server.StartServer() |
- |
- if not self._forwarder: |
- self._forwarder = self._platform_backend.forwarder_factory.Create( |
- _ForwarderPortPairs(started_ports, wpr_port_pairs)) |
- |
- self._active_replay_args = self._pending_replay_args |
- self._pending_replay_args = None |
- |
- def _ReplayServer( |
- self, archive_path, host_ip, http_port, https_port, dns_port, wpr_args): |
- return webpagereplay.ReplayServer( |
- archive_path, host_ip, http_port, https_port, dns_port, wpr_args) |
+ self._archive_path = archive_path |
+ self._make_javascript_deterministic = make_javascript_deterministic |
+ local_ports = self._StartReplayServer() |
+ self._StartForwarder(local_ports) |
def StopReplay(self): |
+ """Stop web page replay. |
+ |
+ Stops both the replay server and the forwarder if currently active. |
+ """ |
if self._forwarder: |
self._forwarder.Close() |
self._forwarder = None |
- self._StopReplayOnly() |
- |
- def _StopReplayOnly(self): |
+ self._StopReplayServer() |
+ |
+ def _StartReplayServer(self): |
+ """Start the replay server and return the started local_ports.""" |
+ self._StopReplayServer() # In case it was already running. |
+ local_ports = self._wpr_port_pairs.local_ports |
+ self._wpr_server = webpagereplay.ReplayServer( |
+ self._archive_path, |
+ self.host_ip, |
+ local_ports.http, |
+ local_ports.https, |
+ local_ports.dns, |
+ self._ReplayCommandLineArgs()) |
+ return self._wpr_server.StartServer() |
+ |
+ def _StopReplayServer(self): |
+ """Stop the replay server only.""" |
if self._wpr_server: |
self._wpr_server.StopServer() |
self._wpr_server = None |
- self._active_replay_args = {} |
- @property |
- def wpr_http_device_port(self): |
- if not self._forwarder or not self._forwarder.port_pairs.http: |
- return None |
- return self._forwarder.port_pairs.http.remote_port |
+ def _ReplayCommandLineArgs(self): |
+ wpr_args = list(self._extra_wpr_args) |
+ if self._netsim: |
+ wpr_args.append('--net=%s' % self._netsim) |
+ if self._wpr_mode == wpr_modes.WPR_APPEND: |
+ wpr_args.append('--append') |
+ elif self._wpr_mode == wpr_modes.WPR_RECORD: |
+ wpr_args.append('--record') |
+ if not self._make_javascript_deterministic: |
+ wpr_args.append('--inject_scripts=') |
+ if self._platform_backend.wpr_ca_cert_path: |
+ wpr_args.extend([ |
+ '--should_generate_certs', |
+ '--https_root_ca_cert_path=%s' |
+ % self._platform_backend.wpr_ca_cert_path]) |
+ return wpr_args |
+ |
+ def _StartForwarder(self, local_ports): |
+ """Start a forwarder from local_ports to the set WPR remote_ports.""" |
+ if self._forwarder is not None: |
+ if local_ports == self._forwarder.port_pairs.local_ports: |
+ return # Safe to reuse existing forwarder. |
+ self._forwarder.Close() |
+ self._forwarder = self._platform_backend.forwarder_factory.Create( |
+ forwarders.PortPairs.Zip(local_ports, |
+ self._wpr_port_pairs.remote_ports)) |
- @property |
- def wpr_https_device_port(self): |
- if not self._forwarder or not self._forwarder.port_pairs.https: |
- return None |
- return self._forwarder.port_pairs.https.remote_port |
- |
- |
-def _ReplayCommandLineArgs(wpr_mode, netsim, extra_wpr_args, |
- make_javascript_deterministic, wpr_ca_cert_path): |
- wpr_args = list(extra_wpr_args) |
- if netsim: |
- wpr_args.append('--net=%s' % netsim) |
- if wpr_mode == wpr_modes.WPR_APPEND: |
- wpr_args.append('--append') |
- elif wpr_mode == wpr_modes.WPR_RECORD: |
- wpr_args.append('--record') |
- if not make_javascript_deterministic: |
- wpr_args.append('--inject_scripts=') |
- if wpr_ca_cert_path: |
- wpr_args.extend([ |
- '--should_generate_certs', |
- '--https_root_ca_cert_path=%s' % wpr_ca_cert_path, |
- ]) |
- return wpr_args |
- |
- |
-def _ForwarderPortPairs(started_ports, wpr_port_pairs): |
- """Return PortPairs with started local ports and requested remote ports. |
- |
- The local host is where Telemetry is run. The remote is host where |
- the target application is run. The local and remote hosts may be |
- the same (e.g., testing a desktop browser) or different (e.g., testing |
- an android browser). |
- |
- The remote ports may be zero. In that case, the forwarder determines |
- the remote ports. |
- |
- Args: |
- started_ports: a tuple of of integer ports from which to forward: |
- (HTTP_PORT, HTTPS_PORT, DNS_PORT) # DNS_PORT may be None |
- wpr_port_pairs: a forwarders.PortPairs instance where the remote ports, |
- if set, are used. |
- Returns: |
- a forwarders.PortPairs instance used to create the forwarder. |
- """ |
- local_http_port, local_https_port, local_dns_port = started_ports |
- return forwarders.PortPairs( |
- forwarders.PortPair(local_http_port, wpr_port_pairs.http.remote_port), |
- forwarders.PortPair(local_https_port, wpr_port_pairs.https.remote_port), |
- (forwarders.PortPair(local_dns_port, wpr_port_pairs.dns.remote_port) |
- if wpr_port_pairs.dns is not None else None)) |
+ def SetReplayArgs(self, archive_path, wpr_mode, netsim, extra_wpr_args, |
+ make_javascript_deterministic=False): |
+ """DEPRECATED: New clients should not call this method.""" |
+ if not archive_path: |
+ return # Same as above. Keep an existing replay server running. |
+ self._pending_replay_args = dict( |
+ archive_path=archive_path, |
+ wpr_mode=wpr_mode, |
+ netsim=netsim, |
+ extra_wpr_args=extra_wpr_args, |
+ make_javascript_deterministic=make_javascript_deterministic) |
+ |
+ def UpdateReplay(self, browser_backend=None): |
+ """DEPRECATED: New clients should not call this method.""" |
+ if not self._pending_replay_args: |
+ # In some cases (e.g., unit tests), the browser is used without replay. |
+ return |
+ |
+ # TODO(perezju): Move code to figure out WPR port pairts out of browser |
+ # backends and into the Open method above. |
+ if browser_backend is None: |
+ # If no browser_backend, then this is an update for an existing browser. |
+ assert self.is_open and self._wpr_port_pairs is not None |
+ wpr_port_pairs = self._wpr_port_pairs |
+ may_reuse_session = ( |
+ self._pending_replay_args['wpr_mode'] in [wpr_modes.WPR_OFF, |
+ self._wpr_mode] and |
+ self._pending_replay_args['netsim'] == self._netsim and |
+ self._pending_replay_args['extra_wpr_args'] == self._extra_wpr_args) |
+ else: |
+ # Use WPR port pairs selected by the browser backend. |
+ wpr_port_pairs = browser_backend.wpr_port_pairs |
+ may_reuse_session = False |
+ |
+ if not may_reuse_session: |
+ self.Close() # In case it is already open. |
+ self.Open(self._pending_replay_args['wpr_mode'], |
+ self._pending_replay_args['netsim'], |
+ self._pending_replay_args['extra_wpr_args']) |
+ # Override port pairs with those chosen by the browser. |
+ self._wpr_port_pairs = wpr_port_pairs |
+ |
+ self.StartReplay(self._pending_replay_args['archive_path'], |
+ self._pending_replay_args['make_javascript_deterministic']) |
+ |
+ # Remember actual port pairs being used after defaults have been resolved. |
+ # Note: the forwarder would be None if WPR mode is set to off. |
+ if self._forwarder is not None: |
+ self._wpr_port_pairs = self._forwarder.port_pairs |
+ self._pending_replay_args = None |