Get the Satellite connection parameters from Heat
Change-Id: I1a66d2388f72b718169404232eac4861199f457c
This commit is contained in:
parent
6791631cd0
commit
af87a9a795
@ -1 +1,3 @@
|
||||
pbr>=0.6,!=0.7,<1.0
|
||||
oauthlib>=0.7.2,<0.8
|
||||
requests_oauthlib>=0.4.2,<0.5
|
||||
|
@ -6,7 +6,7 @@ set -o errexit
|
||||
# Increment me any time the environment should be rebuilt.
|
||||
# This includes dependency changes, directory renames, etc.
|
||||
# Simple integer sequence: 1, 2, 3...
|
||||
environment_version=42
|
||||
environment_version=43
|
||||
#--------------------------------------------------------#
|
||||
|
||||
function usage {
|
||||
|
@ -12,14 +12,27 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import collections
|
||||
import json
|
||||
import logging
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.messages
|
||||
from horizon import tabs
|
||||
import requests
|
||||
import requests_oauthlib
|
||||
from tuskar_ui import api
|
||||
from tuskar_ui.infrastructure.nodes import tabs as nodes_tabs
|
||||
|
||||
from tuskar_sat_ui.nodes import tables
|
||||
|
||||
|
||||
SAT_HOST_PARAM = 'SatelliteHost'
|
||||
SAT_AUTH_PARAM = 'SatelliteAuth'
|
||||
SAT_ORG_PARAM = 'SatelliteOrg'
|
||||
VERIFY_SSL = not getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
LOG = logging.getLogger('tuskar_sat_ui')
|
||||
ErrataItem = collections.namedtuple('ErrataItem', [
|
||||
'title',
|
||||
'type',
|
||||
@ -28,52 +41,168 @@ ErrataItem = collections.namedtuple('ErrataItem', [
|
||||
])
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoConfigError(Error):
|
||||
"""Failed to find the Satellite configuration in Heat parameters."""
|
||||
|
||||
def __init__(self, param=None, *args, **kwargs):
|
||||
super(NoConfigError, self).__init__(*args, **kwargs)
|
||||
self.param = param
|
||||
|
||||
|
||||
class NodeNotFound(Error):
|
||||
"""Failed to find the Satellite node."""
|
||||
|
||||
|
||||
class BadAuthError(Error):
|
||||
"""Unknown authentication method for Satellite."""
|
||||
|
||||
def __init__(self, auth=None, *args, **kwargs):
|
||||
super(BadAuthError, self).__init__(*args, **kwargs)
|
||||
self.auth = auth
|
||||
|
||||
|
||||
class NoErrataError(Error):
|
||||
"""There is no errata for that node."""
|
||||
|
||||
|
||||
def _get_satellite_config(parameters):
|
||||
"""Find the Satellite configuration data.
|
||||
|
||||
The configuration data is store in Heat as parameters. They may be
|
||||
stored directly as Heat parameters, or in a the JSON structure stored
|
||||
in ExtraConfig.
|
||||
"""
|
||||
|
||||
param = 'Satellite'
|
||||
try:
|
||||
config = parameters[param]
|
||||
except KeyError:
|
||||
try:
|
||||
extra = json.loads(parameters['compute-1::ExtraConfig'])
|
||||
config = extra[param]
|
||||
except (KeyError, ValueError, TypeError):
|
||||
raise NoConfigError(param, 'Parameter %r missing.' % param)
|
||||
|
||||
for param in [SAT_HOST_PARAM, SAT_AUTH_PARAM, SAT_ORG_PARAM]:
|
||||
if param not in config:
|
||||
raise NoConfigError(param, 'Parameter %r missing.' % param)
|
||||
|
||||
host = config[SAT_HOST_PARAM]
|
||||
host = host.strip('/') # Get rid of any trailing slash in the host url
|
||||
|
||||
try:
|
||||
auth = config[SAT_AUTH_PARAM].split(':', 2)
|
||||
except ValueError:
|
||||
raise BadAuthError(auth=config[SAT_AUTH_PARAM])
|
||||
if auth[0] == 'oauth':
|
||||
auth = requests_oauthlib.OAuth1(auth[1], auth[2])
|
||||
elif auth[0] == 'basic':
|
||||
auth = auth[1], auth[2]
|
||||
else:
|
||||
raise BadAuthError(auth=auth[0])
|
||||
organization = config[SAT_ORG_PARAM]
|
||||
return host, auth, organization
|
||||
|
||||
|
||||
def _get_stack(request):
|
||||
"""Find the stack."""
|
||||
|
||||
# TODO(rdopiera) We probably should use the StackMixin instead.
|
||||
try:
|
||||
plan = api.tuskar.Plan.get_the_plan(request)
|
||||
stack = api.heat.Stack.get_by_plan(request, plan)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
horizon.messages.error(request, _("Could not retrieve errata."))
|
||||
return None
|
||||
return stack
|
||||
|
||||
|
||||
def _find_uuid_by_mac(host, auth, organization, addresses):
|
||||
"""Pick up the UUID from the MAC address.
|
||||
|
||||
This makes no sense, as we need both MAC address and the interface, and
|
||||
we don't have the interface, so we need to make multiple slow searches.
|
||||
If the Satellite UUID isn't the same as this one, and it probably
|
||||
isn't, we need to store a mapping somewhere.
|
||||
"""
|
||||
|
||||
url = '{host}/katello/api/v2/systems'.format(host=host)
|
||||
for mac in addresses:
|
||||
for interface in ['eth0', 'eth1', 'en0', 'en1']:
|
||||
q = 'facts.net.interface.{iface}.mac_address:{mac}'.format(
|
||||
iface=interface, mac=mac)
|
||||
params = {'search': q, 'organization_id': organization}
|
||||
r = requests.get(url, params=params, auth=auth,
|
||||
verify=VERIFY_SSL)
|
||||
r.raise_for_status() # Raise an error if the request failed
|
||||
contexts = r.json()['results']
|
||||
if contexts:
|
||||
return contexts[0]['uuid']
|
||||
raise NodeNotFound()
|
||||
|
||||
|
||||
def _get_errata_data(self, host, auth, uuid):
|
||||
"""Get the errata here, while it's hot."""
|
||||
|
||||
url = '{host}/katello/api/v2/systems/{id}/errata'.format(host=host,
|
||||
id=uuid)
|
||||
r = requests.get(url, auth=auth, verify=VERIFY_SSL)
|
||||
r.raise_for_status() # Raise an error if the request failed
|
||||
errata = r.json()['contexts']
|
||||
if not errata:
|
||||
raise NoErrataError()
|
||||
data = [ErrataItem(x['title'], x['type'], x['id'], x['issued'])
|
||||
for x in errata]
|
||||
return data
|
||||
|
||||
|
||||
class DetailOverviewTab(nodes_tabs.DetailOverviewTab):
|
||||
template_name = 'infrastructure/nodes/_detail_overview_sat.html'
|
||||
|
||||
def get_context_data(self, request):
|
||||
result = super(DetailOverviewTab, self).get_context_data(request)
|
||||
if result['node'].uuid is None:
|
||||
return result
|
||||
def get_context_data(self, request, **kwargs):
|
||||
context = super(DetailOverviewTab,
|
||||
self).get_context_data(request, **kwargs)
|
||||
if context['node'].uuid is None:
|
||||
return context
|
||||
|
||||
# Some currently hardcoded values:
|
||||
mac = '"52:54:00:4F:D8:65"' # Hardcode for now
|
||||
host = 'http://sat-perf-04.idm.lab.bos.redhat.com' # Hardcode for now
|
||||
auth = ('admin', 'changeme')
|
||||
# TODO(rdopiera) We can probably get the stack from the context.
|
||||
stack = _get_stack(request)
|
||||
if stack is None:
|
||||
return context
|
||||
|
||||
# Get the errata here
|
||||
host = host.strip('/') # Get rid of any trailing slash in the host url
|
||||
try:
|
||||
host, auth, organization = _get_satellite_config(stack.parameters)
|
||||
except NoConfigError as e:
|
||||
horizon.messages.error(request, _(
|
||||
"No Satellite configuration found. "
|
||||
"Missing parameter %r."
|
||||
) % e.param)
|
||||
return context
|
||||
except BadAuthError as e:
|
||||
horizon.messages.error(request, _(
|
||||
"Satellite configuration error, "
|
||||
"unknown authentication method %r."
|
||||
) % e.auth)
|
||||
return context
|
||||
|
||||
# Pick up the UUID from the MAC address This makes no sense, as we
|
||||
# need both MAC address and the interface, and we don't have the
|
||||
# interface, so we need to make multiple slow searches. If the
|
||||
# Satellite UUID isn't the same as this one, and it probably isn't we
|
||||
# need to store a mapping somewhere.
|
||||
url = '{host}/katello/api/v2/systems'.format(host=host)
|
||||
for interface in ['eth0', 'eth1', 'en0', 'en1']:
|
||||
addresses = context['node'].addresses
|
||||
try:
|
||||
uuid = _find_uuid_by_mac(host, auth, organization, addresses)
|
||||
except NodeNotFound:
|
||||
return context
|
||||
|
||||
q = 'facts.net.interface.{iface}.mac_address:{mac}'.format(
|
||||
iface=interface, mac=mac)
|
||||
r = requests.get(url, params={'search': q}, auth=auth)
|
||||
results = r.json()['results']
|
||||
if results:
|
||||
break
|
||||
else:
|
||||
# No node found
|
||||
result['errata'] = None
|
||||
return result
|
||||
|
||||
uuid = results[0]['uuid']
|
||||
errata_url = '{host}/katello/api/v2/systems/{id}/errata'
|
||||
r = requests.get(errata_url.format(host=host, id=uuid), auth=auth)
|
||||
errata = r.json()['results']
|
||||
if not errata:
|
||||
result['errata'] = None
|
||||
else:
|
||||
data = [ErrataItem(x['title'], x['type'], x['id'], x['issued'])
|
||||
for x in errata]
|
||||
result['errata'] = tables.ErrataTable(request, data=data)
|
||||
return result
|
||||
# TODO(rdopiera) Should probably catch that requests exception here.
|
||||
try:
|
||||
data = self._get_errata_data(host, auth, uuid)
|
||||
except NoErrataError:
|
||||
return context
|
||||
context['errata'] = tables.ErrataTable(request, data=data)
|
||||
return context
|
||||
|
||||
|
||||
class NodeDetailTabs(tabs.TabGroup):
|
||||
|
98
tuskar_sat_ui/nodes/tests.py
Normal file
98
tuskar_sat_ui/nodes/tests.py
Normal file
@ -0,0 +1,98 @@
|
||||
# -*- coding: utf8 -*-
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
|
||||
from tuskar_ui.test import helpers
|
||||
|
||||
from tuskar_sat_ui.nodes import tabs
|
||||
|
||||
|
||||
class SatTests(helpers.BaseAdminViewTests):
|
||||
def test_satellite_config_direct(self):
|
||||
config = {
|
||||
'Satellite': {
|
||||
'SatelliteHost': 'http://example.com/',
|
||||
'SatelliteAuth': 'basic:user:pass',
|
||||
'SatelliteOrg': 'ACME',
|
||||
},
|
||||
}
|
||||
host, auth, org = tabs._get_satellite_config(config)
|
||||
self.assertEqual(host, 'http://example.com')
|
||||
self.assertEqual(auth, ('user', 'pass'))
|
||||
self.assertEqual(org, 'ACME')
|
||||
|
||||
def test_satellite_config_extra(self):
|
||||
config = {
|
||||
'compute-1::ExtraConfig': json.dumps({
|
||||
'Satellite': {
|
||||
'SatelliteHost': 'http://example.com/',
|
||||
'SatelliteAuth': 'basic:user:pass',
|
||||
'SatelliteOrg': 'ACME',
|
||||
}
|
||||
}),
|
||||
}
|
||||
host, auth, org = tabs._get_satellite_config(config)
|
||||
self.assertEqual(host, 'http://example.com')
|
||||
self.assertEqual(auth, ('user', 'pass'))
|
||||
self.assertEqual(org, 'ACME')
|
||||
|
||||
def test_satellite_config_missing_all(self):
|
||||
config = {}
|
||||
with self.assertRaises(tabs.NoConfigError) as e:
|
||||
host, auth, org = tabs._get_satellite_config(config)
|
||||
self.assertEqual(e.exception.param, 'Satellite')
|
||||
|
||||
def test_satellite_config_missing_one(self):
|
||||
params = {
|
||||
'SatelliteHost': 'http://example.com/',
|
||||
'SatelliteAuth': 'basic:user:pass',
|
||||
'SatelliteOrg': 'ACME',
|
||||
}
|
||||
for param in [
|
||||
tabs.SAT_HOST_PARAM,
|
||||
tabs.SAT_AUTH_PARAM,
|
||||
tabs.SAT_ORG_PARAM,
|
||||
]:
|
||||
broken_config = {
|
||||
'Satellite': dict(kv for kv in params.items()
|
||||
if kv[0] != param),
|
||||
}
|
||||
with self.assertRaises(tabs.NoConfigError) as e:
|
||||
host, auth, org = tabs._get_satellite_config(broken_config)
|
||||
self.assertEqual(e.exception.param, param)
|
||||
|
||||
def test_satellite_config_unknown_auth(self):
|
||||
config = {
|
||||
'Satellite': {
|
||||
'SatelliteHost': 'http://example.com/',
|
||||
'SatelliteAuth': 'bad:user:pass',
|
||||
'SatelliteOrg': 'ACME',
|
||||
},
|
||||
}
|
||||
with self.assertRaises(tabs.BadAuthError) as e:
|
||||
host, auth, org = tabs._get_satellite_config(config)
|
||||
self.assertEqual(e.exception.auth, 'bad')
|
||||
|
||||
def test_satellite_config_malformed_auth(self):
|
||||
config = {
|
||||
'Satellite': {
|
||||
'SatelliteHost': 'http://example.com/',
|
||||
'SatelliteAuth': 'bad',
|
||||
'SatelliteOrg': 'ACME',
|
||||
},
|
||||
}
|
||||
with self.assertRaises(tabs.BadAuthError) as e:
|
||||
host, auth, org = tabs._get_satellite_config(config)
|
||||
self.assertEqual(e.exception.auth, 'bad')
|
Loading…
x
Reference in New Issue
Block a user