9d6b648ec7
- Impala node processes can be installed on different nodes - OS precondition is checked for impala. - Impala 2.2 should be supported for 5.1.0 Change-Id: I21bd64b9c529a3ed25296b087f08c793ead44aee Closes-Bug: #1580979
263 lines
9.6 KiB
Python
263 lines
9.6 KiB
Python
# Copyright (c) 2015, MapR Technologies
|
|
#
|
|
# 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 functools as ft
|
|
|
|
from sahara.conductor import resource as r
|
|
import sahara.exceptions as ex
|
|
from sahara.i18n import _
|
|
import sahara.plugins.exceptions as e
|
|
from sahara.service.api import v10 as api
|
|
import sahara.utils.openstack.nova as nova
|
|
|
|
|
|
class LessThanCountException(e.InvalidComponentCountException):
|
|
MESSAGE = _("Hadoop cluster should contain at least"
|
|
" %(expected_count)d %(component)s component(s)."
|
|
" Actual %(component)s count is %(actual_count)d")
|
|
|
|
def __init__(self, component, expected_count, count):
|
|
super(LessThanCountException, self).__init__(
|
|
component, expected_count, count)
|
|
args = {
|
|
'expected_count': expected_count,
|
|
'component': component,
|
|
'actual_count': count,
|
|
}
|
|
self.message = LessThanCountException.MESSAGE % args
|
|
|
|
|
|
class MoreThanCountException(e.InvalidComponentCountException):
|
|
MESSAGE = _("Hadoop cluster should contain at most"
|
|
" %(expected_count)d %(component)s component(s)."
|
|
" Actual %(component)s count is %(actual_count)d")
|
|
|
|
def __init__(self, component, expected_count, count):
|
|
super(MoreThanCountException, self).__init__(
|
|
component, expected_count, count)
|
|
args = {
|
|
"expected_count": expected_count,
|
|
"component": component,
|
|
"actual_count": count,
|
|
}
|
|
self.message = MoreThanCountException.MESSAGE % args
|
|
|
|
|
|
class EvenCountException(ex.SaharaException):
|
|
MESSAGE = _("Hadoop cluster should contain odd number of %(component)s"
|
|
" but %(actual_count)s found.")
|
|
|
|
def __init__(self, component, count):
|
|
super(EvenCountException, self).__init__()
|
|
args = {'component': component, 'actual_count': count}
|
|
self.message = EvenCountException.MESSAGE % args
|
|
|
|
|
|
class NodeRequiredServiceMissingException(e.RequiredServiceMissingException):
|
|
MISSING_MSG = _('Node "%(ng_name)s" is missing component %(component)s')
|
|
REQUIRED_MSG = _('%(message)s, required by %(required_by)s')
|
|
|
|
def __init__(self, service_name, ng_name, required_by=None):
|
|
super(NodeRequiredServiceMissingException, self).__init__(
|
|
service_name, required_by)
|
|
args = {'ng_name': ng_name, 'component': service_name}
|
|
self.message = (
|
|
NodeRequiredServiceMissingException.MISSING_MSG % args)
|
|
if required_by:
|
|
args = {'message': self.message, 'required_by': required_by}
|
|
self.message = (
|
|
NodeRequiredServiceMissingException.REQUIRED_MSG % args)
|
|
|
|
|
|
class NodeServiceConflictException(ex.SaharaException):
|
|
MESSAGE = _('%(service)s service cannot be installed alongside'
|
|
' %(package)s package')
|
|
ERROR_CODE = "NODE_PROCESS_CONFLICT"
|
|
|
|
def __init__(self, service_name, conflicted_package):
|
|
super(NodeServiceConflictException, self).__init__()
|
|
args = {
|
|
'service': service_name,
|
|
'package': conflicted_package,
|
|
}
|
|
self.message = NodeServiceConflictException.MESSAGE % args
|
|
self.code = NodeServiceConflictException.ERROR_CODE
|
|
|
|
|
|
class NoVolumesException(ex.SaharaException):
|
|
MESSAGE = _('%s must have at least 1 volume or ephemeral drive')
|
|
ERROR_CODE = "NO_VOLUMES"
|
|
|
|
def __init__(self, ng_name):
|
|
super(NoVolumesException, self).__init__()
|
|
self.message = NoVolumesException.MESSAGE % ng_name
|
|
self.code = NoVolumesException.ERROR_CODE
|
|
|
|
|
|
class NotRequiredImageException(ex.SaharaException):
|
|
MESSAGE = _('Service %(service)s requires %(os)s OS.'
|
|
' Use %(os)s image and add "%(os)s" tag to it.')
|
|
ERROR_CODE = "INVALID_IMAGE"
|
|
|
|
def __init__(self, service, os):
|
|
super(NotRequiredImageException, self).__init__()
|
|
self.message = NotRequiredImageException.MESSAGE % {'service': service,
|
|
'os': os}
|
|
self.code = NotRequiredImageException.ERROR_CODE
|
|
|
|
|
|
def at_least(count, component):
|
|
def validate(cluster_context, component, count):
|
|
actual_count = cluster_context.get_instances_count(component)
|
|
if not actual_count >= count:
|
|
raise LessThanCountException(
|
|
component.ui_name, count, actual_count)
|
|
|
|
return ft.partial(validate, component=component, count=count)
|
|
|
|
|
|
def at_most(count, component):
|
|
def validate(cluster_context, component, count):
|
|
actual_count = cluster_context.get_instances_count(component)
|
|
if actual_count > count:
|
|
raise MoreThanCountException(
|
|
component.ui_name, count, actual_count)
|
|
|
|
return ft.partial(validate, component=component, count=count)
|
|
|
|
|
|
def exactly(count, component):
|
|
def validate(cluster_context, component, count):
|
|
actual_count = cluster_context.get_instances_count(component)
|
|
if not actual_count == count:
|
|
raise e.InvalidComponentCountException(
|
|
component.ui_name, count, actual_count)
|
|
|
|
return ft.partial(validate, component=component, count=count)
|
|
|
|
|
|
def each_node_has(component):
|
|
def validate(cluster_context, component):
|
|
for node_group in cluster_context.cluster.node_groups:
|
|
if component.ui_name not in node_group.node_processes:
|
|
raise NodeRequiredServiceMissingException(
|
|
component.ui_name, node_group.name)
|
|
|
|
return ft.partial(validate, component=component)
|
|
|
|
|
|
def odd_count_of(component):
|
|
def validate(cluster_context, component):
|
|
actual_count = cluster_context.get_instances_count(component)
|
|
if actual_count > 1 and actual_count % 2 == 0:
|
|
raise EvenCountException(component.ui_name, actual_count)
|
|
|
|
return ft.partial(validate, component=component)
|
|
|
|
|
|
def on_same_node(component, dependency):
|
|
def validate(cluster_context, component, dependency):
|
|
for ng in cluster_context.get_node_groups(component):
|
|
if dependency.ui_name not in ng.node_processes:
|
|
raise NodeRequiredServiceMissingException(
|
|
dependency.ui_name, ng.name, component.ui_name)
|
|
|
|
return ft.partial(validate, component=component, dependency=dependency)
|
|
|
|
|
|
def depends_on(service, required_by):
|
|
def validate(cluster_context, service, required_by):
|
|
if not cluster_context.is_present(service):
|
|
service_name = service.ui_name
|
|
if service.version:
|
|
service_name += " %s" % service.version
|
|
|
|
raise e.RequiredServiceMissingException(
|
|
service_name, required_by.ui_name)
|
|
|
|
return ft.partial(validate, service=service, required_by=required_by)
|
|
|
|
|
|
def node_client_package_conflict_vr(components, client_component):
|
|
def validate(cluster_context, components):
|
|
for ng in cluster_context.get_node_groups():
|
|
for c in components:
|
|
nps = ng.node_processes
|
|
if c in nps and client_component in nps:
|
|
raise NodeServiceConflictException(c, client_component)
|
|
|
|
return ft.partial(validate, components=components)
|
|
|
|
|
|
def assert_present(service, cluster_context):
|
|
if not cluster_context.is_present(service):
|
|
raise e.RequiredServiceMissingException(service.ui_name)
|
|
|
|
|
|
def required_os(os, required_by):
|
|
def validate(cluster_context, os, required_by):
|
|
for ng in cluster_context.get_node_groups():
|
|
nps = ng.node_processes
|
|
for node_process in required_by.node_processes:
|
|
if node_process.ui_name in nps:
|
|
image_id = (ng.image_id or
|
|
cluster_context.cluster.default_image_id)
|
|
if not image_has_tag(image_id, os):
|
|
raise NotRequiredImageException(required_by.ui_name,
|
|
os)
|
|
|
|
return ft.partial(validate, os=os, required_by=required_by)
|
|
|
|
|
|
def create_fake_cluster(cluster, existing, additional):
|
|
counts = existing.copy()
|
|
counts.update(additional)
|
|
|
|
def update_ng(node_group):
|
|
ng_dict = node_group.to_dict()
|
|
count = counts[node_group.id]
|
|
ng_dict.update(dict(count=count))
|
|
return r.NodeGroupResource(ng_dict)
|
|
|
|
def need_upd(node_group):
|
|
return node_group.id in counts and counts[node_group.id] > 0
|
|
|
|
updated = map(update_ng, filter(need_upd, cluster.node_groups))
|
|
not_updated = filter(lambda ng:
|
|
not need_upd(ng) and ng is not None,
|
|
cluster.node_groups)
|
|
cluster_dict = cluster.to_dict()
|
|
cluster_dict.update({'node_groups': updated + not_updated})
|
|
fake = r.ClusterResource(cluster_dict)
|
|
return fake
|
|
|
|
|
|
def get_ephemeral(node_group):
|
|
return nova.get_flavor(id=node_group.flavor_id).ephemeral
|
|
|
|
|
|
def has_volumes():
|
|
def validate(cluster_context):
|
|
for node_group in cluster_context.cluster.node_groups:
|
|
if not (node_group.volumes_per_node or get_ephemeral(node_group)):
|
|
raise NoVolumesException(node_group.name)
|
|
|
|
return validate
|
|
|
|
|
|
def image_has_tag(image_id, tag):
|
|
image = api.get_registered_image(image_id)
|
|
return tag in image.tags
|