From 89978d130541937c9b5cdc8a0cfe57bc2a430bfc Mon Sep 17 00:00:00 2001 From: Andrew Gauld Date: Fri, 19 Jan 2018 13:53:49 -0500 Subject: dnsdesig plugin support for Identity v3 api Change-Id: Ic00bf6e18abc680bd99fd02b752b9da065107eb7 Issue-ID: CCSDK-181 Signed-off-by: Andrew Gauld --- LICENSE.txt | 2 +- dnsdesig/LICENSE.txt | 2 +- dnsdesig/dns_types.yaml | 4 +- dnsdesig/dnsdesig/dns_plugin.py | 89 +++++++++++++++++---- dnsdesig/setup.py | 4 +- dnsdesig/tests/test_plugin.py | 168 +++++++++++++++++++++++++++++++++------- 6 files changed, 218 insertions(+), 51 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index c233514..e19c6c0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ ===========LICENSE_START========================================== =================================================================== -Copyright © 2017 AT&T Intellectual Property. All rights reserved. +Copyright © 2018 AT&T Intellectual Property. All rights reserved. =================================================================== Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/dnsdesig/LICENSE.txt b/dnsdesig/LICENSE.txt index f90f8f1..1fedf86 100644 --- a/dnsdesig/LICENSE.txt +++ b/dnsdesig/LICENSE.txt @@ -1,7 +1,7 @@ ============LICENSE_START======================================================= org.onap.ccsdk ================================================================================ -Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. ================================================================================ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/dnsdesig/dns_types.yaml b/dnsdesig/dns_types.yaml index 9af2422..3161213 100644 --- a/dnsdesig/dns_types.yaml +++ b/dnsdesig/dns_types.yaml @@ -1,7 +1,7 @@ # ============LICENSE_START==================================================== # org.onap.ccsdk # ============================================================================= -# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. # ============================================================================= # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ plugins: dns_designate: executor: central_deployment_agent package_name: dnsdesig - package_version: 1.0.0 + package_version: 1.0.1 node_types: ccsdk.nodes.dns.arecord: diff --git a/dnsdesig/dnsdesig/dns_plugin.py b/dnsdesig/dnsdesig/dns_plugin.py index ee755aa..d46468d 100644 --- a/dnsdesig/dnsdesig/dns_plugin.py +++ b/dnsdesig/dnsdesig/dns_plugin.py @@ -1,7 +1,7 @@ # ============LICENSE_START==================================================== # org.onap.ccsdk # ============================================================================= -# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. # ============================================================================= # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -30,23 +30,82 @@ def _check_status(resp, msg): raise NonRecoverableError(msg) def _get_auth_info(openstack): + if openstack['auth_url'].endswith('/v2.0'): + (tok, gbls, urls) = _get_auth_info_v2(openstack) + else: + (tok, gbls, urls) = _get_auth_info_v3(openstack) + if len(urls.keys()) == 1: + reg = urls.keys()[0] + else: + reg = openstack['region'] + if reg in urls and 'dns' in urls[reg]: + url = urls[reg]['dns'] + elif 'dns' in gbls: + url = gbls['dns'] + else: + raise NonRecoverableError('DNS service not found') + return { 'osauth': { 'X-Auth-Token': tok }, 'dns': url } + +def _get_auth_info_v3(openstack): + domain = openstack['domain'] if 'domain' in openstack else 'default' + resp = requests.post('{0}/auth/tokens'.format(openstack['auth_url']), json={ + 'auth': { + 'identity': { + 'methods': [ + 'password' + ], + 'password': { + 'user': { + 'name': openstack['username'], + 'domain': { + 'id': domain + }, + 'password': openstack['password'] + } + } + }, + 'scope': { + 'project': { + 'name': openstack['tenant_name'], + 'domain': { + 'id': domain + } + } + } + } + }) + _check_status(resp, 'Failed to get authorization token from OpenStack identity service v3') + gbls = {} + urls = {} + for sc in resp.json()['token']['catalog']: + type = sc['type'] + for ep in sc['endpoints']: + if 'region' in ep and ep['region'] not in urls: + urls[ep['region']] = {} + if ep['interface'] == 'public' and ep['url'] != '': + if 'region' not in ep: + gbls[type] = ep['url'] + else: + urls[ep['region']][type] = ep['url'] + return (resp.headers['X-Subject-Token'], gbls, urls) + +def _get_auth_info_v2(openstack): resp = requests.post('{0}/tokens'.format(openstack['auth_url']), json={'auth':{'tenantName':openstack['tenant_name'],'passwordCredentials':{'username':openstack['username'], 'password':openstack['password']}}}) - _check_status(resp, 'Failed to get authorization token from OpenStack identity service') + _check_status(resp, 'Failed to get authorization token from OpenStack identity service v2') respj = resp.json()['access'] - osauth={'X-Auth-Token': respj['token']['id'] } + gbls = {} urls = {} for se in respj['serviceCatalog']: type = se['type'] for ep in se['endpoints']: - url = ep['publicURL'] - reg = ep['region'] - if not urls.has_key(reg): - urls[reg] = { } - if type not in urls[reg] or urls[reg][type] == '': - urls[reg][type] = url - if len(urls.keys()) == 1: - openstack['region'] = urls.keys()[0] - return { 'osauth': osauth, 'dns': urls[openstack['region']]['dns'] } + if 'region' in ep and ep['region'] not in urls: + urls[ep['region']] = {} + if 'publicURL' in ep and ep['publicURL'] != '': + if 'region' not in ep: + gbls[type] = ep['publicURL'] + else: + urls[ep['region']][type] = ep['publicURL'] + return (respj['token']['id'], gbls, urls) def _dot(fqdn): """ @@ -86,7 +145,7 @@ def aneeded(**kwargs): """ try: _doneed('A', kwargs['args']['ip_addresses']) - except NonRecoverableError as nre: + except (NonRecoverableError, RecoverableError) as nre: raise nre except Exception as e: raise NonRecoverableError(e) @@ -105,7 +164,7 @@ def cnameneeded(**kwargs): """ try: _doneed('CNAME', [ _dot(kwargs['args']['cname']) ] ) - except NonRecoverableError as nre: + except (NonRecoverableError, RecoverableError) as nre: raise nre except Exception as e: raise NonRecoverableError(e) @@ -145,7 +204,7 @@ def _noneed(type): if rs: resp = requests.delete('{0}/v2/zones/{1}/recordsets/{2}'.format(access['dns'], zid, rs['id']), headers=access['osauth']) _check_status(resp, 'Failed to delete DNS record set for {0}'.format(fqdn)) - except NonRecoverableError as nre: + except (NonRecoverableError, RecoverableError) as nre: raise nre except Exception as e: raise NonRecoverableError(e) diff --git a/dnsdesig/setup.py b/dnsdesig/setup.py index 9450390..35578ce 100644 --- a/dnsdesig/setup.py +++ b/dnsdesig/setup.py @@ -1,7 +1,7 @@ # ============LICENSE_START==================================================== # org.onap.ccsdk # ============================================================================= -# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. # ============================================================================= # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ from setuptools import setup, find_packages setup( name='dnsdesig', - version='1.0.0', + version='1.0.1', packages=find_packages(), author='AT&T', description=('Cloudify plugin for creating DNS entries using Designate.'), diff --git a/dnsdesig/tests/test_plugin.py b/dnsdesig/tests/test_plugin.py index 730897a..d2b9174 100644 --- a/dnsdesig/tests/test_plugin.py +++ b/dnsdesig/tests/test_plugin.py @@ -1,7 +1,7 @@ # ============LICENSE_START==================================================== # org.onap.ccsdk # ============================================================================= -# Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. # ============================================================================= # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,17 +22,23 @@ import dnsdesig.dns_plugin from cloudify.mocks import MockCloudifyContext from cloudify.state import current_ctx from cloudify.exceptions import NonRecoverableError +from cloudify.exceptions import RecoverableError from cloudify import ctx class _resp(object): - def __init__(self, code, body = None): + def __init__(self, code, body = None, rhdrs = None): self.status_code = code + if rhdrs is not None: + self.headers = rhdrs if body is not None: self._json = body def json(self): return self._json + def rhdrs(self): + return self.headers + def _same(a, b): t1 = type(a) t2 = type(b) @@ -74,6 +80,7 @@ class _req(object): _nf = _resp(404) _ar = _resp(401) _np = _resp(403) +_svcunavail = _resp(503) _ok = _resp(200, { 'something': 'or-other' }) _tok = 'at' @@ -81,7 +88,7 @@ _tok = 'at' _hdrs = { 'X-Auth-Token': _tok } _goodos = { - 'auth_url': 'https://example.com/identity', + 'auth_url': 'https://example.com/identity/v3', 'password': 'pw', 'region': 'r', 'tenant_name': 'tn', @@ -89,7 +96,23 @@ _goodos = { } _bados = { - 'auth_url': 'https://example.com/identity', + 'auth_url': 'https://example.com/identity/v3', + 'password': 'xx', + 'region': 'r', + 'tenant_name': 'tn', + 'username': 'un' +} + +_goodosv2 = { + 'auth_url': 'https://example.com/identity/v2.0', + 'password': 'pw', + 'region': 'r', + 'tenant_name': 'tn', + 'username': 'un' +} + +_badosv2 = { + 'auth_url': 'https://example.com/identity/v2.0', 'password': 'xx', 'region': 'r', 'tenant_name': 'tn', @@ -98,21 +121,79 @@ _bados = { _answers = [ - # Authenticate - _req('POST', 'https://example.com/identity/tokens', headers=None, resp=_resp(200, { + # Authenticate v3 + _req('POST', 'https://example.com/identity/v3/auth/tokens', headers=None, resp=_resp(200, { + 'token': { + 'catalog': [ + { + 'type': 'dns', + 'endpoints': [ + { + 'interface': 'public', + 'region': 'r2', + 'url': 'https://example.com/invalid2' + }, + { + 'interface': 'public', + 'region': 'r3', + 'url': 'https://example.com/invalid3' + }, + { + 'interface': 'public', + 'url': 'https://example.com/dns' + } + ] + } + ] + } + }, rhdrs = { + 'X-Subject-Token': _tok + }), json={ + 'auth': { + 'identity': { + 'methods': [ + 'password' + ], + 'password': { + 'user': { + 'name': 'un', + 'domain': { + 'id': 'default' + }, + 'password': 'pw' + } + } + }, + 'scope': { + 'project': { + 'name': 'tn', + 'domain': { + 'id': 'default' + } + } + } + } + }), + # Invalid authentication v3 + _req('POST', 'https://example.com/identity/v3/auth/tokens', headers=None, resp=_np), + # Authenticate v2.0 + _req('POST', 'https://example.com/identity/v2.0/tokens', headers=None, resp=_resp(200, { 'access': { 'token': { 'id': _tok }, 'serviceCatalog': [ { - 'type': 'dns', - 'endpoints': [ + 'type': 'dns', + 'endpoints': [ + { + 'publicURL': 'https://example.com/dns', + 'region': 'r' + }, { - 'publicURL': 'https://example.com/dns', - 'region': 'r' + 'publicURL': 'https://example.com/otherregions' } - ] - } + ] + } ] } }), json={ @@ -120,18 +201,18 @@ _answers = [ 'tenantName': 'tn', 'passwordCredentials': { 'username': 'un', - 'password': 'pw' + 'password': 'pw' } } }), - # Invalid authentication - _req('POST', 'https://example.com/identity/tokens', headers=None, resp=_np), + # Invalid authentication v2.0 + _req('POST', 'https://example.com/identity/v2.0/tokens', headers=None, resp=_np), # Get zones _req('GET', 'https://example.com/dns/v2/zones', headers=_hdrs, resp=_resp(200, { 'zones': [ { 'name': 'x.example.com.', - 'id': 'z1' + 'id': 'z1' } ] })), @@ -139,22 +220,30 @@ _answers = [ _req('GET', 'https://example.com/dns/v2/zones/z1/recordsets?limit=1000', headers=_hdrs, resp=_resp(200, { 'recordsets': [ { - 'id': 'ar1', + 'id': 'ar1', 'type': 'A', - 'name': 'a.x.example.com.', - 'ttl': 300, - 'records': [ - '87.65.43.21', - '98.76,54.32' - ] + 'name': 'a.x.example.com.', + 'ttl': 300, + 'records': [ + '87.65.43.21', + '98.76,54.32' + ] + }, { + 'id': 'cname1', + 'type': 'CNAME', + 'name': 'c.x.example.com.', + 'ttl': 300, + 'records': [ + 'a.x.example.com.' + ] }, { - 'id': 'cname1', + 'id': 'noservice', 'type': 'CNAME', - 'name': 'c.x.example.com.', - 'ttl': 300, - 'records': [ - 'a.x.example.com.' - ] + 'name': 'noservice.x.example.com.', + 'ttl': 300, + 'records': [ + 'a.x.example.com.' + ] } ] })), @@ -196,7 +285,9 @@ _answers = [ # Delete A recordset _req('DELETE', 'https://example.com/dns/v2/zones/z1/recordsets/ar1', headers=_hdrs, resp=_ok), # Delete CNAME recordset - _req('DELETE', 'https://example.com/dns/v2/zones/z1/recordsets/cname1', headers=_hdrs, resp=_ok) + _req('DELETE', 'https://example.com/dns/v2/zones/z1/recordsets/cname1', headers=_hdrs, resp=_ok), + # service unavailable + _req('DELETE', 'https://example.com/dns/v2/zones/z1/recordsets/noservice', headers=_hdrs, resp=_svcunavail) ] def _match(op, url, headers, json = None): @@ -237,6 +328,15 @@ def _setup(os, fqdn, ttl=None): return newfcn return fcnbuilder +@_setup(_badosv2, 'a.x.example.com') +def test_dns_badauthv2(): + with pytest.raises(NonRecoverableError): + dnsdesig.dns_plugin.anotneeded() + +@_setup(_goodosv2, 'a.x.example.com') +def test_dns_goodauthv2(): + dnsdesig.dns_plugin.anotneeded() + @_setup(_bados, 'a.x.example.com') def test_dns_badauth(): with pytest.raises(NonRecoverableError): @@ -270,3 +370,11 @@ def test_dns_modcnamerecord(): @_setup(_goodos, 'c.x.example.com') def test_dns_delcname(): dnsdesig.dns_plugin.cnamenotneeded() + +@_setup(_goodos, 'noservice.x.example.com') +def test_dns_delcname(): + with pytest.raises(RecoverableError): + dnsdesig.dns_plugin.cnamenotneeded() + +def test_module_logger(): + dnsdesig.get_module_logger('dnsdesig') -- cgit 1.2.3-korg