Skip to content

Commit

Permalink
Merge pull request #49977 from slivik/fix-keystone-api-detection-for-…
Browse files Browse the repository at this point in the history
…nova

Fix nova module cooperation with python-novaclient
  • Loading branch information
Nicole Thomas authored Oct 11, 2018
2 parents 1dc126a + 5637d9c commit 97e859b
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 26 deletions.
50 changes: 35 additions & 15 deletions salt/modules/nova.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,17 @@
keystone.user: admin
keystone.password: verybadpass
keystone.tenant: admin
keystone.auth_url: 'http://127.0.0.1:5000/v3/'
keystone.use_keystoneauth: true
keystone.auth_url: 'http://127.0.0.1:5000'
keystone.use_keystoneauth: True
keystone.region_name: RegionOne
keystone.project_id: befcf35ea5094743b81ee12fb43484f5
keystone.user_domain_name: Default
# Optional
keystone.verify: '/path/to/custom/certs/ca-bundle.crt'
.. note::
Auto detection of API version is added so there is no need to add /v3
to auth_url.
.. note::
By default the nova module will attempt to verify its connection
Expand All @@ -67,12 +74,19 @@
path to a bundle or CA certs to check against, or None to allow
keystoneauth to search for the certificates on its own. (defaults to
True)
'''
from __future__ import absolute_import, unicode_literals, print_function

# Import python libs
import logging

# Import salt libs
try:
import salt.utils.openstack.nova as suon
HAS_NOVA = True
except ImportError as exc:
HAS_NOVA = False

# Get logging started
log = logging.getLogger(__name__)
Expand All @@ -82,19 +96,19 @@
'list_': 'list'
}

try:
import salt.utils.openstack.nova as suon
HAS_NOVA = True
except NameError as exc:
HAS_NOVA = False
# Define the module's virtual name
__virtualname__ = 'nova'


def __virtual__():
'''
Only load this module if nova
is installed on this minion.
'''
return HAS_NOVA
if HAS_NOVA:
return __virtualname__
return (False, 'The nova execution module failed to load: '
'only available if nova client is installed.')


__opts__ = {}
Expand All @@ -114,7 +128,6 @@ def _auth(profile=None):
api_key = credentials.get('keystone.api_key', None)
os_auth_system = credentials.get('keystone.os_auth_system', None)
use_keystoneauth = credentials.get('keystone.use_keystoneauth', False)
verify = credentials.get('keystone.verify', None)
else:
user = __salt__['config.option']('keystone.user')
password = __salt__['config.option']('keystone.password')
Expand All @@ -124,16 +137,23 @@ def _auth(profile=None):
api_key = __salt__['config.option']('keystone.api_key')
os_auth_system = __salt__['config.option']('keystone.os_auth_system')
use_keystoneauth = __salt__['config.option']('keystone.use_keystoneauth')
verify = __salt__['config.option']('keystone.verify')

if use_keystoneauth is True:
project_domain_name = credentials['keystone.project_domain_name']
user_domain_name = credentials['keystone.user_domain_name']
if profile:
project_id = credentials.get('keystone.project_id', None)
user_domain_name = credentials.get('keystone.user_domain_name', None)
project_domain_name = credentials.get('keystone.project_domain_name', None)
verify = credentials.get('keystone.verify', None)
else:
project_id = __salt__['config.option']('keystone.project_id')
user_domain_name = __salt__['config.option']('keystone.user_domain_name')
project_domain_name = __salt__['config.option']('keystone.project_domain_name')
verify = __salt__['config.option']('keystone.verify')

kwargs = {
'username': user,
'password': password,
'project_id': tenant,
'project_id': project_id,
'auth_url': auth_url,
'region_name': region_name,
'use_keystoneauth': use_keystoneauth,
Expand Down Expand Up @@ -345,7 +365,7 @@ def volume_attach(name,
.. code-block:: bash
salt '*' nova.volume_attach myblock slice.example.com profile=openstack
salt '*' nova.volume_attach myblock server.example.com device=/dev/xvdb profile=openstack
salt '*' nova.volume_attach myblock server.example.com device='/dev/xvdb' profile=openstack
'''
conn = _auth(profile)
Expand Down Expand Up @@ -516,7 +536,7 @@ def keypair_add(name, pubfile=None, pubkey=None, profile=None):
.. code-block:: bash
salt '*' nova.keypair_add mykey pubfile=/home/myuser/.ssh/id_rsa.pub
salt '*' nova.keypair_add mykey pubfile='/home/myuser/.ssh/id_rsa.pub'
salt '*' nova.keypair_add mykey pubkey='ssh-rsa <key> myuser@mybox'
'''
conn = _auth(profile)
Expand Down
72 changes: 61 additions & 11 deletions salt/utils/openstack/nova.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import inspect
import logging
import time
import re
import json

# Import third party libs
from salt.ext import six
Expand All @@ -18,7 +20,6 @@
from novaclient import client
from novaclient.shell import OpenStackComputeShell
import novaclient.utils
import novaclient.auth_plugin
import novaclient.exceptions
import novaclient.extension
import novaclient.base
Expand Down Expand Up @@ -64,20 +65,25 @@


def check_nova():
'''
Check version of novaclient
'''
if HAS_NOVA:
novaclient_ver = _LooseVersion(novaclient.__version__)
min_ver = _LooseVersion(NOVACLIENT_MINVER)
max_ver = _LooseVersion(NOVACLIENT_MAXVER)
if min_ver <= novaclient_ver <= max_ver:
if min_ver <= novaclient_ver:
return HAS_NOVA
elif novaclient_ver > max_ver:
log.debug('Older novaclient version required. Maximum: %s',
NOVACLIENT_MAXVER)
return False
log.debug('Newer novaclient version required. Minimum: %s',
NOVACLIENT_MINVER)
return False

if check_nova():
try:
import novaclient.auth_plugin
except ImportError:
log.debug('Using novaclient version 7.0.0 or newer. Authentication '
'plugin auth_plugin.py is not available anymore.')


# kwargs has to be an object instead of a dictionary for the __post_parse_arg__
class KwargsStruct(object):
Expand Down Expand Up @@ -199,7 +205,7 @@ def get_endpoint_url_v3(catalog, service_type, region_name):
if service_entry['type'] == service_type:
for endpoint_entry in service_entry['endpoints']:
if (endpoint_entry['region'] == region_name and
endpoint_entry['interface'] == 'public'):
endpoint_entry['interface'] == 'public'):
return endpoint_entry['url']
return None

Expand Down Expand Up @@ -259,9 +265,44 @@ def __init__(
os_auth_plugin=os_auth_plugin,
**kwargs)

def _new_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, auth=None, verify=True, **kwargs):
def _get_version_from_url(self, url):
'''
Exctract API version from provided URL
'''
regex = re.compile(r"^https?:\/\/.*\/(v[0-9])(\.[0-9])?(\/)?$")
try:
ver = regex.match(url)
if ver.group(1):
retver = ver.group(1)
if ver.group(2):
retver = retver + ver.group(2)
return retver
except AttributeError:
return ''

def _discover_ks_version(self, url):
'''
Keystone API version discovery
'''
result = salt.utils.http.query(url, backend='requests', status=True, decode=True, decode_type='json')
versions = json.loads(result['body'])
try:
links = [ver['links'] for ver in versions['versions']['values'] if ver['status'] == 'stable'][0] \
if result['status'] == 300 else versions['version']['links']
resurl = [link['href'] for link in links if link['rel'] == 'self'][0]
return self._get_version_from_url(resurl)
except KeyError as exc:
raise SaltCloudSystemExit('KeyError: key {0} not found in API response: {1}'.format(exc, versions))

def _new_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, auth=None, **kwargs):
if auth is None:
auth = {}
verify = kwargs.get('verify', False)

ks_version = self._get_version_from_url(auth_url)
if not ks_version:
ks_version = self._discover_ks_version(auth_url)
auth_url = '{0}/{1}'.format(auth_url, ks_version)

loader = keystoneauth1.loading.get_plugin_loader(os_auth_plugin or 'password')

Expand All @@ -278,6 +319,7 @@ def _new_init(self, username, project_id, auth_url, region_name, password, os_au

self.kwargs['username'] = username
self.kwargs['project_name'] = project_id
self.kwargs['project_id'] = project_id
self.kwargs['auth_url'] = auth_url
self.kwargs['password'] = password
if auth_url.endswith('3'):
Expand All @@ -303,8 +345,16 @@ def _new_init(self, username, project_id, auth_url, region_name, password, os_au
conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
self.kwargs['auth_token'] = conn.client.session.get_token()
identity_service_type = kwargs.get('identity_service_type', 'identity')
self.catalog = conn.client.session.get('/auth/catalog', endpoint_filter={'service_type': identity_service_type}).json().get('catalog', [])
if conn.client.get_endpoint(service_type=identity_service_type).endswith('v3'):
self.catalog = conn.client.session.get('/' + ks_version + '/auth/catalog',
endpoint_filter={'service_type': identity_service_type}
).json().get('catalog', [])
for ep_type in self.catalog:
if ep_type['type'] == identity_service_type:
for ep_id in ep_type['endpoints']:
ep_ks_version = self._get_version_from_url(ep_id['url'])
if not ep_ks_version:
ep_id['url'] = '{0}/{1}'.format(ep_id['url'], ks_version)
if ks_version == 'v3':
self._v3_setup(region_name)
else:
self._v2_setup(region_name)
Expand Down

0 comments on commit 97e859b

Please sign in to comment.