sahara/sahara/utils/cluster.py
Jeremy Freudberg bf68afba5f Allow overriding of /etc/hosts entries
Sometimes the DNS lookup performed where Sahara services are running
may be inaccurate.

Change-Id: I27d2bc4ad9340440878a2f342549d7fe74ee7e68
Story: 2001737
Task: 12092
2018-07-31 11:59:46 -04:00

194 lines
6.4 KiB
Python

# Copyright (c) 2015 Intel Inc.
#
# 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 socket
from keystoneauth1 import exceptions as keystone_ex
from oslo_config import cfg
from oslo_log import log as logging
from six.moves.urllib import parse
from sahara import conductor as c
from sahara import context
from sahara import exceptions as e
from sahara.utils.notification import sender
from sahara.utils.openstack import base as auth_base
conductor = c.API
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
# cluster status
CLUSTER_STATUS_VALIDATING = "Validating"
CLUSTER_STATUS_INFRAUPDATING = "InfraUpdating"
CLUSTER_STATUS_SPAWNING = "Spawning"
CLUSTER_STATUS_WAITING = "Waiting"
CLUSTER_STATUS_PREPARING = "Preparing"
CLUSTER_STATUS_CONFIGURING = "Configuring"
CLUSTER_STATUS_STARTING = "Starting"
CLUSTER_STATUS_ACTIVE = "Active"
CLUSTER_STATUS_DECOMMISSIONING = "Decommissioning"
CLUSTER_STATUS_ERROR = "Error"
CLUSTER_STATUS_DELETING = "Deleting"
CLUSTER_STATUS_AWAITINGTERMINATION = "AwaitingTermination"
# cluster status -- Instances
CLUSTER_STATUS_DELETING_INSTANCES = "Deleting Instances"
CLUSTER_STATUS_ADDING_INSTANCES = "Adding Instances"
# Scaling status
CLUSTER_STATUS_SCALING = "Scaling"
CLUSTER_STATUS_SCALING_SPAWNING = (CLUSTER_STATUS_SCALING +
": " + CLUSTER_STATUS_SPAWNING)
CLUSTER_STATUS_SCALING_WAITING = (CLUSTER_STATUS_SCALING +
": " + CLUSTER_STATUS_WAITING)
CLUSTER_STATUS_SCALING_PREPARING = (CLUSTER_STATUS_SCALING +
": " + CLUSTER_STATUS_PREPARING)
# Rollback status
CLUSTER_STATUS_ROLLBACK = "Rollback"
CLUSTER_STATUS_ROLLBACK_SPAWNING = (CLUSTER_STATUS_ROLLBACK +
": " + CLUSTER_STATUS_SPAWNING)
CLUSTER_STATUS_ROLLBACK_WAITING = (CLUSTER_STATUS_ROLLBACK +
": " + CLUSTER_STATUS_WAITING)
CLUSTER_STATUS_ROLLBACK__PREPARING = (CLUSTER_STATUS_ROLLBACK +
": " + CLUSTER_STATUS_PREPARING)
def change_cluster_status_description(cluster, status_description):
try:
ctx = context.ctx()
return conductor.cluster_update(
ctx, cluster, {'status_description': status_description})
except e.NotFoundException:
return None
def change_cluster_status(cluster, status, status_description=None):
ctx = context.ctx()
# Update cluster status. Race conditions with deletion are still possible,
# but this reduces probability at least.
cluster = conductor.cluster_get(ctx, cluster) if cluster else None
if status_description is not None:
change_cluster_status_description(cluster, status_description)
# 'Deleting' is final and can't be changed
if cluster is None or cluster.status == CLUSTER_STATUS_DELETING:
return cluster
update_dict = {"status": status}
cluster = conductor.cluster_update(ctx, cluster, update_dict)
conductor.cluster_provision_progress_update(ctx, cluster.id)
LOG.info("Cluster status has been changed. New status="
"{status}".format(status=cluster.status))
sender.status_notify(cluster.id, cluster.name, cluster.status,
"update")
return cluster
def count_instances(cluster):
return sum([node_group.count for node_group in cluster.node_groups])
def check_cluster_exists(cluster):
ctx = context.ctx()
# check if cluster still exists (it might have been removed)
cluster = conductor.cluster_get(ctx, cluster)
return cluster is not None
def get_instances(cluster, instances_ids=None):
inst_map = {}
for node_group in cluster.node_groups:
for instance in node_group.instances:
inst_map[instance.id] = instance
if instances_ids is not None:
return [inst_map[id] for id in instances_ids]
else:
return [v for v in inst_map.values()]
def clean_cluster_from_empty_ng(cluster):
ctx = context.ctx()
for ng in cluster.node_groups:
if ng.count == 0:
conductor.node_group_remove(ctx, ng)
def etc_hosts_entry_for_service(service):
result = ""
try:
hostname = parse.urlparse(
auth_base.url_for(service_type=service,
endpoint_type="publicURL")).hostname
except keystone_ex.EndpointNotFound:
LOG.debug("Endpoint not found for service: '{}'".format(service))
return result
overridden_ip = (
getattr(CONF, "%s_ip_accessible" % service.replace('-', '_'), None)
)
if overridden_ip is not None:
return "%s %s\n" % (overridden_ip, hostname)
try:
result = "%s %s\n" % (socket.gethostbyname(hostname), hostname)
except socket.gaierror:
LOG.warning("Failed to resolve hostname of service: '{}'"
.format(service))
result = "# Failed to resolve {} during deployment\n".format(hostname)
return result
def _etc_hosts_for_services(hosts):
# add alias for keystone and swift
for service in ["identity", "object-store"]:
hosts += etc_hosts_entry_for_service(service)
return hosts
def _etc_hosts_for_instances(hosts, cluster):
for node_group in cluster.node_groups:
for instance in node_group.instances:
hosts += "%s %s %s\n" % (instance.internal_ip,
instance.fqdn(),
instance.hostname())
return hosts
def generate_etc_hosts(cluster):
hosts = "127.0.0.1 localhost\n"
if not cluster.use_designate_feature():
hosts = _etc_hosts_for_instances(hosts, cluster)
hosts = _etc_hosts_for_services(hosts)
return hosts
def generate_resolv_conf_diff(curr_resolv_conf):
# returns string that contains nameservers
# which are lacked in the 'curr_resolve_conf'
resolv_conf = ""
for ns in CONF.nameservers:
if ns not in curr_resolv_conf:
resolv_conf += "nameserver {}\n".format(ns)
return resolv_conf