dnsdesig plugin support for Identity v3 api 85/28685/1
authorAndrew Gauld <ag1282@att.com>
Fri, 19 Jan 2018 18:53:49 +0000 (13:53 -0500)
committerAndrew Gauld <ag1282@att.com>
Fri, 19 Jan 2018 19:24:16 +0000 (14:24 -0500)
Change-Id: Ic00bf6e18abc680bd99fd02b752b9da065107eb7
Issue-ID: CCSDK-181
Signed-off-by: Andrew Gauld <ag1282@att.com>
LICENSE.txt
dnsdesig/LICENSE.txt
dnsdesig/dns_types.yaml
dnsdesig/dnsdesig/dns_plugin.py
dnsdesig/setup.py
dnsdesig/tests/test_plugin.py

index c233514..e19c6c0 100644 (file)
@@ -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.
index f90f8f1..1fedf86 100644 (file)
@@ -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.
index 9af2422..3161213 100644 (file)
@@ -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:
index ee755aa..d46468d 100644 (file)
@@ -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)
index 9450390..35578ce 100644 (file)
@@ -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.'),
index 730897a..d2b9174 100644 (file)
@@ -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')