Added support of availability zones for compute.

models.Service got additional field availability_zone and was created ZoneScheduler that make decisions based on this field. 
Also replaced fake 'nova' zone in EC2 cloud api.
This commit is contained in:
Ilya Alekseyev
2011-01-12 11:34:16 +00:00
committed by Tarmac
5 changed files with 155 additions and 3 deletions

View File

@@ -15,6 +15,7 @@ Eldar Nugaev <enugaev@griddynamics.com>
Eric Day <eday@oddments.org> Eric Day <eday@oddments.org>
Ewan Mellor <ewan.mellor@citrix.com> Ewan Mellor <ewan.mellor@citrix.com>
Hisaki Ohara <hisaki.ohara@intel.com> Hisaki Ohara <hisaki.ohara@intel.com>
Ilya Alekseyev <ialekseev@griddynamics.com>
Jay Pipes <jaypipes@gmail.com> Jay Pipes <jaypipes@gmail.com>
Jesse Andrews <anotherjesse@gmail.com> Jesse Andrews <anotherjesse@gmail.com>
Joe Heck <heckj@mac.com> Joe Heck <heckj@mac.com>

View File

@@ -308,6 +308,5 @@ DEFINE_string('image_service', 'nova.image.s3.S3ImageService',
DEFINE_string('host', socket.gethostname(), DEFINE_string('host', socket.gethostname(),
'name of this node') 'name of this node')
# UNUSED
DEFINE_string('node_availability_zone', 'nova', DEFINE_string('node_availability_zone', 'nova',
'availability zone of this node') 'availability zone of this node')

56
nova/scheduler/zone.py Normal file
View File

@@ -0,0 +1,56 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2010 Openstack, LLC.
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
"""
Availability Zone Scheduler implementation
"""
import random
from nova.scheduler import driver
from nova import db
class ZoneScheduler(driver.Scheduler):
"""Implements Scheduler as a random node selector."""
def hosts_up_with_zone(self, context, topic, zone):
"""Return the list of hosts that have a running service
for topic and availability zone (if defined).
"""
if zone is None:
return self.hosts_up(context, topic)
services = db.service_get_all_by_topic(context, topic)
return [service.host
for service in services
if self.service_is_up(service)
and service.availability_zone == zone]
def schedule(self, context, topic, *_args, **_kwargs):
"""Picks a host that is up at random in selected
availability zone (if defined).
"""
zone = _kwargs.get('availability_zone')
hosts = self.hosts_up_with_zone(context, topic, zone)
if not hosts:
raise driver.NoValidHost(_("No hosts found"))
return hosts[int(random.random() * len(hosts))]

View File

@@ -133,10 +133,35 @@ class CloudTestCase(test.TestCase):
db.volume_destroy(self.context, vol1['id']) db.volume_destroy(self.context, vol1['id'])
db.volume_destroy(self.context, vol2['id']) db.volume_destroy(self.context, vol2['id'])
def test_describe_availability_zones(self):
"""Makes sure describe_availability_zones works and filters results."""
service1 = db.service_create(self.context, {'host': 'host1_zones',
'binary': "nova-compute",
'topic': 'compute',
'report_count': 0,
'availability_zone': "zone1"})
service2 = db.service_create(self.context, {'host': 'host2_zones',
'binary': "nova-compute",
'topic': 'compute',
'report_count': 0,
'availability_zone': "zone2"})
result = self.cloud.describe_availability_zones(self.context)
self.assertEqual(len(result['availabilityZoneInfo']), 3)
db.service_destroy(self.context, service1['id'])
db.service_destroy(self.context, service2['id'])
def test_describe_instances(self): def test_describe_instances(self):
"""Makes sure describe_instances works and filters results.""" """Makes sure describe_instances works and filters results."""
inst1 = db.instance_create(self.context, {'reservation_id': 'a'}) inst1 = db.instance_create(self.context, {'reservation_id': 'a',
inst2 = db.instance_create(self.context, {'reservation_id': 'a'}) 'host': 'host1'})
inst2 = db.instance_create(self.context, {'reservation_id': 'a',
'host': 'host2'})
comp1 = db.service_create(self.context, {'host': 'host1',
'availability_zone': 'zone1',
'topic': "compute"})
comp2 = db.service_create(self.context, {'host': 'host2',
'availability_zone': 'zone2',
'topic': "compute"})
result = self.cloud.describe_instances(self.context) result = self.cloud.describe_instances(self.context)
result = result['reservationSet'][0] result = result['reservationSet'][0]
self.assertEqual(len(result['instancesSet']), 2) self.assertEqual(len(result['instancesSet']), 2)
@@ -147,8 +172,12 @@ class CloudTestCase(test.TestCase):
self.assertEqual(len(result['instancesSet']), 1) self.assertEqual(len(result['instancesSet']), 1)
self.assertEqual(result['instancesSet'][0]['instanceId'], self.assertEqual(result['instancesSet'][0]['instanceId'],
instance_id) instance_id)
self.assertEqual(result['instancesSet'][0]
['placement']['availabilityZone'], 'zone2')
db.instance_destroy(self.context, inst1['id']) db.instance_destroy(self.context, inst1['id'])
db.instance_destroy(self.context, inst2['id']) db.instance_destroy(self.context, inst2['id'])
db.service_destroy(self.context, comp1['id'])
db.service_destroy(self.context, comp2['id'])
def test_console_output(self): def test_console_output(self):
image_id = FLAGS.default_image image_id = FLAGS.default_image
@@ -241,6 +270,19 @@ class CloudTestCase(test.TestCase):
LOG.debug(_("Terminating instance %s"), instance_id) LOG.debug(_("Terminating instance %s"), instance_id)
rv = self.compute.terminate_instance(instance_id) rv = self.compute.terminate_instance(instance_id)
def test_describe_instances(self):
"""Makes sure describe_instances works."""
instance1 = db.instance_create(self.context, {'host': 'host2'})
comp1 = db.service_create(self.context, {'host': 'host2',
'availability_zone': 'zone1',
'topic': "compute"})
result = self.cloud.describe_instances(self.context)
self.assertEqual(result['reservationSet'][0]
['instancesSet'][0]
['placement']['availabilityZone'], 'zone1')
db.instance_destroy(self.context, instance1['id'])
db.service_destroy(self.context, comp1['id'])
def test_instance_update_state(self): def test_instance_update_state(self):
def instance(num): def instance(num):
return { return {

View File

@@ -21,6 +21,7 @@ Tests For Scheduler
import datetime import datetime
from mox import IgnoreArg
from nova import context from nova import context
from nova import db from nova import db
from nova import flags from nova import flags
@@ -76,6 +77,59 @@ class SchedulerTestCase(test.TestCase):
scheduler.named_method(ctxt, 'topic', num=7) scheduler.named_method(ctxt, 'topic', num=7)
class ZoneSchedulerTestCase(test.TestCase):
"""Test case for zone scheduler"""
def setUp(self):
super(ZoneSchedulerTestCase, self).setUp()
self.flags(scheduler_driver='nova.scheduler.zone.ZoneScheduler')
def _create_service_model(self, **kwargs):
service = db.sqlalchemy.models.Service()
service.host = kwargs['host']
service.disabled = False
service.deleted = False
service.report_count = 0
service.binary = 'nova-compute'
service.topic = 'compute'
service.id = kwargs['id']
service.availability_zone = kwargs['zone']
service.created_at = datetime.datetime.utcnow()
return service
def test_with_two_zones(self):
scheduler = manager.SchedulerManager()
ctxt = context.get_admin_context()
service_list = [self._create_service_model(id=1,
host='host1',
zone='zone1'),
self._create_service_model(id=2,
host='host2',
zone='zone2'),
self._create_service_model(id=3,
host='host3',
zone='zone2'),
self._create_service_model(id=4,
host='host4',
zone='zone2'),
self._create_service_model(id=5,
host='host5',
zone='zone2')]
self.mox.StubOutWithMock(db, 'service_get_all_by_topic')
arg = IgnoreArg()
db.service_get_all_by_topic(arg, arg).AndReturn(service_list)
self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True)
rpc.cast(ctxt,
'compute.host1',
{'method': 'run_instance',
'args': {'instance_id': 'i-ffffffff',
'availability_zone': 'zone1'}})
self.mox.ReplayAll()
scheduler.run_instance(ctxt,
'compute',
instance_id='i-ffffffff',
availability_zone='zone1')
class SimpleDriverTestCase(test.TestCase): class SimpleDriverTestCase(test.TestCase):
"""Test case for simple driver""" """Test case for simple driver"""
def setUp(self): def setUp(self):