コード例 #1
0
    def _find_iso(self):
        identifier = self.options.pversion
        build = self.options.pbuild

        if identifier:
            identifier = str(identifier)

        if build:
            build = str(build)

        # BotD mode - All constants hardcoded here for now.
        if build and build.lower() in ('bod', 'botd', 'auto'):
            assert self.options.product, "A product identifier needs to be specified for BotD mode."

            if self.options.phf:
                LOG.warning("Hotfix parameter ignored in BotD mode.")

            with RestInterface(address=BOTD_HOST, proto='http') as restifc:
                r = restifc.api
                LOG.info('Querying build of the day...')
                response = r.get(BOTD_PATH)
                botd_product = self.options.product.upper()

                if self.options.phf:
                    botd_branch = "%s-%s" % (identifier, self.options.phf)
                else:
                    botd_branch = identifier

                build = response.products[botd_product].branches[botd_branch].build_id
                LOG.info("Found: %s", build)

        if self.options.image:
            filename = self.options.image.strip()
        else:
            base_build = None if self.options.phf else build
            filename = cm.isofile(identifier=identifier, build=base_build,
                                  product=self.options.product,
                                  root=self.options.build_path)

        if self.options.hfimage:
            hfiso = self.options.hfimage.strip()
        elif self.options.phf:
            hfiso = cm.isofile(identifier=identifier, build=build,
                               hotfix=self.options.phf,
                               product=self.options.product,
                               root=self.options.build_path)
        else:
            hfiso = None

        return filename, hfiso
コード例 #2
0
ファイル: server.py プロジェクト: pombredanne/f5test2
def bvt_emdeviso_post():
    """Handles requests from BIGIP teams.

    All the logic needed to translate the user input into what makes sense to
    us happens right here.
    """
    CONFIG_FILE = 'config/shared/web_emdeviso_request.yaml'

    # For people who don't like to set the application/json header.
    data = AttrDict(json.load(bottle.request.body))
    data._referer = bottle.request.url

    our_config = AttrDict(yaml.load(open(get_harness('em')).read()))

    # Prepare placeholders in our config
    our_config.update({'stages': {'main': {'setup': {'install': {'parameters': {}}}}}})
    our_config.update({'plugins': {'email': {'to': [], 'variables': {}}}})

    plugins = our_config.plugins

    # Append submitter's email to recipient list
    if data.get('email'):
        plugins.email.to.append(data['email'])
    plugins.email.to.extend(CONFIG.web.recipients)

    # Set version and build in the install stage
    v = None
    if data.get('iso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom iso'] = data['iso']
        v = version_from_metadata(data['iso'])

    if data.get('hfiso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom hf iso'] = data['hfiso']
        v = version_from_metadata(data['hfiso'])
        # Find the RTM ISO that goes with this HF image.
        if not data.get('iso'):
            params['custom iso'] = isofile(v.version, product=str(v.product))

    args = []
    args[:] = NOSETESTS_ARGS

    args.append('--tc-file={VENV}/%s' % CONFIG_FILE)
    args.append('--tc=stages.enabled:1')
    args.append('--eval-attr=rank > 0 and rank < 11')
    args.append('--with-email')
    #args.append('--with-bvtinfo')
    args.append('--with-irack')
    args.append('{VENV}/%s' % CONFIG.paths.em)

    result = nosetests.delay(our_config, args, data)  # @UndefinedVariable
    link = app.router.build('status', task_id=result.id)
    return dict(status=result.status, id=result.id, link=link)
