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 module to manage AWS Storage gateways #40494

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 241 additions & 0 deletions lib/ansible/modules/cloud/amazon/storage_gateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#!/usr/bin/python

# Copyright: (c) 2018, Aaron Smith <ajsmith10381@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}


DOCUMENTATION = r'''
---
module: storage_gateway
short_description: Manage AWS Storage Gateway instances
description:
- Activate or deactivate AWS Storage Gateway instances.
version_added: "2.7"
requirements: [ 'botocore', 'boto3' ]
author:
- "Aaron Smith (@slapula)"
options:
name:
description:
- The name you configured for your gateway.
required: true
state:
description:
- Whether the resource should be present or absent.
default: present
choices: ['present', 'absent']
activation_key:
description:
- Your gateway activation key. See AWS documentation on how to acquire this key.
required: true
timezone:
description:
- A value that indicates the time zone you want to set for the gateway.
required: true
gateway_region:
description:
- A value that indicates the region where you want to store your data.
required: true
gateway_type:
description:
- A value that defines the type of gateway to activate.
- The type specified is critical to all later functions of the gateway and cannot be changed after activation.
default: 'STORED'
choices: ['STORED', 'CACHED', 'VTL', 'FILE_S3']
tape_drive_type:
description:
- The value that indicates the type of tape drive to use for tape gateway.
choices: ['IBM-ULT3580-TD5']
medium_changer_type:
description:
- The value that indicates the type of medium changer to use for tape gateway.
choices: ['STK-L700', 'AWS-Gateway-VTL']
extends_documentation_fragment:
- aws
- ec2
'''

EXAMPLES = r'''
- name: Activate file gateway
storage_gateway:
name: "example-file-gateway"
state: present
activation_key: "{{ activation_code.stdout }}"
timezone: 'GMT-6:00'
gateway_region: "{{ aws_region }}"
gateway_type: 'FILE_S3'
'''

RETURN = r'''
gateway_arn:
description: The ARN of the gateway you just created or updated.
returned: always
type: string
'''

import time
import ast
Copy link
Contributor

Choose a reason for hiding this comment

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

These imports are unused.


try:
import botocore
from botocore.exceptions import BotoCoreError, ClientError
except ImportError:
pass # handled by AnsibleAWSModule

from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.ec2 import boto3_conn, get_aws_connection_info, AWSRetry
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, boto3_tag_list_to_ansible_dict
Copy link
Contributor

Choose a reason for hiding this comment

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

Line 94 and 95 are also unused imports.



def gateway_exists(client, module, params, result):
try:
gateway_list = client.list_gateways()
for i in gateway_list['Gateways']:
if i['GatewayName'] == params['GatewayName']:
disks_response = client.list_local_disks(
GatewayARN=i['GatewayARN']
)
result['GatewayARN'] = i['GatewayARN']
result['Disks'] = disks_response['Disks']
return True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError):
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like list_gateways() returns an empty list rather than failing if no gateways exist, so I think fail_json_aws should probably be used here. Right now valid ClientError and BotoCoreError exceptions are masked. If there's a specific boto3 exception that needs to be handled, use except is_boto3_error_code(... right before line 109 .


return False


def create_gateway(client, module, params, result):
try:
gw_response = client.activate_gateway(**params)
time.sleep(15) # Need a waiter here but it doesn't exist yet in the StorageGateway API
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you verify that the gateway is active here before moving on? Maybe with https://boto3.readthedocs.io/en/latest/reference/services/storagegateway.html#StorageGateway.Client.describe_gateway_information. We've been adding waiters for things here https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/aws/waiters.py and at a glance looks like this could use that.

disks_response = client.list_local_disks(
GatewayARN=gw_response['GatewayARN']
)
if disks_response['Disks']:
if params['GatewayType'] == 'FILE_S3':
vol_response = client.add_cache(
GatewayARN=gw_response['GatewayARN'],
DiskIds=[disks_response['Disks'][0]['DiskId']]
)
if params['GatewayType'] == 'CACHED' or params['GatewayType'] == 'VTL':
vol_response = client.add_cache(
GatewayARN=gw_response['GatewayARN'],
DiskIds=[disks_response['Disks'][0]['DiskId']]
)
vol_response = client.add_upload_buffer(
GatewayARN=gw_response['GatewayARN'],
DiskIds=[disks_response['Disks'][1]['DiskId']]
)
if params['GatewayType'] == 'STORED':
vol_response = client.add_upload_buffer(
GatewayARN=gw_response['GatewayARN'],
DiskIds=[disks_response['Disks'][0]['DiskId']]
)
result['gateway_arn'] = gw_response['GatewayARN']
result['Disks'] = disks_response['Disks']
result['changed'] = True
return result
Copy link
Contributor

Choose a reason for hiding this comment

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

Unused return here and on line 149.

except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't activate storage gateway instance")

return result


def update_gateway(client, module, params, result):
current_params = client.describe_gateway_information(
GatewayARN=result['GatewayARN']
)

