summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Authors1
-rw-r--r--nova/api/ec2/cloud.py45
-rw-r--r--nova/compute/api.py3
-rw-r--r--nova/db/api.py13
-rw-r--r--nova/db/sqlalchemy/api.py17
-rw-r--r--nova/db/sqlalchemy/models.py1
-rw-r--r--nova/flags.py1
-rw-r--r--nova/scheduler/zone.py56
-rw-r--r--nova/service.py4
-rw-r--r--nova/tests/test_cloud.py46
-rw-r--r--nova/tests/test_scheduler.py54
-rw-r--r--nova/tests/test_service.py15
12 files changed, 235 insertions, 21 deletions
diff --git a/Authors b/Authors
index 6e6ecfe1b..56344957e 100644
--- a/Authors
+++ b/Authors
@@ -15,6 +15,7 @@ Eldar Nugaev <enugaev@griddynamics.com>
Eric Day <eday@oddments.org>
Ewan Mellor <ewan.mellor@citrix.com>
Hisaki Ohara <hisaki.ohara@intel.com>
+Ilya Alekseyev <ialekseev@griddynamics.com>
Jay Pipes <jaypipes@gmail.com>
Jesse Andrews <anotherjesse@gmail.com>
Joe Heck <heckj@mac.com>
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 39174d554..832426b94 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -132,6 +132,21 @@ class CloudController(object):
result[key] = [line]
return result
+ def _trigger_refresh_security_group(self, context, security_group):
+ nodes = set([instance['host'] for instance in security_group.instances
+ if instance['host'] is not None])
+ for node in nodes:
+ rpc.cast(context,
+ '%s.%s' % (FLAGS.compute_topic, node),
+ {"method": "refresh_security_group",
+ "args": {"security_group_id": security_group.id}})
+
+ def _get_availability_zone_by_host(self, context, host):
+ services = db.service_get_all_by_host(context, host)
+ if len(services) > 0:
+ return services[0]['availability_zone']
+ return 'unknown zone'
+
def get_metadata(self, address):
ctxt = context.get_admin_context()
instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
@@ -144,6 +159,8 @@ class CloudController(object):
else:
keys = ''
hostname = instance_ref['hostname']
+ host = instance_ref['host']
+ availability_zone = self._get_availability_zone_by_host(ctxt, host)
floating_ip = db.instance_get_floating_address(ctxt,
instance_ref['id'])
ec2_id = id_to_ec2_id(instance_ref['id'])
@@ -166,8 +183,7 @@ class CloudController(object):
'local-hostname': hostname,
'local-ipv4': address,
'kernel-id': instance_ref['kernel_id'],
- # TODO(vish): real zone
- 'placement': {'availability-zone': 'nova'},
+ 'placement': {'availability-zone': availability_zone},
'public-hostname': hostname,
'public-ipv4': floating_ip or '',
'public-keys': keys,
@@ -191,8 +207,26 @@ class CloudController(object):
return self._describe_availability_zones(context, **kwargs)
def _describe_availability_zones(self, context, **kwargs):
- return {'availabilityZoneInfo': [{'zoneName': 'nova',
- 'zoneState': 'available'}]}
+ enabled_services = db.service_get_all(context)
+ disabled_services = db.service_get_all(context, True)
+ available_zones = []
+ for zone in [service.availability_zone for service
+ in enabled_services]:
+ if not zone in available_zones:
+ available_zones.append(zone)
+ not_available_zones = []
+ for zone in [service.availability_zone for service in disabled_services
+ if not service['availability_zone'] in available_zones]:
+ if not zone in not_available_zones:
+ not_available_zones.append(zone)
+ result = []
+ for zone in available_zones:
+ result.append({'zoneName': zone,
+ 'zoneState': "available"})
+ for zone in not_available_zones:
+ result.append({'zoneName': zone,
+ 'zoneState': "not available"})
+ return {'availabilityZoneInfo': result}
def _describe_availability_zones_verbose(self, context, **kwargs):
rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
@@ -654,6 +688,9 @@ class CloudController(object):
i['amiLaunchIndex'] = instance['launch_index']
i['displayName'] = instance['display_name']
i['displayDescription'] = instance['display_description']
+ host = instance['host']
+ zone = self._get_availability_zone_by_host(context, host)
+ i['placement'] = {'availabilityZone': zone}
if instance['reservation_id'] not in reservations:
r = {}
r['reservationId'] = instance['reservation_id']
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 56402c11b..bf921aa00 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -186,7 +186,8 @@ class API(base.Base):
FLAGS.scheduler_topic,
{"method": "run_instance",
"args": {"topic": FLAGS.compute_topic,
- "instance_id": instance_id}})
+ "instance_id": instance_id,
+ "availability_zone": availability_zone}})
for group_id in security_groups:
self.trigger_security_group_members_refresh(elevated, group_id)
diff --git a/nova/db/api.py b/nova/db/api.py
index cf84157bc..1f81ef145 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -81,16 +81,21 @@ def service_get(context, service_id):
return IMPL.service_get(context, service_id)
-def service_get_all(context):
- """Get a list of all services on any machine on any topic of any type"""
- return IMPL.service_get_all(context)
+def service_get_all(context, disabled=False):
+ """Get all service."""
+ return IMPL.service_get_all(context, None, disabled)
def service_get_all_by_topic(context, topic):
- """Get all compute services for a given topic."""
+ """Get all services for a given topic."""
return IMPL.service_get_all_by_topic(context, topic)
+def service_get_all_by_host(context, host):
+ """Get all services for a given host."""
+ return IMPL.service_get_all_by_host(context, host)
+
+
def service_get_all_compute_sorted(context):
"""Get all compute services sorted by instance count.
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 4561fa219..2e4f8fc39 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -135,14 +135,14 @@ def service_get(context, service_id, session=None):
@require_admin_context
-def service_get_all(context, session=None):
+def service_get_all(context, session=None, disabled=False):
if not session:
session = get_session()
result = session.query(models.Service).\
- filter_by(deleted=can_read_deleted(context)).\
- all()
-
+ filter_by(deleted=can_read_deleted(context)).\
+ filter_by(disabled=disabled).\
+ all()
return result
@@ -157,6 +157,15 @@ def service_get_all_by_topic(context, topic):
@require_admin_context
+def service_get_all_by_host(context, host):
+ session = get_session()
+ return session.query(models.Service).\
+ filter_by(deleted=False).\
+ filter_by(host=host).\
+ all()
+
+
+@require_admin_context
def _service_get_all_topic_subquery(context, session, topic, subq, label):
sort_value = getattr(subq.c, label)
return session.query(models.Service, func.coalesce(sort_value, 0)).\
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 2a966448c..1dc46fe78 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -149,6 +149,7 @@ class Service(BASE, NovaBase):
topic = Column(String(255))
report_count = Column(Integer, nullable=False, default=0)
disabled = Column(Boolean, default=False)
+ availability_zone = Column(String(255), default='nova')
class Certificate(BASE, NovaBase):
diff --git a/nova/flags.py b/nova/flags.py
index 76ab2f788..fdcba6c72 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -308,6 +308,5 @@ DEFINE_string('image_service', 'nova.image.s3.S3ImageService',
DEFINE_string('host', socket.gethostname(),
'name of this node')
-# UNUSED
DEFINE_string('node_availability_zone', 'nova',
'availability zone of this node')
diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py
new file mode 100644
index 000000000..49786cd32
--- /dev/null
+++ b/nova/scheduler/zone.py
@@ -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))]
diff --git a/nova/service.py b/nova/service.py
index 523c1a8d7..8b2a22ce0 100644
--- a/nova/service.py
+++ b/nova/service.py
@@ -113,11 +113,13 @@ class Service(object):
self.timers.append(periodic)
def _create_service_ref(self, context):
+ zone = FLAGS.node_availability_zone
service_ref = db.service_create(context,
{'host': self.host,
'binary': self.binary,
'topic': self.topic,
- 'report_count': 0})
+ 'report_count': 0,
+ 'availability_zone': zone})
self.service_id = service_ref['id']
def __getattr__(self, key):
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index 8e43eec00..fdacb04f6 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -133,10 +133,35 @@ class CloudTestCase(test.TestCase):
db.volume_destroy(self.context, vol1['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):
"""Makes sure describe_instances works and filters results."""
- inst1 = db.instance_create(self.context, {'reservation_id': 'a'})
- inst2 = db.instance_create(self.context, {'reservation_id': 'a'})
+ inst1 = 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 = result['reservationSet'][0]
self.assertEqual(len(result['instancesSet']), 2)
@@ -147,8 +172,12 @@ class CloudTestCase(test.TestCase):
self.assertEqual(len(result['instancesSet']), 1)
self.assertEqual(result['instancesSet'][0]['instanceId'],
instance_id)
+ self.assertEqual(result['instancesSet'][0]
+ ['placement']['availabilityZone'], 'zone2')
db.instance_destroy(self.context, inst1['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):
image_id = FLAGS.default_image
@@ -241,6 +270,19 @@ class CloudTestCase(test.TestCase):
LOG.debug(_("Terminating instance %s"), 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 instance(num):
return {
diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py
index a9937d797..9d458244b 100644
--- a/nova/tests/test_scheduler.py
+++ b/nova/tests/test_scheduler.py
@@ -21,6 +21,7 @@ Tests For Scheduler
import datetime
+from mox import IgnoreArg
from nova import context
from nova import db
from nova import flags
@@ -76,6 +77,59 @@ class SchedulerTestCase(test.TestCase):
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):
"""Test case for simple driver"""
def setUp(self):
diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py
index 9f1a181a0..a67c8d1e8 100644
--- a/nova/tests/test_service.py
+++ b/nova/tests/test_service.py
@@ -133,7 +133,8 @@ class ServiceTestCase(test.TestCase):
service_create = {'host': host,
'binary': binary,
'topic': topic,
- 'report_count': 0}
+ 'report_count': 0,
+ 'availability_zone': 'nova'}
service_ref = {'host': host,
'binary': binary,
'report_count': 0,
@@ -161,11 +162,13 @@ class ServiceTestCase(test.TestCase):
service_create = {'host': host,
'binary': binary,
'topic': topic,
- 'report_count': 0}
+ 'report_count': 0,
+ 'availability_zone': 'nova'}
service_ref = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0,
+ 'availability_zone': 'nova',
'id': 1}
service.db.service_get_by_args(mox.IgnoreArg(),
@@ -193,11 +196,13 @@ class ServiceTestCase(test.TestCase):
service_create = {'host': host,
'binary': binary,
'topic': topic,
- 'report_count': 0}
+ 'report_count': 0,
+ 'availability_zone': 'nova'}
service_ref = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0,
+ 'availability_zone': 'nova',
'id': 1}
service.db.service_get_by_args(mox.IgnoreArg(),
@@ -224,11 +229,13 @@ class ServiceTestCase(test.TestCase):
service_create = {'host': host,
'binary': binary,
'topic': topic,
- 'report_count': 0}
+ 'report_count': 0,
+ 'availability_zone': 'nova'}
service_ref = {'host': host,
'binary': binary,
'topic': topic,
'report_count': 0,
+ 'availability_zone': 'nova',
'id': 1}
service.db.service_get_by_args(mox.IgnoreArg(),