コード例 #3
0
    def by_api(self):
        o = self.options
        timeout = o.timeout

        identifier = self.options.pversion
        build = self.options.pbuild

        if identifier:
            identifier = str(identifier)
            if build:
                build = str(build)

        if self.options.image:
            filename = self.options.image
        else:
            filename = cm.isofile(identifier=identifier, build=build,
                                  product=o.product, root=o.build_path)

        if self.options.hfimage:
            hfiso = self.options.hfimage
        elif self.options.phf:
            hfiso = cm.isofile(identifier=identifier, build=build,
                               hotfix=o.phf,
                               product=o.product,
                               root=o.build_path)
        else:
            hfiso = None

        iso_version = cm.version_from_metadata(filename)
        if (iso_version.product.is_bigip and iso_version >= 'bigip 10.0.0' or
                iso_version.product.is_em and iso_version >= 'em 2.0.0'):
            raise VersionNotSupported('Only legacy images supported through EMInstaller.')

        emifc = EMInterface(device=o.device, address=o.address,
                            username=o.admin_username, password=o.admin_password)
        emifc.open()

        with SSHInterface(device=o.device, address=o.address,
                          username=o.root_username, password=o.root_password,
                          port=self.options.ssh_port) as ssh:
            status = SCMD.ssh.get_prompt(ifc=ssh)
            if status in ['LICENSE EXPIRED', 'REACTIVATE LICENSE']:
                SCMD.ssh.relicense(ifc=ssh)
            elif status in ['LICENSE INOPERATIVE', 'NO LICENSE']:
                raise MacroError('Device at %s needs to be licensed.' % ssh)

            reachable_devices = [x['access_address'] for x in
                                 EMSQL.device.get_reachable_devices(ifc=ssh)]
            for x in self.devices:
                x.address = net.resolv(x.address)

            to_discover = [x for x in self.devices
                           if x.address not in reachable_devices]

            if to_discover:
                uid = EMAPI.device.discover(to_discover, ifc=emifc)
                task = EMSQL.device.GetDiscoveryTask(uid, ifc=ssh) \
                            .run_wait(lambda x: x['status'] != 'started',
                                      timeout=timeout,
                                      progress_cb=lambda x: 'discovery: %d%%' % x.progress_percent)
                assert task['error_count'] == 0, 'Discovery failed: %s' % task
            targets = []
            for device in self.devices:
                mgmtip = device.address
                version = EMSQL.device.get_device_version(mgmtip, ifc=ssh)
                if not o.essential_config and abs(iso_version) < abs(version):
                    LOG.warning('Enforcing --esential-config')
                    o.essential_config = True

                device_info = EMSQL.device.get_device_info(mgmtip, ifc=ssh)
                active_slot = EMSQL.device.get_device_active_slot(mgmtip,
                                                                  ifc=ssh)
                targets.append(dict(device_uid=device_info['uid'],
                                    slot_uid=active_slot['uid']))

            image_list = EMSQL.software.get_image_list(ifc=ssh)
            if iso_version not in image_list:
                base = os.path.basename(filename)
                destination = '%s.%d' % (os.path.join(SHARED_TMP, base), os.getpid())
                LOG.info('Importing base iso %s', base)
                SCMD.ssh.scp_put(device=o.device, address=o.address,
                                 destination=destination,
                                 username=self.options.root_username,
                                 password=self.options.root_password,
                                 port=self.options.ssh_port,
                                 source=filename, nokex=False)

                imuid = EMAPI.software.import_image(destination, ifc=emifc)
            else:
                imuid = image_list[iso_version]
                LOG.info('Image already imported: %d', imuid)

            if hfiso:
                hf_list = EMSQL.software.get_hotfix_list(ifc=ssh)
                hfiso_version = cm.version_from_metadata(hfiso)
                if hfiso_version not in hf_list:
                    hfbase = os.path.basename(hfiso)
                    destination = '%s.%d' % (os.path.join(SHARED_TMP, hfbase), os.getpid())
                    LOG.info('Importing hotfix iso %s', hfbase)
                    SCMD.ssh.scp_put(device=o.device, address=o.address,
                                     destination=destination,
                                     username=self.options.root_username,
                                     password=self.options.root_password,
                                     port=self.options.ssh_port,
                                     source=hfiso, nokex=False)
                    hfuid = EMAPI.software.import_image(destination, ifc=emifc)
                else:
                    hfuid = hf_list[hfiso_version]
            else:
                hfuid = None

            EMSQL.software.get_hotfix_list(ifc=ssh)

            EMSQL.device.CountActiveTasks(ifc=ssh) \
                        .run_wait(lambda x: x == 0, timeout=timeout,
                                  progress_cb=lambda x: 'waiting for other tasks')

            LOG.info('Installing %s...', iso_version)
            ret = EMAPI.software.install_image(targets, imuid, hfuid, o, ifc=emifc)
            ret = EMSQL.software.GetInstallationTask(ret['uid'], ifc=ssh).\
                run_wait(lambda x: x['status'] != 'started',
                         progress_cb=lambda x: 'install: %d%%' % x.progress_percent,
                         timeout=o.timeout)

        LOG.info('Deleting %d device(s)...', len(targets))
        EMAPI.device.delete(uids=[x['device_uid'] for x in targets], ifc=emifc)
        emifc.close()

        messages = []
        for d in ret['details']:
            if int(d['error_code']):
                messages.append("%(display_device_address)s:%(error_message)s" % d)
            if int(d['hf_error_code'] or 0):
                messages.append("%(display_device_address)s:%(hf_error_message)s" % d)
        if messages:
            raise InstallFailed('Install did not succeed: %s' %
                                ', '.join(messages))

        self.has_essential_config = o.essential_config
        return ret
