Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

feat: convert to use AWS boto3 #1061

Merged
merged 1 commit into from
Nov 3, 2017
Merged

feat: convert to use AWS boto3 #1061

merged 1 commit into from
Nov 3, 2017

Conversation

jrconlin
Copy link
Member

Issue #1050
Closes #1049

@jrconlin jrconlin requested review from pjenvey and bbangert October 25, 2017 19:28
@jrconlin jrconlin force-pushed the feat/1049 branch 6 times, most recently from 489ef4a to e96303d Compare October 25, 2017 23:27
@jrconlin jrconlin force-pushed the feat/1049 branch 9 times, most recently from fd99c2b to 5566b79 Compare October 27, 2017 00:39
@codecov-io
Copy link

codecov-io commented Oct 27, 2017

Codecov Report

Merging #1061 into master will decrease coverage by 0.05%.
The diff coverage is 98.19%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1061      +/-   ##
==========================================
- Coverage   99.73%   99.67%   -0.06%     
==========================================
  Files          56       56              
  Lines        8904     8999      +95     
==========================================
+ Hits         8880     8970      +90     
- Misses         24       29       +5
Impacted Files Coverage Δ
autopush/tests/test_websocket.py 100% <100%> (ø) ⬆️
autopush/web/webpush.py 100% <100%> (ø) ⬆️
autopush/tests/test_db.py 100% <100%> (ø) ⬆️
autopush/websocket.py 100% <100%> (ø) ⬆️
autopush/tests/__init__.py 100% <100%> (ø) ⬆️
autopush/web/health.py 100% <100%> (ø) ⬆️
autopush/tests/test_web_base.py 100% <100%> (ø) ⬆️
autopush/web/base.py 100% <100%> (ø) ⬆️
autopush/db.py 100% <100%> (ø) ⬆️
autopush/tests/test_integration.py 100% <100%> (ø) ⬆️
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fa47b5c...0278472. Read the comment docs.

autopush/db.py Outdated
@@ -92,6 +95,16 @@
TRACK_DB_CALLS = False
DB_CALLS = []

# See https://botocore.readthedocs.io/en/stable/reference/config.html for
# additional config options
DYNAMODB = boto3.resource(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and client are ultimately stateful (mutable) objects underneath: so I'd say they should not have constant-like upper case names

@@ -43,16 +44,17 @@
attrib,
Factory
)
from boto.exception import JSONResponseError

from boto.dynamodb2.exceptions import (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still raise ItemNotFound but AFAICT the other 2 boto2 exceptions aren't in use any longer

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, ProvisionedThroughputExceededException is in botocore and still could be returned. ConditionalCheckFailedException appears to be wrapped into ClientError,so we can probably remove it.

Copy link
Member

@pjenvey pjenvey Oct 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, it looks like even ProvisionedThroughputExceededException becomes a ClientError (or one of those dynamic subclass of it) now. This boto2 one is defined in that boto.dynamodb2.exceptions module: boto3/botocore don't use boto2 at all AFAICT

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revised the errors to be ClientErrors and added handling for the special cases.

autopush/db.py Outdated
}
)
except ClientError as ex: # pragma nocover
if "UnknownOperationException" in ex.message:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tempted to restrict this case to only during tests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno. We're eating an error. I'm not a huge fan of this either, but the other option is that we introduce a potentially complicated test environment check system (mock, env check, passed flag even for integration, etc.). The try block is limited to the one function and the significant return, so I think it may be OK to do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, probably not totally necessary. I think UnknownOperationException is a pretty clear case that we should not ever see in prod. It does show up as the exception "type" (response['Error']['Code'] == 'UnknownOperationException') so we can check that a little more explicitly here at least

autopush/db.py Outdated
@@ -238,8 +345,8 @@ def preflight_check(message, router, uaid="deadbeef00000000deadbeef00000000"):
# Verify tables are ready for use if they just got created
ready = False
while not ready:
tbl_status = [x.describe()["Table"]["TableStatus"]
for x in [message.table, router.table]]
tbl_status = [x.table_status()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can collapse this newline now

autopush/db.py Outdated
consistent=True, limit=limit))

