trove/trove/extensions/mgmt/instances/models.py

285 lines
11 KiB
Python

# Copyright 2012 OpenStack Foundation
#
# 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 datetime
from oslo_log import log as logging
from trove.common import cfg
from trove.common import exception
from trove.common.i18n import _
from trove.common import remote
from trove.common import utils
from trove.extensions.mysql import models as mysql_models
from trove.instance import models as imodels
from trove.instance import models as instance_models
from trove.instance.models import load_instance, InstanceServiceStatus
from trove import rpc
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
def load_mgmt_instances(context, deleted=None, client=None,
include_clustered=None):
if not client:
client = remote.create_nova_client(context, CONF.os_region_name)
try:
mgmt_servers = client.rdservers.list()
except AttributeError:
mgmt_servers = client.servers.list(search_opts={'all_tenants': 1})
LOG.info(_("Found %d servers in Nova") %
len(mgmt_servers if mgmt_servers else []))
args = {}
if deleted is not None:
args['deleted'] = deleted
if not include_clustered:
args['cluster_id'] = None
db_infos = instance_models.DBInstance.find_all(**args)
instances = MgmtInstances.load_status_from_existing(context, db_infos,
mgmt_servers)
return instances
def load_mgmt_instance(cls, context, id, include_deleted):
try:
instance = load_instance(cls, context, id, needs_server=True,
include_deleted=include_deleted)
client = remote.create_nova_client(context, CONF.os_region_name)
try:
server = client.rdservers.get(instance.server_id)
except AttributeError:
server = client.servers.get(instance.server_id)
if hasattr(server, 'host'):
instance.server.host = server.host
elif hasattr(server, 'hostId'):
instance.server.host = server.hostId
if hasattr(server, 'deleted'):
instance.server.deleted = server.deleted
if hasattr(server, 'deleted_at'):
instance.server.deleted_at = server.deleted_at
if hasattr(server, 'local_id'):
instance.server.local_id = server.local_id
assert instance.server is not None
except Exception as e:
LOG.error(e)
instance = load_instance(cls, context, id, needs_server=False,
include_deleted=include_deleted)
return instance
class SimpleMgmtInstance(imodels.BaseInstance):
def __init__(self, context, db_info, server, datastore_status):
super(SimpleMgmtInstance, self).__init__(context, db_info, server,
datastore_status)
@property
def status(self):
if self.deleted:
return imodels.InstanceStatus.SHUTDOWN
return super(SimpleMgmtInstance, self).status
@property
def deleted(self):
return self.db_info.deleted
@property
def deleted_at(self):
return self.db_info.deleted_at
@classmethod
def load(cls, context, id, include_deleted=False):
return load_mgmt_instance(cls, context, id, include_deleted)
@property
def task_description(self):
return self.db_info.task_description
class DetailedMgmtInstance(SimpleMgmtInstance):
def __init__(self, *args, **kwargs):
super(DetailedMgmtInstance, self).__init__(*args, **kwargs)
self.volume = None
self.volume_used = None
self.volume_total = None
self.root_history = None
@classmethod
def load(cls, context, id, include_deleted=False):
instance = load_mgmt_instance(cls, context, id, include_deleted)
client = remote.create_cinder_client(context)
try:
instance.volume = client.volumes.get(instance.volume_id)
except Exception:
instance.volume = None
# Populate the volume_used attribute from the guest agent.
instance_models.load_guest_info(instance, context, id)
instance.root_history = mysql_models.RootHistory.load(context=context,
instance_id=id)
return instance
class MgmtInstance(imodels.Instance):
def get_diagnostics(self):
return self.get_guest().get_diagnostics()
def stop_db(self):
return self.get_guest().stop_db()
def get_hwinfo(self):
return self.get_guest().get_hwinfo()
def rpc_ping(self):
return self.get_guest().rpc_ping()
class MgmtInstances(imodels.Instances):
@staticmethod
def load_status_from_existing(context, db_infos, servers):
def load_instance(context, db, status, server=None):
return SimpleMgmtInstance(context, db, server, status)
if context is None:
raise TypeError(_("Argument context not defined."))
find_server = imodels.create_server_list_matcher(servers)
instances = imodels.Instances._load_servers_status(load_instance,
context,
db_infos,
find_server)
_load_servers(instances, find_server)
return instances
def _load_servers(instances, find_server):
for instance in instances:
db = instance.db_info
instance.server = None
try:
server = find_server(db.id, db.compute_instance_id)
instance.server = server
except Exception as ex:
LOG.exception(ex)
return instances
def publish_exist_events(transformer, admin_context):
notifier = rpc.get_notifier("taskmanager")
notifications = transformer()
# clear out admin_context.auth_token so it does not get logged
admin_context.auth_token = None
for notification in notifications:
notifier.info(admin_context, "trove.instance.exists", notification)
class NotificationTransformer(object):
def __init__(self, **kwargs):
pass
@staticmethod
def _get_audit_period():
now = datetime.datetime.now()
audit_start = utils.isotime(
now - datetime.timedelta(
seconds=CONF.exists_notification_interval),
subsecond=True)
audit_end = utils.isotime(now, subsecond=True)
return audit_start, audit_end
def _get_service_id(self, datastore_manager, id_map):
if datastore_manager in id_map:
datastore_manager_id = id_map[datastore_manager]
else:
datastore_manager_id = cfg.UNKNOWN_SERVICE_ID
LOG.error(_("Datastore ID for Manager (%s) is not configured")
% datastore_manager)
return datastore_manager_id
def transform_instance(self, instance, audit_start, audit_end):
payload = {
'audit_period_beginning': audit_start,
'audit_period_ending': audit_end,
'created_at': instance.created,
'display_name': instance.name,
'instance_id': instance.id,
'instance_name': instance.name,
'instance_type_id': instance.flavor_id,
'launched_at': instance.created,
'nova_instance_id': instance.server_id,
'region': CONF.region,
'state_description': instance.status.lower(),
'state': instance.status.lower(),
'tenant_id': instance.tenant_id
}
payload['service_id'] = self._get_service_id(
instance.datastore_version.manager, CONF.notification_service_id)
return payload
def __call__(self):
audit_start, audit_end = NotificationTransformer._get_audit_period()
messages = []
db_infos = instance_models.DBInstance.find_all(deleted=False)
for db_info in db_infos:
try:
service_status = InstanceServiceStatus.find_by(
instance_id=db_info.id)
except exception.ModelNotFoundError:
# There is a small window of opportunity during when the db
# resource for an instance exists, but no InstanceServiceStatus
# for it has yet been created. We skip sending the notification
# message for all such instances. These instance are too new
# and will get picked up the next round of notifications.
LOG.debug("InstanceServiceStatus not found for %s. "
"Will wait to send notification." % db_info.id)
continue
instance = SimpleMgmtInstance(None, db_info, None, service_status)
message = self.transform_instance(instance, audit_start, audit_end)
messages.append(message)
return messages
class NovaNotificationTransformer(NotificationTransformer):
def __init__(self, **kwargs):
super(NovaNotificationTransformer, self).__init__(**kwargs)
self.context = kwargs['context']
self.nova_client = remote.create_admin_nova_client(self.context)
self._flavor_cache = {}
def _lookup_flavor(self, flavor_id):
if flavor_id in self._flavor_cache:
LOG.debug("Flavor cache hit for %s" % flavor_id)
return self._flavor_cache[flavor_id]
# fetch flavor resource from nova
LOG.info(_("Flavor cache miss for %s") % flavor_id)
flavor = self.nova_client.flavors.get(flavor_id)
self._flavor_cache[flavor_id] = flavor.name if flavor else 'unknown'
return self._flavor_cache[flavor_id]
def __call__(self):
audit_start, audit_end = NotificationTransformer._get_audit_period()
instances = load_mgmt_instances(self.context, deleted=False,
client=self.nova_client)
messages = []
for instance in filter(
lambda inst: inst.status != 'SHUTDOWN' and inst.server,
instances):
message = {
'instance_type': self._lookup_flavor(instance.flavor_id),
'user_id': instance.server.user_id
}
message.update(self.transform_instance(instance,
audit_start,
audit_end))
messages.append(message)
return messages