コード例 #4
0
ファイル: emdeviso.py プロジェクト: xiaotdl/nosest
def bvt_emdeviso_post():
    """Handles requests from BIGIP teams.

    All the logic needed to translate the user input into what makes sense to
    us happens right here.
    """
    LOG.info("EMDEVISO: Called")
    CONFIG_FILE = 'config/shared/web_emdeviso_request.yaml'

    # For people who don't like to set the application/json header.
    data = AttrDict(json.load(bottle.request.body))
    LOG.info("EMDEVISO: POST Request: " + str(data))
    data._referer = bottle.request.url

    our_config = AttrDict(yaml.load(open(get_harness('em')).read()))

    # Prepare placeholders in our config
    our_config.update(
        {'stages': {
            'main': {
                'setup': {
                    'install': {
                        'parameters': {}
                    }
                }
            }
        }})
    our_config.update({'plugins': {'email': {'to': [], 'variables': {}}}})

    plugins = our_config.plugins

    # Append submitter's email to recipient list
    if data.get('email'):
        plugins.email.to.append(data['email'])
    plugins.email.to.extend(CONFIG.web.recipients)

    # Set version and build in the install stage
    v = None
    if data.get('iso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom iso'] = data['iso']
        v = version_from_metadata(data['iso'])

    if data.get('hfiso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom hf iso'] = data['hfiso']
        v = version_from_metadata(data['hfiso'])
        # Find the RTM ISO that goes with this HF image.
        if not data.get('iso'):
            params['custom iso'] = isofile(v.version, product=str(v.product))

    args = []
    args[:] = NOSETESTS_ARGS

    args.append('--tc-file={VENV}/%s' % CONFIG_FILE)
    args.append('--tc=stages.enabled:1')
    args.append('--eval-attr=rank > 0 and rank < 11')
    args.append('--with-email')
    # args.append('--with-bvtinfo')
    args.append('--with-irack')
    args.append('{VENV}/%s' % CONFIG.paths.em)
    LOG.info("EMDEVISO: Nose Args: " + str(args))
    LOG.info("EMDEVISO: our_config: " + str(our_config))

    result = nosetests.delay(our_config, args, data)  # @UndefinedVariable
    link = common_app.router.build('status', task_id=result.id)
    emdeviso_result = dict(status=result.status, id=result.id, link=link)
    LOG.info("EMDEVISO: Result: " + str(emdeviso_result))
    return emdeviso_result