response = self.table.query(
KeyConditions={
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KeyConditions is legacy now, they're recommending KeyConditionExpressions:

http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.html

I guess this becomes KeyConditionExpression=Key('uaid').eq(uaid.hex) & Key('chidmessageid').lt("02")?. don't quote me on that

autopush/db.py Outdated
except ClientError as ex:
# ClientErrors are generated by a factory, and while they have a
# class, it's dynamically generated.
if 'ConditionalCheckFailedException' == ex.__class__.__name__:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since they recommend checking against [Error']['Code'] like you did above for ResourceNotFoundException, let's just standardize on that. The test can raise ClientError({'Error': {'Code': 'ConditionalCheckFailedException'}})

autopush/db.py Outdated
return self.table.delete_item(uaid=huaid,
expected={"uaid__eq": huaid})
try:
self.get_uaid(uaid)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add the get_uaid call here? It could be dangerous as get_uaid potentially calls drop_user

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much for the reason noted. We only delete UAIDs that already exist. If get_uaid deletes the uaid, then it was already scheduled for deletion and returning False is acceptable. Either way, it's deleted.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can turn into infinite recursion w/ an invalid record, though. See the case in test_db.test_incomplete_uaid -- if you disable the mock of drop_user in there it'll blow up the stack

autopush.db.DYNAMODB = boto3.resource(
'dynamodb',
config=conf,
endpoint_url=os.getenv("AWS_LOCAL_DYNAMODB", "http://127.0.0.1:8000")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll have to do something about credentials here (hardcode?) so tests run locally without needing to set them

@@ -1,9 +1,13 @@
[Credentials]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we should kill this entire file now, boto3 only reads "Credentials" from a BOTO_CONFIG (which it considers an old boto2 config file).

Also if we're no longer able to configure our local dynamodb through this file our docker-compose setup will no longer work. I think that's a whole separate issue entirely from this PR, though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, going to hardcode the credentials, since they're bogus anyway and we only need them for the test.

@@ -301,6 +302,8 @@ class WebPushNotification(object):
# Whether this notification should follow legacy non-topic rules
legacy = attrib(default=False) # type: bool

expry = attrib(default=MAX_EXPRY) # type: int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this expry attribute here even necessary? We seem to always fill the expry headed towards the database from the ttl attribute. Only thing I see this doing is propagating it over the connection node via JSON, but it's not even used there

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was being a bit pre-emptive. will remove for now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, going to leave it. We use it for several tests and it identifies as a keyword parameter.

@jrconlin jrconlin force-pushed the feat/1049 branch 2 times, most recently from f08e679 to 5e239e4 Compare November 1, 2017 00:17
@@ -1 +1,4 @@
__version__ = '1.39.0' # pragma: nocover

# Max DynamoDB record lifespan (~ 30 days)
MAX_EXPRY = 2592000 # pragma: nocover
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXPIRY?

autopush/db.py Outdated
return repr(uaid_data.items())
else:
return repr(uaid_data)
return repr(uaid_data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we just drop this function then and use repr? This only existed to unify the if/else statement.

autopush/db.py Outdated
'KeyType': 'RANGE'
}],
AttributeDefinitions=[{
'AttributeName': 'uaid',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the different style of [{ here than above? Should be nicer when we use an auto-formatter I guess.

autopush/db.py Outdated
'ReadCapacityUnits': read_throughput,
'WriteCapacityUnits': write_throughput,
},
GlobalSecondaryIndexes=[{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To [{ or [\n\t{

autopush/db.py Outdated
@@ -333,26 +441,31 @@ def generate_last_connect_values(date):
yield int(val)


def list_tables(conn):
def list_tables(client=CLIENT):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutable defaults generally aren't great, it looks a little odd.

autopush/db.py Outdated
@@ -256,6 +363,7 @@ def preflight_check(message, router, uaid="deadbeef00000000deadbeef00000000"):
update_id=message_id,
message_id=message_id,
ttl=60,
expry=_expry(60),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some reason we can't include the i to make it a real word? It is supposed to be expiry, right?

@jrconlin jrconlin force-pushed the feat/1049 branch 4 times, most recently from 34cf8f5 to e0a9a85 Compare November 2, 2017 17:49
@jrconlin jrconlin requested a review from pjenvey November 2, 2017 18:36
@jrconlin jrconlin requested a review from bbangert November 2, 2017 18:37
@@ -115,7 +115,7 @@ def _obsolete_args(parser):
parser.add_argument('--external_router', help='OBSOLETE')
parser.add_argument('--max_message_size', type=int, help="OBSOLETE")
parser.add_argument('--s3_bucket', help='OBSOLETE')
parser.add_argument('--senderid_expry', help='OBSOLETE')
parser.add_argument('--senderid_expiry', help='OBSOLETE')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha

@jrconlin jrconlin force-pushed the feat/1049 branch 3 times, most recently from 555d8a1 to 17f28ee Compare November 2, 2017 23:54
@@ -335,6 +335,12 @@ def generate_message_id(self, fernet):
self.update_id = self.message_id
return self.message_id

@classmethod
def calc_expiry(cls, ttl=0):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is now dead code: kill

autopush/db.py Outdated
return self.table.delete_item(uaid=huaid,
expected={"uaid__eq": huaid})
try:
self.get_uaid(uaid)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can turn into infinite recursion w/ an invalid record, though. See the case in test_db.test_incomplete_uaid -- if you disable the mock of drop_user in there it'll blow up the stack

Copy link
Member

@pjenvey pjenvey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a couple more things, most importantly the drop_user/get_uaid issue

@@ -92,6 +94,16 @@
TRACK_DB_CALLS = False
DB_CALLS = []

# See https://botocore.readthedocs.io/en/stable/reference/config.html for
# additional config options
g_dynamodb = boto3.resource(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the g_ mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, i wanted to make sure that there was no potential for symbol conflict, and I picked "g_" because it's effectively a global.

autopush/db.py Outdated
consistent=True, limit=limit))
response = self.table.query(
KeyConditionExpression=Key('uaid').eq(hasher(uaid.hex))
& Key('chidmessageid').gt(sortkey),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe indent so only the keys line up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flake8 complains about that, but I can move the & to the previous line.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can also wrap the whole statement in parens if that helps

@jrconlin jrconlin force-pushed the feat/1049 branch 2 times, most recently from 782cb5f to 2a5d4ea Compare November 3, 2017 20:47
@bbangert bbangert merged commit bc32908 into master Nov 3, 2017
@bbangert bbangert deleted the feat/1049 branch November 3, 2017 22:00
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants