daisycloud-core/code/daisy/daisy/api/v1/install.py

444 lines
16 KiB
Python
Executable File

# Copyright 2013 OpenStack Foundation
# All 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.
"""
/hosts endpoint for Daisy v1 API
"""
import time
import webob.exc
from oslo_log import log as logging
from webob.exc import HTTPBadRequest
from webob.exc import HTTPForbidden
from threading import Thread
from daisy import i18n
from daisy import notifier
from daisy.api import policy
import daisy.api.v1
from daisy.common import exception
from daisy.common import property_utils
from daisy.common import utils
from daisy.common import wsgi
import daisy.registry.client.v1.api as registry
from daisy.api.v1 import controller
from daisy.api.v1 import filters
import daisy.api.backends.common as daisy_cmn
from daisy.api.backends import driver
from daisy.api.backends import os as os_handle
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
_LW = i18n._LW
SUPPORTED_PARAMS = daisy.api.v1.SUPPORTED_PARAMS
SUPPORTED_FILTERS = daisy.api.v1.SUPPORTED_FILTERS
ACTIVE_IMMUTABLE = daisy.api.v1.ACTIVE_IMMUTABLE
# if some backends have order constraint, please add here
# if backend not in the next three order list, we will be
# think it does't have order constraint.
BACKENDS_INSTALL_ORDER = ['proton', 'zenic', 'tecs']
BACKENDS_UPGRADE_ORDER = ['proton', 'zenic', 'tecs']
BACKENDS_UNINSTALL_ORDER = []
def get_deployment_backends(req, cluster_id, backends_order):
cluster_roles = daisy_cmn.get_cluster_roles_detail(req, cluster_id)
cluster_backends = set([role['deployment_backend']
for role in cluster_roles if
daisy_cmn.get_hosts_of_role(req, role['id'])])
ordered_backends = [
backend for backend in backends_order if backend in cluster_backends]
other_backends = [
backend for backend in cluster_backends if
backend not in backends_order]
deployment_backends = ordered_backends + other_backends
return deployment_backends
class InstallTask(object):
"""
Class for install OS and TECS.
"""
""" Definition for install states."""
def __init__(self, req, cluster_id):
self.req = req
self.cluster_id = cluster_id
def _backends_install(self):
backends = get_deployment_backends(
self.req, self.cluster_id, BACKENDS_INSTALL_ORDER)
if not backends:
LOG.info(_("No backends need to install."))
return
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
backend_driver.install(self.req, self.cluster_id)
# this will be raise raise all the exceptions of the thread to log file
def run(self):
try:
self._run()
except Exception as e:
LOG.exception(e.message)
def _run(self):
"""
Exectue os installation with sync mode.
:return:
"""
# get hosts config which need to install OS
all_hosts_need_os = os_handle.get_cluster_hosts_config(
self.req, self.cluster_id)
if all_hosts_need_os:
hosts_with_role_need_os = [
host_detail for host_detail in all_hosts_need_os if
host_detail['status'] == 'with-role']
hosts_without_role_need_os = [
host_detail for host_detail in all_hosts_need_os if
host_detail['status'] != 'with-role']
else:
LOG.info(_("No host need to install os, begin to install "
"backends for cluster %s." % self.cluster_id))
self._backends_install()
return
run_once_flag = True
# if no hosts with role need os, install backend applications
# immediately
if not hosts_with_role_need_os:
run_once_flag = False
role_hosts_need_os = []
LOG.info(_("All of hosts with role is 'active', begin to install "
"backend applications for cluster %s first." %
self.cluster_id))
self._backends_install()
else:
role_hosts_need_os = [host_detail['id']
for host_detail in hosts_with_role_need_os]
# hosts with role put the head of the list
order_hosts_need_os = hosts_with_role_need_os + \
hosts_without_role_need_os
while order_hosts_need_os:
os_install = os_handle.OSInstall(self.req, self.cluster_id)
# all os will be installed batch by batch with
# max_parallel_os_number which was set in daisy-api.conf
(order_hosts_need_os, role_hosts_need_os) = os_install.install_os(
order_hosts_need_os, role_hosts_need_os)
# after a batch of os install over, judge if all
# role hosts install os completely,
# if role_hosts_need_os is empty, install TECS immediately
if run_once_flag and not role_hosts_need_os:
run_once_flag = False
# wait to reboot os after new os installed
time.sleep(10)
LOG.info(_("All hosts with role install successfully, "
"begin to install backend applications "
"for cluster %s." %
self.cluster_id))
self._backends_install()
class Controller(controller.BaseController):
"""
WSGI controller for hosts resource in Daisy v1 API
The hosts resource API is a RESTful web service for host data. The API
is as follows::
GET /hosts -- Returns a set of brief metadata about hosts
GET /hosts/detail -- Returns a set of detailed metadata about
hosts
HEAD /hosts/<ID> -- Return metadata about an host with id <ID>
GET /hosts/<ID> -- Return host data for host with id <ID>
POST /hosts -- Store host data and return metadata about the
newly-stored host
PUT /hosts/<ID> -- Update host metadata and/or upload host
data for a previously-reserved host
DELETE /hosts/<ID> -- Delete the host with id <ID>
"""
def __init__(self):
self.notifier = notifier.Notifier()
registry.configure_registry_client()
self.policy = policy.Enforcer()
if property_utils.is_property_protection_enabled():
self.prop_enforcer = property_utils.PropertyRules(self.policy)
else:
self.prop_enforcer = None
def _enforce(self, req, action, target=None):
"""Authorize an action against our policies"""
if target is None:
target = {}
try:
self.policy.enforce(req.context, action, target)
except exception.Forbidden:
raise HTTPForbidden()
def _raise_404_if_cluster_deleted(self, req, cluster_id):
cluster = self.get_cluster_meta_or_404(req, cluster_id)
if cluster['deleted']:
msg = _("Cluster with identifier %s has been deleted.") % \
cluster_id
raise webob.exc.HTTPNotFound(msg)
def _get_filters(self, req):
"""
Return a dictionary of query param filters from the request
:param req: the Request object coming from the wsgi layer
:retval a dict of key/value filters
"""
query_filters = {}
for param in req.params:
if param in SUPPORTED_FILTERS:
query_filters[param] = req.params.get(param)
if not filters.validate(param, query_filters[param]):
raise HTTPBadRequest(_('Bad value passed to filter '
'%(filter)s got %(val)s')
% {'filter': param,
'val': query_filters[param]})
return query_filters
def _get_query_params(self, req):
"""
Extracts necessary query params from request.
:param req: the WSGI Request object
:retval dict of parameters that can be used by registry client
"""
params = {'filters': self._get_filters(req)}
for PARAM in SUPPORTED_PARAMS:
if PARAM in req.params:
params[PARAM] = req.params.get(PARAM)
return params
@utils.mutating
def install_cluster(self, req, install_meta):
"""
Install TECS to a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
if 'deployment_interface' in install_meta:
os_handle.pxe_server_build(req, install_meta)
return {"status": "pxe is installed"}
cluster_id = install_meta['cluster_id']
self._enforce(req, 'install_cluster')
self._raise_404_if_cluster_deleted(req, cluster_id)
daisy_cmn.set_role_status_and_progress(
req, cluster_id, 'install',
{'messages': 'Waiting for TECS installation', 'progress': '0'},
'tecs')
# if have hosts need to install os,
# TECS installataion executed in InstallTask
os_install_obj = InstallTask(req, cluster_id)
os_install_thread = Thread(target=os_install_obj.run)
os_install_thread.start()
return {"status": "begin install"}
@utils.mutating
def uninstall_cluster(self, req, cluster_id):
"""
Uninstall TECS to a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'uninstall_cluster')
self._raise_404_if_cluster_deleted(req, cluster_id)
backends = get_deployment_backends(
req, cluster_id, BACKENDS_UNINSTALL_ORDER)
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
uninstall_thread = Thread(
target=backend_driver.uninstall, args=(
req, cluster_id))
uninstall_thread.start()
return {"status": "begin uninstall"}
@utils.mutating
def uninstall_progress(self, req, cluster_id):
self._enforce(req, 'uninstall_progress')
self._raise_404_if_cluster_deleted(req, cluster_id)
all_nodes = {}
backends = get_deployment_backends(
req, cluster_id, BACKENDS_UNINSTALL_ORDER)
if not backends:
LOG.info(_("No backends need to uninstall."))
return all_nodes
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
nodes_process = backend_driver.uninstall_progress(req, cluster_id)
all_nodes.update(nodes_process)
return all_nodes
@utils.mutating
def update_cluster(self, req, cluster_id):
"""
Uninstall TECS to a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'update_cluster')
self._raise_404_if_cluster_deleted(req, cluster_id)
backends = get_deployment_backends(
req, cluster_id, BACKENDS_UPGRADE_ORDER)
if not backends:
LOG.info(_("No backends need to update."))
return {"status": ""}
daisy_cmn.set_role_status_and_progress(
req, cluster_id, 'upgrade',
{'messages': 'Waiting for TECS upgrading', 'progress': '0'},
'tecs')
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
update_thread = Thread(target=backend_driver.upgrade,
args=(req, cluster_id))
update_thread.start()
return {"status": "begin update"}
@utils.mutating
def update_progress(self, req, cluster_id):
self._enforce(req, 'update_progress')
self._raise_404_if_cluster_deleted(req, cluster_id)
backends = get_deployment_backends(
req, cluster_id, BACKENDS_UPGRADE_ORDER)
all_nodes = {}
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
nodes_process = backend_driver.upgrade_progress(req, cluster_id)
all_nodes.update(nodes_process)
return all_nodes
@utils.mutating
def export_db(self, req, install_meta):
"""
Export daisy db data to tecs.conf and HA.conf.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-install-cluster is missing
"""
self._enforce(req, 'export_db')
cluster_id = install_meta['cluster_id']
self._raise_404_if_cluster_deleted(req, cluster_id)
all_config_files = {}
backends = get_deployment_backends(
req, cluster_id, BACKENDS_INSTALL_ORDER)
if not backends:
LOG.info(_("No backends need to export."))
return all_config_files
for backend in backends:
backend_driver = driver.load_deployment_dirver(backend)
backend_config_files = backend_driver.export_db(req, cluster_id)
all_config_files.update(backend_config_files)
return all_config_files
@utils.mutating
def update_disk_array(self, req, cluster_id):
"""
update TECS Disk Array config for a cluster.
:param req: The WSGI/Webob Request object
:raises HTTPBadRequest if x-cluster is missing
"""
self._enforce(req, 'update_disk_array')
self._raise_404_if_cluster_deleted(req, cluster_id)
tecs_backend_name = 'tecs'
backends = get_deployment_backends(
req, cluster_id, BACKENDS_UNINSTALL_ORDER)
if tecs_backend_name not in backends:
message = "No tecs backend"
LOG.info(_(message))
else:
backend_driver = driver.load_deployment_dirver(tecs_backend_name)
message = backend_driver.update_disk_array(req, cluster_id)
return {'status': message}
class InstallDeserializer(wsgi.JSONRequestDeserializer):
"""Handles deserialization of specific controller method requests."""
def _deserialize(self, request):
result = {}
result["install_meta"] = utils.get_dict_meta(request)
return result
def install_cluster(self, request):
return self._deserialize(request)
def export_db(self, request):
return self._deserialize(request)
def update_disk_array(self, request):
return {}
class InstallSerializer(wsgi.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""
def __init__(self):
self.notifier = notifier.Notifier()
def install_cluster(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def export_db(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def update_disk_array(self, response, result):
response.status = 201
response.headers['Content-Type'] = 'application/json'
response.body = self.to_json(result)
return response
def create_resource():
"""Image members resource factory method"""
deserializer = InstallDeserializer()
serializer = InstallSerializer()
return wsgi.Resource(Controller(), deserializer, serializer)