コード例 #5
0
    def _find_iso(self):
        identifier = self.options.pversion
        build = self.options.pbuild

        if identifier:
            identifier = str(identifier)

        if build:
            build = str(build)

        # BotD mode - All constants hardcoded here for now.
        if build and build.lower() in ('bod', 'botd', 'auto'):
            assert self.options.product, "A product identifier needs to be specified for BotD mode."

            if self.options.phf:
                LOG.warning("Hotfix parameter ignored in BotD mode.")

            with RestInterface(address=BOTD_HOST) as restifc:
                r = restifc.api
                LOG.info('Querying for build of the day...')
                response = r.get(BOTD_PATH)
                LOG.debug("BOTD response: " + str(response))
                botd_product = self.options.product.upper()

                if self.options.phf:
                    botd_branch = "%s-%s" % (identifier, self.options.phf)
                else:
                    botd_branch = identifier

                LOG.info("Looking for the BOTD for Product=" + botd_product +
                         ", Branch=" + botd_branch)
                # Make sure data actually exists before trying to use it
                if response and response.products[botd_product] and\
                   response.products[botd_product].branches[botd_branch] and\
                   response.products[botd_product].branches[botd_branch].build_id:
                    build = response.products[botd_product].branches[botd_branch].build_id
                    LOG.info("Found BOTD: %s", build)
                else:
                    LOG.info("BOTD info is not available, defaulting to the highest build number.")
                    # Let the call to cm.isofile() find the latest one
                    build = None

        if self.options.image:
            filename = self.options.image.strip()
        else:
            # Don't change this logic without considering the BOTD logic above
            base_build = None if self.options.phf else build
            filename = cm.isofile(identifier=identifier, build=base_build,
                                  product=self.options.product,
                                  root=self.options.build_path)

        if self.options.hfimage:
            hfiso = self.options.hfimage.strip()
        elif self.options.phf:
            hfiso = cm.isofile(identifier=identifier, build=build,
                               hotfix=self.options.phf,
                               product=self.options.product,
                               root=self.options.build_path)
        else:
            hfiso = None

        return filename, hfiso
コード例 #6
0
ファイル: server.py プロジェクト: pombredanne/f5test2
def bvt_deviso_post():
    """Handles requests from Dev team for user builds ISOs.
    """
    # BVTINFO_PROJECT_PATTERN = '(\D+)?(\d+\.\d+\.\d+)-?(hf\d+)?'
    DEFAULT_SUITE = 'bvt'
    SUITES = {'bvt': '%s/' % CONFIG.paths.current,
              'dev': '%s/cloud/external/devtest_wrapper.py' % CONFIG.paths.current,
              'dev-cloud': '%s/cloud/external/restservicebus.py' % CONFIG.paths.current
              }
    CONFIG_FILE = 'config/shared/web_deviso_request.yaml'

    # For people who don't like to set the application/json header.
    data = AttrDict(json.load(bottle.request.body))
    # data = bottle.request.json
    data._referer = bottle.request.url

    our_config = AttrDict(yaml.load(open(get_harness('bigiq')).read()))

    # Prepare placeholders in our config
    our_config.update({'stages': {'main': {'setup': {'install': {'parameters': {}}}}}})
    our_config.update({'stages': {'main': {'setup': {'install-bigips': {'parameters': {}}}}}})
    our_config.update({'plugins': {'email': {'to': [], 'variables': {}}}})

    plugins = our_config.plugins
    # Append submitter's email to recipient list
    if data.get('email'):
        plugins.email.to.append(data['email'])
    plugins.email.to.extend(CONFIG.web.recipients)

    # Set version and build in the install stage
    v = None
    if data.get('iso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom iso'] = data['iso']
        v = version_from_metadata(data['iso'])

    if data.get('hfiso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom hf iso'] = data['hfiso']
        v = version_from_metadata(data['hfiso'])
        # Find the RTM ISO that goes with this HF image.
        if not data.get('iso'):
            params['custom iso'] = isofile(v.version, product=str(v.product))

    args = []
    args[:] = NOSETESTS_ARGS

    rank = Literal('rank')
    expr = (rank > Literal(0)) & (rank < Literal(11))
    # Include all migrated tests, example: functional/standalone/security/migrated/...
    # Assumption is that all tests are rank=505
    expr |= rank == Literal(505)
    # Only Greenflash tests have extended attributes
    if v is None or v >= 'bigiq 4.5':
        # build hamode argument
        if data.ha:
            hamode = Literal('hamode')
            expr2 = Or()
            for x in data.ha:
                if x != 'standalone':
                    expr2 += [In(String(x.upper()), hamode)]
            if 'standalone' in data.ha:
                expr &= (~hamode | expr2)
            else:
                expr &= hamode & expr2

        if data.ui:
            uimode = Literal('uimode')
            if data.ui == 'api':
                expr &= ~uimode
            elif data.ui == 'ui':
                expr &= uimode & (uimode > Literal(0))
            else:
                raise ValueError('Unknown value {}'.format(data.ui))

        if data.module:
            module = Literal('module')
            expr2 = Or()
            for x in data.module:
                expr2 += [In(String(x.upper()), module)]
            expr &= (module & expr2)

    args.append('--tc-file={VENV}/%s' % CONFIG_FILE)

    # Default is our BVT suite.
    if v:
        suite = os.path.join(CONFIG.suites.root, CONFIG.suites[v.version])
    else:
        suite = SUITES[data.get('suite', DEFAULT_SUITE)]
    args.append('--tc=stages.enabled:1')
    # XXX: No quotes around the long argument value!
    args.append('--eval-attr={}'.format(str(expr)))
    args.append('--with-email')
    # args.append('--collect-only')
    args.append('--with-irack')
    args.append('{VENV}/%s' % suite)

    v = plugins.email.variables
    v.args = args
    v.iso = data.iso
    v.module = data.module

    result = nosetests.delay(our_config, args, data)  # @UndefinedVariable
    link = app.router.build('status', task_id=result.id)
    return dict(status=result.status, id=result.id, link=link)