if params['GatewayName'] != current_params['GatewayName'] or params['GatewayTimezone'] != current_params['GatewayTimezone']:
try:
response = client.update_gateway_information(
GatewayARN=result['GatewayARN'],
GatewayName=params['GatewayName'],
GatewayTimezone=params['GatewayTimezone']
)
disks_response = client.list_local_disks(
GatewayARN=result['GatewayARN']
)
result['gateway_arn'] = response['GatewayARN']
result['Disks'] = disks_response['Disks']
result['changed'] = True
return result
Copy link
Contributor

Choose a reason for hiding this comment

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

This return and the one on line 174 don't do anything (same for returns in delete_gateway())

except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't update storage gateway")

return result


def delete_gateway(client, module, result):
try:
response = client.delete_gateway(
GatewayARN=result['GatewayARN']
)
result['changed'] = True
return result
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't delete storage gateway")

return result


def main():
module = AnsibleAWSModule(
argument_spec={
'name': dict(type='str', required=True),
'state': dict(type='str', choices=['present', 'absent'], default='present'),
'activation_key': dict(type='str', required=True),
'timezone': dict(type='str', required=True),
'gateway_region': dict(type='str', required=True),
'gateway_type': dict(type='str', default='STORED', choices=['STORED', 'CACHED', 'VTL', 'FILE_S3']),
'tape_drive_type': dict(type='str', choices=['IBM-ULT3580-TD5']),
'medium_changer_type': dict(type='str', choices=['STK-L700', 'AWS-Gateway-VTL']),
},
supports_check_mode=False,
)

result = {
'changed': False,
'gateway_arn': ''
}

desired_state = module.params.get('state')

params = {}
params['GatewayName'] = module.params.get('name')
params['ActivationKey'] = module.params.get('activation_key')
params['GatewayTimezone'] = module.params.get('timezone')
params['GatewayRegion'] = module.params.get('gateway_region')
params['GatewayType'] = module.params.get('gateway_type')
if module.params.get('tape_drive_type'):
params['TapeDriveType'] = module.params.get('tape_drive_type')
if module.params.get('medium_changer_type'):
params['MediumChangerType'] = module.params.get('medium_changer_type')

client = module.client('storagegateway')

gateway_status = gateway_exists(client, module, params, result)

if desired_state == 'present':
if not gateway_status:
create_gateway(client, module, params, result)
if gateway_status:
update_gateway(client, module, params, result)

if desired_state == 'absent':
if gateway_status:
delete_gateway(client, module, result)

module.exit_json(changed=result['changed'], gateway_arn=result['gateway_arn'])
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a blocker, but it seems like returning tuples could be an alternative to passing around a reference to a result dictionary that gets modified and not returned.



if __name__ == '__main__':
main()
2 changes: 2 additions & 0 deletions test/integration/targets/storage_gateway/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cloud/aws
posix/ci/cloud/group4/aws
39 changes: 39 additions & 0 deletions test/integration/targets/storage_gateway/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
# defaults file for ec2_instance
ec2_instance_name: '{{resource_prefix}}-node'
ec2_instance_owner: 'integration-run-{{resource_prefix}}'
ec2_file_ami_image:
# amazon/aws-storage-gateway-file-2018-03-21
Copy link
Contributor

Choose a reason for hiding this comment

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

If there's a way to filter for these you could use ec2_ami_facts so we always get the latest ones.

ap-northeast-1: ami-798cdc1f
ap-northeast-2: ami-d10ca0bf
ap-south-1: ami-e238638d
ap-southeast-1: ami-48114d34
ap-southeast-2: ami-47d41925
ca-central-1: ami-edd95f89
eu-central-1: ami-9aa9f971
eu-west-1: ami-10f1a569
eu-west-2: ami-4c34d22b
eu-west-3: ami-38388e45
sa-east-1: ami-0f481c63
us-east-1: ami-6e14c313
us-east-2: ami-7845741d
us-west-1: ami-ba9483da
us-west-2: ami-be21bdc6
ec2_vol_vtl_ami_image:
# amazon/aws-storage-gateway-2.0.9.2
ap-northeast-1: ami-0a8bcd6c
ap-northeast-2: ami-6667ca08
ap-south-1: ami-c04718af
ap-southeast-1: ami-37175c4b
ap-southeast-2: ami-95d91ff7
ca-central-1: ami-7e880f1a
eu-central-1: ami-71ec811e
eu-west-1: ami-dc82c7a5
eu-west-2: ami-4d64802a
eu-west-3: ami-2b66d056
sa-east-1: ami-ec165d80
us-east-1: ami-571ff42a
us-east-2: ami-a7facdc2
us-west-1: ami-68e7ec08
us-west-2: ami-989119e0
us-gov-west-1: ami-9ce277fd
3 changes: 3 additions & 0 deletions test/integration/targets/storage_gateway/meta/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies:
- prepare_tests
- setup_ec2
Loading