tuskar/tuskar/api/controllers/v1/overcloud.py

286 lines
9.8 KiB
Python

# 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 logging
import pecan
import wsme
from pecan import rest
import six
from wsmeext import pecan as wsme_pecan
from tuskar.api.controllers.v1 import models
from tuskar.common import exception
from tuskar.heat.client import HeatClient
import tuskar.heat.template_tools as template_tools
LOG = logging.getLogger(__name__)
# FIXME(lsmola) this is for debugging purposes only, remove before I3
POC_PARAMS = {'controller': 1, 'compute': 2}
def parse_counts(counts):
"""Helper for parsing the OvercloudRoleCount object
Given a list of OvercloudRoleCount objects return a dict of
(image_name, count) in a format used for building a template.
:param counts: List of tuskar.api.controllers.v1.models.OvercloudRoleCount
:type counts: list
:return: Dict of (image_name, count)
:rtype: dict
"""
parsed_counts = {}
for count_obj in counts:
image_name = count_obj.overcloud_role.image_name
count = count_obj.num_nodes
parsed_counts[image_name] = count
return parsed_counts
def filter_template_attributes(allowed_data, attributes):
"""Helper filtering attributes for template
Given a list of allowed data and attributes, filter the attributes
only with keys of allowed data and return filtered data.
:param allowed_data: Dict of allowed attributes for template returned by
validating of template.
:type allowed_data: dict
:param attributes: Dict of attributes sent from user in deploying stack
operation
:type attributes: Dict
:return: Dict of filtered attributes
:rtype: dict
"""
allowed_keys = allowed_data.get("Parameters", {}).keys()
filtered_data = dict([(key, value) for key, value in attributes.items()
if key in allowed_keys])
return filtered_data
def process_stack(attributes, counts, create=False):
"""Helper function for processing the stack.
Given a params dict containing the Overcloud Roles and initialization
parameters create or update the stack.
:param attributes: Dictionary of initialization params and overcloud roles
for heat template and initialization of stack
:type attributes: dict
:param counts: Dictionary of counts of roles to be deployed
:type counts: dict
:param create: A flag to designate if we are creating or updating the stack
:type create: bool
"""
try:
overcloud = template_tools.merge_templates(parse_counts(counts))
except Exception as e:
raise exception.HeatTemplateCreateFailed(six.text_type(e))
heat_client = HeatClient()
stack_exists = heat_client.exists_stack()
try:
allowed_data = heat_client.validate_template(overcloud)
except Exception as e:
raise exception.HeatTemplateValidateFailed(six.text_type(e))
if stack_exists and create:
raise exception.StackAlreadyCreated()
elif not stack_exists and not create:
raise exception.StackNotFound()
if create:
operation = heat_client.create_stack
else:
operation = heat_client.update_stack
try:
operation(overcloud,
filter_template_attributes(allowed_data, attributes))
except Exception as e:
if create:
raise exception.HeatStackCreateFailed(six.text_type(e))
else:
raise exception.HeatStackUpdateFailed(six.text_type(e))
class OvercloudsController(rest.RestController):
"""REST controller for the Overcloud class."""
_custom_actions = {'template_get': ['GET']}
# FIXME(lsmola) this is for debugging purposes only, remove before I3
@pecan.expose()
def template_get(self):
overcloud = template_tools.merge_templates(POC_PARAMS)
return overcloud
@wsme.validate(models.Overcloud)
@wsme_pecan.wsexpose(models.Overcloud,
body=models.Overcloud,
status_code=201)
def post(self, transfer_overcloud):
"""Creates a new overcloud.
:param transfer_overcloud: data submitted by the user
:type transfer_overcloud:
tuskar.api.controllers.v1.models.Overcloud
:return: created overcloud
:rtype: tuskar.api.controllers.v1.models.Overcloud
:raises: tuskar.common.exception.OvercloudExists: if an overcloud
with the given name exists
"""
LOG.debug('Creating overcloud: %s' % transfer_overcloud)
# Persist to the database
db_overcloud = transfer_overcloud.to_db_model()
result = pecan.request.dbapi.create_overcloud(db_overcloud)
# Package for transfer back to the user
saved_overcloud =\
models.Overcloud.from_db_model(result)
# FIXME(lsmola) This is just POC of creating a stack
# this has to be done properly with proper Work-flow abstraction of:
# step one- build template and start stack-create
# step 2- put the right stack_id to the overcloud
# step 3- initialize the stack
# step 4- set the correct overcloud status
process_stack(saved_overcloud.attributes, result.counts,
create=True)
return saved_overcloud
@wsme.validate(models.Overcloud)
@wsme_pecan.wsexpose(models.Overcloud,
int,
body=models.Overcloud)
def put(self, overcloud_id, overcloud_delta):
"""Updates an existing overcloud, including its attributes and counts.
:param overcloud_id: identifies the overcloud being deleted
:type overcloud_id: int
:param overcloud_delta: contains only values that are to be affected
by the update
:type overcloud_delta:
tuskar.api.controllers.v1.models.Overcloud
:return: created overcloud
:rtype: tuskar.api.controllers.v1.models.Overcloud
:raises: tuskar.common.exception.OvercloudNotFound if there
is no overcloud with the given ID
"""
LOG.debug('Updating overcloud: %s' % overcloud_id)
# ID is in the URL so make sure it's in the transfer object
# before translation
overcloud_delta.id = overcloud_id
db_delta = overcloud_delta.to_db_model(omit_unset=True)
# Will raise a not found if there is no overcloud with the ID
result = pecan.request.dbapi.update_overcloud(db_delta)
updated_overcloud = models.Overcloud.from_db_model(result)
# FIXME(lsmola) This is just POC of updating a stack
# this probably should also have workflow
# step one- build template and stack-update
# step 2- set the correct overcloud status
process_stack(updated_overcloud.attributes, updated_overcloud.counts,
create=True)
return updated_overcloud
@wsme_pecan.wsexpose(None, int, status_code=204)
def delete(self, overcloud_id):
"""Deletes the given overcloud.
:param overcloud_id: identifies the overcloud being deleted
:type overcloud_id: int
:raises: tuskar.common.exception.OvercloudNotFound if there
is no overcloud with the given ID
"""
# FIXME(lsmola) this should always try to delete both overcloud
# and stack. So it requires some exception catch over below.
# FIXME(lsmola) there is also a workflow needed
# step one- delete stack and set status deleting in progress to
# overcloud
# step 2 - once stack is deleted, delete the overcloud
LOG.debug('Deleting overcloud with ID: %s' % overcloud_id)
pecan.request.dbapi.delete_overcloud_by_id(overcloud_id)
heat_client = HeatClient()
if not heat_client.exists_stack():
# If the stack doesn't exist, we have nothing else to do here.
return
try:
heat_client.delete_stack()
except Exception:
raise exception.HeatStackDeleteFailed()
@wsme_pecan.wsexpose(models.Overcloud, int)
def get_one(self, overcloud_id):
"""Returns a specific overcloud.
An exception is raised if no overcloud is found with the
given ID.
:param overcloud_id: identifies the overcloud being deleted
:type overcloud_id: int
:return: matching overcloud
:rtype: tuskar.api.controllers.v1.models.Overcloud
:raises: tuskar.common.exception.OvercloudNotFound if there
is no overcloud with the given ID
"""
LOG.debug('Retrieving overcloud with ID: %s' % overcloud_id)
overcloud = pecan.request.dbapi.get_overcloud_by_id(overcloud_id)
transfer_overcloud = models.Overcloud.from_db_model(overcloud)
return transfer_overcloud
@wsme_pecan.wsexpose([models.Overcloud])
def get_all(self):
"""Returns all overclouds.
An empty list is returned if no overclouds are present.
:return: list of overclouds; empty list if none are found
:rtype: list of tuskar.api.controllers.v1.models.Overcloud
"""
LOG.debug('Retrieving all overclouds')
overclouds = pecan.request.dbapi.get_overclouds()
transfer_overclouds = [models.Overcloud.from_db_model(o)
for o in overclouds]
return transfer_overclouds