Example #1
0
    def test_invalid_configuration_non_existent_file(self):
        with self.assertRaises(FileNotFoundError):
            args = argparse.Namespace(cfg='/dev/null')
            cfg = configure(args)

        with self.assertRaises(FileNotFoundError):
            args = argparse.Namespace(cfg='some-non-existent-file')
            cfg = configure(args)
Example #2
0
def test_blastdb_not_found(gke_mock, mocker):
    """Test that UserReportError is raised when database is not found"""
    def mocked_check_cluster(cfg):
        """Mocked check cluster that simulates non-existent cluster status"""
        return ''

    mocker.patch('elb.commands.submit.gcp_check_cluster',
                 side_effect=mocked_check_cluster)

    def mock_safe_exec(cmd):
        if isinstance(cmd, list):
            cmd = ' '.join(cmd)
        if cmd == 'gsutil cat gs://blast-db/latest-dir':
            return MockedCompletedProcess(stdout='2020-20-20')
        elif cmd == 'gsutil cat gs://blast-db/2020-20-20/blastdb-manifest.json':
            return MockedCompletedProcess(
                stdout='{"nt":{"size":93.36}, "nr":{"size":227.4}}')
        return MockedCompletedProcess()

    mocker.patch('elb.util.safe_exec', side_effect=mock_safe_exec)

    print(INI_NO_BLASTDB)

    args = Namespace(cfg=INI_NO_BLASTDB)

    # test that UserReportError is raised
    with pytest.raises(UserReportError) as err:
        submit(args, ElasticBlastConfig(configure(args),
                                        task=ElbCommand.SUBMIT), [])

    # test error code and message
    assert err.value.returncode == constants.BLASTDB_ERROR
    assert 'BLAST database' in err.value.message
    assert 'not found' in err.value.message
Example #3
0
def test_cluster_name_from_environment(env_config):
    """Test cluster name from environment overrides everything else"""
    args = argparse.Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'gcp-defaults.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)

    assert cfg.cluster.results == env_config['ELB_RESULTS']
    assert cfg.cluster.name == env_config['ELB_CLUSTER_NAME']
Example #4
0
def test_multiple_query_files():
    """Test getting config with multiple query files"""
    args = argparse.Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'multiple-query-files.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
    expected_query_files = ['query-file-1', 'query-file-2']
    assert sorted(
        cfg.blast.queries_arg.split()) == sorted(expected_query_files)
Example #5
0
 def test_default_outfmt(self):
     """ Test that default optional BLAST parameters has -outfmt 11 set """
     args = argparse.Namespace(
         cfg=os.path.join(TEST_DATA_DIR, 'minimal-cfg-file.ini'))
     self.cfg = configure(args)
     cfg = ElasticBlastConfig(self.cfg, task=ElbCommand.SUBMIT)
     self.assertEqual(cfg.blast.options.strip(),
                      f'-outfmt {ELB_DFLT_OUTFMT}')
Example #6
0
def test_aws_defaults():
    """Test that default config parameters are set correctly for AWS"""
    args = argparse.Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'aws-defaults.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
    check_common_defaults(cfg)

    assert cfg.cloud_provider.cloud == CSP.AWS
    assert cfg.cluster.pd_size == constants.ELB_DFLT_AWS_PD_SIZE
Example #7
0
def test_mem_limit_too_high():
    """Test that setting memory limit that exceeds cloud instance memory
    triggers an error"""
    args = argparse.Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'mem-limit-too-high.ini'))
    with pytest.raises(UserReportError) as err:
        cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
    assert err.value.returncode == INPUT_ERROR
    m = re.match(r'Memory limit.*exceeds', err.value.message)
    assert m is not None
Example #8
0
def test_instance_too_small_gcp():
    """Test that using too small an instance triggers an error"""
    args = argparse.Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'instance-too-small-gcp.ini'))
    with pytest.raises(UserReportError) as err:
        cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
        cfg.validate()
    assert err.value.returncode == INPUT_ERROR
    print(err.value.message)
    assert 'does not have enough memory' in err.value.message
Example #9
0
def test_get_gke_credentials_no_cluster_real():
    """Test that util.SafeExecError is raised when getting credentials of a
    non-existent cluster"""
    data_dir = os.path.join(os.path.dirname(__file__), 'data')
    args = Namespace(cfg=os.path.join(data_dir, 'test-cfg-file.ini'))
    cfg = ElasticBlastConfig(config.configure(args), task=ElbCommand.SUBMIT)
    cfg.cluster.name = 'some-strange-cluster-name'
    assert cfg.cluster.name not in gcp.get_gke_clusters(cfg)
    with pytest.raises(SafeExecError):
        gcp.get_gke_credentials(cfg)
Example #10
0
def test_generated_cluster_name(env_config_no_cluster):
    """Test cluster name generated from results, and value from config file is ignored"""
    args = argparse.Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'gcp-defaults.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)

    assert cfg.cluster.results == TEST_RESULTS_BUCKET
    user = getpass.getuser()
    digest = hashlib.md5(TEST_RESULTS_BUCKET.encode()).hexdigest()[0:9]
    assert cfg.cluster.name == f'elasticblast-{user.lower()}-{digest}'
