Bug 1506912 - Raptor support for tp6 pageload on android geckoview; r=jmaher

Differential Revision: https://phabricator.services.mozilla.com/D14186

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Rob Wood 2018-12-14 07:19:59 +00:00
Родитель e1336d9a63
Коммит a63f5d5395
4 изменённых файлов: 139 добавлений и 49 удалений

Просмотреть файл

@ -10,7 +10,7 @@ playback_cls = {
}
def get_playback(config):
def get_playback(config, android_device=None):
tool_name = config.get('playback_tool', None)
if tool_name is None:
LOG.critical("playback_tool name not found in config")
@ -20,4 +20,4 @@ def get_playback(config):
return None
cls = playback_cls.get(tool_name)
return cls(config)
return cls(config, android_device)

Просмотреть файл

@ -11,8 +11,9 @@ from abc import ABCMeta, abstractmethod
class Playback(object):
__metaclass__ = ABCMeta
def __init__(self, config):
def __init__(self, config, android_device=None):
self.config = config
self.android_device = android_device
@abstractmethod
def download(self):

Просмотреть файл

@ -90,12 +90,13 @@ POLICIES_CONTENT_OFF = '''{
class Mitmproxy(Playback, Python3Virtualenv, TestingMixin, MercurialScript):
def __init__(self, config):
def __init__(self, config, android_device=None):
self.config = config
self.mitmproxy_proc = None
self.mitmdump_path = None
self.recordings = config.get('playback_recordings', None)
self.browser_path = config.get('binary', None)
self.android_device = android_device
# raptor_dir is where we will download all mitmproxy required files
# when running locally it comes from obj_path via mozharness/mach
@ -202,9 +203,15 @@ class Mitmproxy(Playback, Python3Virtualenv, TestingMixin, MercurialScript):
# for google chromium this is not necessary as chromium will be
# started with --ignore-certificate-errors cmd line arg
if self.config['app'] == "firefox":
# install the generated CA certificate into Firefox
self.install_mitmproxy_cert(self.mitmproxy_proc,
self.browser_path)
# install the generated CA certificate into Firefox desktop
self.install_mitmproxy_cert_desktop(self.mitmproxy_proc,
self.browser_path)
elif self.config['app'] == "geckoview":
# install the generated CA certificate into android geckoview
self.install_mitmproxy_cert_android(self.mitmproxy_proc,
self.browser_path)
else:
return
def start(self):
# if on windows, the mitmdump_path was already set when creating py3 env
@ -223,19 +230,14 @@ class Mitmproxy(Playback, Python3Virtualenv, TestingMixin, MercurialScript):
self.turn_off_browser_proxy()
return
def install_mitmproxy_cert(self, mitmproxy_proc, browser_path):
def install_mitmproxy_cert_desktop(self, mitmproxy_proc, browser_path):
"""Install the CA certificate generated by mitmproxy, into Firefox
1. Create a directory called distribution in the same directory as the Firefox executable
2. Create a file called policies.json with:
{
"policies": {
"certificates": {
"Install": ["FULL_PATH_TO_CERT"]
}
}
}
1. Create a dir called 'distribution' in the same directory as the Firefox executable
2. Create the policies.json file inside that folder; which points to the certificate
location, and turns on the the browser proxy settings
"""
LOG.info("Installing mitmproxy CA certficate into Firefox")
# browser_path is the exe, we want the folder
self.policies_dir = os.path.dirname(browser_path)
# on macosx we need to remove the last folders 'MacOS'
@ -262,11 +264,68 @@ class Mitmproxy(Playback, Python3Virtualenv, TestingMixin, MercurialScript):
'host': self.config['host']})
# cannot continue if failed to add CA cert to Firefox, need to check
if not self.is_mitmproxy_cert_installed():
LOG.error('Aborting: failed to install mitmproxy CA cert into Firefox')
if not self.is_mitmproxy_cert_installed_desktop():
LOG.error('Aborting: failed to install mitmproxy CA cert into Firefox desktop')
self.stop_mitmproxy_playback()
sys.exit()
def install_mitmproxy_cert_android(self, mitmproxy_proc, browser_path):
"""Install the CA certificate generated by mitmproxy, into geckoview android
1. Get the `certutil` tool.
2. Create an NSS certificate database in the geckoview browser profile dir.
`certutil -N -d sql:<path to profile> --empty-password`
3. Import the mitmproxy certificate into the database.
`certutil -A -d sql:<path to profile> -n "some nickname" -t TC,, -a -i <path to CA.pem>`
"""
# get the certutil tool
if self.config.get("obj_path", None) is not None:
# when running locally, it is found in the Firefox desktop build (..obj../dist/bin)
self.certutil = os.path.join(self.config['obj_path'], 'dist', 'bin')
else:
# in production it is already downloaded on the host automation machines via hostutils
# self.certutil = TODO
LOG.info("TODO: where is the path in production to certutil/hostutils?")
bin_suffix = mozinfo.info.get('bin_suffix', '')
self.certutil = os.path.join(self.certutil, "certutil" + bin_suffix)
if os.path.isfile(self.certutil):
LOG.info("certutil is found at: %s" % self.certutil)
else:
LOG.critical("unable to find certutil at %s" % self.certutil)
# DEFAULT_CERT_PATH has local path and name of mitmproxy cert i.e.
# /home/cltbld/.mitmproxy/mitmproxy-ca-cert.cer
self.local_cert_path = DEFAULT_CERT_PATH
# create cert db
param1 = "sql:%s/" % self.config['local_profile_dir']
command = [self.certutil, '-N', '-d', param1, '--empty-password']
LOG.info("creating nss cert database using command: %s" % ' '.join(command))
cmd_proc = subprocess.Popen(command, env=os.environ.copy())
time.sleep(3)
cmd_terminated = cmd_proc.poll()
if cmd_terminated is None: # None value indicates process hasn't terminated
LOG.critical("nss cert db creation command failed to complete")
# import mitmproxy cert into the db
command = [self.certutil, '-A', '-d', param1, '-n',
'mitmproxy-cert', '-t', 'TC,,', '-a', '-i', self.local_cert_path]
LOG.info("importing mitmproxy cert into db using command: %s" % ' '.join(command))
cmd_proc = subprocess.Popen(command, env=os.environ.copy())
time.sleep(3)
cmd_terminated = cmd_proc.poll()
if cmd_terminated is None: # None value indicates process hasn't terminated
LOG.critical("command to import mitmproxy cert into cert db failed to complete")
# cannot continue if failed to add CA cert to Firefox, need to check
# if not self.is_mitmproxy_cert_installed_android():
# LOG.error('Aborting: failed to install mitmproxy CA cert into Firefox')
# self.stop_mitmproxy_playback()
# sys.exit()
def write_policies_json(self, location, policies_content):
policies_file = os.path.join(location, "policies.json")
LOG.info("writing: %s" % policies_file)
@ -281,7 +340,7 @@ class Mitmproxy(Playback, Python3Virtualenv, TestingMixin, MercurialScript):
with open(policies_file, 'r') as fd:
return fd.read()
def is_mitmproxy_cert_installed(self):
def is_mitmproxy_cert_installed_desktop(self):
"""Verify mitmxproy CA cert was added to Firefox"""
try:
# read autoconfig file, confirm mitmproxy cert is in there
@ -300,6 +359,11 @@ class Mitmproxy(Playback, Python3Virtualenv, TestingMixin, MercurialScript):
return False
return True
def is_mitmproxy_cert_installed_android(self):
"""Verify mitmxproy CA cert was added to Firefox"""
LOG.info("* TODO: verify cert is installed on android")
return False
def start_mitmproxy_playback(self,
mitmdump_path,
mitmproxy_recording_path,

Просмотреть файл

@ -28,8 +28,6 @@ else:
mozharness_dir = os.path.join(here, '../../../mozharness')
sys.path.insert(0, mozharness_dir)
from mozharness.mozilla.firefox.autoconfig import _cfg_file_path, _autoconfig_path
webext_dir = os.path.join(os.path.dirname(here), 'webext')
sys.path.insert(0, here)
@ -50,16 +48,6 @@ from results import RaptorResultsHandler
from gecko_profile import GeckoProfile
def remove_autoconfig(binary):
bindir = os.path.dirname(binary)
mozillacfg = _cfg_file_path(bindir)
if os.path.isfile(mozillacfg):
os.unlink(mozillacfg)
autoconfig = _autoconfig_path(bindir)
if os.path.isfile(autoconfig):
os.unlink(autoconfig)
class Raptor(object):
"""Container class for Raptor"""
@ -86,6 +74,7 @@ class Raptor(object):
self.benchmark = None
self.gecko_profiler = None
self.post_startup_delay = 30000
self.device = None
# debug mode is currently only supported when running locally
self.debug_mode = debug_mode if self.config['run_local'] else False
@ -101,9 +90,6 @@ class Raptor(object):
self.profile = create_profile('firefox')
else:
self.profile = create_profile(self.config['app'])
# Clear any existing mozilla.cfg file to prevent earlier
# runs using mitmproxy from interfering with settings.
remove_autoconfig(binary)
# Merge in base profiles
with open(os.path.join(self.profile_data_dir, 'profiles.json'), 'r') as fh:
@ -114,6 +100,9 @@ class Raptor(object):
self.log.info("Merging profile: {}".format(path))
self.profile.merge(path)
# add profile dir to our config
self.config['local_profile_dir'] = self.profile.profile
# create results holder
self.results_handler = RaptorResultsHandler()
@ -177,6 +166,13 @@ class Raptor(object):
if test.get('type') == "benchmark":
self.benchmark = Benchmark(self.config, test)
benchmark_port = int(self.benchmark.port)
# for android we must make the benchmarks server available to the device
if self.config['app'] == "geckoview" and self.config['host'] \
in ('localhost', '127.0.0.1'):
self.log.info("making the raptor benchmarks server port available to device")
_tcp_port = "tcp:%s" % benchmark_port
self.device.create_socket_connection('reverse', _tcp_port, _tcp_port)
else:
benchmark_port = 0
@ -188,12 +184,6 @@ class Raptor(object):
b_port=benchmark_port,
debug_mode=1 if self.debug_mode else 0)
# for android we must make the benchmarks server available to the device
if self.config['app'] == "geckoview" and self.config['host'] in ('localhost', '127.0.0.1'):
self.log.info("making the raptor benchmarks server port available to device")
_tcp_port = "tcp:%s" % benchmark_port
self.device.create_socket_connection('reverse', _tcp_port, _tcp_port)
# must intall raptor addon each time because we dynamically update some content
# note: for chrome the addon is just a list of paths that ultimately are added
# to the chromium command line '--load-extension' argument
@ -213,11 +203,30 @@ class Raptor(object):
if self.config['app'] in ["firefox", "geckoview"]:
webext_id = self.profile.addons.addon_details(raptor_webext)['id']
# for android/geckoview, create a top-level raptor folder on the device
# sdcard; if it already exists remove it so we start fresh each time
if self.config['app'] == "geckoview":
self.device_raptor_dir = "/sdcard/raptor"
self.config['device_raptor_dir'] = self.device_raptor_dir
if self.device.is_dir(self.device_raptor_dir):
self.log.info("deleting existing device raptor dir: %s" % self.device_raptor_dir)
self.device.rm(self.device_raptor_dir, recursive=True)
self.log.info("creating raptor folder on sdcard: %s" % self.device_raptor_dir)
self.device.mkdir(self.device_raptor_dir)
self.device.chmod(self.device_raptor_dir, recursive=True)
# some tests require tools to playback the test pages
if test.get('playback', None) is not None:
self.get_playback_config(test)
# startup the playback tool
self.playback = get_playback(self.config)
self.playback = get_playback(self.config, self.device)
# for android we must make the playback server available to the device
if self.config['app'] == "geckoview" and self.config['host'] \
in ('localhost', '127.0.0.1'):
self.log.info("making the raptor playback server port available to device")
_tcp_port = "tcp:8080"
self.device.create_socket_connection('reverse', _tcp_port, _tcp_port)
if self.config['app'] in ("geckoview", "firefox") and \
self.config['host'] not in ('localhost', '127.0.0.1'):
@ -230,19 +239,36 @@ class Raptor(object):
with open(userjspath, 'w') as userjsfile:
userjsfile.writelines(prefs)
# for geckoview we must copy the profile onto the device and set perms
# for geckoview/android pageload playback we can't use a policy to turn on the
# proxy; we need to set prefs instead; note that the 'host' may be different
# than '127.0.0.1' so we must set the prefs accordingly
if self.config['app'] == "geckoview" and test.get('playback', None) is not None:
self.log.info("setting profile prefs to turn on the geckoview browser proxy")
no_proxies_on = "localhost, 127.0.0.1, %s" % self.config['host']
proxy_prefs = {}
proxy_prefs["network.proxy.type"] = 1
proxy_prefs["network.proxy.http"] = self.config['host']
proxy_prefs["network.proxy.http_port"] = 8080
proxy_prefs["network.proxy.ssl"] = self.config['host']
proxy_prefs["network.proxy.ssl_port"] = 8080
proxy_prefs["network.proxy.no_proxies_on"] = no_proxies_on
self.profile.set_preferences(proxy_prefs)
# now some final settings, and then startup of the browser under test
if self.config['app'] == "geckoview":
# for android/geckoview we must copy the profile onto the device and set perms
if not self.device.is_app_installed(self.config['binary']):
raise Exception('%s is not installed' % self.config['binary'])
self.log.info("copying firefox profile onto the android device")
self.device_profile = "/sdcard/raptor-profile"
self.device_profile = os.path.join(self.device_raptor_dir, "profile")
if self.device.is_dir(self.device_profile):
self.log.info("deleting existing device profile folder: %s" % self.device_profile)
self.device.rm(self.device_profile, recursive=True)
self.log.info("creating profile folder on device: %s" % self.device_profile)
self.device.mkdir(self.device_profile)
self.log.info("copying firefox profile onto the device")
self.log.info("note: the profile folder being copied is: %s" % self.profile.profile)
self.log.info('the adb push cmd copies that profile dir to a new temp dir before copy')
self.device.push(self.profile.profile, self.device_profile)
self.log.info("setting permisions to profile dir on the device")
self.device.chmod(self.device_profile, recursive=True)
# now start the geckoview app
@ -425,7 +451,6 @@ class Raptor(object):
self.device.remove_socket_connections('reverse')
else:
pass
remove_autoconfig(self.config['binary'])
self.log.info("finished")