Get the Satellite connection parameters from Heat

Change-Id: I1a66d2388f72b718169404232eac4861199f457c
This commit is contained in:
Lennart Regebro 2014-11-21 16:42:14 +01:00
parent 6791631cd0
commit af87a9a795
4 changed files with 269 additions and 40 deletions

View File

@ -1 +1,3 @@
pbr>=0.6,!=0.7,<1.0 pbr>=0.6,!=0.7,<1.0
oauthlib>=0.7.2,<0.8
requests_oauthlib>=0.4.2,<0.5

View File

@ -6,7 +6,7 @@ set -o errexit
# Increment me any time the environment should be rebuilt. # Increment me any time the environment should be rebuilt.
# This includes dependency changes, directory renames, etc. # This includes dependency changes, directory renames, etc.
# Simple integer sequence: 1, 2, 3... # Simple integer sequence: 1, 2, 3...
environment_version=42 environment_version=43
#--------------------------------------------------------# #--------------------------------------------------------#
function usage { function usage {

View File

@ -12,14 +12,27 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections 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 from horizon import tabs
import requests import requests
import requests_oauthlib
from tuskar_ui import api
from tuskar_ui.infrastructure.nodes import tabs as nodes_tabs from tuskar_ui.infrastructure.nodes import tabs as nodes_tabs
from tuskar_sat_ui.nodes import tables 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', [ ErrataItem = collections.namedtuple('ErrataItem', [
'title', 'title',
'type', '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): class DetailOverviewTab(nodes_tabs.DetailOverviewTab):
template_name = 'infrastructure/nodes/_detail_overview_sat.html' template_name = 'infrastructure/nodes/_detail_overview_sat.html'
def get_context_data(self, request): def get_context_data(self, request, **kwargs):
result = super(DetailOverviewTab, self).get_context_data(request) context = super(DetailOverviewTab,
if result['node'].uuid is None: self).get_context_data(request, **kwargs)
return result if context['node'].uuid is None:
return context
# Some currently hardcoded values: # TODO(rdopiera) We can probably get the stack from the context.
mac = '"52:54:00:4F:D8:65"' # Hardcode for now stack = _get_stack(request)
host = 'http://sat-perf-04.idm.lab.bos.redhat.com' # Hardcode for now if stack is None:
auth = ('admin', 'changeme') return context
# Get the errata here try:
host = host.strip('/') # Get rid of any trailing slash in the host url 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 addresses = context['node'].addresses
# need both MAC address and the interface, and we don't have the try:
# interface, so we need to make multiple slow searches. If the uuid = _find_uuid_by_mac(host, auth, organization, addresses)
# Satellite UUID isn't the same as this one, and it probably isn't we except NodeNotFound:
# need to store a mapping somewhere. return context
url = '{host}/katello/api/v2/systems'.format(host=host)
for interface in ['eth0', 'eth1', 'en0', 'en1']:
q = 'facts.net.interface.{iface}.mac_address:{mac}'.format( # TODO(rdopiera) Should probably catch that requests exception here.
iface=interface, mac=mac) try:
r = requests.get(url, params={'search': q}, auth=auth) data = self._get_errata_data(host, auth, uuid)
results = r.json()['results'] except NoErrataError:
if results: return context
break context['errata'] = tables.ErrataTable(request, data=data)
else: return context
# 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
class NodeDetailTabs(tabs.TabGroup): class NodeDetailTabs(tabs.TabGroup):

View 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')