Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ElastiCache Hook for creating, describing and deleting replication groups #8701

Merged
merged 20 commits into from
Oct 9, 2020
Merged

Adding ElastiCache Hook for creating, describing and deleting replication groups #8701

merged 20 commits into from
Oct 9, 2020

Conversation

VijayantSoni
Copy link
Contributor

@VijayantSoni VijayantSoni commented May 4, 2020

Adding ElastiCache Hook for creating, describing and deleting replication groups.
This hook interacts with AWS ElastiCache and supports following operations:

  1. Creating replication groups
  2. Deleting replication groups
  3. Describing replication groups

Make sure to mark the boxes below before creating PR: [x]

  • Description above provides context of the change
  • Unit tests coverage for changes (not needed for documentation changes)
  • Target Github ISSUE in description if exists
  • Commits follow "How to write a good git commit message"
  • Relevant documentation is updated including usage instructions.
  • I will engage committers as explained in Contribution Workflow Example.

In case of fundamental code change, Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in UPDATING.md.
Read the Pull Request Guidelines for more information.

@boring-cyborg boring-cyborg bot added the provider:amazon-aws AWS/Amazon - related issues label May 4, 2020
@boring-cyborg
Copy link

boring-cyborg bot commented May 4, 2020

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contribution Guide (https://github.com/apache/airflow/blob/master/CONTRIBUTING.rst)
Here are some useful points:

  • Pay attention to the quality of your code (flake8, pylint and type annotations). Our pre-commits will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example DAG that shows how users should use it.
  • Consider using Breeze environment for testing locally, it’s a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Be sure to read the Airflow Coding style.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://apache-airflow-slack.herokuapp.com/

Copy link
Member

@ashb ashb left a comment

Choose a reason for hiding this comment

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

This needs some unit tests adding too please.

(I have only given the code a cursory glance at this point)

airflow/providers/amazon/aws/hooks/elasticache.py Outdated Show resolved Hide resolved
airflow/providers/amazon/aws/hooks/elasticache.py Outdated Show resolved Hide resolved
airflow/providers/amazon/aws/hooks/elasticache.py Outdated Show resolved Hide resolved
airflow/providers/amazon/aws/hooks/elasticache.py Outdated Show resolved Hide resolved
Copy link
Contributor Author

@VijayantSoni VijayantSoni left a comment

Choose a reason for hiding this comment

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

Thanks @ashb , I will work on the changes.

airflow/providers/amazon/aws/hooks/elasticache.py Outdated Show resolved Hide resolved
@VijayantSoni
Copy link
Contributor Author

VijayantSoni commented May 17, 2020

@ashb Made the changes as per your suggestion. Also added unit tests, but moto does not support mocking aws elasticache hence the tests won't run.
I looked at other tests and tried following:

try:
    from moto import mock_elasticache
except ImportError:
    mock_elasticache = None


@unittest.skipIf(mock_elasticache is None, 'mock_elasticache package not available')

But this does not seem to work, we do get following error:

@mock_elasticache
E   TypeError: 'NoneType' object is not callable

Let me know if there is some other way run the tests.

@VijayantSoni VijayantSoni requested a review from ashb May 17, 2020 19:44
Comment on lines 62 to 63
"""
Call ElastiCache API for creating a replication group
:param config: Python dictionary to use as config for creating replication group
:return: Response from ElastiCache create replication group API
"""
Copy link
Member

Choose a reason for hiding this comment

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

All of your doc comments need a blank line between prose and params, else it won't render correctly

Suggested change
"""
Call ElastiCache API for creating a replication group
:param config: Python dictionary to use as config for creating replication group
:return: Response from ElastiCache create replication group API
"""
"""
Call ElastiCache API for creating a replication group
:param config: Python dictionary to use as config for creating replication group
:return: Response from ElastiCache create replication group API
"""

Copy link
Member

@ashb ashb left a comment

Choose a reason for hiding this comment

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

See individual comments

@VijayantSoni VijayantSoni requested a review from ashb May 20, 2020 06:41
@VijayantSoni
Copy link
Contributor Author

rebased with latest upstream master hence the force push

@VijayantSoni
Copy link
Contributor Author

@ashb made the changes. Test are failing due to moto missing support for mocking ElastiCache. Do I need to write custom mock for this ? Let me know how should we test this.

@ashb
Copy link
Member

ashb commented May 22, 2020

@VijayantSoni Probably just using unittest.mock is enough here, yes please -- we've done that in a few other places I think

@VijayantSoni
Copy link
Contributor Author

@ashb updated the tests using unittest.mock and those are working fine. I am not able to understand other checks which are failing as they don't seem to be related to changes done here, can you please help with those ?

@VijayantSoni
Copy link
Contributor Author

@ashb rebased with latest master, that has resolve the failing checks. Please take a look when you get a chance. Thanks !

Copy link
Member

@ashb ashb left a comment

Choose a reason for hiding this comment

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

I'm not sure an operator like this belongs in Aiflow.

Yes, it could, but there are better tools for managing (creating/deleting) resources such as HashiCorp's Terraform, or AWS CloudFormation (which already has an operator in master).

Can you describe in more detail why you want this please?

@VijayantSoni
Copy link
Contributor Author

VijayantSoni commented Jun 17, 2020

Hi @ashb , I see your point and yes we can discuss about it.
Please find below details for adding this.

Consider following scenario:

  1. We have some tasks that need to use redis (elasticache replication group in aws world)
  2. We will have some task that will create and setup redis (replication group)
  3. Downstream tasks will use it and then at the end one task will terminate the replication group.

The basic idea is to create resources when needed and terminate them once the work is done. This flow can be orchestrated within Airflow itself hence the PR.

Regarding managing resources via Terraform or Cloudformation, I agree with that but is it an anti-pattern to have this kind of hook in airflow ?

I do see following hooks which provision resources in AWS infra:

  1. https://github.com/apache/airflow/blob/master/airflow/providers/amazon/aws/hooks/emr.py
  2. https://github.com/apache/airflow/blob/master/airflow/providers/amazon/aws/hooks/redshift.py
  3. https://github.com/apache/airflow/blob/master/airflow/providers/amazon/aws/hooks/s3.py (creates bucket but maybe that's not comparable here, still adding)
  4. https://github.com/apache/airflow/blob/master/airflow/providers/amazon/aws/hooks/sagemaker.py
  5. https://github.com/apache/airflow/blob/master/airflow/providers/amazon/aws/hooks/sqs.py

@VijayantSoni
Copy link
Contributor Author

@ashb did you get a chance to take a look at this ? Thanks

@ashb
Copy link
Member

ashb commented Jul 15, 2020

Gotcha, I guess this one makes sense. Happy with the feature now.

(I'm not sure I have time to review this again before I go on paterntiy leave for six weeks!)

Copy link
Member

@ashb ashb left a comment

Choose a reason for hiding this comment

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

Your tests need some work still -- you aren't actually importing the hook you want to test in your new test file, so you haven't tested any of the code.

You probably want to do so something like:

    def setUp(self):
        self.hook = ElastiCacheHook()
        setattr(self.hook, 'conn', Mock())`

and then set various properties/return values of self.hook.conn (similar to how you are doing on self.hook) the goal being to test all the code in your Hook class, but not reach out to an actual AWS API.

I won't be able to re-review this again for 6 weeks, so someone you'll have to ask in slack for a re-review -- I'd suggest one of @kaxil, @mik-laj or @potiuk.

:param replication_group_id: ID of replication group to check for status
:return: Current status of replication group
"""
return self.describe_replication_group(replication_group_id)[self.REPLICATION_GROUPS][0][self.STATUS]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return self.describe_replication_group(replication_group_id)[self.REPLICATION_GROUPS][0][self.STATUS]
return self.describe_replication_group(replication_group_id)["ReplicationGroups"][0]["Status"]

Comment on lines 29 to 41

# Constants for ElastiCache describe API response keys
REPLICATION_GROUPS = 'ReplicationGroups'
REPLICATION_GROUP = 'ReplicationGroup'
STATUS = 'Status'

# Constants for ElastiCache replication group status
STATUS_CREATING = 'creating'
STATUS_AVAILABLE = 'available'
STATUS_DELETING = 'deleting'
STATUS_CREATE_FAILED = 'create-failed'
STATUS_MODIFYING = 'modifying'
STATUS_SNAPSHOTTING = 'snapshotting'
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# Constants for ElastiCache describe API response keys
REPLICATION_GROUPS = 'ReplicationGroups'
REPLICATION_GROUP = 'ReplicationGroup'
STATUS = 'Status'
# Constants for ElastiCache replication group status
STATUS_CREATING = 'creating'
STATUS_AVAILABLE = 'available'
STATUS_DELETING = 'deleting'
STATUS_CREATE_FAILED = 'create-failed'
STATUS_MODIFYING = 'modifying'
STATUS_SNAPSHOTTING = 'snapshotting'

These constants make it harder to understand the code and isn't the style we use in Airfow, please use the string literals directly.

:param replication_group_id: ID of replication group to check for availability
:return: True if available else False
"""
return self.get_replication_group_status(replication_group_id) == self.STATUS_AVAILABLE
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return self.get_replication_group_status(replication_group_id) == self.STATUS_AVAILABLE
return self.get_replication_group_status(replication_group_id) == "available"

Comment on lines 111 to 115
return status in (
self.STATUS_AVAILABLE,
self.STATUS_CREATE_FAILED,
self.STATUS_DELETING
), status
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return status in (
self.STATUS_AVAILABLE,
self.STATUS_CREATE_FAILED,
self.STATUS_DELETING
), status
return status in (
"available",
"create-failed",
"deleting"
), status

This one could be worth extracting to a class variable:

    TERMINAL_STATES = frozenset({"available", "create-failed", "deleting"})

And then this becomes

Suggested change
return status in (
self.STATUS_AVAILABLE,
self.STATUS_CREATE_FAILED,
self.STATUS_DELETING
), status
return status in self.TERMINAL_STATES, status

max_retries=None
):
"""
Check if replication is available or not by performing a describe over it
Copy link
Member

Choose a reason for hiding this comment

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

This doc talks about replication -- but is it just health of elasticache? (Because this still applies when a single node EC is used, right?)


sleep_time = sleep_time * exponential_back_off_factor

if status != self.STATUS_AVAILABLE:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if status != self.STATUS_AVAILABLE:
if status != "available":

Comment on lines 127 to 130
:param max_retries: Max tries for checking availability of replication group
:param exponential_back_off_factor: Factor for deciding next sleep time
:param initial_sleep_time: Initial sleep time in seconds
:param replication_group_id: ID of replication group to check for availability
Copy link
Member

Choose a reason for hiding this comment

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

Could you add types to these please?

Suggested change
:param max_retries: Max tries for checking availability of replication group
:param exponential_back_off_factor: Factor for deciding next sleep time
:param initial_sleep_time: Initial sleep time in seconds
:param replication_group_id: ID of replication group to check for availability
:param max_retries: Max tries for checking availability of replication group
:type max_retries: int
:param exponential_back_off_factor: Factor for deciding next sleep time
:type exponential_back_off_factor: ...
:param initial_sleep_time: Initial sleep time in seconds
:type initial_sleep_time:
:param replication_group_id: ID of replication group to check for availability
:type replication_group_id:

Same for all the other public methods too please.

response = self.delete_replication_group(replication_group_id=replication_group_id)

except self.conn.exceptions.ReplicationGroupNotFoundFault:
self.log.info("Replication group with ID : '%s' does not exist", replication_group_id)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self.log.info("Replication group with ID : '%s' does not exist", replication_group_id)
self.log.info("Replication group with ID '%s' does not exist", replication_group_id)


message = exp.response['Error']['Message']

self.log.info('Received message : %s', message)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self.log.info('Received message : %s', message)
self.log.error('Received error message from AWS API: %s', message)

Copy link
Member

Choose a reason for hiding this comment

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

(Or maybe at warning?)

Comment on lines 52 to 80
def test_create_replication_group(self):
"""
Test creation of replication group
"""
self.hook.create_replication_group.return_value = {
"ReplicationGroup": {
"ReplicationGroupId": self.replication_group_id,
"Status": "creating"
}
}
response = self.hook.create_replication_group(config=self.replication_group_config)

assert response["ReplicationGroup"]["ReplicationGroupId"] == self.replication_group_id
assert response["ReplicationGroup"]["Status"] == "creating"
Copy link
Member

Choose a reason for hiding this comment

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

This isn't testing real code here -- you are only calling a mock function but not actually calling the real code anywhere from the hook.

@VijayantSoni
Copy link
Contributor Author

Hi @kaxil @mik-laj @potiuk , unable to understand the failing test, can someone please help with it ?

@mik-laj
Copy link
Member

mik-laj commented Aug 6, 2020

@VijayantSoni It looks like fllaky test. Can you do a rebase? We have recently made changes to improve the stability of integration tests.

@potiuk
Copy link
Member

potiuk commented Aug 6, 2020

Yeah @VijayantSoni -> this particular problem (transient) is likely to be already solved, so if you rebase on top of the latest master it should succeed.

@coopergillan
Copy link
Contributor

coopergillan commented Aug 6, 2020 via email

@mik-laj
Copy link
Member

mik-laj commented Aug 6, 2020

https://github.com/apache/airflow/blob/master/CONTRIBUTING.rst#id8

A rebase is recommended as it allows us to better manage the changes. Much easier to review the history of the change if you are doing rebasse instead of merge.

@codecov-commenter
Copy link

codecov-commenter commented Aug 6, 2020

Codecov Report

Merging #8701 into master will decrease coverage by 54.70%.
The diff coverage is 19.23%.

Impacted file tree graph

@@             Coverage Diff             @@
##           master    #8701       +/-   ##
===========================================
- Coverage   89.42%   34.71%   -54.71%     
===========================================
  Files        1037     1038        +1     
  Lines       49985    49771      -214     
===========================================
- Hits        44699    17279    -27420     
- Misses       5286    32492    +27206     
Flag Coverage Δ
#kubernetes-tests-3.6-9.6 ?
#kubernetes-tests-image-3.6-v1.16.9 ?
#kubernetes-tests-image-3.6-v1.17.5 ?
#kubernetes-tests-image-3.6-v1.18.6 ?
#kubernetes-tests-image-3.7-v1.16.9 ?
#kubernetes-tests-image-3.7-v1.17.5 ?
#kubernetes-tests-image-3.7-v1.18.6 ?
#mysql-tests-Core-3.7-5.7 ?
#mysql-tests-Core-3.8-5.7 ?
#mysql-tests-Integration-3.7-5.7 ?
#mysql-tests-Integration-3.8-5.7 ?
#postgres-tests-Core-3.6-10 ?
#postgres-tests-Core-3.6-9.6 ?
#postgres-tests-Core-3.7-10 ?
#postgres-tests-Core-3.7-9.6 ?
#postgres-tests-Integration-3.6-10 34.71% <19.23%> (-0.03%) ⬇️
#postgres-tests-Integration-3.6-9.6 ?
#postgres-tests-Integration-3.7-10 34.71% <19.23%> (-0.03%) ⬇️
#postgres-tests-Integration-3.7-9.6 ?
#sqlite-tests-Core-3.6 ?
#sqlite-tests-Integration-3.6 ?
#sqlite-tests-Integration-3.8 ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
.../amazon/aws/hooks/elasticache_replication_group.py 19.23% <19.23%> (ø)
airflow/hooks/S3_hook.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/hooks/pig_hook.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/hooks/hdfs_hook.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/hooks/http_hook.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/hooks/jdbc_hook.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/contrib/__init__.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/hooks/druid_hook.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/hooks/hive_hooks.py 0.00% <0.00%> (-100.00%) ⬇️
airflow/hooks/mssql_hook.py 0.00% <0.00%> (-100.00%) ⬇️
... and 920 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 817e1ac...4c1c97c. Read the comment docs.

vijayant and others added 16 commits September 23, 2020 08:36
…g a delete request, doc string formaating, remove redundant log, minor refactor
…her types as well within ElasatiCache

Removed separate string constants for API response and replaced with hardcoded strings everywhere
Fixed tests to mock hook.conn and not hook, updated tests accordingly
changed semantics of max_retries for polling, now it means number of retries excluding first try
max_retries = 2 will mean 2 retires after the initial try
Fix name of test file to correspond with hook file name
Some python cosmetic changes
Replace occurences of elasticache-hook with elasticache-replication-group-hook
Remove doc strings from tests
Fix formatting in operators-and-hooks-ref.rst
…ing feature

Moved doc string from __init__ method to class doc string
…ture

Adding default behaviour of params to doc string
@VijayantSoni
Copy link
Contributor Author

Hi @feluelle , can you please help with understanding the static checks ?
It first asked me to convert from this:

response = self.hook.wait_for_availability(
    replication_group_id=self.REPLICATION_GROUP_ID,
    max_retries=1,
    initial_sleep_time=1,  # seconds
)

to this:

response = self.hook.wait_for_availability(
    replication_group_id=self.REPLICATION_GROUP_ID, max_retries=1, initial_sleep_time=1,  # seconds
)

and now it is asking to do the reverse (for such all cases in code). I am confused 😕

@feluelle
Copy link
Member

feluelle commented Sep 24, 2020

It might have sth. to do with the change of black version recently. See #10818

So I think yes, you have to do the reverse.

@VijayantSoni
Copy link
Contributor Author

@feluelle resolved static check issues, please have a look when you get a chance. Thanks!

Copy link
Member

@feluelle feluelle left a comment

Choose a reason for hiding this comment

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

LGTM @VijayantSoni 👍

Sorry for the late reply.

@feluelle
Copy link
Member

feluelle commented Oct 9, 2020

@ashb do you have time to review once more? (You requested changes.)

@ashb
Copy link
Member

ashb commented Oct 9, 2020

👀

@ashb ashb merged commit 422b61a into apache:master Oct 9, 2020
@boring-cyborg
Copy link

boring-cyborg bot commented Oct 9, 2020

Awesome work, congrats on your first merged pull request!

@VijayantSoni
Copy link
Contributor Author

Thank you @ashb @feluelle 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
provider:amazon-aws AWS/Amazon - related issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants