 2cf1f39d9e
			
		
	
	2cf1f39d9e
	
	
	
		
			
			The volume.usage notifications are used to enable traffic based billing on volumes. The availability_zone of the instance is an important piece of information that can enable more specialized billing of customers. This makes the volume.usage events consistent with the volume.create.start/end snapshot.create.start/end, etc. emitted from cinder, which contain the availability_zone field. Change-Id: I9b6b03174cd03235d0e6cf01e34a6da13bd9bb70
		
			
				
	
	
		
			336 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # vim: tabstop=4 shiftwidth=4 softtabstop=4
 | |
| 
 | |
| # Copyright (c) 2011 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.
 | |
| 
 | |
| """Compute-related Utilities and helpers."""
 | |
| 
 | |
| import re
 | |
| import string
 | |
| import traceback
 | |
| 
 | |
| from oslo.config import cfg
 | |
| 
 | |
| from nova import block_device
 | |
| from nova.compute import instance_types
 | |
| from nova import exception
 | |
| from nova.network import model as network_model
 | |
| from nova import notifications
 | |
| from nova.openstack.common import log
 | |
| from nova.openstack.common.notifier import api as notifier_api
 | |
| from nova.openstack.common import timeutils
 | |
| from nova import utils
 | |
| from nova.virt import driver
 | |
| 
 | |
| CONF = cfg.CONF
 | |
| CONF.import_opt('host', 'nova.netconf')
 | |
| LOG = log.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| def add_instance_fault_from_exc(context, conductor,
 | |
|                                 instance, fault, exc_info=None):
 | |
|     """Adds the specified fault to the database."""
 | |
| 
 | |
|     code = 500
 | |
|     message = fault.__class__.__name__
 | |
| 
 | |
|     if hasattr(fault, "kwargs"):
 | |
|         code = fault.kwargs.get('code', 500)
 | |
|         # get the message from the exception that was thrown
 | |
|         # if that does not exist, use the name of the exception class itself
 | |
|         message = fault.kwargs.get('value', message)
 | |
| 
 | |
|     details = unicode(fault)
 | |
|     if exc_info and code == 500:
 | |
|         tb = exc_info[2]
 | |
|         details += '\n' + ''.join(traceback.format_tb(tb))
 | |
| 
 | |
|     values = {
 | |
|         'instance_uuid': instance['uuid'],
 | |
|         'code': code,
 | |
|         'message': unicode(message),
 | |
|         'details': unicode(details),
 | |
|         'host': CONF.host
 | |
|     }
 | |
|     conductor.instance_fault_create(context, values)
 | |
| 
 | |
| 
 | |
| def pack_action_start(context, instance_uuid, action_name):
 | |
|     values = {'action': action_name,
 | |
|               'instance_uuid': instance_uuid,
 | |
|               'request_id': context.request_id,
 | |
|               'user_id': context.user_id,
 | |
|               'project_id': context.project_id,
 | |
|               'start_time': context.timestamp}
 | |
|     return values
 | |
| 
 | |
| 
 | |
| def pack_action_finish(context, instance_uuid):
 | |
|     values = {'instance_uuid': instance_uuid,
 | |
|               'request_id': context.request_id,
 | |
|               'finish_time': timeutils.utcnow()}
 | |
|     return values
 | |
| 
 | |
| 
 | |
| def pack_action_event_start(context, instance_uuid, event_name):
 | |
|     values = {'event': event_name,
 | |
|               'instance_uuid': instance_uuid,
 | |
|               'request_id': context.request_id,
 | |
|               'start_time': timeutils.utcnow()}
 | |
|     return values
 | |
| 
 | |
| 
 | |
| def pack_action_event_finish(context, instance_uuid, event_name, exc_val=None,
 | |
|                              exc_tb=None):
 | |
|     values = {'event': event_name,
 | |
|               'instance_uuid': instance_uuid,
 | |
|               'request_id': context.request_id,
 | |
|               'finish_time': timeutils.utcnow()}
 | |
|     if exc_tb is None:
 | |
|         values['result'] = 'Success'
 | |
|     else:
 | |
|         values['result'] = 'Error'
 | |
|         values['message'] = str(exc_val)
 | |
|         values['traceback'] = ''.join(traceback.format_tb(exc_tb))
 | |
| 
 | |
|     return values
 | |
| 
 | |
| 
 | |
| def get_device_name_for_instance(context, instance, bdms, device):
 | |
|     """Validates (or generates) a device name for instance.
 | |
| 
 | |
|     If device is not set, it will generate a unique device appropriate
 | |
|     for the instance. It uses the block device mapping table to find
 | |
|     valid device names. If the device name is valid but applicable to
 | |
|     a different backend (for example /dev/vdc is specified but the
 | |
|     backend uses /dev/xvdc), the device name will be converted to the
 | |
|     appropriate format.
 | |
|     """
 | |
|     req_prefix = None
 | |
|     req_letter = None
 | |
| 
 | |
|     if device:
 | |
|         try:
 | |
|             req_prefix, req_letter = block_device.match_device(device)
 | |
|         except (TypeError, AttributeError, ValueError):
 | |
|             raise exception.InvalidDevicePath(path=device)
 | |
| 
 | |
|     mappings = block_device.instance_block_mapping(instance, bdms)
 | |
| 
 | |
|     try:
 | |
|         prefix = block_device.match_device(mappings['root'])[0]
 | |
|     except (TypeError, AttributeError, ValueError):
 | |
|         raise exception.InvalidDevicePath(path=mappings['root'])
 | |
| 
 | |
|     # NOTE(vish): remove this when xenapi is setting default_root_device
 | |
|     if driver.compute_driver_matches('xenapi.XenAPIDriver'):
 | |
|         prefix = '/dev/xvd'
 | |
| 
 | |
|     if req_prefix != prefix:
 | |
|         LOG.debug(_("Using %(prefix)s instead of %(req_prefix)s") % locals())
 | |
| 
 | |
|     used_letters = set()
 | |
|     for device_path in mappings.itervalues():
 | |
|         letter = block_device.strip_prefix(device_path)
 | |
|         # NOTE(vish): delete numbers in case we have something like
 | |
|         #             /dev/sda1
 | |
|         letter = re.sub("\d+", "", letter)
 | |
|         used_letters.add(letter)
 | |
| 
 | |
|     # NOTE(vish): remove this when xenapi is properly setting
 | |
|     #             default_ephemeral_device and default_swap_device
 | |
|     if driver.compute_driver_matches('xenapi.XenAPIDriver'):
 | |
|         instance_type = instance_types.extract_instance_type(instance)
 | |
|         if instance_type['ephemeral_gb']:
 | |
|             used_letters.add('b')
 | |
| 
 | |
|         if instance_type['swap']:
 | |
|             used_letters.add('c')
 | |
| 
 | |
|     if not req_letter:
 | |
|         req_letter = _get_unused_letter(used_letters)
 | |
| 
 | |
|     if req_letter in used_letters:
 | |
|         raise exception.DevicePathInUse(path=device)
 | |
| 
 | |
|     device_name = prefix + req_letter
 | |
|     return device_name
 | |
| 
 | |
| 
 | |
| def _get_unused_letter(used_letters):
 | |
|     doubles = [first + second for second in string.ascii_lowercase
 | |
|                for first in string.ascii_lowercase]
 | |
|     all_letters = set(list(string.ascii_lowercase) + doubles)
 | |
|     letters = list(all_letters - used_letters)
 | |
|     # NOTE(vish): prepend ` so all shorter sequences sort first
 | |
|     letters.sort(key=lambda x: x.rjust(2, '`'))
 | |
|     return letters[0]
 | |
| 
 | |
| 
 | |
| def notify_usage_exists(context, instance_ref, current_period=False,
 | |
|                         ignore_missing_network_data=True,
 | |
|                         system_metadata=None, extra_usage_info=None):
 | |
|     """Generates 'exists' notification for an instance for usage auditing
 | |
|     purposes.
 | |
| 
 | |
|     :param current_period: if True, this will generate a usage for the
 | |
|         current usage period; if False, this will generate a usage for the
 | |
|         previous audit period.
 | |
| 
 | |
|     :param ignore_missing_network_data: if True, log any exceptions generated
 | |
|         while getting network info; if False, raise the exception.
 | |
|     :param system_metadata: system_metadata DB entries for the instance,
 | |
|         if not None.  *NOTE*: Currently unused here in trunk, but needed for
 | |
|         potential custom modifications.
 | |
|     :param extra_usage_info: Dictionary containing extra values to add or
 | |
|         override in the notification if not None.
 | |
|     """
 | |
| 
 | |
|     audit_start, audit_end = notifications.audit_period_bounds(current_period)
 | |
| 
 | |
|     bw = notifications.bandwidth_usage(instance_ref, audit_start,
 | |
|             ignore_missing_network_data)
 | |
| 
 | |
|     if system_metadata is None:
 | |
|         system_metadata = utils.metadata_to_dict(
 | |
|                 instance_ref['system_metadata'])
 | |
| 
 | |
|     # add image metadata to the notification:
 | |
|     image_meta = notifications.image_meta(system_metadata)
 | |
| 
 | |
|     extra_info = dict(audit_period_beginning=str(audit_start),
 | |
|                       audit_period_ending=str(audit_end),
 | |
|                       bandwidth=bw, image_meta=image_meta)
 | |
| 
 | |
|     if extra_usage_info:
 | |
|         extra_info.update(extra_usage_info)
 | |
| 
 | |
|     notify_about_instance_usage(context, instance_ref, 'exists',
 | |
|             system_metadata=system_metadata, extra_usage_info=extra_info)
 | |
| 
 | |
| 
 | |
| def notify_about_instance_usage(context, instance, event_suffix,
 | |
|                                 network_info=None, system_metadata=None,
 | |
|                                 extra_usage_info=None, host=None):
 | |
|     """
 | |
|     Send a notification about an instance.
 | |
| 
 | |
|     :param event_suffix: Event type like "delete.start" or "exists"
 | |
|     :param network_info: Networking information, if provided.
 | |
|     :param system_metadata: system_metadata DB entries for the instance,
 | |
|         if provided.
 | |
|     :param extra_usage_info: Dictionary containing extra values to add or
 | |
|         override in the notification.
 | |
|     :param host: Compute host for the instance, if specified.  Default is
 | |
|         CONF.host
 | |
|     """
 | |
| 
 | |
|     if not host:
 | |
|         host = CONF.host
 | |
| 
 | |
|     if not extra_usage_info:
 | |
|         extra_usage_info = {}
 | |
| 
 | |
|     usage_info = notifications.info_from_instance(context, instance,
 | |
|             network_info, system_metadata, **extra_usage_info)
 | |
| 
 | |
|     if event_suffix.endswith("error"):
 | |
|         level = notifier_api.ERROR
 | |
|     else:
 | |
|         level = notifier_api.INFO
 | |
| 
 | |
|     notifier_api.notify(context, 'compute.%s' % host,
 | |
|                         'compute.instance.%s' % event_suffix, level,
 | |
|                         usage_info)
 | |
| 
 | |
| 
 | |
| def get_nw_info_for_instance(instance):
 | |
|     info_cache = instance['info_cache'] or {}
 | |
|     cached_nwinfo = info_cache.get('network_info') or []
 | |
|     return network_model.NetworkInfo.hydrate(cached_nwinfo)
 | |
| 
 | |
| 
 | |
| def has_audit_been_run(context, conductor, host, timestamp=None):
 | |
|     begin, end = utils.last_completed_audit_period(before=timestamp)
 | |
|     task_log = conductor.task_log_get(context, "instance_usage_audit",
 | |
|                                       begin, end, host)
 | |
|     if task_log:
 | |
|         return True
 | |
|     else:
 | |
|         return False
 | |
| 
 | |
| 
 | |
| def start_instance_usage_audit(context, conductor, begin, end, host,
 | |
|                                num_instances):
 | |
|     conductor.task_log_begin_task(context, "instance_usage_audit", begin,
 | |
|                                   end, host, num_instances,
 | |
|                                   "Instance usage audit started...")
 | |
| 
 | |
| 
 | |
| def finish_instance_usage_audit(context, conductor, begin, end, host, errors,
 | |
|                                 message):
 | |
|     conductor.task_log_end_task(context, "instance_usage_audit", begin, end,
 | |
|                                 host, errors, message)
 | |
| 
 | |
| 
 | |
| def usage_volume_info(vol_usage):
 | |
|     def null_safe_str(s):
 | |
|         return str(s) if s else ''
 | |
| 
 | |
|     tot_refreshed = vol_usage['tot_last_refreshed']
 | |
|     curr_refreshed = vol_usage['curr_last_refreshed']
 | |
|     last_refreshed_time = (tot_refreshed if tot_refreshed > curr_refreshed
 | |
|                            else curr_refreshed)
 | |
| 
 | |
|     usage_info = dict(
 | |
|           volume_id=vol_usage['volume_id'],
 | |
|           tenant_id=vol_usage['project_id'],
 | |
|           user_id=vol_usage['user_id'],
 | |
|           availability_zone=vol_usage['availability_zone'],
 | |
|           instance_id=vol_usage['instance_uuid'],
 | |
|           last_refreshed=null_safe_str(last_refreshed_time),
 | |
|           reads=vol_usage['tot_reads'] + vol_usage['curr_reads'],
 | |
|           read_bytes=vol_usage['tot_read_bytes'] +
 | |
|                 vol_usage['curr_read_bytes'],
 | |
|           writes=vol_usage['tot_writes'] + vol_usage['curr_writes'],
 | |
|           write_bytes=vol_usage['tot_write_bytes'] +
 | |
|                 vol_usage['curr_write_bytes'])
 | |
| 
 | |
|     return usage_info
 | |
| 
 | |
| 
 | |
| class EventReporter(object):
 | |
|     """Context manager to report instance action events."""
 | |
| 
 | |
|     def __init__(self, context, conductor, event_name, *instance_uuids):
 | |
|         self.context = context
 | |
|         self.conductor = conductor
 | |
|         self.event_name = event_name
 | |
|         self.instance_uuids = instance_uuids
 | |
| 
 | |
|     def __enter__(self):
 | |
|         for uuid in self.instance_uuids:
 | |
|             event = pack_action_event_start(self.context, uuid,
 | |
|                                             self.event_name)
 | |
|             self.conductor.action_event_start(self.context, event)
 | |
| 
 | |
|         return self
 | |
| 
 | |
|     def __exit__(self, exc_type, exc_val, exc_tb):
 | |
|         for uuid in self.instance_uuids:
 | |
|             event = pack_action_event_finish(self.context, uuid,
 | |
|                                              self.event_name, exc_val, exc_tb)
 | |
|             self.conductor.action_event_finish(self.context, event)
 | |
|         return False
 |