Exemple #1
0
def test_sanitize_drop_empty_indexes():
    expected = expected_table_description(SimpleModel)
    # Start from the same base, but inject an unnecessary NonKeyAttributes
    description = expected_table_description(SimpleModel)
    description["GlobalSecondaryIndexes"] = []

    assert_unordered(expected, sanitize_table_description(description))
Exemple #2
0
def test_sanitize_drop_empty_lists():
    expected = expected_table_description(ComplexModel)
    # Start from the same base, but inject an unnecessary NonKeyAttributes
    description = expected_table_description(ComplexModel)
    index = description["GlobalSecondaryIndexes"][0]
    index["Projection"]["NonKeyAttributes"] = []

    assert_unordered(expected, sanitize_table_description(description))
Exemple #3
0
def test_validate_unspecified_gsi_throughput(session, dynamodb, caplog):
    """Model doesn't care what GSI read/write units are"""
    class MyModel(BaseModel):
        class Meta:
            read_units = 1
            write_units = 1
        id = Column(String, hash_key=True)
        other = Column(String)
        by_other = GlobalSecondaryIndex(projection="keys", hash_key=other)

    description = expected_table_description(MyModel)
    description["TableStatus"] = "ACTIVE"
    description["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"
    throughput = description["GlobalSecondaryIndexes"][0]["ProvisionedThroughput"]
    throughput["ReadCapacityUnits"] = 15
    throughput["WriteCapacityUnits"] = 20

    dynamodb.describe_table.return_value = {"Table": description}

    assert MyModel.by_other.read_units is None
    assert MyModel.by_other.write_units is None
    caplog.handler.records.clear()
    session.validate_table(MyModel)
    assert MyModel.by_other.read_units == 15
    assert MyModel.by_other.write_units == 20

    assert caplog.record_tuples == [
        ("bloop.session", logging.DEBUG,
         "validate_table: table \"MyModel\" was in ACTIVE state after 1 calls"),
        ("bloop.session", logging.DEBUG,
         "MyModel.by_other does not specify read_units, set to 15 from DescribeTable response"),
        ("bloop.session", logging.DEBUG,
         "MyModel.by_other does not specify write_units, set to 20 from DescribeTable response")
    ]
Exemple #4
0
def test_sanitize_expected():
    expected = expected_table_description(User)
    # Add some extra fields
    description = {
        'AttributeDefinitions': [
            {'AttributeType': 'S', 'AttributeName': 'email'},
            {'AttributeType': 'S', 'AttributeName': 'id'}],
        'CreationDateTime': 'EXTRA_FIELD',
        'ItemCount': 'EXTRA_FIELD',
        'KeySchema': [{'AttributeName': 'id', 'KeyType': 'HASH'}],
        'GlobalSecondaryIndexes': [{
            'IndexArn': 'EXTRA_FIELD',
            'IndexName': 'by_email',
            'IndexSizeBytes': 'EXTRA_FIELD',
            'IndexStatus': 'EXTRA_FIELD',
            'KeySchema': [{'AttributeName': 'email', 'KeyType': 'HASH'}],
            'Projection': {'ProjectionType': 'ALL'},
            'ProvisionedThroughput': {
                'NumberOfDecreasesToday': 'EXTRA_FIELD',
                'ReadCapacityUnits': 1,
                'WriteCapacityUnits': 1}}],
        'ProvisionedThroughput': {
            'LastDecreaseDateTime': 'EXTRA_FIELD',
            'LastIncreaseDateTime': 'EXTRA_FIELD',
            'NumberOfDecreasesToday': 'EXTRA_FIELD',
            'ReadCapacityUnits': 1,
            'WriteCapacityUnits': 1},
        'TableArn': 'EXTRA_FIELD',
        'TableName': 'User',
        'TableSizeBytes': 'EXTRA_FIELD',
        'TableStatus': 'EXTRA_FIELD'}
    sanitized = sanitize_table_description(description)
    assert_unordered(expected, sanitized)
Exemple #5
0
def test_validate_compares_tables(session, dynamodb):
    description = expected_table_description(User)
    description["TableStatus"] = "ACTIVE"
    description["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"

    dynamodb.describe_table.return_value = {"Table": description}
    session.validate_table(User)
    dynamodb.describe_table.assert_called_once_with(TableName="User")
Exemple #6
0
def test_validate_missing_index(session, dynamodb):
    """Required GSI is missing"""
    description = expected_table_description(ProjectedIndexes)
    description["TableStatus"] = "ACTIVE"
    dynamodb.describe_table.return_value = {"Table": description}

    del description["GlobalSecondaryIndexes"]
    with pytest.raises(TableMismatch):
        session.validate_table(ProjectedIndexes)
Exemple #7
0
def test_validate_wrong_table(session, dynamodb):
    """dynamo returns a valid document but it doesn't match"""
    full = expected_table_description(SimpleModel)
    full["TableStatus"] = "ACTIVE"

    full["TableName"] = "wrong table name"

    dynamodb.describe_table.return_value = {"Table": full}
    with pytest.raises(TableMismatch):
        session.validate_table(SimpleModel)
Exemple #8
0
def test_validate_unknown_projection_type(session, dynamodb):
    """DynamoDB starts returning a new projection type"""
    description = expected_table_description(ProjectedIndexes)
    description["TableStatus"] = "ACTIVE"
    description["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"
    dynamodb.describe_table.return_value = {"Table": description}

    description["GlobalSecondaryIndexes"][0]["Projection"][
        "ProjectionType"] = "NewProjectionType"
    with pytest.raises(TableMismatch):
        session.validate_table(ProjectedIndexes)
Exemple #9
0
def test_validate_bad_index_provisioned_throughput(session, dynamodb):
    """KeySchema doesn't match"""
    description = expected_table_description(ProjectedIndexes)
    description["TableStatus"] = "ACTIVE"
    description["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"
    dynamodb.describe_table.return_value = {"Table": description}

    description["GlobalSecondaryIndexes"][0]["ProvisionedThroughput"][
        "WriteCapacityUnits"] = -2
    with pytest.raises(TableMismatch):
        session.validate_table(ProjectedIndexes)
Exemple #10
0
def test_validate_bad_index_key_schema(session, dynamodb, caplog):
    """KeySchema doesn't match"""
    description = expected_table_description(ProjectedIndexes)
    description["TableStatus"] = "ACTIVE"
    description["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"
    dynamodb.describe_table.return_value = {"Table": description}

    description["GlobalSecondaryIndexes"][0]["KeySchema"] = [{"KeyType": "HASH", "AttributeName": "unknown"}]
    with pytest.raises(TableMismatch):
        session.validate_table(ProjectedIndexes)

    assert "key schema mismatch for \"by_gsi\"" in caplog.text
Exemple #11
0
def test_validate_bad_index_projection_type(session, dynamodb, caplog):
    """Required GSI is missing"""
    description = expected_table_description(ProjectedIndexes)
    description["TableStatus"] = "ACTIVE"
    description["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"
    dynamodb.describe_table.return_value = {"Table": description}

    description["GlobalSecondaryIndexes"][0]["Projection"] = {"ProjectionType": "KEYS_ONLY"}
    with pytest.raises(TableMismatch):
        session.validate_table(ProjectedIndexes)

    assert "actual projection for index \"by_gsi\" is missing expected columns" in caplog.text
Exemple #12
0
def test_validate_superset_index(session, dynamodb):
    """Validation passes if an Index's projection is a superset of the required projection"""
    description = expected_table_description(ProjectedIndexes)
    description["TableStatus"] = "ACTIVE"
    description["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"

    # projection is ALL in DynamoDB, not the exact ["both", "gsi_only"] from the model
    description["GlobalSecondaryIndexes"][0]["Projection"] = {"ProjectionType": "ALL"}

    dynamodb.describe_table.return_value = {"Table": description}
    session.validate_table(ProjectedIndexes)
    dynamodb.describe_table.assert_called_once_with(TableName="ProjectedIndexes")
Exemple #13
0
def test_validate_checks_status(session, dynamodb):
    # Don't care about the value checking, just want to observe retries
    # based on busy tables or indexes
    full = expected_table_description(ProjectedIndexes)
    full["TableStatus"] = "ACTIVE"
    full["GlobalSecondaryIndexes"][0]["IndexStatus"] = "ACTIVE"

    dynamodb.describe_table.side_effect = [
        {"Table": {"TableStatus": "CREATING"}},
        {"Table": {"TableStatus": "ACTIVE",
                   "GlobalSecondaryIndexes": [
                       {"IndexStatus": "CREATING"}]}},
        {"Table": full}
    ]
    session.validate_table(ProjectedIndexes)
    dynamodb.describe_table.assert_called_with(TableName="ProjectedIndexes")
    assert dynamodb.describe_table.call_count == 3
Exemple #14
0
def test_validate_unexpected_index(session, dynamodb):
    """Validation doesn't fail when the backing table has an extra GSI"""
    full = expected_table_description(ComplexModel)
    full["GlobalSecondaryIndexes"].append({
        "IndexName":
        "extra_gsi",
        "Projection": {
            "ProjectionType": "KEYS_ONLY"
        },
        "KeySchema": [{
            "KeyType": "HASH",
            "AttributeName": "date"
        }],
        "ProvisionedThroughput": {
            "WriteCapacityUnits": 1,
            "ReadCapacityUnits": 1
        },
    })

    full["LocalSecondaryIndexes"].append({
        "IndexName":
        "extra_lsi",
        "Projection": {
            "ProjectionType": "KEYS_ONLY"
        },
        "KeySchema": [{
            "KeyType": "RANGE",
            "AttributeName": "date"
        }],
        "ProvisionedThroughput": {
            "WriteCapacityUnits": 1,
            "ReadCapacityUnits": 1
        }
    })
    dynamodb.describe_table.return_value = {"Table": full}

    full["TableStatus"] = "ACTIVE"
    for gsi in full["GlobalSecondaryIndexes"]:
        gsi["IndexStatus"] = "ACTIVE"
    # Validation passes even though there are extra Indexes and AttributeDefinitions
    session.validate_table(ComplexModel)
Exemple #15
0
def test_expected_description():
    # Eventually expected_table_description will probably diverge from create_table
    # This will guard against (or coverage should show) if there's drift
    create = create_table_request(ComplexModel)
    expected = expected_table_description(ComplexModel)
    assert_unordered(create, expected)