Skip to content
This repository has been archived by the owner on Oct 30, 2018. It is now read-only.

Create os_keystone_endpoint.py #2994

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
334 changes: 334 additions & 0 deletions cloud/openstack/os_keystone_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
#!/usr/bin/python

# Copyright (c) 2016 Helicom
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we get a contact information on this, in case of future relicensing requests?

#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.

try:
import shade
HAS_SHADE = True
except ImportError:
HAS_SHADE = False

from distutils.version import StrictVersion


DOCUMENTATION = '''
---
module: os_api_endpoint
Copy link
Contributor

Choose a reason for hiding this comment

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

os_keystone_endpoint

short_description: Create, update or delete API endpoints.
extends_documentation_fragment: openstack
author: "Michel Labarre (@mlabarre)"
version_added: "2.2"
description:
- Add, update or delete API endpoints.
- V2.0 and V3 API version are supported. So arguments as interface,
url, public_url, admin_url and internal_url are supported. But
- for V2 version, only public_url, internal_url and admin_url
can be specified (not interface, neither url).
- for V3 version, only interface AND url can be specified
(no public_url, internal_url and admin_url).
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it not possible to just take in URL and interface as module arguments, and then internally or through shade decide how to represent the data to the API? The input to the module shouldn't change. The admin still has to provide a URL and some indication of what type of URL it is. I don't think the arguments to the ansible module should change depending on API version.

Copy link

Choose a reason for hiding this comment

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

Correct j2sol, this should be using ansibles mutually exclusive options. The options can be either public_url, admin_url, internal_url OR interface and url. Both of these types work with either v2.0 or v3. There is no version dependency on making them work.

options:
service_name:
description:
- Name for the target service (ie 'keystone', etc.).
required: true
service_type:
description:
- type for the target service (ie 'identity', etc.).
required: true
interface:
description:
- Endpoint type (ie 'public', 'internal', 'admin') (v3 format).
Copy link

Choose a reason for hiding this comment

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

all references to "v3 format" or "v2 format" should be removed

required: false
url:
description:
- Endpoint URL (v3 format).
required: false
public_url:
description:
- Public endpoint URL (v2 format).
required: true
internal_url:
description:
- Internal endpoint URL (v2 format).
required: false
admin_url:
description:
- Admin endpoint URL (v2 format).
required: false
region:
description:
- Region name or id.
required: false
enabled:
description:
- Enable endpoint.
required: false
default: true
state:
description:
- Should the endpoint be present or absent on the user.
choices: [present, absent]
default: present
requirements:
- "python >= 2.6"
- "shade"
'''

EXAMPLES = '''
# Some examples contain a 'cloud' attribute.
# This attribute is not required if you work always with the same API version.

# Add/Update an endpoint internal url with v3 version
- os_keystone_endpoint:
cloud: cloud_v3_api
service_name: keystone
service_type: identity
interface: internal
region: myregion
enabled: True
url: "http://server:5000/v3"
state: present

# Add all endpoints public, admin, internal
- os_keystone_endpoint:
cloud: cloud_v3_api
service_name: keystone
service_type: identity
interface: "{{ item.interface }}"
url: "{{ item.interface }}"
region: "{{ item.region }}"
enabled: "{{ item.enabled }}"
state: present
with_items:
- { interface: 'public', url: 'http://server:5000',
region: 'myregion', enabled: true }
- { interface: 'internal', url: 'http://server:5000',
region: 'myregion', enabled: true }
- { interface: 'admin', url: 'http://server:35357',
region: 'myregion', enabled: true }

# Add/Update an endpoint with v2.0 version
- os_keystone_endpoint:
cloud: cloud_v2_api
service_name: keystone
service_type: identity
public_url: "http://server:5000/v2.0"
internal_url: "http://server:5000/v2.0"
admin_url: "http://server:35357/v2.0"
region: myregion
state: present

# Remove an endpoint with v3 version
- os_keystone_endpoint:
cloud: cloud_v3_api
service_name: keystone
service_type: identity
interface_type: internal
url: "http://internal:5000"
region: myregion
state: absent

# Remove an endpoint with v2 version
- os_keystone_endpoint:
service_name: keystone
service_type: identity
public_url: "http://server:5000/v2.0"
internal_url: "http://server:5000/v2.0"
admin_url: "http://server:35357/v2.0"
region: myregion
state: absent

'''

RETURN = '''
#
Copy link

Choose a reason for hiding this comment

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

commenting so its not lost, im sure you are just waiting, but this should be filled out before merge

'''

def get_endpoint(service, interface):
"""
Retrieve existing endpoint(s) depending on service and interface values.
:param service: Service id.
:param interface: Interface (will be None with v2.0).
:return: Endpoint entry.
"""
if interface:
endpoints_list = cloud.search_endpoints(
filters={'service_id': service, 'interface': interface})
else:
endpoints_list = cloud.search_endpoints(
filters={'service_id': service})
if len(endpoints_list) > 1:
module.fail_json(msg='Multiple endpoint entries exist service %s.'
% service)
return endpoints_list[0] if len(endpoints_list) == 1 else None


def main():

global cloud, module
Copy link

Choose a reason for hiding this comment

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

please do not use globals


argument_spec = openstack_full_argument_spec(
service_name=dict(required=True),
service_type=dict(required=True),
interface=dict(required=False),
url=dict(required=False),
admin_url=dict(required=False),
public_url=dict(required=False),
internal_url=dict(required=False),
region=dict(required=False),
enabled=dict(default=True, required=False),
Copy link

Choose a reason for hiding this comment

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

set type='bool'

state=dict(default='present', choices=['absent', 'present'])
)

