-
Notifications
You must be signed in to change notification settings - Fork 30
feat: convert to use AWS boto3 #1061
Conversation
489ef4a
to
e96303d
Compare
fd99c2b
to
5566b79
Compare
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
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( |
There was a problem hiding this comment.
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 ( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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={ |
There was a problem hiding this comment.
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__: |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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/tests/__init__.py
Outdated
autopush.db.DYNAMODB = boto3.resource( | ||
'dynamodb', | ||
config=conf, | ||
endpoint_url=os.getenv("AWS_LOCAL_DYNAMODB", "http://127.0.0.1:8000") |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
autopush/utils.py
Outdated
@@ -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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
f08e679
to
5e239e4
Compare
autopush/__init__.py
Outdated
@@ -1 +1,4 @@ | |||
__version__ = '1.39.0' # pragma: nocover | |||
|
|||
# Max DynamoDB record lifespan (~ 30 days) | |||
MAX_EXPRY = 2592000 # pragma: nocover |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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=[{ |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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?
34cf8f5
to
e0a9a85
Compare
autopush/main_argparse.py
Outdated
@@ -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') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ha
555d8a1
to
17f28ee
Compare
autopush/utils.py
Outdated
@@ -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): |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this 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( |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
782cb5f
to
2a5d4ea
Compare
Issue #1050
Closes #1049