ha/service-mgmt-api/sm-api/sm_api/api/controllers/v1/servicenode.py

448 lines
18 KiB
Python
Executable File

#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Red Hat, Inc.
# 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.
#
# Copyright (c) 2013-2014 Wind River Systems, Inc.
#
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from sm_api.api.controllers.v1 import base
from sm_api.api.controllers.v1 import smc_api
from sm_api.openstack.common import log
LOG = log.getLogger(__name__)
ERR_CODE_SUCCESS = "0"
ERR_CODE_HOST_NOT_FOUND = "-1000"
ERR_CODE_ACTION_FAILED = "-1001"
ERR_CODE_NO_HOST_TO_SWACT_TO = "-1002"
SM_NODE_STATE_UNKNOWN = "unknown"
SM_NODE_ADMIN_LOCKED = "locked"
SM_NODE_ADMIN_UNLOCKED = "unlocked"
SM_NODE_OPER_ENABLED = "enabled"
SM_NODE_OPER_DISABLED = "disabled"
SM_NODE_AVAIL_AVAILABLE = "available"
SM_NODE_AVAIL_DEGRADED = "degraded"
SM_NODE_AVAIL_FAILED = "failed"
# sm_types.c
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_NIL = "nil"
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_UNKNOWN = "unknown"
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_NONE = "none"
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_N = "N"
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_N_PLUS_M = "N + M"
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_N_TO_1 = "N to 1"
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_N_TO_N = "N to N"
# sm_types.c
SM_SERVICE_GROUP_STATE_NIL = "nil"
SM_SERVICE_GROUP_STATE_NA = "not-applicable"
SM_SERVICE_GROUP_STATE_INITIAL = "initial"
SM_SERVICE_GROUP_STATE_UNKNOWN = "unknown"
SM_SERVICE_GROUP_STATE_STANDBY = "standby"
SM_SERVICE_GROUP_STATE_GO_STANDBY = "go-standby"
SM_SERVICE_GROUP_STATE_GO_ACTIVE = "go-active"
SM_SERVICE_GROUP_STATE_ACTIVE = "active"
SM_SERVICE_GROUP_STATE_DISABLING = "disabling"
SM_SERVICE_GROUP_STATE_DISABLED = "disabled"
SM_SERVICE_GROUP_STATE_SHUTDOWN = "shutdown"
# sm_types.c
SM_SERVICE_GROUP_STATUS_NIL = "nil"
SM_SERVICE_GROUP_STATUS_NONE = ""
SM_SERVICE_GROUP_STATUS_WARN = "warn"
SM_SERVICE_GROUP_STATUS_DEGRADED = "degraded"
SM_SERVICE_GROUP_STATUS_FAILED = "failed"
# sm_types.c
SM_SERVICE_GROUP_CONDITION_NIL = "nil"
SM_SERVICE_GROUP_CONDITION_NONE = ""
SM_SERVICE_GROUP_CONDITION_DATA_INCONSISTENT = "data-inconsistent"
SM_SERVICE_GROUP_CONDITION_DATA_OUTDATED = "data-outdated"
SM_SERVICE_GROUP_CONDITION_DATA_CONSISTENT = "data-consistent"
SM_SERVICE_GROUP_CONDITION_DATA_SYNC = "data-syncing"
SM_SERVICE_GROUP_CONDITION_DATA_STANDALONE = "data-standalone"
SM_SERVICE_GROUP_CONDITION_RECOVERY_FAILURE = "recovery-failure"
SM_SERVICE_GROUP_CONDITION_ACTION_FAILURE = "action-failure"
SM_SERVICE_GROUP_CONDITION_FATAL_FAILURE = "fatal-failure"
class ServiceNodeCommand(base.APIBase):
origin = wtypes.text
action = wtypes.text # swact | swact-force | unlock | lock | event
admin = wtypes.text # locked | unlocked
oper = wtypes.text # enabled | disabled
avail = wtypes.text # none | ...
class ServiceNodeCommandResult(base.APIBase):
# Origin and Host Information
origin = wtypes.text # e.g. "mtce" or "sm"
hostname = wtypes.text
# Command
action = wtypes.text
admin = wtypes.text
oper = wtypes.text
avail = wtypes.text
# Result
error_code = wtypes.text
error_details = wtypes.text
class ServiceNode(base.APIBase):
origin = wtypes.text
hostname = wtypes.text
admin = wtypes.text
oper = wtypes.text
avail = wtypes.text
active_services = wtypes.text
swactable_services = wtypes.text
class ServiceNodeController(rest.RestController):
def __init__(self, from_isystem=False):
self._seqno = 0
def _seqno_incr_get(self):
self._seqno += 1
return self._seqno
def _seqno_get(self):
return self._seqno
def _get_current_sm_sdas(self):
sm_sdas = pecan.request.dbapi.sm_sda_get_list()
for sm in sm_sdas:
LOG.debug("sm-api sm_sdas= %s" % sm.as_dict())
return sm_sdas
def _sm_sdm_get(self, server, service_group_name):
return pecan.request.dbapi.sm_sdm_get(server, service_group_name)
def _smc_node_exists(self, hostname):
# check whether hostname exists in nodes table
node_exists = False
sm_nodes = pecan.request.dbapi.sm_node_get_by_name(hostname)
for sm_node in sm_nodes:
node_exists = True
return node_exists
def _get_sm_node_state(self, hostname):
sm_nodes = pecan.request.dbapi.sm_node_get_by_name(hostname)
# default values
node_state = {'hostname': hostname,
'admin': SM_NODE_STATE_UNKNOWN,
'oper': SM_NODE_STATE_UNKNOWN,
'avail': SM_NODE_STATE_UNKNOWN}
for sm_node in sm_nodes:
node_state = {'hostname': hostname,
'admin': sm_node.administrative_state,
'oper': sm_node.operational_state,
'avail': sm_node.availability_status}
break
LOG.debug("sm-api get_sm_node_state hostname: %s" % (node_state))
return node_state
def _have_active_sm_services(self, hostname, sm_sdas):
# check db service_domain_assignments for any "active"
# in either state or desired state:
active_sm_services = False
# active: current or transition state
active_attr_list = [SM_SERVICE_GROUP_STATE_ACTIVE,
SM_SERVICE_GROUP_STATE_GO_ACTIVE,
SM_SERVICE_GROUP_STATE_GO_STANDBY,
SM_SERVICE_GROUP_STATE_DISABLING,
SM_SERVICE_GROUP_STATE_UNKNOWN]
for sm_sda in sm_sdas:
if sm_sda.node_name == hostname:
for aa in active_attr_list:
if sm_sda.state == aa or sm_sda.desired_state == aa:
active_sm_services = True
LOG.debug("sm-api have_active_sm_services True")
return active_sm_services
LOG.debug("sm-api have_active_sm_services: False")
return active_sm_services
def _have_swactable_sm_services(self, hostname, sm_sdas):
# check db service_domain_assignments for any "active"
# in either state or desired state:
swactable_sm_services = False
# active: current or transition state
active_attr_list = [SM_SERVICE_GROUP_STATE_ACTIVE,
SM_SERVICE_GROUP_STATE_GO_ACTIVE,
SM_SERVICE_GROUP_STATE_GO_STANDBY,
SM_SERVICE_GROUP_STATE_DISABLING,
SM_SERVICE_GROUP_STATE_UNKNOWN]
for sm_sda in sm_sdas:
if sm_sda.node_name == hostname:
for aa in active_attr_list:
if sm_sda.state == aa or sm_sda.desired_state == aa:
sdm = self._sm_sdm_get(sm_sda.name,
sm_sda.service_group_name)
if sdm.redundancy_model == \
SM_SERVICE_DOMAIN_MEMBER_REDUNDANCY_MODEL_N_PLUS_M:
swactable_sm_services = True
LOG.debug("sm-api have_swactable_sm_services True")
return swactable_sm_services
LOG.debug("sm-api have_active_sm_services: False")
return swactable_sm_services
def _swact_pre_check(self, hostname):
# run pre-swact checks, verify that services are in the right state
# to accept service
have_destination = False
check_result = None
sm_sdas = pecan.request.dbapi.sm_sda_get_list(None, None,
sort_key='name',
sort_dir='asc')
origin_state = self._collect_svc_state(sm_sdas, hostname)
for sm_sda in sm_sdas:
if sm_sda.node_name != hostname:
have_destination = True
# Verify that target host state is unlocked-enabled
node_state = self._get_sm_node_state(sm_sda.node_name)
if SM_NODE_ADMIN_LOCKED == node_state['admin']:
check_result = ("%s is not ready to take service, "
"%s is locked"
% (sm_sda.node_name, sm_sda.node_name))
break
if SM_NODE_OPER_DISABLED == node_state['oper']:
check_result = ("%s is not ready to take service, "
"%s is disabled"
% (sm_sda.node_name, sm_sda.node_name))
break
# Verify that
# all the services are in the standby or active
# state on the other host
# or service only provisioned in the other host
# or service state are the same on both hosts
if SM_SERVICE_GROUP_STATE_ACTIVE != sm_sda.state \
and SM_SERVICE_GROUP_STATE_STANDBY != sm_sda.state \
and origin_state.has_key(sm_sda.service_group_name) \
and origin_state[sm_sda.service_group_name] != sm_sda.state:
check_result = (
"%s on %s is not ready to take service, "
"service not in the active or standby "
"state" % (sm_sda.service_group_name,
sm_sda.node_name))
break
# Verify that all the services are in the desired state on
# the other host
if sm_sda.desired_state != sm_sda.state:
check_result = ("%s on %s is not ready to take service, "
"services transitioning state"
% (sm_sda.service_group_name,
sm_sda.node_name))
break
# Verify that all the services are ready to accept service
# i.e. not failed or syncing data
if SM_SERVICE_GROUP_STATUS_FAILED == sm_sda.status:
check_result = ("%s on %s is not ready to take service, "
"service is failed"
% (sm_sda.service_group_name,
sm_sda.node_name))
break
elif SM_SERVICE_GROUP_STATUS_DEGRADED == sm_sda.status:
degraded_conditions \
= [SM_SERVICE_GROUP_CONDITION_DATA_INCONSISTENT,
SM_SERVICE_GROUP_CONDITION_DATA_OUTDATED,
SM_SERVICE_GROUP_CONDITION_DATA_CONSISTENT,
SM_SERVICE_GROUP_CONDITION_DATA_STANDALONE]
if sm_sda.condition == SM_SERVICE_GROUP_CONDITION_DATA_SYNC:
check_result = ("%s on %s is not ready to take "
"service, service is syncing data"
% (sm_sda.service_group_name,
sm_sda.node_name))
break
elif sm_sda.condition in degraded_conditions:
check_result = ("%s on %s is not ready to take "
"service, service is degraded, %s"
% (sm_sda.service_group_name,
sm_sda.node_name, sm_sda.condition))
break
else:
check_result = ("%s on %s is not ready to take "
"service, service is degraded"
% (sm_sda.service_group_name,
sm_sda.node_name))
break
if check_result is None and not have_destination:
check_result = "no peer available"
if check_result is not None:
LOG.info("swact pre-check failed host %s, reason=%s."
% (hostname, check_result))
return check_result
@staticmethod
def _collect_svc_state(sm_sdas, hostname):
sm_state_ht = {}
for sm_sda in sm_sdas:
if sm_sda.node_name == hostname:
sm_state_ht[sm_sda.service_group_name] = sm_sda.state
LOG.info("%s" % sm_state_ht)
return sm_state_ht
def _do_modify_command(self, hostname, command):
if command.action == smc_api.SM_NODE_ACTION_SWACT_PRE_CHECK or \
command.action == smc_api.SM_NODE_ACTION_SWACT:
check_result = self._swact_pre_check(hostname)
if check_result is not None:
result = ServiceNodeCommandResult(
origin="sm", hostname=hostname, action=command.action,
admin=command.admin, oper=command.oper,
avail=command.avail, error_code=ERR_CODE_ACTION_FAILED,
error_details=check_result)
if command.action == smc_api.SM_NODE_ACTION_SWACT_PRE_CHECK:
return wsme.api.Response(result, status_code=200)
return wsme.api.Response(result, status_code=400)
elif command.action == smc_api.SM_NODE_ACTION_SWACT_PRE_CHECK:
result = ServiceNodeCommandResult(
origin="sm", hostname=hostname, action=command.action,
admin=command.admin, oper=command.oper,
avail=command.avail, error_code=ERR_CODE_SUCCESS,
error_details=check_result)
return wsme.api.Response(result, status_code=200)
if command.action == smc_api.SM_NODE_ACTION_UNLOCK or \
command.action == smc_api.SM_NODE_ACTION_LOCK or \
command.action == smc_api.SM_NODE_ACTION_SWACT or \
command.action == smc_api.SM_NODE_ACTION_SWACT_FORCE or \
command.action == smc_api.SM_NODE_ACTION_EVENT:
sm_ack_dict = smc_api.sm_api_set_node_state(command.origin,
hostname,
command.action,
command.admin,
command.avail,
command.oper,
self._seqno_incr_get())
ack_admin = sm_ack_dict['SM_API_MSG_NODE_ADMIN'].lower()
ack_oper = sm_ack_dict['SM_API_MSG_NODE_OPER'].lower()
ack_avail = sm_ack_dict['SM_API_MSG_NODE_AVAIL'].lower()
LOG.debug("sm-api _do_modify_command sm_ack_dict: %s ACK admin: "
"%s oper: %s avail: %s." % (sm_ack_dict, ack_admin,
ack_oper, ack_avail))
# loose check on admin and oper only
if (command.admin == ack_admin) and (command.oper == ack_oper):
return ServiceNodeCommandResult(
origin=sm_ack_dict['SM_API_MSG_ORIGIN'],
hostname=sm_ack_dict['SM_API_MSG_NODE_NAME'],
action=sm_ack_dict['SM_API_MSG_NODE_ACTION'],
admin=ack_admin,
oper=ack_oper,
avail=ack_avail,
error_code=ERR_CODE_SUCCESS,
error_msg="success")
else:
result = ServiceNodeCommandResult(
origin="sm",
hostname=hostname,
action=sm_ack_dict['SM_API_MSG_NODE_ACTION'],
admin=ack_admin,
oper=ack_oper,
avail=ack_avail,
error_code=ERR_CODE_ACTION_FAILED,
error_details="action failed")
return wsme.api.Response(result, status_code=500)
else:
raise wsme.exc.InvalidInput('action', command.action, "unknown")
@wsme_pecan.wsexpose(ServiceNode, unicode)
def get_one(self, hostname):
try:
data = self._get_sm_node_state(hostname)
except:
LOG.exception("No entry in database for %s:" % hostname)
return ServiceNode(origin="sm",
hostname=hostname,
admin=SM_NODE_STATE_UNKNOWN,
oper=SM_NODE_STATE_UNKNOWN,
avail=SM_NODE_STATE_UNKNOWN,
active_services="unknown",
swactable_services="unknown")
sm_sdas = self._get_current_sm_sdas()
if self._have_active_sm_services(hostname, sm_sdas):
active_services = "yes"
else:
active_services = "no"
if self._have_swactable_sm_services(hostname, sm_sdas):
swactable_services = "yes"
else:
swactable_services = "no"
return ServiceNode(origin="sm",
hostname=data['hostname'],
admin=data['admin'],
oper=data['oper'],
avail=data['avail'],
active_services=active_services,
swactable_services=swactable_services)
@wsme_pecan.wsexpose(ServiceNodeCommandResult, unicode,
body=ServiceNodeCommand)
def patch(self, hostname, command):
if command.origin != "mtce" and command.origin != "sysinv":
LOG.warn("sm-api unexpected origin: %s. Continuing."
% command.origin)
return self._do_modify_command(hostname, command)