Example #11
0
 def test_optional_blast_parameters(self):
     """ Test that optional BLAST parameters properly read from config file """
     args = argparse.Namespace(
         cfg=os.path.join(TEST_DATA_DIR, 'optional-cfg-file.ini'))
     self.cfg = configure(args)
     cfg = ElasticBlastConfig(self.cfg, task=ElbCommand.SUBMIT)
     # str.find is not enough here, need to make sure options are properly merged
     # with whitespace around them.
     options = cfg.blast.options.strip()
     self.assertTrue(re.search('(^| )-outfmt 11($| )', options) != None)
     self.assertTrue(
         re.search('(^| )-task blastp-fast($| )', options) != None)
Example #12
0
def test_label_persistent_disk(safe_exec_mock):
    """Exercises label_persistent_disk with mock safe_exec and prints out
    arguments to safe_exec
    Run pytest -s -v tests/kubernetes to verify correct order of calls"""
    from argparse import Namespace
    args = Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'initialize_persistent_disk.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
    # Replace labels with well-known fake for the purpose of testing command match,
    # see above in safe_exec_mock
    cfg.cluster.labels = FAKE_LABELS
    kubernetes.label_persistent_disk(cfg)
Example #13
0
def test_gcp_defaults():
    """Test that default config parameters are set correctly for GCP"""
    args = argparse.Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'gcp-defaults.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
    check_common_defaults(cfg)

    assert cfg.cloud_provider.cloud == CSP.GCP
    assert cfg.cluster.pd_size == constants.ELB_DFLT_GCP_PD_SIZE

    assert cfg.timeouts.blast_k8s == constants.ELB_DFLT_BLAST_K8S_TIMEOUT
    assert cfg.timeouts.init_pv == constants.ELB_DFLT_INIT_PV_TIMEOUT
Example #14
0
 def test_optional_blast_parameters_from_command_line(self):
     """ Test that parameters are read correctly from command line """
     args = argparse.Namespace(cfg=os.path.join(TEST_DATA_DIR,
                                                'optional-cfg-file.ini'),
                               blast_opts=['-outfmt', '8'])
     print(args)
     self.cfg = configure(args)
     cfg = ElasticBlastConfig(self.cfg, task=ElbCommand.SUBMIT)
     self.assertTrue(
         re.search('(^| )-outfmt 8($| )', cfg.blast.options.strip()) != None
     )
     # NB - options are treated as single entity and command line overwrites them all, not merge, not overwrites selectively
     self.assertTrue(
         cfg.blast.options.strip().find('-task blastp-fast') < 0)
Example #15
0
def test_initialize_persistent_disk_failed(mocker):
    def fake_safe_exec_failed_job(cmd):
        fn = os.path.join(TEST_DATA_DIR, 'job-status-failed.json')
        return MockedCompletedProcess(stdout=Path(fn).read_text())

    mocker.patch('elb.kubernetes.safe_exec',
                 side_effect=fake_safe_exec_failed_job)
    from argparse import Namespace
    args = Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'initialize_persistent_disk.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
    with pytest.raises(RuntimeError):
        kubernetes.initialize_persistent_disk(cfg, cfg.blast.db)
    kubernetes.safe_exec.assert_called()
Example #16
0
def provide_disk():
    """Fixture function that creates GCP disk when setting up a test and
    deletes it when tearing the test down, returns disk name."""

    # test setup
    name = os.environ['USER'] + '-elastic-blast-test-suite'
    data_dir = os.path.join(os.path.dirname(__file__), 'data')
    args = Namespace(cfg=os.path.join(data_dir, 'test-cfg-file.ini'))
    cfg = ElasticBlastConfig(config.configure(args), task=ElbCommand.SUBMIT)
    cmd = f'gcloud beta compute disks create {name} --project={cfg.gcp.project} --type=pd-standard --size=10GB --zone={cfg.gcp.zone}'
    gcp.safe_exec(cmd.split())
    yield name, cfg

    # test teardown
    if name in gcp.get_disks(cfg):
        gcp.delete_disk(name, cfg)
Example #17
0
def test_initialize_persistent_disk(safe_exec_mock, mocker):
    """Exercises initialize_persistent_disk with mock safe_exec and prints out
    arguments to safe_exec
    Run pytest -s -v tests/kubernetes to verify correct order of calls"""
    from argparse import Namespace

    def mocked_upload_file_to_gcs(fname, loc, dryrun):
        """Mocked upload to GS function"""
        pass

    mocker.patch('elb.kubernetes.upload_file_to_gcs',
                 side_effect=mocked_upload_file_to_gcs)

    args = Namespace(
        cfg=os.path.join(TEST_DATA_DIR, 'initialize_persistent_disk.ini'))
    cfg = ElasticBlastConfig(configure(args), task=ElbCommand.SUBMIT)
    kubernetes.initialize_persistent_disk(cfg, cfg.blast.db)
