1253 lines
48 KiB
Python
Executable File
1253 lines
48 KiB
Python
Executable File
#
|
|
# Copyright (c) 2015-2018 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
import json
|
|
from six.moves import http_client as httplib
|
|
|
|
from nfv_common import debug
|
|
|
|
from nfv_vim import nfvi
|
|
|
|
from nfv_plugins.nfvi_plugins import config
|
|
from nfv_plugins.nfvi_plugins.openstack import exceptions
|
|
from nfv_plugins.nfvi_plugins.openstack import guest
|
|
from nfv_plugins.nfvi_plugins.openstack import openstack
|
|
from nfv_plugins.nfvi_plugins.openstack import rest_api
|
|
|
|
from nfv_plugins.nfvi_plugins.openstack.objects import OPENSTACK_SERVICE
|
|
|
|
DLOG = debug.debug_get_logger('nfv_plugins.nfvi_plugins.guest_api')
|
|
|
|
|
|
def guest_service_get_name(name):
|
|
"""
|
|
Convert the nfvi guest service naming
|
|
"""
|
|
if guest.GUEST_SERVICE_NAME.HEARTBEAT == name:
|
|
return nfvi.objects.v1.GUEST_SERVICE_NAME.HEARTBEAT
|
|
else:
|
|
return nfvi.objects.v1.GUEST_SERVICE_NAME.UNKNOWN
|
|
|
|
|
|
def guest_service_get_admin_state(state):
|
|
"""
|
|
Convert the nfvi guest service state to a guest service administrative
|
|
state
|
|
"""
|
|
if guest.GUEST_SERVICE_STATE.ENABLED == state:
|
|
return nfvi.objects.v1.GUEST_SERVICE_ADMIN_STATE.UNLOCKED
|
|
else:
|
|
return nfvi.objects.v1.GUEST_SERVICE_ADMIN_STATE.LOCKED
|
|
|
|
|
|
def guest_service_get_service_state(state):
|
|
"""
|
|
Convert the guest service administrative state to nfvi guest service state
|
|
"""
|
|
if nfvi.objects.v1.GUEST_SERVICE_ADMIN_STATE.UNLOCKED == state:
|
|
return guest.GUEST_SERVICE_STATE.ENABLED
|
|
else:
|
|
return guest.GUEST_SERVICE_STATE.DISABLED
|
|
|
|
|
|
def guest_service_get_oper_state(status):
|
|
"""
|
|
Convert the nfvi guest service status to a guest service operational
|
|
state
|
|
"""
|
|
if guest.GUEST_SERVICE_STATUS.ENABLED == status:
|
|
return nfvi.objects.v1.GUEST_SERVICE_OPER_STATE.ENABLED
|
|
else:
|
|
return nfvi.objects.v1.GUEST_SERVICE_OPER_STATE.DISABLED
|
|
|
|
|
|
def instance_get_event(action_type, pre_notification):
|
|
"""
|
|
Convert the action type to a guest instance event
|
|
"""
|
|
if nfvi.objects.v1.INSTANCE_ACTION_TYPE.PAUSE == action_type:
|
|
event = guest.GUEST_EVENT.PAUSE
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.UNPAUSE == action_type:
|
|
event = guest.GUEST_EVENT.UNPAUSE
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.SUSPEND == action_type:
|
|
event = guest.GUEST_EVENT.SUSPEND
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.RESUME == action_type:
|
|
event = guest.GUEST_EVENT.RESUME
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.REBOOT == action_type:
|
|
event = guest.GUEST_EVENT.REBOOT
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.STOP == action_type:
|
|
event = guest.GUEST_EVENT.STOP
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.LIVE_MIGRATE == action_type:
|
|
if pre_notification:
|
|
event = guest.GUEST_EVENT.LIVE_MIGRATE_BEGIN
|
|
else:
|
|
event = guest.GUEST_EVENT.LIVE_MIGRATE_END
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.COLD_MIGRATE == action_type:
|
|
if pre_notification:
|
|
event = guest.GUEST_EVENT.COLD_MIGRATE_BEGIN
|
|
else:
|
|
event = guest.GUEST_EVENT.COLD_MIGRATE_END
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.RESIZE == action_type:
|
|
if pre_notification:
|
|
event = guest.GUEST_EVENT.RESIZE_BEGIN
|
|
else:
|
|
event = guest.GUEST_EVENT.RESIZE_END
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.CONFIRM_RESIZE == action_type:
|
|
event = guest.GUEST_EVENT.RESIZE_END
|
|
|
|
elif nfvi.objects.v1.INSTANCE_ACTION_TYPE.REVERT_RESIZE == action_type:
|
|
event = guest.GUEST_EVENT.RESIZE_END
|
|
|
|
else:
|
|
event = guest.GUEST_EVENT.UNKNOWN
|
|
DLOG.error("Unsupported action-type %s" % action_type)
|
|
|
|
return event
|
|
|
|
|
|
def instance_get_action_type(event):
|
|
"""
|
|
Convert guest instance event to an action type
|
|
"""
|
|
if guest.GUEST_EVENT.PAUSE == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.PAUSE
|
|
|
|
elif guest.GUEST_EVENT.UNPAUSE == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.UNPAUSE
|
|
|
|
elif guest.GUEST_EVENT.SUSPEND == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.SUSPEND
|
|
|
|
elif guest.GUEST_EVENT.RESUME == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.RESUME
|
|
|
|
elif guest.GUEST_EVENT.REBOOT == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.REBOOT
|
|
|
|
elif guest.GUEST_EVENT.STOP == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.STOP
|
|
|
|
elif guest.GUEST_EVENT.LIVE_MIGRATE_BEGIN == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.LIVE_MIGRATE
|
|
|
|
elif guest.GUEST_EVENT.LIVE_MIGRATE_END == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.LIVE_MIGRATE
|
|
|
|
elif guest.GUEST_EVENT.COLD_MIGRATE_BEGIN == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.COLD_MIGRATE
|
|
|
|
elif guest.GUEST_EVENT.COLD_MIGRATE_END == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.COLD_MIGRATE
|
|
|
|
elif guest.GUEST_EVENT.RESIZE_BEGIN == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.RESIZE
|
|
|
|
elif guest.GUEST_EVENT.RESIZE_END == event:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.RESIZE
|
|
|
|
else:
|
|
action_type = nfvi.objects.v1.INSTANCE_ACTION_TYPE.UNKNOWN
|
|
DLOG.error("Unsupported event %s" % event)
|
|
|
|
return action_type
|
|
|
|
|
|
def get_services_with_guest_service_state(services):
|
|
"""
|
|
Return guest service data with guest service administrative state
|
|
converted to nfvi guest service state
|
|
"""
|
|
services_data = []
|
|
for service in services:
|
|
service_data = dict()
|
|
service_data['service'] = service['service']
|
|
service_data['state'] \
|
|
= guest_service_get_service_state(service['admin_state'])
|
|
services_data.append(service_data)
|
|
return services_data
|
|
|
|
|
|
class NFVIGuestAPI(nfvi.api.v1.NFVIGuestAPI):
|
|
"""
|
|
NFVI Guest API Class Definition
|
|
"""
|
|
_name = 'Guest-API'
|
|
_version = '1.0.0'
|
|
_provider = 'Wind River'
|
|
_signature = '22b3dbf6-e4ba-441b-8797-fb8a51210a43'
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
@property
|
|
def version(self):
|
|
return self._version
|
|
|
|
@property
|
|
def provider(self):
|
|
return self._provider
|
|
|
|
@property
|
|
def signature(self):
|
|
return self._signature
|
|
|
|
def __init__(self):
|
|
super(NFVIGuestAPI, self).__init__()
|
|
self._token = None
|
|
self._directory = None
|
|
self._openstack_directory = None
|
|
self._rest_api_server = None
|
|
self._host_services_query_callback = None
|
|
self._guest_services_query_callback = None
|
|
self._guest_services_state_notify_callbacks = list()
|
|
self._guest_services_alarm_notify_callbacks = list()
|
|
self._guest_services_action_notify_callbacks = list()
|
|
|
|
def _host_supports_nova_compute(self, personality):
|
|
return (('worker' in personality) and
|
|
(self._openstack_directory.get_service_info(
|
|
OPENSTACK_SERVICE.NOVA) is not None))
|
|
|
|
def guest_services_create(self, future, instance_uuid, host_name,
|
|
services, callback):
|
|
"""
|
|
Guest Services Create
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._token is None or self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"instance_uuid=%s." % instance_uuid)
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
future.work(guest.guest_services_create, self._token,
|
|
instance_uuid, host_name, services)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
return
|
|
|
|
response['completed'] = True
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to create "
|
|
"guest services, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to create "
|
|
"guest services, instance_uuid=%s, error=%s."
|
|
% (instance_uuid, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def guest_services_set(self, future, instance_uuid, host_name,
|
|
services, callback):
|
|
"""
|
|
Guest Services Set
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._token is None or self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"instance_uuid=%s." % instance_uuid)
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
services_data = get_services_with_guest_service_state(services)
|
|
future.work(guest.guest_services_set, self._token,
|
|
instance_uuid, host_name, services_data)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
return
|
|
|
|
set_data = future.result.data
|
|
|
|
result_data = dict()
|
|
result_data['instance_uuid'] = set_data['uuid']
|
|
result_data['host_name'] = set_data['hostname']
|
|
|
|
service_objs = list()
|
|
for service in set_data.get('services', list()):
|
|
service_name = guest_service_get_name(service['service'])
|
|
admin_state = guest_service_get_admin_state(service['state'])
|
|
oper_state = guest_service_get_oper_state(service['status'])
|
|
restart_timeout = service.get('restart-timeout', None)
|
|
if restart_timeout is not None:
|
|
restart_timeout = int(restart_timeout)
|
|
|
|
service_obj = nfvi.objects.v1.GuestService(
|
|
service_name, admin_state, oper_state, restart_timeout)
|
|
|
|
service_objs.append(service_obj)
|
|
|
|
result_data['services'] = service_objs
|
|
|
|
response['result-data'] = result_data
|
|
response['completed'] = True
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to set "
|
|
"guest services, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to set "
|
|
"guest services, instance_uuid=%s, error=%s."
|
|
% (instance_uuid, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def guest_services_delete(self, future, instance_uuid, callback):
|
|
"""
|
|
Guest Services Delete
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._token is None or self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"instance_uuid=%s." % instance_uuid)
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
future.work(guest.guest_services_delete, self._token,
|
|
instance_uuid)
|
|
try:
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
return
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.NOT_FOUND != e.http_status_code:
|
|
raise
|
|
|
|
response['completed'] = True
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to delete "
|
|
"guest services, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to delete "
|
|
"guest services, instance_uuid=%s, error=%s."
|
|
% (instance_uuid, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def guest_services_query(self, future, instance_uuid, callback):
|
|
"""
|
|
Guest Services Query
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._token is None or self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"instance_uuid=%s." % instance_uuid)
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
future.work(guest.guest_services_query, self._token, instance_uuid)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
DLOG.error("Guest-Services query failed, operation "
|
|
"did not complete, instance_uuid=%s" %
|
|
instance_uuid)
|
|
return
|
|
|
|
query_data = future.result.data
|
|
|
|
result_data = dict()
|
|
result_data['instance_uuid'] = query_data['uuid']
|
|
result_data['host_name'] = query_data['hostname']
|
|
|
|
service_objs = list()
|
|
for service in query_data.get('services', list()):
|
|
service_name = guest_service_get_name(service['service'])
|
|
admin_state = guest_service_get_admin_state(service['state'])
|
|
oper_state = guest_service_get_oper_state(service['status'])
|
|
restart_timeout = service.get('restart-timeout', None)
|
|
if restart_timeout is not None:
|
|
restart_timeout = int(restart_timeout)
|
|
|
|
service_obj = nfvi.objects.v1.GuestService(
|
|
service_name, admin_state, oper_state, restart_timeout)
|
|
|
|
service_objs.append(service_obj)
|
|
|
|
result_data['services'] = service_objs
|
|
|
|
response['result-data'] = result_data
|
|
response['completed'] = True
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to query "
|
|
"guest services, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to query "
|
|
"guest services, instance_uuid=%s, error=%s."
|
|
% (instance_uuid, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def guest_services_vote(self, future, instance_uuid, host_name,
|
|
action_type, callback):
|
|
"""
|
|
Guest Services Vote
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._token is None or self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"instance_uuid=%s." % instance_uuid)
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
future.work(guest.guest_services_vote, self._token, instance_uuid,
|
|
host_name, instance_get_event(action_type,
|
|
pre_notification=True))
|
|
future.result = (yield)
|
|
if not future.result.is_complete():
|
|
DLOG.error("Guest-Services vote failed, action-type %s "
|
|
"did not complete, instance_uuid=%s" %
|
|
(action_type, instance_uuid))
|
|
return
|
|
|
|
vote_data = future.result.data
|
|
|
|
response['uuid'] = vote_data.get('uuid', instance_uuid)
|
|
response['host-name'] = vote_data.get('hostname', host_name)
|
|
response['action-type'] = vote_data.get('action', action_type)
|
|
response['timeout'] = int(vote_data.get('timeout', '15'))
|
|
response['completed'] = True
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to vote,"
|
|
"action_type=%s error=%s." % (action_type, e))
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to vote "
|
|
"guest services, instance_uuid=%s, error=%s."
|
|
% (instance_uuid, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def guest_services_notify(self, future, instance_uuid, host_name,
|
|
action_type, pre_notification, callback):
|
|
"""
|
|
Guest Services Notify
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._token is None or self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"instance_uuid=%s." % instance_uuid)
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
future.work(guest.guest_services_notify, self._token,
|
|
instance_uuid, host_name,
|
|
instance_get_event(action_type, pre_notification))
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
DLOG.error("Guest-Services_notify failed, action-type %s "
|
|
"did not complete, instance_uuid=%s" %
|
|
(action_type, instance_uuid))
|
|
return
|
|
|
|
notify_data = future.result.data
|
|
|
|
response['uuid'] = notify_data.get('uuid', instance_uuid)
|
|
response['host-name'] = notify_data.get('hostname', host_name)
|
|
response['action-type'] = notify_data.get('action', action_type)
|
|
response['timeout'] = int(notify_data.get('timeout', '15'))
|
|
response['completed'] = True
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to notify,"
|
|
"action_type=%s error=%s." % (action_type, e))
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to notify "
|
|
"guest services, instance_uuid=%s, error=%s."
|
|
% (instance_uuid, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def create_host_services(self, future, host_uuid, host_name,
|
|
host_personality, callback):
|
|
"""
|
|
Create Host Services, notify Guest to create services for a host
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._host_supports_nova_compute(host_personality):
|
|
response['reason'] = 'failed to get platform token from ' \
|
|
'keystone'
|
|
if self._token is None or \
|
|
self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"host_uuid=%s, host_name=%s." % (host_uuid,
|
|
host_name))
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
response['reason'] = 'failed to create guest services'
|
|
|
|
try:
|
|
# Send the create request to Guest.
|
|
future.work(guest.host_services_create,
|
|
self._token,
|
|
host_uuid, host_name)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
DLOG.error("Guest host-services-create failed, "
|
|
"operation did not complete, host_uuid=%s, "
|
|
"host_name=%s."
|
|
% (host_uuid, host_name))
|
|
return
|
|
|
|
response['reason'] = 'failed to disable guest services'
|
|
|
|
# Send the disable request to Guest
|
|
future.work(guest.host_services_disable,
|
|
self._token,
|
|
host_uuid, host_name)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
# do not return since the disable will be retried
|
|
# by audit
|
|
DLOG.error("Guest host-services-disable failed, "
|
|
"operation did not complete, host_uuid=%s, "
|
|
"host_name=%s."
|
|
% (host_uuid, host_name))
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
# Guest can send a 404 if it hasn't got the host
|
|
# inventory yet.
|
|
# Guest will catch up later, no need to fail here.
|
|
if httplib.NOT_FOUND != e.http_status_code:
|
|
raise
|
|
|
|
response['completed'] = True
|
|
response['reason'] = ''
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to create "
|
|
"guest services on host, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to create %s "
|
|
"guest services, error=%s." % (host_name, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def delete_host_services(self, future, host_uuid, host_name,
|
|
host_personality, callback):
|
|
"""
|
|
Delete Host Services, notify Guest to delete services for a host
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._host_supports_nova_compute(host_personality):
|
|
response['reason'] = 'failed to get platform token from ' \
|
|
'keystone'
|
|
if self._token is None or \
|
|
self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"host_uuid=%s, host_name=%s." % (host_uuid,
|
|
host_name))
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
response['reason'] = 'failed to delete guest services'
|
|
|
|
# Send the delete request to Guest.
|
|
future.work(guest.host_services_delete, self._token,
|
|
host_uuid)
|
|
try:
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
DLOG.error("Guest host-services-delete for host "
|
|
"failed, operation did not complete, "
|
|
"host_uuid=%s, host_name=%s."
|
|
% (host_uuid, host_name))
|
|
return
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.NOT_FOUND != e.http_status_code:
|
|
raise
|
|
|
|
response['completed'] = True
|
|
response['reason'] = ''
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to delete "
|
|
"host services on host, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to delete %s "
|
|
"guest services, error=%s."
|
|
% (host_name, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def enable_host_services(self, future, host_uuid, host_name,
|
|
host_personality, callback):
|
|
"""
|
|
Enable Host Services, notify Guest to enable services for a host.
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._host_supports_nova_compute(host_personality):
|
|
response['reason'] = 'failed to get platform token from ' \
|
|
'keystone'
|
|
if self._token is None or \
|
|
self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"host_uuid=%s, host_name=%s." % (host_uuid,
|
|
host_name))
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
response['reason'] = 'failed to enable guest services'
|
|
|
|
# Send the Enable request to Guest
|
|
future.work(guest.host_services_enable, self._token,
|
|
host_uuid, host_name)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
DLOG.error("Guest host-services-enable failed, operation "
|
|
"did not complete, host_uuid=%s, host_name=%s."
|
|
% (host_uuid, host_name))
|
|
return
|
|
|
|
response['completed'] = True
|
|
response['reason'] = ''
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to enable "
|
|
"guest services on host, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to enable %s "
|
|
"guest services, error=%s."
|
|
% (host_name, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def disable_host_services(self, future, host_uuid, host_name,
|
|
host_personality, callback):
|
|
"""
|
|
Notifies Guest to disable their services for the specified
|
|
host (as applicable)
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
# The following only applies to worker hosts
|
|
if self._host_supports_nova_compute(host_personality):
|
|
response['reason'] = 'failed to get platform token from ' \
|
|
'keystone'
|
|
if self._token is None or \
|
|
self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"host_uuid=%s, host_name=%s." % (host_uuid,
|
|
host_name))
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
response['reason'] = 'failed to disable guest services'
|
|
|
|
# Send the Disable request to Guest.
|
|
future.work(guest.host_services_disable, self._token,
|
|
host_uuid, host_name)
|
|
|
|
try:
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
# Do not return since the disable will be retried
|
|
# by audit
|
|
DLOG.error("Guest host-services-disable failed, "
|
|
"operation did not complete, host_uuid=%s, "
|
|
"host_name=%s."
|
|
% (host_uuid, host_name))
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.NOT_FOUND != e.http_status_code:
|
|
raise
|
|
|
|
response['completed'] = True
|
|
response['reason'] = ''
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to disable "
|
|
"guest services, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to disable %s "
|
|
"guest services, error=%s."
|
|
% (host_name, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def query_host_services(self, future, host_uuid, host_name,
|
|
host_personality, callback):
|
|
"""
|
|
Query Host Services, return state of Guest services for a host.
|
|
"""
|
|
response = dict()
|
|
response['completed'] = False
|
|
response['result-data'] = 'enabled'
|
|
response['reason'] = ''
|
|
|
|
try:
|
|
future.set_timeouts(config.CONF.get('nfvi-timeouts', None))
|
|
|
|
if self._host_supports_nova_compute(host_personality):
|
|
if self._token is None or \
|
|
self._token.is_expired():
|
|
future.work(openstack.get_token, self._directory)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete() or \
|
|
future.result.data is None:
|
|
DLOG.error("OpenStack get-token did not complete, "
|
|
"host_uuid=%s, host_name=%s." % (host_uuid,
|
|
host_name))
|
|
return
|
|
|
|
self._token = future.result.data
|
|
|
|
# Send Query request to Guest
|
|
future.work(guest.host_services_query, self._token,
|
|
host_uuid, host_name)
|
|
future.result = (yield)
|
|
|
|
if not future.result.is_complete():
|
|
DLOG.error("Guest query-host-services failed, operation "
|
|
"did not complete, host_uuid=%s, host_name=%s."
|
|
% (host_uuid, host_name))
|
|
|
|
else:
|
|
result_data = future.result.data
|
|
response['result-data'] = result_data['state']
|
|
|
|
response['completed'] = True
|
|
|
|
except exceptions.OpenStackRestAPIException as e:
|
|
if httplib.UNAUTHORIZED == e.http_status_code:
|
|
response['error-code'] = nfvi.NFVI_ERROR_CODE.TOKEN_EXPIRED
|
|
if self._token is not None:
|
|
self._token.set_expired()
|
|
|
|
else:
|
|
DLOG.exception("Caught exception while trying to query "
|
|
"host services, error=%s." % e)
|
|
|
|
except Exception as e:
|
|
DLOG.exception("Caught exception while trying to query %s "
|
|
"nova or neutron openstack services, error=%s."
|
|
% (host_name, e))
|
|
|
|
finally:
|
|
callback.send(response)
|
|
callback.close()
|
|
|
|
def host_services_rest_api_get_handler(self, request_dispatch):
|
|
"""
|
|
Host-Services Rest-API GET handler
|
|
"""
|
|
content_len = int(request_dispatch.headers.getheader('content-length',
|
|
0))
|
|
content = request_dispatch.rfile.read(content_len)
|
|
|
|
DLOG.info("Content=%s, len=%s" % (content, content_len))
|
|
|
|
http_payload = None
|
|
http_response = httplib.OK
|
|
|
|
if content:
|
|
host_data = json.loads(content)
|
|
|
|
if host_data['uuid'] is None:
|
|
DLOG.error("Invalid host uuid received")
|
|
http_response = httplib.BAD_REQUEST
|
|
|
|
elif host_data['hostname'] is None:
|
|
DLOG.info("Invalid host name received")
|
|
http_response = httplib.BAD_REQUEST
|
|
|
|
else:
|
|
(success, host_state) = \
|
|
self._host_services_query_callback(host_data['hostname'])
|
|
if not success:
|
|
http_response = httplib.BAD_REQUEST
|
|
else:
|
|
http_payload = dict()
|
|
http_payload['uuid'] = host_data['uuid']
|
|
http_payload['hostname'] = host_data['hostname']
|
|
http_payload['state'] = host_state
|
|
else:
|
|
http_response = httplib.NO_CONTENT
|
|
|
|
DLOG.debug("Host rest-api get path: %s." % request_dispatch.path)
|
|
request_dispatch.send_response(http_response)
|
|
|
|
if http_payload is not None:
|
|
request_dispatch.send_header('Content-Type', 'application/json')
|
|
request_dispatch.end_headers()
|
|
request_dispatch.wfile.write(json.dumps(http_payload))
|
|
request_dispatch.done()
|
|
|
|
def guest_services_rest_api_get_handler(self, request_dispatch):
|
|
"""
|
|
Guest-Services Rest-API GET handler
|
|
"""
|
|
# /nfvi-plugins/v1/instances/<instance-uuid>
|
|
# /nfvi-plugins/v1/instances/?host_uuid=<host-uuid>
|
|
|
|
host_uuid = None
|
|
instance_uuid = None
|
|
http_payload = None
|
|
path = request_dispatch.path
|
|
|
|
host_prefix = "host_uuid"
|
|
if host_prefix in path:
|
|
host_uuid = path.split('=')[-1]
|
|
else:
|
|
instance_uuid = path.split('/')[-1]
|
|
|
|
DLOG.debug("Guest-Services rest-api path=%s, host_uuid=%s, "
|
|
"instance_uuid=%s" % (path, host_uuid, instance_uuid))
|
|
|
|
http_response = httplib.OK
|
|
(success, result) = \
|
|
self._guest_services_query_callback(host_uuid, instance_uuid)
|
|
if not success:
|
|
http_response = httplib.BAD_REQUEST
|
|
else:
|
|
if instance_uuid:
|
|
result['services'] = \
|
|
get_services_with_guest_service_state(result['services'])
|
|
else:
|
|
for r in result['instances']:
|
|
r['services'] = \
|
|
get_services_with_guest_service_state(r['services'])
|
|
http_payload = result
|
|
|
|
DLOG.debug("Guest-Services rest-api get path: %s." %
|
|
request_dispatch.path)
|
|
request_dispatch.send_response(http_response)
|
|
|
|
if http_payload is not None:
|
|
request_dispatch.send_header('Content-Type', 'application/json')
|
|
request_dispatch.end_headers()
|
|
request_dispatch.wfile.write(json.dumps(http_payload))
|
|
request_dispatch.done()
|
|
|
|
def guest_services_rest_api_patch_handler(self, request_dispatch):
|
|
"""
|
|
Guest-Services Rest-API PATCH handler callback
|
|
"""
|
|
content_len = int(request_dispatch.headers.getheader('content-length',
|
|
0))
|
|
content = request_dispatch.rfile.read(content_len)
|
|
http_payload = None
|
|
http_response = httplib.OK
|
|
|
|
if content:
|
|
instance_data = json.loads(content)
|
|
instance_uuid = instance_data.get('uuid', None)
|
|
host_name = instance_data.get('hostname', None)
|
|
event_type = instance_data.get('event-type', None)
|
|
event_data = instance_data.get('event-data', None)
|
|
result = None
|
|
|
|
if instance_uuid is None:
|
|
DLOG.info("Invalid instance uuid received")
|
|
http_response = httplib.BAD_REQUEST
|
|
elif event_type is None:
|
|
DLOG.info("Invalid event-type received")
|
|
http_response = httplib.BAD_REQUEST
|
|
elif event_data is None:
|
|
DLOG.info("Invalid event-data received")
|
|
http_response = httplib.BAD_REQUEST
|
|
else:
|
|
if 'service' == event_type:
|
|
services = event_data.get('services', list())
|
|
|
|
service_objs = list()
|
|
for service in services:
|
|
restart_timeout = service.get('restart-timeout', None)
|
|
if restart_timeout is not None:
|
|
restart_timeout = int(restart_timeout)
|
|
|
|
service_obj = nfvi.objects.v1.GuestService(
|
|
guest_service_get_name(service['service']),
|
|
guest_service_get_admin_state(service['state']),
|
|
guest_service_get_oper_state(service['status']),
|
|
restart_timeout)
|
|
|
|
service_objs.append(service_obj)
|
|
|
|
for callback in self._guest_services_state_notify_callbacks:
|
|
callback(instance_uuid, host_name, service_objs)
|
|
|
|
elif 'alarm' == event_type:
|
|
services = event_data.get('services', list())
|
|
for service_data in services:
|
|
if guest.GUEST_SERVICE_NAME.HEARTBEAT == \
|
|
service_data['service']:
|
|
avail_status = service_data.get('state', None)
|
|
recovery_action = \
|
|
service_data.get('repair-action', None)
|
|
if 'reboot' == recovery_action:
|
|
recovery_action = \
|
|
nfvi.objects.v1.INSTANCE_ACTION_TYPE.REBOOT
|
|
elif 'stop' == recovery_action:
|
|
recovery_action = \
|
|
nfvi.objects.v1.INSTANCE_ACTION_TYPE.STOP
|
|
elif 'log' == recovery_action:
|
|
recovery_action = \
|
|
nfvi.objects.v1.INSTANCE_ACTION_TYPE.LOG
|
|
|
|
if 'failed' == avail_status:
|
|
avail_status = \
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.FAILED
|
|
elif 'unhealthy' == avail_status:
|
|
avail_status = \
|
|
nfvi.objects.v1.INSTANCE_AVAIL_STATUS.UNHEALTHY
|
|
|
|
for callback in \
|
|
self._guest_services_alarm_notify_callbacks:
|
|
(success, result) = callback(instance_uuid,
|
|
avail_status,
|
|
recovery_action)
|
|
if not success:
|
|
DLOG.error("Callback failed for "
|
|
"instance_uuid=%s" % instance_uuid)
|
|
http_response = httplib.BAD_REQUEST
|
|
|
|
elif 'action' == event_type:
|
|
action_type = \
|
|
instance_get_action_type(event_data.get('action'))
|
|
|
|
guest_response = event_data.get('guest-response')
|
|
if guest.GUEST_VOTE_STATE.REJECT == guest_response:
|
|
action_state = \
|
|
nfvi.objects.v1.INSTANCE_ACTION_STATE.REJECTED
|
|
elif guest.GUEST_VOTE_STATE.ALLOW == guest_response:
|
|
action_state = \
|
|
nfvi.objects.v1.INSTANCE_ACTION_STATE.ALLOWED
|
|
elif guest.GUEST_VOTE_STATE.PROCEED == guest_response:
|
|
action_state = \
|
|
nfvi.objects.v1.INSTANCE_ACTION_STATE.PROCEED
|
|
else:
|
|
action_state = guest.GUEST_VOTE_STATE.PROCEED
|
|
DLOG.info("Invalid guest-response received, "
|
|
"defaulting to proceed, response=%s."
|
|
% guest_response)
|
|
|
|
reason = event_data.get('reason')
|
|
|
|
for callback in \
|
|
self._guest_services_action_notify_callbacks:
|
|
result = None
|
|
success = callback(instance_uuid, action_type,
|
|
action_state, reason)
|
|
if not success:
|
|
DLOG.error("callback failed for instance_uuid=%s"
|
|
% instance_uuid)
|
|
http_response = httplib.BAD_REQUEST
|
|
else:
|
|
DLOG.info("Invalid event-type %s received" % event_type)
|
|
http_response = httplib.BAD_REQUEST
|
|
|
|
if httplib.OK == http_response:
|
|
http_payload = result
|
|
|
|
else:
|
|
http_response = httplib.NO_CONTENT
|
|
|
|
DLOG.debug("Guest-Services rest-api patch path: %s."
|
|
% request_dispatch.path)
|
|
request_dispatch.send_response(http_response)
|
|
|
|
if http_payload is not None:
|
|
request_dispatch.send_header('Content-Type', 'application/json')
|
|
request_dispatch.end_headers()
|
|
request_dispatch.wfile.write(json.dumps(http_payload))
|
|
request_dispatch.done()
|
|
|
|
def register_host_services_query_callback(self, callback):
|
|
"""
|
|
Register for Host Services query
|
|
"""
|
|
self._host_services_query_callback = callback
|
|
|
|
def register_guest_services_query_callback(self, callback):
|
|
"""
|
|
Register for Guest Services query
|
|
"""
|
|
self._guest_services_query_callback = callback
|
|
|
|
def register_guest_services_state_notify_callback(self, callback):
|
|
"""
|
|
Register for Guest Services notify service type event
|
|
"""
|
|
self._guest_services_state_notify_callbacks.append(callback)
|
|
|
|
def register_guest_services_alarm_notify_callback(self, callback):
|
|
"""
|
|
Register for Guest Services notify for alarm type event
|
|
"""
|
|
self._guest_services_alarm_notify_callbacks.append(callback)
|
|
|
|
def register_guest_services_action_notify_callback(self, callback):
|
|
"""
|
|
Register for Guest Services notify for action type event
|
|
"""
|
|
self._guest_services_action_notify_callbacks.append(callback)
|
|
|
|
def initialize(self, config_file):
|
|
"""
|
|
Initialize the plugin
|
|
"""
|
|
config.load(config_file)
|
|
self._directory = openstack.get_directory(
|
|
config, openstack.SERVICE_CATEGORY.PLATFORM)
|
|
self._openstack_directory = openstack.get_directory(
|
|
config, openstack.SERVICE_CATEGORY.OPENSTACK)
|
|
|
|
self._rest_api_server = rest_api.rest_api_get_server(
|
|
config.CONF['guest-rest-api']['host'],
|
|
config.CONF['guest-rest-api']['port'])
|
|
|
|
self._rest_api_server.add_handler(
|
|
'GET', '/nfvi-plugins/v1/hosts*',
|
|
self.host_services_rest_api_get_handler)
|
|
|
|
self._rest_api_server.add_handler(
|
|
'GET', '/nfvi-plugins/v1/instances*',
|
|
self.guest_services_rest_api_get_handler)
|
|
|
|
self._rest_api_server.add_handler(
|
|
'PATCH', '/nfvi-plugins/v1/instances*',
|
|
self.guest_services_rest_api_patch_handler)
|
|
|
|
def finalize(self):
|
|
"""
|
|
Finalize the plugin
|
|
"""
|
|
return
|