refactoring compute.api.create()

This commit is contained in:
Sandy Walsh
2011-05-26 08:28:57 -07:00
parent 660d1802a6
commit bc176751de
4 changed files with 135 additions and 75 deletions

View File

@@ -54,7 +54,11 @@ def generate_default_hostname(instance_id):
class API(base.Base):
"""API for interacting with the compute manager."""
scheduler_rules = None
# Should we create instances all-at-once or as single-shot requests.
# Different schedulers use different approaches.
# This is cached across all API instances.
should_create_all_at_once = None # None implies uninitialized.
def __init__(self, image_service=None, network_api=None,
volume_api=None, hostname_factory=generate_default_hostname,
@@ -219,8 +223,74 @@ class API(base.Base):
'availability_zone': availability_zone,
'os_type': os_type}
return (num_instances, base_options)
return (num_instances, base_options, security_groups)
def create_db_entry_for_new_instance(self, context, base_options,
security_groups, num=1):
"""Create an entry in the DB for this new instance,
including any related table updates (such as security
groups, MAC address, etc). This will called by create()
in the majority of situations, but all-at-once style
Schedulers may initiate the call."""
instance = dict(mac_address=utils.generate_mac(),
launch_index=num,
**base_options)
instance = self.db.instance_create(context, instance)
instance_id = instance['id']
elevated = context.elevated()
if not security_groups:
security_groups = []
for security_group_id in security_groups:
self.db.instance_add_security_group(elevated,
instance_id,
security_group_id)
# Set sane defaults if not specified
updates = dict(hostname=self.hostname_factory(instance_id))
if (not hasattr(instance, 'display_name') or
instance.display_name is None):
updates['display_name'] = "Server %s" % instance_id
instance = self.update(context, instance_id, **updates)
for group_id in security_groups:
self.trigger_security_group_members_refresh(elevated, group_id)
return instance
def _ask_scheduler_to_create_instance(self, context, base_options,
instance_type, zone_blob,
availability_zone, injected_files,
instance_id=None, num_instances=1):
"""Send the run_instance request to the schedulers for processing."""
pid = context.project_id
uid = context.user_id
if instance_id:
LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
" instance %(instance_id)s (single-shot)") % locals())
else:
LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
" (all-at-once)") % locals())
filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter'
request_spec = {
'instance_properties': base_options,
'instance_type': instance_type,
'filter': filter_class,
'blob': zone_blob,
'num_instances': num_instances
}
rpc.cast(context,
FLAGS.scheduler_topic,
{"method": "run_instance",
"args": {"topic": FLAGS.compute_topic,
"instance_id": instance_id,
"request_spec": request_spec,
"availability_zone": availability_zone,
"injected_files": injected_files}})
def create_all_at_once(self, context, instance_type,
image_id, kernel_id=None, ramdisk_id=None,
min_count=1, max_count=1,
@@ -229,13 +299,24 @@ class API(base.Base):
availability_zone=None, user_data=None, metadata={},
injected_files=None, zone_blob=None):
"""Provision the instances by passing the whole request to
the Scheduler for execution."""
self._check_create_parameters(self, context, instance_type,
image_id, kernel_id, ramdisk_id, min_count=1, max_count=1,
display_name, display_description,
key_name, key_data, security_group,
availability_zone, user_data, metadata,
injected_files, zone_blob)
the Scheduler for execution. Returns a Reservation ID
related to the creation of all of these instances."""
num_instances, base_options, security_groups = \
self._check_create_parameters(
context, instance_type,
image_id, kernel_id, ramdisk_id,
min_count, max_count,
display_name, display_description,
key_name, key_data, security_group,
availability_zone, user_data, metadata,
injected_files, zone_blob)
self._ask_scheduler_to_create_instance(context, base_options,
instance_type, zone_blob,
availability_zone, injected_files,
num_instances=num_instances)
return base_options['reservation_id']
def create(self, context, instance_type,
image_id, kernel_id=None, ramdisk_id=None,
@@ -244,15 +325,20 @@ class API(base.Base):
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata={},
injected_files=None, zone_blob=None):
"""Provision the instances by sending off a series of single
"""
Provision the instances by sending off a series of single
instance requests to the Schedulers. This is fine for trival
Scheduler drivers, but may remove the effectiveness of the
more complicated drivers."""
more complicated drivers.
Returns a list of instance dicts.
"""
num_instances, base_options = self._check_create_parameters(
num_instances, base_options, security_groups = \
self._check_create_parameters(
context, instance_type,
image_id, kernel_id, ramdisk_id,
min_count=1, max_count=1,
min_count, max_count,
display_name, display_description,
key_name, key_data, security_group,
availability_zone, user_data, metadata,
@@ -261,74 +347,31 @@ class API(base.Base):
instances = []
LOG.debug(_("Going to run %s instances..."), num_instances)
for num in range(num_instances):
instance = dict(mac_address=utils.generate_mac(),
launch_index=num,
**base_options)
instance = self.db.instance_create(context, instance)
instance_id = instance['id']
elevated = context.elevated()
if not security_groups:
security_groups = []
for security_group_id in security_groups:
self.db.instance_add_security_group(elevated,
instance_id,
security_group_id)
# Set sane defaults if not specified
updates = dict(hostname=self.hostname_factory(instance_id))
if (not hasattr(instance, 'display_name') or
instance.display_name is None):
updates['display_name'] = "Server %s" % instance_id
instance = self.update(context, instance_id, **updates)
instance = self.create_db_entry_for_new_instance(context,
base_options, security_groups, num=num)
instances.append(instance)
pid = context.project_id
uid = context.user_id
LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
" instance %(instance_id)s") % locals())
# NOTE(sandy): For now we're just going to pass in the
# instance_type record to the scheduler. In a later phase
# we'll be ripping this whole for-loop out and deferring the
# creation of the Instance record. At that point all this will
# change.
filter_driver = 'nova.scheduler.host_filter.InstanceTypeFilter'
request_spec = {
'instance_properties': base_options,
'instance_type': instance_type,
'filter_driver': filter_driver,
'blob': zone_blob
}
rpc.cast(context,
FLAGS.scheduler_topic,
{"method": "run_instance",
"args": {"topic": FLAGS.compute_topic,
"instance_id": instance_id,
"request_spec": request_spec,
"availability_zone": availability_zone,
"injected_files": injected_files}})
for group_id in security_groups:
self.trigger_security_group_members_refresh(elevated, group_id)
return [dict(x.iteritems()) for x in instances]
instance_id = instance['id']
self._ask_scheduler_to_create_instance(context, base_options,
instance_type, zone_blob,
availability_zone, injected_files,
instance_id=instance_id)
return [x.items() for x in instances]
def smart_create(self, *args, **kwargs):
"""Ask the scheduler if we should: 1. do single shot instance
requests or all-at-once, and 2. defer the DB work until
a suitable host has been selected (if at all). Cache this
information and act accordingly."""
"""
Ask the scheduler if we should do single shot instance requests
or all-at-once.
if API.scheduler_rules == None:
API.scheduler_rules = scheduler_api.get_scheduler_rules(context)
Cache this information on first request and act accordingly.
"""
should_create_all_at_once, should_defer_database_create = \
API.scheduler_rules
if API.should_create_all_at_once == None:
API.should_create_all_at_once = \
scheduler_api.should_create_all_at_once(context)
if should_create_all_at_once:
if API.should_create_all_at_once:
return self.create_all_at_once(*args, **kwargs)
return self.create(*args, **kwargs)

View File

@@ -72,6 +72,13 @@ class Scheduler(object):
for service in services
if self.service_is_up(service)]
def should_create_all_at_once(self, context=None, *args, **kwargs):
"""
Does this driver prefer single-shot requests or all-at-once?
By default, prefer single-shot.
"""
return False
def schedule(self, context, topic, *_args, **_kwargs):
"""Must override at least this method for scheduler to work."""
raise NotImplementedError(_("Must implement a fallback schedule"))

View File

@@ -74,6 +74,10 @@ class SchedulerManager(manager.Manager):
"""Select a list of hosts best matching the provided specs."""
return self.driver.select(context, *args, **kwargs)
def get_scheduler_rules(self, context=None, *args, **kwargs):
"""Ask the driver how requests should be made of it."""
return self.driver.get_scheduler_rules(context, *args, **kwargs)
def _schedule(self, method, context, topic, *args, **kwargs):
"""Tries to call schedule_* method on the driver to retrieve host.

View File

@@ -157,6 +157,12 @@ class ZoneAwareScheduler(driver.Scheduler):
self._provision_resource_from_blob(context, item, instance_id,
request_spec, kwargs)
def should_create_all_at_once(self, context=None, *args, **kwargs):
"""
This driver prefers all-at-once requests.
"""
return True
def schedule_run_instance(self, context, instance_id, request_spec,
*args, **kwargs):
"""This method is called from nova.compute.api to provision