コード例 #7
0
def bvt_deviso_post():
    """ Handles requests from Dev team for user builds ISOs.

        Input POST request looks similar to this:

    { '_referer': 'http://shiraz/internaltest',
    u 'module': [u 'access', u 'adc', u 'afm', u 'asm', u 'avr', u 'cloud', u 'device', u 'system', u 'platform'],
    u 'bigip_v': u '12.0.0',
    u 'hfiso': u '/path/to/hf.iso',
    u 'iso': u '/path/to/base.iso',
    u 'custom iso': u'/path/to/custom.iso',
    u 'custom hf iso': u'/path/to/custom-hf.iso',
    u 'ui': False,
    u 'testruntype': u 'biq-standard-bvt',
    u 'ha': [u 'standalone'],
    u 'email': u '*****@*****.**'    }
    """
    LOG.info("DEVISO: Called")
    # BVTINFO_PROJECT_PATTERN = '(\D+)?(\d+\.\d+\.\d+)-?(hf\d+)?'
    DEFAULT_SUITE = 'bvt'
    SUITES = {
        'bvt': '%s/' % CONFIG.paths.current,
        'dev': '%s/cloud/external/devtest_wrapper.py' % CONFIG.paths.current,
        'dev-cloud':
        '%s/cloud/external/restservicebus.py' % CONFIG.paths.current
    }
    CONFIG_FILE = 'config/shared/web_deviso_request.yaml'

    BIGIP_V = CONFIG.bigip_versions
    AUTOMATION_RUN_TYPES = CONFIG.automation_run_types

    # For people who don't like to set the application/json header.
    data = AttrDict(json.load(bottle.request.body))
    # data = bottle.request.json
    LOG.info("DEVISO: POST Request: " + str(data))
    data._referer = bottle.request.url

    our_config = AttrDict(yaml.load(open(get_harness('bigiq')).read()))

    # Prepare placeholders in our config
    our_config.update(
        {'stages': {
            'main': {
                'setup': {
                    'install': {
                        'parameters': {}
                    }
                }
            }
        }})
    our_config.update({
        'stages': {
            'main': {
                'setup': {
                    'install-bigips': {
                        'parameters': {}
                    }
                }
            }
        }
    })
    our_config.update({'plugins': {'email': {'to': [], 'variables': {}}}})

    plugins = our_config.plugins
    # Append submitter's email to recipient list
    if data.get('email'):
        plugins.email.to.append(data['email'])
    plugins.email.to.extend(CONFIG.web.recipients)

    # Set BIGIP version config
    if data.get('bigip_v') in BIGIP_V:
        bigip_cfg = BIGIP_V[data['bigip_v']]
    else:
        bigip_cfg = BIGIP_V['default']

    # If a custom BIG-IP Base is specified, then do NOT append this .yaml
    if data.get('custom_bigip_iso') is None:
        our_config.setdefault('$extends', []).append(bigip_cfg)

    # Set BIG-IQ version and build in the install stage
    v = None
    if data.get('iso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom iso'] = data['iso']
        v = version_from_metadata(data['iso'])

    if data.get('hfiso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom hf iso'] = data['hfiso']
        v = version_from_metadata(data['hfiso'])
        # Find the RTM ISO that goes with this HF image.
        if not data.get('iso'):
            params['custom iso'] = isofile(v.version, product=str(v.product))

    # Set the BIG-IP version and build in the install stage, if it was
    # specified in the POST request.
    if data.get('custom_bigip_iso'):
        params = our_config.stages.main.setup['install-bigips'].parameters
        params['custom iso'] = data['custom_bigip_iso']
        # Only append BIG-IP HF info if a Base was specified
        if data.get('custom_bigip_hf_iso'):
            params = our_config.stages.main.setup['install-bigips'].parameters
            params['custom hf iso'] = data['custom_bigip_hf_iso']

    args = []
    args[:] = NOSETESTS_ARGS

    # Set the NOSE rank string based on the automation type
    expr = Literal(AUTOMATION_RUN_TYPES[data['testruntype']])
    # Only Greenflash tests have extended attributes
    if v is None or v >= 'bigiq 4.5':
        # build hamode argument
        if data.ha:
            hamode = Literal('hamode')
            expr2 = Or()
            for x in data.ha:
                if x != 'standalone':
                    expr2 += [In(String(x.upper()), hamode)]
            if 'standalone' in data.ha:
                expr &= (~hamode | expr2)
            else:
                expr &= hamode & expr2

        if data.ui:
            uimode = Literal('uimode')
            if data.ui == 'api':
                expr &= ~uimode
            elif data.ui == 'ui':
                expr &= uimode & (uimode > Literal(0))
            else:
                raise ValueError('Unknown value {}'.format(data.ui))

        if data.module:
            module = Literal('module')
            expr2 = Or()
            for x in data.module:
                expr2 += [In(String(x.upper()), module)]
            expr &= (module & expr2)

    args.append('--tc-file={VENV}/%s' % CONFIG_FILE)

    # Default is our BVT suite.
    if v:
        suite = os.path.join(CONFIG.suites.root, CONFIG.suites[v.version])
    else:
        suite = SUITES[data.get('suite', DEFAULT_SUITE)]
    args.append('--tc=stages.enabled:1')
    # XXX: No quotes around the long argument value!
    args.append('--eval-attr={}'.format(str(expr)))
    args.append('--with-email')
    # args.append('--collect-only')
    args.append('--with-irack')
    args.append('{VENV}/%s' % suite)

    v = plugins.email.variables
    v.args = args
    v.iso = data.iso
    v.module = data.module
    LOG.info("DEVISO: Nose Args: " + str(v))
    LOG.info("DEVISO: our_config: " + str(our_config))

    result = nosetests.delay(our_config, args, data)  # @UndefinedVariable
    link = common_app.router.build('status', task_id=result.id)
    deviso_result = dict(status=result.status, id=result.id, link=link)
    LOG.info("DEVISO: Result: " + str(deviso_result))
    return deviso_result