Example #18
0
def provide_cluster():
    """Create a GCKE cluster before and delete it after a test"""
    # setup
    data_dir = os.path.join(os.path.dirname(__file__), 'data')
    args = Namespace(cfg=os.path.join(data_dir, 'test-cfg-file.ini'))
    cfg = ElasticBlastConfig(config.configure(args), task=ElbCommand.SUBMIT)
    cfg.cluster.name = cfg.cluster.name + f'-{os.environ["USER"]}'

    cmd = f'gcloud container clusters create {cfg.cluster.name} --num-nodes 1 --machine-type n1-standard-1 --labels elb=test-suite'
    gcp.safe_exec(cmd.split())
    yield cfg

    # teardown
    name = cfg.cluster.name
    if name in gcp.get_gke_clusters(cfg):
        cmd = f'gcloud container clusters delete {name} -q'
        gcp.safe_exec(cmd.split())
Example #19
0
def test_load_config_from_environment(env_config):
    """Test config values set from environment"""
    args = argparse.Namespace()
    cfg = configure(args)

    assert cfg[CFG_CLOUD_PROVIDER][CFG_CP_GCP_PROJECT] == env_config[
        'ELB_GCP_PROJECT']
    assert cfg[CFG_CLOUD_PROVIDER][CFG_CP_GCP_REGION] == env_config[
        'ELB_GCP_REGION']
    assert cfg[CFG_CLOUD_PROVIDER][CFG_CP_GCP_ZONE] == env_config[
        'ELB_GCP_ZONE']
    assert cfg[CFG_BLAST][CFG_BLAST_BATCH_LEN] == env_config['ELB_BATCH_LEN']
    assert cfg[CFG_CLUSTER][CFG_CLUSTER_NAME] == env_config['ELB_CLUSTER_NAME']
    assert cfg[CFG_CLUSTER][CFG_CLUSTER_USE_PREEMPTIBLE] == env_config[
        'ELB_USE_PREEMPTIBLE']
    assert cfg[CFG_CLUSTER][CFG_CLUSTER_BID_PERCENTAGE] == env_config[
        'ELB_BID_PERCENTAGE']
Example #20
0
def main():
    """Local main entry point which sets up arguments, undo stack,
    and processes exceptions """
    try:
        signal.signal(signal.SIGINT, signal.default_int_handler)
        clean_up_stack = []
        # Check parameters for Unicode letters and reject if codes higher than 255 occur
        reject_cli_args_with_unicode(sys.argv[1:])
        parser = create_arg_parser()
        args = parser.parse_args()
        if not args.subcommand:
            # report missing command line task
            raise UserReportError(returncode=constants.INPUT_ERROR,
                                  message=NO_TASK_MSG)
        config_logging(args)
        cfg = configure(args)
        logging.info(f"ElasticBLAST {args.subcommand} {VERSION}")
        task = ElbCommand(args.subcommand.lower())
        cfg = ElasticBlastConfig(cfg, task=task)
        logging.debug(pprint.pformat(cfg.asdict()))
        check_prerequisites(cfg)
        #TODO: use cfg only when args.wait, args.sync, and args.run_label are replicated in cfg
        return args.func(args, cfg, clean_up_stack)
    except (SafeExecError, UserReportError) as e:
        logging.error(e.message)
        # SafeExecError return code is the exit code from command line
        # application ran via subprocess
        if isinstance(e, SafeExecError):
            return constants.DEPENDENCY_ERROR
        return e.returncode
    except KeyboardInterrupt:
        return constants.INTERRUPT_ERROR
    #TODO: process filehelper.TarReadError here
    finally:
        messages = clean_up(clean_up_stack)
        if messages:
            for msg in messages:
                logging.error(msg)
            sys.exit(constants.UNKNOWN_ERROR)
Example #21
0
    def test_minimal_configuration(self):
        """Test the auto-configurable parameters"""
        args = argparse.Namespace(
            cfg=os.path.join(TEST_DATA_DIR, 'minimal-cfg-file.ini'))
        self.cfg = configure(args)
        cfg = ElasticBlastConfig(self.cfg, task=ElbCommand.SUBMIT)

        self.assertTrue(cfg.blast.db_source)
        self.assertEqual(cfg.blast.db_source, DBSource.GCP)

        self.assertTrue(cfg.blast.batch_len)
        self.assertEqual(cfg.blast.batch_len, 10000)

        self.assertTrue(cfg.blast.mem_request)
        self.assertEqual(cfg.blast.mem_request, '0.5G')

        self.assertTrue(cfg.blast.mem_limit)
        expected_mem_limit = f'{get_machine_properties(cfg.cluster.machine_type).memory - SYSTEM_MEMORY_RESERVE}G'
        self.assertEqual(cfg.blast.mem_limit, expected_mem_limit)

        self.assertTrue(cfg.timeouts.init_pv > 0)
        self.assertTrue(cfg.timeouts.blast_k8s > 0)

        ElasticBlastConfig(self.cfg, task=ElbCommand.SUBMIT)