module_kwargs = openstack_module_kwargs(
required_one_of=[
['url', 'admin_url', 'internal_url', 'public_url']
],
mutually_exclusive=[
['url', 'admin_url'],
['url', 'internal_url'],
['url', 'public_url'],
],
required_together=[['interface', 'url']]
)

module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
# role grant/revoke API introduced in 1.11.0
Copy link

Choose a reason for hiding this comment

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

this comment is not accurate, specifically it should be that update was added in 1.11.0 and v3 support was fully implemented. But i dont think the comment is needed at all

if not HAS_SHADE or \
Copy link

Choose a reason for hiding this comment

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

For consistency, please have this section read:

if not HAS_SHADE:
    module.fail_json(msg='shade is required for this module')
if StrictVersion(shade.__version__) < StrictVersion('1.11.0'):
    module.fail_json(msg="To utilize this module, the installed version of"
                         "the shade library MUST be >=1.11.0")

(StrictVersion(shade.__version__) < StrictVersion('1.11.0')):
module.fail_json(msg='shade 1.11.0 or higher is required for this module')

try:

cloud = shade.operator_cloud(**module.params)

service_name = module.params.get('service_name', None)
service_type = module.params.get('service_type', None)
interface = module.params.get('interface', None)
url = module.params.get('url', None)
region = module.params.get('region', None)
enabled = "True" == module.params.get('enabled', True)
Copy link

Choose a reason for hiding this comment

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

the bool type if set correctly will prevent the need to do this

state = module.params.get('state', None)
admin_url = module.params.get('admin_url', None)
internal_url = module.params.get('internal_url', None)
public_url = module.params.get('public_url', None)

services_list = cloud.search_services(
filters={'name': service_name, 'type': service_type})
if len(services_list) != 1:
module.fail_json(
msg='Service %s not found or more than 1 service have this name'
% service_name)
# Only service id is capable to identify unique entry.
service_id = services_list[0].id

current_endpoint = get_endpoint(service_id, interface)

if current_endpoint is None and state == 'absent':
module.exit_json(changed=False)
Copy link

@ghost ghost Sep 22, 2016

Choose a reason for hiding this comment

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

See the functions named '_system_state_change' and '_needs_update' in the other ansible shade modules and how those are structured. Please implement that here (rather than in the main() function)


# Check if changes.
changed = False
if current_endpoint is None:
changed = True
else:
if url and (enabled is not None
Copy link

Choose a reason for hiding this comment

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

this logic is not correct since you can pass interface+url to a v2.0 endpoint through shade (shade handles that on the backed). Therefore it is not safe to assume just because the url is set this will be a v2.0 endpoint like you appear to be

and current_endpoint.enabled != enabled):
changed = True
if hasattr(current_endpoint, 'region') \
and current_endpoint.region != region or \
not hasattr(current_endpoint, 'region') \
and region is not None:
changed = True
if url:
if current_endpoint.url != url:
changed = True
else:
if hasattr(current_endpoint, 'internalurl') \
and current_endpoint.internalurl != internal_url \
or not hasattr(current_endpoint, 'internalurl') \
and internal_url is not None:
changed = True
if hasattr(current_endpoint, 'adminurl') \
and current_endpoint.adminurl != admin_url \
or not hasattr(current_endpoint, 'adminurl') \
and admin_url is not None:
changed = True
if hasattr(current_endpoint, 'publicurl') \
and current_endpoint.publicurl != public_url \
or not hasattr(current_endpoint, 'publicurl') \
and public_url is not None:
changed = True

# Handle check mode.
if module.check_mode:
if changed and state == "absent":
changed = False
module.exit_json(changed=changed)

args = {}
for endpoint in ["public_url", "internal_url", "admin_url"]:
args[endpoint] = module.params.get(endpoint)

# Do changes.
if changed:
if state == "present":
if current_endpoint is None:
cloud.create_endpoint(service_id,
Copy link

Choose a reason for hiding this comment

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

due to the way shade works, it is safe to pass interface, url, public_url, admin_url, and internal_url for each request, as long as they are set to None. That just means the logic for mutually_exclusive bits must be handled appropriately in the ansible module and then we can safely pass them all through.

interface=interface,
url=url,
region=region,
enabled=enabled,
**args)
changed = True
else:
# Update endpoints.
# Shade endpoint update is only possible in API V3.
# So we can only use interface, url and region
if url:
cloud.update_endpoint(current_endpoint.id,
url=url,
interface=interface,
region=region)
else:
# No update possible in V2.0. So we must delete
# and recreate.
cloud.delete_endpoint(current_endpoint.id)
Copy link

Choose a reason for hiding this comment

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

This prevents the module from being idempotent. We cannot do this. Since this is a module that could only be used by cloud operators it is expected they should know if they are using v2.0 and v3, and the appropriate capabilities. Therefore, if shade returns and error letting them know the cloud doesnt support this feature, it should be passed to the user.

cloud.create_endpoint(service_id,
interface=interface,
url=url,
region=region,
enabled=enabled,
**args)
else:
changed = False
else:
if state == 'absent':
cloud.delete_endpoint(current_endpoint.id)
changed = True

module.exit_json(changed=changed)

except shade.OpenStackCloudException as e:
module.fail_json(msg=str(e))

from ansible.module_utils.basic import *
from ansible.module_utils.openstack import *

if __name__ == '__main__':
main()