コード例 #8
0
def bvt_deviso_post():
    """Handles requests from Dev team for user builds ISOs.
    """
    # BVTINFO_PROJECT_PATTERN = '(\D+)?(\d+\.\d+\.\d+)-?(hf\d+)?'
    DEFAULT_SUITE = 'bvt'
    SUITES = {
        'bvt': '%s/' % CONFIG.paths.current,
        'dev': '%s/cloud/external/devtest_wrapper.py' % CONFIG.paths.current,
        'dev-cloud':
        '%s/cloud/external/restservicebus.py' % CONFIG.paths.current
    }
    CONFIG_FILE = 'config/shared/web_deviso_request.yaml'

    # For people who don't like to set the application/json header.
    data = AttrDict(json.load(bottle.request.body))
    # data = bottle.request.json
    data._referer = bottle.request.url

    our_config = AttrDict(yaml.load(open(get_harness('bigiq')).read()))

    # Prepare placeholders in our config
    our_config.update(
        {'stages': {
            'main': {
                'setup': {
                    'install': {
                        'parameters': {}
                    }
                }
            }
        }})
    our_config.update({
        'stages': {
            'main': {
                'setup': {
                    'install-bigips': {
                        'parameters': {}
                    }
                }
            }
        }
    })
    our_config.update({'plugins': {'email': {'to': [], 'variables': {}}}})

    plugins = our_config.plugins
    # Append submitter's email to recipient list
    if data.get('email'):
        plugins.email.to.append(data['email'])
    plugins.email.to.extend(CONFIG.web.recipients)

    # Set version and build in the install stage
    v = None
    if data.get('iso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom iso'] = data['iso']
        v = version_from_metadata(data['iso'])

    if data.get('hfiso'):
        params = our_config.stages.main.setup['install'].parameters
        params['custom hf iso'] = data['hfiso']
        v = version_from_metadata(data['hfiso'])
        # Find the RTM ISO that goes with this HF image.
        if not data.get('iso'):
            params['custom iso'] = isofile(v.version, product=str(v.product))

    args = []
    args[:] = NOSETESTS_ARGS

    rank = Literal('rank')
    expr = (rank > Literal(0)) & (rank < Literal(11))
    # Include all migrated tests, example: functional/standalone/security/migrated/...
    # Assumption is that all tests are rank=505
    expr |= rank == Literal(505)
    # Only Greenflash tests have extended attributes
    if v is None or v >= 'bigiq 4.5':
        # build hamode argument
        if data.ha:
            hamode = Literal('hamode')
            expr2 = Or()
            for x in data.ha:
                if x != 'standalone':
                    expr2 += [In(String(x.upper()), hamode)]
            if 'standalone' in data.ha:
                expr &= (~hamode | expr2)
            else:
                expr &= hamode & expr2

        if data.ui:
            uimode = Literal('uimode')
            if data.ui == 'api':
                expr &= ~uimode
            elif data.ui == 'ui':
                expr &= uimode & (uimode > Literal(0))
            else:
                raise ValueError('Unknown value {}'.format(data.ui))

        if data.module:
            module = Literal('module')
            expr2 = Or()
            for x in data.module:
                expr2 += [In(String(x.upper()), module)]
            expr &= (module & expr2)

    args.append('--tc-file={VENV}/%s' % CONFIG_FILE)

    # Default is our BVT suite.
    if v:
        suite = os.path.join(CONFIG.suites.root, CONFIG.suites[v.version])
    else:
        suite = SUITES[data.get('suite', DEFAULT_SUITE)]
    args.append('--tc=stages.enabled:1')
    # XXX: No quotes around the long argument value!
    args.append('--eval-attr={}'.format(str(expr)))
    args.append('--with-email')
    # args.append('--collect-only')
    args.append('--with-irack')
    args.append('{VENV}/%s' % suite)

    v = plugins.email.variables
    v.args = args
    v.iso = data.iso
    v.module = data.module

    result = nosetests.delay(our_config, args, data)  # @UndefinedVariable
    link = app.router.build('status', task_id=result.id)
    return dict(status=result.status, id=result.id, link=link)