181 lines
6.8 KiB
Python
181 lines
6.8 KiB
Python
# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# 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.
|
|
"""Handle resources for boot action API endpoints. """
|
|
|
|
import falcon
|
|
import ulid2
|
|
import tarfile
|
|
import io
|
|
import logging
|
|
|
|
from .base import StatefulResource
|
|
|
|
logger = logging.getLogger('drydock')
|
|
|
|
class BootactionResource(StatefulResource):
|
|
def __init__(self, orchestrator=None, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.orchestrator = orchestrator
|
|
|
|
def on_post(self, req, resp, action_id):
|
|
"""Post status messages or final status for a boot action.
|
|
|
|
This endpoint does not use the standard oslo_policy enforcement as this endpoint
|
|
is accessed by unmanned nodes. Instead it uses a internal key authentication
|
|
|
|
:param req: falcon request
|
|
:param resp: falcone response
|
|
:param action_id: ULID ID of the boot action
|
|
"""
|
|
|
|
|
|
class BootactionAssetsResource(StatefulResource):
|
|
def __init__(self, orchestrator=None, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.orchestrator = orchestrator
|
|
|
|
def do_get(self, req, resp, hostname, asset_type):
|
|
"""Render ``unit`` type boot action assets for hostname.
|
|
|
|
Get the boot action context for ``hostname`` from the database
|
|
and render all ``unit`` type assets for the host. Validate host
|
|
is providing the correct idenity key in the ``X-Bootaction-Key``
|
|
header.
|
|
|
|
:param req: falcon request object
|
|
:param resp: falcon response object
|
|
:param hostname: URL path parameter indicating the calling host
|
|
:param asset_type: Asset type to include in the response - ``unit``, ``file``, ``pkg_list``, ``all``
|
|
"""
|
|
try:
|
|
ba_ctx = self.state_manager.get_boot_action_context(hostname)
|
|
except Exception as ex:
|
|
self.logger.error(
|
|
"Error locating boot action for %s" % hostname, exc_info=ex)
|
|
raise falcon.HTTPNotFound()
|
|
|
|
if ba_ctx is None:
|
|
raise falcon.HTTPNotFound(
|
|
description="Error locating boot action for %s" % hostname)
|
|
|
|
BootactionUtils.check_auth(ba_ctx, req)
|
|
|
|
asset_type_filter = None if asset_type == 'all' else asset_type
|
|
|
|
try:
|
|
task = self.state_manager.get_task(ba_ctx['task_id'])
|
|
design_status, site_design = self.orchestrator.get_effective_site(
|
|
task.design_ref)
|
|
|
|
assets = list()
|
|
for ba in site_design.bootactions:
|
|
if hostname in ba.target_nodes:
|
|
action_id = ulid2.generate_binary_ulid()
|
|
assets.extend(
|
|
ba.render_assets(
|
|
hostname,
|
|
site_design,
|
|
action_id,
|
|
type_filter=asset_type_filter))
|
|
self.state_manager.post_boot_action(
|
|
hostname, ba_ctx['task_id'], ba_ctx['identity_key'],
|
|
action_id)
|
|
|
|
tarball = BootactionUtils.tarbuilder(asset_list=assets)
|
|
resp.set_header('Content-Type', 'application/gzip')
|
|
resp.set_header('Content-Disposition',
|
|
"attachment; filename=\"%s-%s.tar.gz\"" %
|
|
(hostname, asset_type))
|
|
resp.data = tarball
|
|
resp.status = falcon.HTTP_200
|
|
return
|
|
except Exception as ex:
|
|
self.logger.debug("Exception in boot action API.", exc_info=ex)
|
|
raise falcon.HTTPInternalServerError(str(ex))
|
|
|
|
|
|
class BootactionUnitsResource(BootactionAssetsResource):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
def on_get(self, req, resp, hostname):
|
|
self.logger.debug(
|
|
"Accessing boot action units resource for host %s." % hostname)
|
|
super().do_get(req, resp, hostname, 'unit')
|
|
|
|
|
|
class BootactionFilesResource(BootactionAssetsResource):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
def on_get(self, req, resp, hostname):
|
|
super().do_get(req, resp, hostname, 'file')
|
|
|
|
|
|
class BootactionUtils(object):
|
|
"""Utility class shared by Boot Action API resources."""
|
|
|
|
@staticmethod
|
|
def check_auth(ba_ctx, req):
|
|
"""Check request authentication based on boot action context.
|
|
|
|
Raise proper Falcon exception if authentication fails, otherwise
|
|
silently return
|
|
|
|
:param ba_ctx: Boot Action context from database
|
|
:param req: The falcon request object of the API call
|
|
"""
|
|
identity_key = req.get_header('X-Bootaction-Key', default='')
|
|
|
|
if identity_key == '':
|
|
raise falcon.HTTPUnauthorized(
|
|
title='Unauthorized',
|
|
description='No X-Bootaction-Key',
|
|
challenges=['Bootaction-Key'])
|
|
|
|
if ba_ctx['identity_key'] != bytes.fromhex(identity_key):
|
|
logger.warn(
|
|
"Forbidding boot action access - node: %s, identity_key: %s, req header: %s"
|
|
% (ba_ctx['node_name'], str(ba_ctx['identity_key']),
|
|
str(bytes.fromhex(identity_key))))
|
|
raise falcon.HTTPForbidden(
|
|
title='Unauthorized', description='Invalid X-Bootaction-Key')
|
|
|
|
@staticmethod
|
|
def tarbuilder(asset_list=None):
|
|
"""Create a tar file from rendered assets.
|
|
|
|
Add each asset in ``asset_list`` to a tar file with the defined
|
|
path and permission. The assets need to have the rendered_bytes field
|
|
populated. Return a tarfile.TarFile.
|
|
|
|
:param hostname: the hostname the tar is destined for
|
|
:param balltype: the type of assets being included
|
|
:param asset_list: list of objects.BootActionAsset instances
|
|
"""
|
|
tarbytes = io.BytesIO()
|
|
tarball = tarfile.open(
|
|
mode='w:gz', fileobj=tarbytes, format=tarfile.GNU_FORMAT)
|
|
asset_list = asset_list or []
|
|
for a in asset_list:
|
|
fileobj = io.BytesIO(a.rendered_bytes)
|
|
tarasset = tarfile.TarInfo(name=a.path)
|
|
tarasset.size = len(a.rendered_bytes)
|
|
tarasset.mode = a.permissions if a.permissions else 0o600
|
|
tarasset.uid = 0
|
|
tarasset.gid = 0
|
|
tarball.addfile(tarasset, fileobj=fileobj)
|
|
tarball.close()
|
|
return tarbytes.getvalue()
|