From 06fd2363578b21726636cd189c7ab1ce0db632ba Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 5 Aug 2010 10:30:06 -0500 Subject: [PATCH 01/34] added bin/nova-listinstances, which is mostly just a duplication of euca-describe-instances but doesn't go through the API. --- bin/nova-listinstances | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100755 bin/nova-listinstances diff --git a/bin/nova-listinstances b/bin/nova-listinstances new file mode 100755 index 00000000..2f8ff28f --- /dev/null +++ b/bin/nova-listinstances @@ -0,0 +1,19 @@ +#!/usr/bin/python + +# +# Duplicates the functionality of euca-describe-instances, but doesn't require +# going through the API. Does a direct query to the datastore. This is +# mostly a test program written for the scheduler +# + +from nova.compute import model + +data_needed = ['image_id', 'memory_kb', 'local_gb', 'node_name', 'vcpus'] + +instances = model.InstanceDirectory().all + +for instance in instances: + print 'Instance: %s' % instance['instance_id'] + for x in data_needed: + print ' %s: %s' % (x, instance[x]) + From 6beb3aa2bea06e3be0f53083cf687d09932e2200 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 5 Aug 2010 16:11:59 -0500 Subject: [PATCH 02/34] almost there on random scheduler. not pushing to correct compute node topic, yet, apparently... --- bin/nova-scheduler | 32 ++++++++++++++ nova/endpoint/cloud.py | 2 +- nova/flags.py | 1 + nova/scheduler/__init__.py | 33 +++++++++++++++ nova/scheduler/service.py | 87 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100755 bin/nova-scheduler create mode 100644 nova/scheduler/__init__.py create mode 100644 nova/scheduler/service.py diff --git a/bin/nova-scheduler b/bin/nova-scheduler new file mode 100755 index 00000000..1ad41bbd --- /dev/null +++ b/bin/nova-scheduler @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + +""" + Twistd daemon for the nova scheduler nodes. +""" + +from nova import twistd +from nova.scheduler import service + + +if __name__ == '__main__': + twistd.serve(__file__) + +if __name__ == '__builtin__': + application = service.SchedulerService.create() diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 0ee278f8..a808e54c 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -576,7 +576,7 @@ class CloudController(object): inst['private_dns_name'] = str(address) # TODO: allocate expresses on the router node inst.save() - rpc.cast(FLAGS.compute_topic, + rpc.cast(FLAGS.scheduler_topic, {"method": "run_instance", "args": {"instance_id" : inst.instance_id}}) logging.debug("Casting to node for %s's instance with IP of %s" % diff --git a/nova/flags.py b/nova/flags.py index f35f5fa1..7f92e3f7 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -41,6 +41,7 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') #DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') +DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') diff --git a/nova/scheduler/__init__.py b/nova/scheduler/__init__.py new file mode 100644 index 00000000..516ea61b --- /dev/null +++ b/nova/scheduler/__init__.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + +""" +:mod:`nova.scheduler` -- Scheduler Nodes +===================================================== + +.. automodule:: nova.scheduler + :platform: Unix + :synopsis: Daemon that picks a host for a VM instance. +.. moduleauthor:: Jesse Andrews +.. moduleauthor:: Devin Carlen +.. moduleauthor:: Vishvananda Ishaya +.. moduleauthor:: Joshua McKenty +.. moduleauthor:: Manish Singh +.. moduleauthor:: Andy Smith +.. moduleauthor:: Chris Behrens +""" diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py new file mode 100644 index 00000000..aca5b5db --- /dev/null +++ b/nova/scheduler/service.py @@ -0,0 +1,87 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + +""" +Scheduler Service +""" + +import logging +import random +import sys +from twisted.internet import defer +from twisted.internet import task + +from nova import exception +from nova import flags +from nova import process +from nova import rpc +from nova import service +from nova import utils +from nova.compute import model +from nova.datastore import Redis + +FLAGS = flags.FLAGS + + +class SchedulerService(service.Service): + """ + Manages the running instances. + """ + def __init__(self): + super(SchedulerService, self).__init__() + self.instdir = model.InstanceDirectory() + + def noop(self): + """ simple test of an AMQP message call """ + return defer.succeed('PONG') + + @defer.inlineCallbacks + def report_state(self, nodename, daemon): + # TODO(termie): make this pattern be more elegant. -todd + try: + record = model.Daemon(nodename, daemon) + record.heartbeat() + if getattr(self, "model_disconnected", False): + self.model_disconnected = False + logging.error("Recovered model server connection!") + + except model.ConnectionError, ex: + if not getattr(self, "model_disconnected", False): + self.model_disconnected = True + logging.exception("model server went away") + yield + + @property + def compute_identifiers(self): + return [identifier for identifier in Redis.instance().smembers("daemons") if (identifier.split(':')[1] == "nova-compute")] + + def pick_node(self, instance_id, **_kwargs): + identifiers = self.compute_identifiers + return identifiers[int(random.random() * len(identifiers))].split(':')[0] + + @exception.wrap_exception + def run_instance(self, instance_id, **_kwargs): + node = self.pick_node(instance_id, **_kwargs) + + rpc.cast('%s:%s' % (FLAGS.compute_topic, node), + {"method": "run_instance", + "args": {"instance_id" : instance_id}}) + logging.debug("Casting to node %s for instance %s" % + (node, instance_id)) + + From 04b31cca07b9e34001ae83fb039868238334c990 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 5 Aug 2010 16:19:21 -0500 Subject: [PATCH 03/34] compute topic for a node is compute.node not compute:node! --- nova/scheduler/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index aca5b5db..2875dd55 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -78,7 +78,7 @@ class SchedulerService(service.Service): def run_instance(self, instance_id, **_kwargs): node = self.pick_node(instance_id, **_kwargs) - rpc.cast('%s:%s' % (FLAGS.compute_topic, node), + rpc.cast('%s.%s' % (FLAGS.compute_topic, node), {"method": "run_instance", "args": {"instance_id" : instance_id}}) logging.debug("Casting to node %s for instance %s" % From 5574f3923a73d417e6134a3063d61cb8ec0ce406 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 5 Aug 2010 15:10:56 -0700 Subject: [PATCH 04/34] fixed doc string --- nova/scheduler/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 2875dd55..46e541a4 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -40,7 +40,7 @@ FLAGS = flags.FLAGS class SchedulerService(service.Service): """ - Manages the running instances. + Picks nodes for instances to run. """ def __init__(self): super(SchedulerService, self).__init__() From 08ded6406630a6de0ed43016d6c0f187e3bc1bec Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 6 Aug 2010 14:40:24 -0500 Subject: [PATCH 05/34] Start breaking out scheduler classes... --- nova/scheduler/service.py | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 46e541a4..3a226322 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -23,6 +23,7 @@ Scheduler Service import logging import random import sys +import time from twisted.internet import defer from twisted.internet import task @@ -36,11 +37,12 @@ from nova.compute import model from nova.datastore import Redis FLAGS = flags.FLAGS - +flags.DEFINE_integer('node_down_time', 60, + 'seconds without heartbeat that determines a compute node to be down') class SchedulerService(service.Service): """ - Picks nodes for instances to run. + Manages the running instances. """ def __init__(self): super(SchedulerService, self).__init__() @@ -67,12 +69,20 @@ class SchedulerService(service.Service): yield @property - def compute_identifiers(self): - return [identifier for identifier in Redis.instance().smembers("daemons") if (identifier.split(':')[1] == "nova-compute")] + def compute_nodes(self): + return [identifier.split(':')[0] for identifier in Redis.instance().smembers("daemons") if (identifier.split(':')[1] == "nova-compute")] + + def compute_node_is_up(self, node): + time_str = Redis.instance().hget('%s:%s:%s' % ('daemon', node, 'nova-compute'), 'updated_at') + return(time_str and + (time.time() - (int(time.mktime(time.strptime(time_str.replace('Z', 'UTC'), '%Y-%m-%dT%H:%M:%S%Z'))) - time.timezone) < FLAGS.node_down_time)) + + def compute_nodes_up(self): + return [node for node in self.compute_nodes if self.compute_node_is_up(node)] def pick_node(self, instance_id, **_kwargs): - identifiers = self.compute_identifiers - return identifiers[int(random.random() * len(identifiers))].split(':')[0] + """You DEFINITELY want to define this in your subclass""" + raise NotImplementedError("Your subclass should define pick_node") @exception.wrap_exception def run_instance(self, instance_id, **_kwargs): @@ -84,4 +94,16 @@ class SchedulerService(service.Service): logging.debug("Casting to node %s for instance %s" % (node, instance_id)) +class RandomService(SchedulerService): + """ + Implements SchedulerService as a random node selector + """ + + def __init__(self): + super(RandomService, self).__init__() + + def pick_node(self, instance_id, **_kwargs): + nodes = self.compute_nodes_up() + return nodes[int(random.random() * len(nodes))] + From 05a643a960d4cdfa266edc15b879d86df6ab483b Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 6 Aug 2010 17:40:10 -0500 Subject: [PATCH 06/34] Moved Scheduler classes into scheduler.py. Created a way to specify scheduler class that the SchedulerService uses... --- nova/scheduler/scheduler.py | 82 +++++++++++++++++++++++++++++++++++++ nova/scheduler/service.py | 50 ++++++---------------- 2 files changed, 95 insertions(+), 37 deletions(-) create mode 100644 nova/scheduler/scheduler.py diff --git a/nova/scheduler/scheduler.py b/nova/scheduler/scheduler.py new file mode 100644 index 00000000..0da7b95c --- /dev/null +++ b/nova/scheduler/scheduler.py @@ -0,0 +1,82 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + +""" +Scheduler Classes +""" + +import logging +import random +import sys +import time + +from nova import exception +from nova import flags +from nova.datastore import Redis + +FLAGS = flags.FLAGS +flags.DEFINE_integer('node_down_time', + 60, + 'seconds without heartbeat that determines a compute node to be down') + + +class Scheduler(object): + """ + The base class that all Scheduler clases should inherit from + """ + + @property + def compute_nodes(self): + return [identifier.split(':')[0] for identifier in Redis.instance().smembers("daemons") if (identifier.split(':')[1] == "nova-compute")] + + def compute_node_is_up(self, node): + time_str = Redis.instance().hget('%s:%s:%s' % ('daemon', node, 'nova-compute'), 'updated_at') + # Would be a lot easier if we stored heartbeat time in epoch :) + return(time_str and + (time.time() - (int(time.mktime(time.strptime(time_str.replace('Z', 'UTC'), '%Y-%m-%dT%H:%M:%S%Z'))) - time.timezone) < FLAGS.node_down_time)) + + def compute_nodes_up(self): + return [node for node in self.compute_nodes if self.compute_node_is_up(node)] + + def pick_node(self, instance_id, **_kwargs): + """You DEFINITELY want to define this in your subclass""" + raise NotImplementedError("Your subclass should define pick_node") + +class RandomScheduler(Scheduler): + """ + Implements Scheduler as a random node selector + """ + + def __init__(self): + super(RandomScheduler, self).__init__() + + def pick_node(self, instance_id, **_kwargs): + nodes = self.compute_nodes_up() + return nodes[int(random.random() * len(nodes))] + +class BestFitScheduler(Scheduler): + """ + Implements Scheduler as a best-fit node selector + """ + + def __init__(self): + super(BestFitScheduler, self).__init__() + + def pick_node(self, instance_id, **_kwargs): + raise NotImplementedError("BestFitScheduler is not done yet") + diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 3a226322..3a86cefb 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -21,32 +21,34 @@ Scheduler Service """ import logging -import random -import sys -import time from twisted.internet import defer -from twisted.internet import task from nova import exception from nova import flags -from nova import process from nova import rpc from nova import service -from nova import utils from nova.compute import model -from nova.datastore import Redis +from nova.scheduler import scheduler FLAGS = flags.FLAGS -flags.DEFINE_integer('node_down_time', 60, - 'seconds without heartbeat that determines a compute node to be down') +flags.DEFINE_string('scheduler_type', + 'random', + 'the scheduler to use') +scheduler_classes = { + 'random': scheduler.RandomScheduler, + 'bestfit': scheduler.BestFitScheduler + } + class SchedulerService(service.Service): """ Manages the running instances. """ def __init__(self): super(SchedulerService, self).__init__() - self.instdir = model.InstanceDirectory() + if (FLAGS.scheduler_type not in scheduler_classes): + raise exception.Error("Scheduler '%s' does not exist" % FLAGS.scheduler_type) + self._scheduler_class = scheduler_classes[FLAGS.scheduler_type] def noop(self): """ simple test of an AMQP message call """ @@ -68,21 +70,8 @@ class SchedulerService(service.Service): logging.exception("model server went away") yield - @property - def compute_nodes(self): - return [identifier.split(':')[0] for identifier in Redis.instance().smembers("daemons") if (identifier.split(':')[1] == "nova-compute")] - - def compute_node_is_up(self, node): - time_str = Redis.instance().hget('%s:%s:%s' % ('daemon', node, 'nova-compute'), 'updated_at') - return(time_str and - (time.time() - (int(time.mktime(time.strptime(time_str.replace('Z', 'UTC'), '%Y-%m-%dT%H:%M:%S%Z'))) - time.timezone) < FLAGS.node_down_time)) - - def compute_nodes_up(self): - return [node for node in self.compute_nodes if self.compute_node_is_up(node)] - def pick_node(self, instance_id, **_kwargs): - """You DEFINITELY want to define this in your subclass""" - raise NotImplementedError("Your subclass should define pick_node") + return self._scheduler_class().pick_node(instance_id, **_kwargs) @exception.wrap_exception def run_instance(self, instance_id, **_kwargs): @@ -94,16 +83,3 @@ class SchedulerService(service.Service): logging.debug("Casting to node %s for instance %s" % (node, instance_id)) -class RandomService(SchedulerService): - """ - Implements SchedulerService as a random node selector - """ - - def __init__(self): - super(RandomService, self).__init__() - - def pick_node(self, instance_id, **_kwargs): - nodes = self.compute_nodes_up() - return nodes[int(random.random() * len(nodes))] - - From 56a6ed754d23681f9d120d1282f270660b41fdd2 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 6 Aug 2010 18:10:41 -0500 Subject: [PATCH 07/34] fix copyrights for new files, etc --- bin/nova-listinstances | 21 ++++++++++++++++++++- nova/scheduler/__init__.py | 12 ++---------- nova/scheduler/scheduler.py | 4 +--- nova/scheduler/service.py | 4 +--- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/bin/nova-listinstances b/bin/nova-listinstances index 2f8ff28f..386283d2 100755 --- a/bin/nova-listinstances +++ b/bin/nova-listinstances @@ -1,4 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# +# 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. # # Duplicates the functionality of euca-describe-instances, but doesn't require @@ -6,6 +21,10 @@ # mostly a test program written for the scheduler # +""" +List instances by doing a direct query to the datastore +""" + from nova.compute import model data_needed = ['image_id', 'memory_kb', 'local_gb', 'node_name', 'vcpus'] diff --git a/nova/scheduler/__init__.py b/nova/scheduler/__init__.py index 516ea61b..8359a7ae 100644 --- a/nova/scheduler/__init__.py +++ b/nova/scheduler/__init__.py @@ -1,8 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. +# Copyright (c) 2010 Openstack, LLC. # # 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 @@ -22,12 +20,6 @@ .. automodule:: nova.scheduler :platform: Unix - :synopsis: Daemon that picks a host for a VM instance. -.. moduleauthor:: Jesse Andrews -.. moduleauthor:: Devin Carlen -.. moduleauthor:: Vishvananda Ishaya -.. moduleauthor:: Joshua McKenty -.. moduleauthor:: Manish Singh -.. moduleauthor:: Andy Smith + :synopsis: Module that picks a compute node to run a VM instance. .. moduleauthor:: Chris Behrens """ diff --git a/nova/scheduler/scheduler.py b/nova/scheduler/scheduler.py index 0da7b95c..79ed9dc0 100644 --- a/nova/scheduler/scheduler.py +++ b/nova/scheduler/scheduler.py @@ -1,8 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. +# Copyright (c) 2010 Openstack, LLC. # # 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 diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 3a86cefb..39bfd6e0 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -1,8 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. +# Copyright (c) 2010 Openstack, LLC. # # 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 From 738630605af009ddde6c9595802270c298210f33 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Fri, 6 Aug 2010 18:54:45 -0500 Subject: [PATCH 08/34] pep8 and pylint cleanups --- nova/scheduler/scheduler.py | 29 +++++++++++++++++++---------- nova/scheduler/service.py | 18 +++++++++--------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/nova/scheduler/scheduler.py b/nova/scheduler/scheduler.py index 79ed9dc0..49ef40f0 100644 --- a/nova/scheduler/scheduler.py +++ b/nova/scheduler/scheduler.py @@ -18,19 +18,17 @@ Scheduler Classes """ -import logging import random -import sys import time -from nova import exception from nova import flags from nova.datastore import Redis FLAGS = flags.FLAGS flags.DEFINE_integer('node_down_time', 60, - 'seconds without heartbeat that determines a compute node to be down') + 'seconds without heartbeat that determines a ' + 'compute node to be down') class Scheduler(object): @@ -40,21 +38,32 @@ class Scheduler(object): @property def compute_nodes(self): - return [identifier.split(':')[0] for identifier in Redis.instance().smembers("daemons") if (identifier.split(':')[1] == "nova-compute")] + return [identifier.split(':')[0] + for identifier in Redis.instance().smembers("daemons") + if (identifier.split(':')[1] == "nova-compute")] def compute_node_is_up(self, node): - time_str = Redis.instance().hget('%s:%s:%s' % ('daemon', node, 'nova-compute'), 'updated_at') + time_str = Redis.instance().hget('%s:%s:%s' % + ('daemon', node, 'nova-compute'), + 'updated_at') + if not time_str: + return False + # Would be a lot easier if we stored heartbeat time in epoch :) - return(time_str and - (time.time() - (int(time.mktime(time.strptime(time_str.replace('Z', 'UTC'), '%Y-%m-%dT%H:%M:%S%Z'))) - time.timezone) < FLAGS.node_down_time)) + time_str = time_str.replace('Z', 'UTC') + time_split = time.strptime(time_str, '%Y-%m-%dT%H:%M:%S%Z') + epoch_time = int(time.mktime(time_split)) - time.timezone + return (time.time() - epoch_time) < FLAGS.node_down_time def compute_nodes_up(self): - return [node for node in self.compute_nodes if self.compute_node_is_up(node)] + return [node for node in self.compute_nodes + if self.compute_node_is_up(node)] def pick_node(self, instance_id, **_kwargs): """You DEFINITELY want to define this in your subclass""" raise NotImplementedError("Your subclass should define pick_node") + class RandomScheduler(Scheduler): """ Implements Scheduler as a random node selector @@ -67,6 +76,7 @@ class RandomScheduler(Scheduler): nodes = self.compute_nodes_up() return nodes[int(random.random() * len(nodes))] + class BestFitScheduler(Scheduler): """ Implements Scheduler as a best-fit node selector @@ -77,4 +87,3 @@ class BestFitScheduler(Scheduler): def pick_node(self, instance_id, **_kwargs): raise NotImplementedError("BestFitScheduler is not done yet") - diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 39bfd6e0..1246b6e7 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -33,19 +33,20 @@ flags.DEFINE_string('scheduler_type', 'random', 'the scheduler to use') -scheduler_classes = { - 'random': scheduler.RandomScheduler, - 'bestfit': scheduler.BestFitScheduler - } - +scheduler_classes = {'random': scheduler.RandomScheduler, + 'bestfit': scheduler.BestFitScheduler} + + class SchedulerService(service.Service): """ Manages the running instances. """ + def __init__(self): super(SchedulerService, self).__init__() if (FLAGS.scheduler_type not in scheduler_classes): - raise exception.Error("Scheduler '%s' does not exist" % FLAGS.scheduler_type) + raise exception.Error("Scheduler '%s' does not exist" % + FLAGS.scheduler_type) self._scheduler_class = scheduler_classes[FLAGS.scheduler_type] def noop(self): @@ -77,7 +78,6 @@ class SchedulerService(service.Service): rpc.cast('%s.%s' % (FLAGS.compute_topic, node), {"method": "run_instance", - "args": {"instance_id" : instance_id}}) - logging.debug("Casting to node %s for instance %s" % + "args": {"instance_id": instance_id}}) + logging.debug("Casting to node %s for running instance %s" % (node, instance_id)) - From 139b7e9a1781357bd9eedeefbd6fb459b00d50f1 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 9 Aug 2010 09:37:50 -0500 Subject: [PATCH 09/34] remove duplicated report_state that exists in the base class more pylint fixes --- nova/scheduler/service.py | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 1246b6e7..9d2d35f1 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -25,7 +25,6 @@ from nova import exception from nova import flags from nova import rpc from nova import service -from nova.compute import model from nova.scheduler import scheduler FLAGS = flags.FLAGS @@ -33,7 +32,7 @@ flags.DEFINE_string('scheduler_type', 'random', 'the scheduler to use') -scheduler_classes = {'random': scheduler.RandomScheduler, +SCHEDULER_CLASSES = {'random': scheduler.RandomScheduler, 'bestfit': scheduler.BestFitScheduler} @@ -44,40 +43,33 @@ class SchedulerService(service.Service): def __init__(self): super(SchedulerService, self).__init__() - if (FLAGS.scheduler_type not in scheduler_classes): + if (FLAGS.scheduler_type not in SCHEDULER_CLASSES): raise exception.Error("Scheduler '%s' does not exist" % FLAGS.scheduler_type) - self._scheduler_class = scheduler_classes[FLAGS.scheduler_type] + self._scheduler_class = SCHEDULER_CLASSES[FLAGS.scheduler_type] def noop(self): """ simple test of an AMQP message call """ return defer.succeed('PONG') - @defer.inlineCallbacks - def report_state(self, nodename, daemon): - # TODO(termie): make this pattern be more elegant. -todd - try: - record = model.Daemon(nodename, daemon) - record.heartbeat() - if getattr(self, "model_disconnected", False): - self.model_disconnected = False - logging.error("Recovered model server connection!") - - except model.ConnectionError, ex: - if not getattr(self, "model_disconnected", False): - self.model_disconnected = True - logging.exception("model server went away") - yield - def pick_node(self, instance_id, **_kwargs): + """ + Return a node to use based on the selected Scheduler + """ + return self._scheduler_class().pick_node(instance_id, **_kwargs) @exception.wrap_exception def run_instance(self, instance_id, **_kwargs): + """ + Picks a node for a running VM and casts the run_instance request + """ + node = self.pick_node(instance_id, **_kwargs) rpc.cast('%s.%s' % (FLAGS.compute_topic, node), {"method": "run_instance", "args": {"instance_id": instance_id}}) - logging.debug("Casting to node %s for running instance %s" % - (node, instance_id)) + logging.debug("Casting to node %s for running instance %s", + node, instance_id) + From e58966c18384c2b5893d9ce4b2276c3b98e15e1a Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Mon, 9 Aug 2010 12:56:32 -0500 Subject: [PATCH 10/34] separated scheduler types into own modules --- nova/scheduler/{scheduler.py => base.py} | 32 +++--------------------- nova/scheduler/bestfit.py | 30 ++++++++++++++++++++++ nova/scheduler/service.py | 10 ++++---- 3 files changed, 39 insertions(+), 33 deletions(-) rename nova/scheduler/{scheduler.py => base.py} (75%) create mode 100644 nova/scheduler/bestfit.py diff --git a/nova/scheduler/scheduler.py b/nova/scheduler/base.py similarity index 75% rename from nova/scheduler/scheduler.py rename to nova/scheduler/base.py index 49ef40f0..5c359943 100644 --- a/nova/scheduler/scheduler.py +++ b/nova/scheduler/base.py @@ -15,10 +15,9 @@ # under the License. """ -Scheduler Classes +Scheduler base class that all Schedulers should inherit from """ -import random import time from nova import flags @@ -50,7 +49,9 @@ class Scheduler(object): return False # Would be a lot easier if we stored heartbeat time in epoch :) - time_str = time_str.replace('Z', 'UTC') + + # The 'str()' here is to get rid of a pylint error + time_str = str(time_str).replace('Z', 'UTC') time_split = time.strptime(time_str, '%Y-%m-%dT%H:%M:%S%Z') epoch_time = int(time.mktime(time_split)) - time.timezone return (time.time() - epoch_time) < FLAGS.node_down_time @@ -62,28 +63,3 @@ class Scheduler(object): def pick_node(self, instance_id, **_kwargs): """You DEFINITELY want to define this in your subclass""" raise NotImplementedError("Your subclass should define pick_node") - - -class RandomScheduler(Scheduler): - """ - Implements Scheduler as a random node selector - """ - - def __init__(self): - super(RandomScheduler, self).__init__() - - def pick_node(self, instance_id, **_kwargs): - nodes = self.compute_nodes_up() - return nodes[int(random.random() * len(nodes))] - - -class BestFitScheduler(Scheduler): - """ - Implements Scheduler as a best-fit node selector - """ - - def __init__(self): - super(BestFitScheduler, self).__init__() - - def pick_node(self, instance_id, **_kwargs): - raise NotImplementedError("BestFitScheduler is not done yet") diff --git a/nova/scheduler/bestfit.py b/nova/scheduler/bestfit.py new file mode 100644 index 00000000..1bd24456 --- /dev/null +++ b/nova/scheduler/bestfit.py @@ -0,0 +1,30 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# +# 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. + +""" +Best Fit Scheduler +""" + +from nova.scheduler.base import Scheduler + + +class BestFitScheduler(Scheduler): + """ + Implements Scheduler as a best-fit node selector + """ + + def pick_node(self, instance_id, **_kwargs): + raise NotImplementedError("BestFitScheduler is not done yet") diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 9d2d35f1..44b30ecb 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -25,15 +25,16 @@ from nova import exception from nova import flags from nova import rpc from nova import service -from nova.scheduler import scheduler +from nova.scheduler import chance +from nova.scheduler import bestfit FLAGS = flags.FLAGS flags.DEFINE_string('scheduler_type', - 'random', + 'chance', 'the scheduler to use') -SCHEDULER_CLASSES = {'random': scheduler.RandomScheduler, - 'bestfit': scheduler.BestFitScheduler} +SCHEDULER_CLASSES = {'chance': chance.ChanceScheduler, + 'bestfit': bestfit.BestFitScheduler} class SchedulerService(service.Service): @@ -72,4 +73,3 @@ class SchedulerService(service.Service): "args": {"instance_id": instance_id}}) logging.debug("Casting to node %s for running instance %s", node, instance_id) - From d53ad0a002ab5f4a23f0e1c256bbd880d702acf6 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 10 Aug 2010 18:48:33 -0500 Subject: [PATCH 11/34] more pylint fixes --- nova/scheduler/base.py | 23 +++++++++++++++++++---- nova/scheduler/bestfit.py | 4 ++++ nova/scheduler/service.py | 3 ++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/nova/scheduler/base.py b/nova/scheduler/base.py index 5c359943..2872ae6f 100644 --- a/nova/scheduler/base.py +++ b/nova/scheduler/base.py @@ -35,13 +35,23 @@ class Scheduler(object): The base class that all Scheduler clases should inherit from """ - @property - def compute_nodes(self): + @staticmethod + def compute_nodes(): + """ + Return a list of compute nodes + """ + return [identifier.split(':')[0] for identifier in Redis.instance().smembers("daemons") if (identifier.split(':')[1] == "nova-compute")] - def compute_node_is_up(self, node): + @staticmethod + def compute_node_is_up(node): + """ + Given a node name, return whether the node is considered 'up' by + if it's sent a heartbeat recently + """ + time_str = Redis.instance().hget('%s:%s:%s' % ('daemon', node, 'nova-compute'), 'updated_at') @@ -57,9 +67,14 @@ class Scheduler(object): return (time.time() - epoch_time) < FLAGS.node_down_time def compute_nodes_up(self): - return [node for node in self.compute_nodes + """ + Return the list of compute nodes that are considered 'up' + """ + + return [node for node in self.compute_nodes() if self.compute_node_is_up(node)] def pick_node(self, instance_id, **_kwargs): """You DEFINITELY want to define this in your subclass""" + raise NotImplementedError("Your subclass should define pick_node") diff --git a/nova/scheduler/bestfit.py b/nova/scheduler/bestfit.py index 1bd24456..bdd4fcbd 100644 --- a/nova/scheduler/bestfit.py +++ b/nova/scheduler/bestfit.py @@ -27,4 +27,8 @@ class BestFitScheduler(Scheduler): """ def pick_node(self, instance_id, **_kwargs): + """ + Picks a node that is up and is a best fit for the new instance + """ + raise NotImplementedError("BestFitScheduler is not done yet") diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py index 44b30ecb..136f262c 100644 --- a/nova/scheduler/service.py +++ b/nova/scheduler/service.py @@ -49,7 +49,8 @@ class SchedulerService(service.Service): FLAGS.scheduler_type) self._scheduler_class = SCHEDULER_CLASSES[FLAGS.scheduler_type] - def noop(self): + @staticmethod + def noop(): """ simple test of an AMQP message call """ return defer.succeed('PONG') From 063af4dfcd54bbe2aca0ace2fa73d069a5562fce Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 2 Sep 2010 11:32:37 -0700 Subject: [PATCH 12/34] scheduler + unittests --- bin/nova-scheduler | 4 +- nova/endpoint/cloud.py | 6 +- nova/flags.py | 2 + nova/scheduler/base.py | 80 ------------------------ nova/scheduler/bestfit.py | 34 ---------- nova/scheduler/service.py | 76 ----------------------- nova/scheduler/simple.py | 81 ++++++++++++++++++++++++ nova/tests/compute_unittest.py | 2 +- nova/tests/scheduler_unittest.py | 103 +++++++++++++++++++++++++++++++ run_tests.py | 1 + 10 files changed, 193 insertions(+), 196 deletions(-) delete mode 100644 nova/scheduler/base.py delete mode 100644 nova/scheduler/bestfit.py delete mode 100644 nova/scheduler/service.py create mode 100644 nova/scheduler/simple.py create mode 100644 nova/tests/scheduler_unittest.py diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 1ad41bbd..97f98b17 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -21,12 +21,12 @@ Twistd daemon for the nova scheduler nodes. """ +from nova import service from nova import twistd -from nova.scheduler import service if __name__ == '__main__': twistd.serve(__file__) if __name__ == '__builtin__': - application = service.SchedulerService.create() + application = service.Service.create() diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 4e86145d..2c88ef40 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -488,9 +488,9 @@ class CloudController(object): host = db.network_get_host(context, network_ref['id']) if not host: host = yield rpc.call(FLAGS.network_topic, - {"method": "set_network_host", - "args": {"context": None, - "project_id": context.project.id}}) + {"method": "set_network_host", + "args": {"context": None, + "project_id": context.project.id}}) defer.returnValue(db.queue_get_for(context, FLAGS.network_topic, host)) @rbac.allow('projectmanager', 'sysadmin') diff --git a/nova/flags.py b/nova/flags.py index aa964884..40ce9c73 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -220,5 +220,7 @@ DEFINE_string('network_manager', 'nova.network.manager.VlanManager', 'Manager for network') DEFINE_string('volume_manager', 'nova.volume.manager.AOEManager', 'Manager for volume') +DEFINE_string('scheduler_manager', 'nova.scheduler.manager.SchedulerManager', + 'Manager for scheduler') diff --git a/nova/scheduler/base.py b/nova/scheduler/base.py deleted file mode 100644 index 2872ae6f..00000000 --- a/nova/scheduler/base.py +++ /dev/null @@ -1,80 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Openstack, LLC. -# -# 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. - -""" -Scheduler base class that all Schedulers should inherit from -""" - -import time - -from nova import flags -from nova.datastore import Redis - -FLAGS = flags.FLAGS -flags.DEFINE_integer('node_down_time', - 60, - 'seconds without heartbeat that determines a ' - 'compute node to be down') - - -class Scheduler(object): - """ - The base class that all Scheduler clases should inherit from - """ - - @staticmethod - def compute_nodes(): - """ - Return a list of compute nodes - """ - - return [identifier.split(':')[0] - for identifier in Redis.instance().smembers("daemons") - if (identifier.split(':')[1] == "nova-compute")] - - @staticmethod - def compute_node_is_up(node): - """ - Given a node name, return whether the node is considered 'up' by - if it's sent a heartbeat recently - """ - - time_str = Redis.instance().hget('%s:%s:%s' % - ('daemon', node, 'nova-compute'), - 'updated_at') - if not time_str: - return False - - # Would be a lot easier if we stored heartbeat time in epoch :) - - # The 'str()' here is to get rid of a pylint error - time_str = str(time_str).replace('Z', 'UTC') - time_split = time.strptime(time_str, '%Y-%m-%dT%H:%M:%S%Z') - epoch_time = int(time.mktime(time_split)) - time.timezone - return (time.time() - epoch_time) < FLAGS.node_down_time - - def compute_nodes_up(self): - """ - Return the list of compute nodes that are considered 'up' - """ - - return [node for node in self.compute_nodes() - if self.compute_node_is_up(node)] - - def pick_node(self, instance_id, **_kwargs): - """You DEFINITELY want to define this in your subclass""" - - raise NotImplementedError("Your subclass should define pick_node") diff --git a/nova/scheduler/bestfit.py b/nova/scheduler/bestfit.py deleted file mode 100644 index bdd4fcbd..00000000 --- a/nova/scheduler/bestfit.py +++ /dev/null @@ -1,34 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Openstack, LLC. -# -# 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. - -""" -Best Fit Scheduler -""" - -from nova.scheduler.base import Scheduler - - -class BestFitScheduler(Scheduler): - """ - Implements Scheduler as a best-fit node selector - """ - - def pick_node(self, instance_id, **_kwargs): - """ - Picks a node that is up and is a best fit for the new instance - """ - - raise NotImplementedError("BestFitScheduler is not done yet") diff --git a/nova/scheduler/service.py b/nova/scheduler/service.py deleted file mode 100644 index 136f262c..00000000 --- a/nova/scheduler/service.py +++ /dev/null @@ -1,76 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Openstack, LLC. -# -# 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. - -""" -Scheduler Service -""" - -import logging -from twisted.internet import defer - -from nova import exception -from nova import flags -from nova import rpc -from nova import service -from nova.scheduler import chance -from nova.scheduler import bestfit - -FLAGS = flags.FLAGS -flags.DEFINE_string('scheduler_type', - 'chance', - 'the scheduler to use') - -SCHEDULER_CLASSES = {'chance': chance.ChanceScheduler, - 'bestfit': bestfit.BestFitScheduler} - - -class SchedulerService(service.Service): - """ - Manages the running instances. - """ - - def __init__(self): - super(SchedulerService, self).__init__() - if (FLAGS.scheduler_type not in SCHEDULER_CLASSES): - raise exception.Error("Scheduler '%s' does not exist" % - FLAGS.scheduler_type) - self._scheduler_class = SCHEDULER_CLASSES[FLAGS.scheduler_type] - - @staticmethod - def noop(): - """ simple test of an AMQP message call """ - return defer.succeed('PONG') - - def pick_node(self, instance_id, **_kwargs): - """ - Return a node to use based on the selected Scheduler - """ - - return self._scheduler_class().pick_node(instance_id, **_kwargs) - - @exception.wrap_exception - def run_instance(self, instance_id, **_kwargs): - """ - Picks a node for a running VM and casts the run_instance request - """ - - node = self.pick_node(instance_id, **_kwargs) - - rpc.cast('%s.%s' % (FLAGS.compute_topic, node), - {"method": "run_instance", - "args": {"instance_id": instance_id}}) - logging.debug("Casting to node %s for running instance %s", - node, instance_id) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py new file mode 100644 index 00000000..6c76fd32 --- /dev/null +++ b/nova/scheduler/simple.py @@ -0,0 +1,81 @@ +# 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. + +""" +Simple Scheduler +""" + +from nova import db +from nova import flags +from nova.scheduler import driver + +FLAGS = flags.FLAGS +flags.DEFINE_integer("max_instances", 16, + "maximum number of instances to allow per host") +flags.DEFINE_integer("max_volumes", 100, + "maximum number of volumes to allow per host") +flags.DEFINE_integer("max_networks", 1000, + "maximum number of networks to allow per host") + +class SimpleScheduler(driver.Scheduler): + """ + Implements Naive Scheduler that tries to find least loaded host + """ + + def pick_compute_host(self, context, instance_id, **_kwargs): + """ + Picks a host that is up and has the fewest running instances + """ + + results = db.daemon_get_all_compute_sorted(context) + for result in results: + (daemon, instance_count) = result + if instance_count >= FLAGS.max_instances: + raise driver.NoValidHost("All hosts have too many instances") + if self.daemon_is_up(daemon): + return daemon['host'] + raise driver.NoValidHost("No hosts found") + + def pick_volume_host(self, context, volume_id, **_kwargs): + """ + Picks a host that is up and has the fewest volumes + """ + + results = db.daemon_get_all_volume_sorted(context) + for result in results: + (daemon, instance_count) = result + if instance_count >= FLAGS.max_volumes: + raise driver.NoValidHost("All hosts have too many volumes") + if self.daemon_is_up(daemon): + return daemon['host'] + raise driver.NoValidHost("No hosts found") + + def pick_network_host(self, context, network_id, **_kwargs): + """ + Picks a host that is up and has the fewest networks + """ + + results = db.daemon_get_all_network_sorted(context) + for result in results: + (daemon, instance_count) = result + if instance_count >= FLAGS.max_networks: + raise driver.NoValidHost("All hosts have too many networks") + if self.daemon_is_up(daemon): + return daemon['host'] + raise driver.NoValidHost("No hosts found") diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 867b572f..23013e4c 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -61,7 +61,7 @@ class ComputeTestCase(test.TrialTestCase): inst['instance_type'] = 'm1.tiny' inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 - return db.instance_create(None, inst) + return db.instance_create(self.context, inst) @defer.inlineCallbacks def test_run_terminate(self): diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py new file mode 100644 index 00000000..d3616dd6 --- /dev/null +++ b/nova/tests/scheduler_unittest.py @@ -0,0 +1,103 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. +""" +Tests For Scheduler +""" +import logging + +from twisted.internet import defer + +from nova import db +from nova import flags +from nova import service +from nova import test +from nova import utils +from nova.auth import manager as auth_manager +from nova.scheduler import manager + + +FLAGS = flags.FLAGS + + +class SchedulerTestCase(test.TrialTestCase): + """Test case for scheduler""" + def setUp(self): # pylint: disable-msg=C0103 + super(SchedulerTestCase, self).setUp() + self.flags(connection_type='fake', + scheduler_driver='nova.scheduler.simple.SimpleScheduler') + self.scheduler = manager.SchedulerManager() + self.context = None + self.manager = auth_manager.AuthManager() + self.user = self.manager.create_user('fake', 'fake', 'fake') + self.project = self.manager.create_project('fake', 'fake', 'fake') + self.context = None + + def tearDown(self): # pylint: disable-msg=C0103 + self.manager.delete_user(self.user) + self.manager.delete_project(self.project) + + def _create_instance(self): + """Create a test instance""" + inst = {} + inst['image_id'] = 'ami-test' + inst['reservation_id'] = 'r-fakeres' + inst['launch_time'] = '10' + inst['user_id'] = self.user.id + inst['project_id'] = self.project.id + inst['instance_type'] = 'm1.tiny' + inst['mac_address'] = utils.generate_mac() + inst['ami_launch_index'] = 0 + return db.instance_create(self.context, inst) + + def test_hosts_are_up(self): + # NOTE(vish): constructing service without create method + # because we are going to use it without queue + service1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + service2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + service1.report_state() + service2.report_state() + hosts = self.scheduler.driver.hosts_up(self.context, 'compute') + self.assertEqual(len(hosts), 2) + + def test_least_busy_host_gets_instance(self): + # NOTE(vish): constructing service without create method + # because we are going to use it without queue + service1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + service2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + service1.report_state() + service2.report_state() + instance_id = self._create_instance() + FLAGS.host = 'host1' + service1.run_instance(self.context, + instance_id) + print type(self.scheduler.driver) + host = self.scheduler.driver.pick_compute_host(self.context, + instance_id) + self.assertEqual(host, 'host2') diff --git a/run_tests.py b/run_tests.py index c47cbe2e..5d76a74c 100644 --- a/run_tests.py +++ b/run_tests.py @@ -60,6 +60,7 @@ from nova.tests.network_unittest import * from nova.tests.objectstore_unittest import * from nova.tests.process_unittest import * from nova.tests.rpc_unittest import * +from nova.tests.scheduler_unittest import * from nova.tests.service_unittest import * from nova.tests.validator_unittest import * from nova.tests.volume_unittest import * From ab776939c1adf5c66713b3c1fae5312f455551ab Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 2 Sep 2010 15:15:39 -0700 Subject: [PATCH 13/34] test for too many instances work --- nova/scheduler/simple.py | 25 ++++++------- nova/tests/scheduler_unittest.py | 63 +++++++++++++------------------- 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 294dc111..83241720 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -43,14 +43,13 @@ class SimpleScheduler(driver.Scheduler): Picks a host that is up and has the fewest running instances """ - results = db.daemon_get_all_compute_sorted(context) + results = db.service_get_all_compute_sorted(context) for result in results: - (daemon, instance_count) = result - print daemon.host, instance_count + (service, instance_count) = result if instance_count >= FLAGS.max_instances: raise driver.NoValidHost("All hosts have too many instances") - if self.daemon_is_up(daemon): - return daemon['host'] + if self.service_is_up(service): + return service['host'] raise driver.NoValidHost("No hosts found") def pick_volume_host(self, context, volume_id, **_kwargs): @@ -58,13 +57,13 @@ class SimpleScheduler(driver.Scheduler): Picks a host that is up and has the fewest volumes """ - results = db.daemon_get_all_volume_sorted(context) + results = db.service_get_all_volume_sorted(context) for result in results: - (daemon, instance_count) = result + (service, instance_count) = result if instance_count >= FLAGS.max_volumes: raise driver.NoValidHost("All hosts have too many volumes") - if self.daemon_is_up(daemon): - return daemon['host'] + if self.service_is_up(service): + return service['host'] raise driver.NoValidHost("No hosts found") def pick_network_host(self, context, network_id, **_kwargs): @@ -72,11 +71,11 @@ class SimpleScheduler(driver.Scheduler): Picks a host that is up and has the fewest networks """ - results = db.daemon_get_all_network_sorted(context) + results = db.service_get_all_network_sorted(context) for result in results: - (daemon, instance_count) = result + (service, instance_count) = result if instance_count >= FLAGS.max_networks: raise driver.NoValidHost("All hosts have too many networks") - if self.daemon_is_up(daemon): - return daemon['host'] + if self.service_is_up(service): + return service['host'] raise driver.NoValidHost("No hosts found") diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index 45ffac43..bdd77713 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -18,9 +18,6 @@ """ Tests For Scheduler """ -import logging - -from twisted.internet import defer from nova import db from nova import flags @@ -36,10 +33,10 @@ FLAGS = flags.FLAGS flags.DECLARE('max_instances', 'nova.scheduler.simple') -class SchedulerTestCase(test.TrialTestCase): +class SimpleSchedulerTestCase(test.TrialTestCase): """Test case for scheduler""" def setUp(self): # pylint: disable-msg=C0103 - super(SchedulerTestCase, self).setUp() + super(SimpleSchedulerTestCase, self).setUp() self.flags(connection_type='fake', max_instances=4, scheduler_driver='nova.scheduler.simple.SimpleScheduler') @@ -49,10 +46,20 @@ class SchedulerTestCase(test.TrialTestCase): self.user = self.manager.create_user('fake', 'fake', 'fake') self.project = self.manager.create_project('fake', 'fake', 'fake') self.context = None + self.service1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + self.service2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) def tearDown(self): # pylint: disable-msg=C0103 self.manager.delete_user(self.user) self.manager.delete_project(self.project) + self.service1.kill() + self.service2.kill() def _create_instance(self): """Create a test instance""" @@ -70,53 +77,33 @@ class SchedulerTestCase(test.TrialTestCase): def test_hosts_are_up(self): # NOTE(vish): constructing service without create method # because we are going to use it without queue - service1 = service.Service('host1', - 'nova-compute', - 'compute', - FLAGS.compute_manager) - service2 = service.Service('host2', - 'nova-compute', - 'compute', - FLAGS.compute_manager) - hosts = self.scheduler.driver.hosts_up(self.context, 'compute') - self.assertEqual(len(hosts), 0) - service1.report_state() - service2.report_state() hosts = self.scheduler.driver.hosts_up(self.context, 'compute') self.assertEqual(len(hosts), 2) def test_least_busy_host_gets_instance(self): - service1 = service.Service('host1', - 'nova-compute', - 'compute', - FLAGS.compute_manager) - service2 = service.Service('host2', - 'nova-compute', - 'compute', - FLAGS.compute_manager) - service1.report_state() - service2.report_state() instance_id = self._create_instance() - service1.run_instance(self.context, instance_id) + self.service1.run_instance(self.context, instance_id) host = self.scheduler.driver.pick_compute_host(self.context, instance_id) self.assertEqual(host, 'host2') - service1.terminate_instance(self.context, instance_id) + self.service1.terminate_instance(self.context, instance_id) def test_too_many_instances(self): - service1 = service.Service('host', - 'nova-compute', - 'compute', - FLAGS.compute_manager) - instance_ids = [] + instance_ids1 = [] + instance_ids2 = [] for index in xrange(FLAGS.max_instances): instance_id = self._create_instance() - service1.run_instance(self.context, instance_id) - instance_ids.append(instance_id) + self.service1.run_instance(self.context, instance_id) + instance_ids1.append(instance_id) + instance_id = self._create_instance() + self.service2.run_instance(self.context, instance_id) + instance_ids2.append(instance_id) instance_id = self._create_instance() self.assertRaises(driver.NoValidHost, self.scheduler.driver.pick_compute_host, self.context, instance_id) - for instance_id in instance_ids: - service1.terminate_instance(self.context, instance_id) + for instance_id in instance_ids1: + self.service1.terminate_instance(self.context, instance_id) + for instance_id in instance_ids2: + self.service2.terminate_instance(self.context, instance_id) From bf1f12e13194489808753b7ae35e6a2101eea99c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 2 Sep 2010 17:06:01 -0700 Subject: [PATCH 14/34] send ultimate topic in to scheduler --- nova/endpoint/cloud.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 2c88ef40..1ff22042 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -567,6 +567,7 @@ class CloudController(object): rpc.cast(FLAGS.scheduler_topic, {"method": "run_instance", "args": {"context": None, + "topic": FLAGS.compute_topic, "instance_id": inst_id}}) logging.debug("Casting to scheduler for %s/%s's instance %s" % (context.project.name, context.user.name, inst_id)) From 088b765655b2e8667791288ecfe73e16eed16d2e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 3 Sep 2010 00:53:41 -0700 Subject: [PATCH 15/34] removed extra file and updated sql note --- bin/nova-listinstances | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100755 bin/nova-listinstances diff --git a/bin/nova-listinstances b/bin/nova-listinstances deleted file mode 100755 index 386283d2..00000000 --- a/bin/nova-listinstances +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Openstack, LLC. -# -# 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. - -# -# Duplicates the functionality of euca-describe-instances, but doesn't require -# going through the API. Does a direct query to the datastore. This is -# mostly a test program written for the scheduler -# - -""" -List instances by doing a direct query to the datastore -""" - -from nova.compute import model - -data_needed = ['image_id', 'memory_kb', 'local_gb', 'node_name', 'vcpus'] - -instances = model.InstanceDirectory().all - -for instance in instances: - print 'Instance: %s' % instance['instance_id'] - for x in data_needed: - print ' %s: %s' % (x, instance[x]) - From 871d08de05cd88017a5b75fccb136bd6cef091fc Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 7 Sep 2010 13:01:21 -0700 Subject: [PATCH 16/34] fix docstrings and formatting --- nova/scheduler/simple.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index d10ddaba..ea4eef98 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -35,14 +35,10 @@ flags.DEFINE_integer("max_networks", 1000, "maximum number of networks to allow per host") class SimpleScheduler(chance.ChanceScheduler): - """ - Implements Naive Scheduler that tries to find least loaded host - """ + """Implements Naive Scheduler that tries to find least loaded host.""" def schedule_run_instance(self, context, _instance_id, *_args, **_kwargs): - """ - Picks a host that is up and has the fewest running instances - """ + """Picks a host that is up and has the fewest running instances.""" results = db.service_get_all_compute_sorted(context) for result in results: @@ -54,9 +50,7 @@ class SimpleScheduler(chance.ChanceScheduler): raise driver.NoValidHost("No hosts found") def schedule_create_volume(self, context, _volume_id, *_args, **_kwargs): - """ - Picks a host that is up and has the fewest volumes - """ + """Picks a host that is up and has the fewest volumes.""" results = db.service_get_all_volume_sorted(context) for result in results: @@ -67,10 +61,9 @@ class SimpleScheduler(chance.ChanceScheduler): return service['host'] raise driver.NoValidHost("No hosts found") - def schedule_set_network_host(self, context, _network_id, *_args, **_kwargs): - """ - Picks a host that is up and has the fewest networks - """ + def schedule_set_network_host(self, context, _network_id, + *_args, **_kwargs): + """Picks a host that is up and has the fewest networks.""" results = db.service_get_all_network_sorted(context) for result in results: From 5832292b8127c5b047e36ef5603ad14b1a16562d Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 9 Sep 2010 02:30:07 -0700 Subject: [PATCH 17/34] put soren's fancy path code in scheduler bin as well --- bin/nova-scheduler | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 97f98b17..38a8f213 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -21,6 +21,17 @@ Twistd daemon for the nova scheduler nodes. """ +import os +import sys + +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + from nova import service from nova import twistd From 7f99bf70d7b92e319b0c0b236a3e56a561918550 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 9 Sep 2010 21:29:00 -0700 Subject: [PATCH 18/34] kwargs don't work if you prepend an underscore --- nova/scheduler/simple.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index ea4eef98..e53e9fa7 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -37,7 +37,7 @@ flags.DEFINE_integer("max_networks", 1000, class SimpleScheduler(chance.ChanceScheduler): """Implements Naive Scheduler that tries to find least loaded host.""" - def schedule_run_instance(self, context, _instance_id, *_args, **_kwargs): + def schedule_run_instance(self, context, *_args, **_kwargs): """Picks a host that is up and has the fewest running instances.""" results = db.service_get_all_compute_sorted(context) @@ -49,7 +49,7 @@ class SimpleScheduler(chance.ChanceScheduler): return service['host'] raise driver.NoValidHost("No hosts found") - def schedule_create_volume(self, context, _volume_id, *_args, **_kwargs): + def schedule_create_volume(self, context, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" results = db.service_get_all_volume_sorted(context) @@ -61,8 +61,7 @@ class SimpleScheduler(chance.ChanceScheduler): return service['host'] raise driver.NoValidHost("No hosts found") - def schedule_set_network_host(self, context, _network_id, - *_args, **_kwargs): + def schedule_set_network_host(self, context, *_args, **_kwargs): """Picks a host that is up and has the fewest networks.""" results = db.service_get_all_network_sorted(context) From c0f3fbc02bd34812f70d80c20065b2d70b6c32a2 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 9 Sep 2010 21:59:09 -0700 Subject: [PATCH 19/34] fix unittest --- nova/tests/scheduler_unittest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index 51b9aeaa..09e45ea6 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -109,7 +109,7 @@ class SimpleDriverTestCase(test.TrialTestCase): inst['instance_type'] = 'm1.tiny' inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 - return db.instance_create(self.context, inst) + return db.instance_create(self.context, inst)['id'] def test_hosts_are_up(self): # NOTE(vish): constructing service without create method From ab76ae87b228c000780db743f7200a0b074132fd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 9 Sep 2010 23:32:43 -0700 Subject: [PATCH 20/34] don't pass topic into schedule_run_instance --- nova/tests/scheduler_unittest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index 09e45ea6..27e100fa 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -121,7 +121,6 @@ class SimpleDriverTestCase(test.TrialTestCase): instance_id = self._create_instance() self.service1.run_instance(self.context, instance_id) host = self.scheduler.driver.schedule_run_instance(self.context, - 'compute', instance_id) self.assertEqual(host, 'host2') self.service1.terminate_instance(self.context, instance_id) From b9609f0873cfd50a1b2184930921721bad66601c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 10 Sep 2010 01:13:11 -0700 Subject: [PATCH 21/34] set host when item is scheduled --- nova/scheduler/simple.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index e53e9fa7..48be4c1a 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -37,7 +37,7 @@ flags.DEFINE_integer("max_networks", 1000, class SimpleScheduler(chance.ChanceScheduler): """Implements Naive Scheduler that tries to find least loaded host.""" - def schedule_run_instance(self, context, *_args, **_kwargs): + def schedule_run_instance(self, context, instance_id, *_args, **_kwargs): """Picks a host that is up and has the fewest running instances.""" results = db.service_get_all_compute_sorted(context) @@ -46,10 +46,13 @@ class SimpleScheduler(chance.ChanceScheduler): if instance_count >= FLAGS.max_instances: raise driver.NoValidHost("All hosts have too many instances") if self.service_is_up(service): + db.instance_update(context, + instance_id, + {'host': service['host']}) return service['host'] raise driver.NoValidHost("No hosts found") - def schedule_create_volume(self, context, *_args, **_kwargs): + def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" results = db.service_get_all_volume_sorted(context) @@ -58,6 +61,9 @@ class SimpleScheduler(chance.ChanceScheduler): if instance_count >= FLAGS.max_volumes: raise driver.NoValidHost("All hosts have too many volumes") if self.service_is_up(service): + db.instance_update(context, + volume_id, + {'host': service['host']}) return service['host'] raise driver.NoValidHost("No hosts found") From e7ae11c9fcfeb5f66235e743427f9838c52ef9ae Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 04:48:37 -0700 Subject: [PATCH 22/34] fixed typo network => network_manager in cloud.py --- nova/endpoint/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 622b4e2a..0eedd9fe 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -613,7 +613,7 @@ class CloudController(object): # NOTE(vish): Currently, nothing needs to be done on the # network node until release. If this changes, # we will need to cast here. - self.network.deallocate_fixed_ip(context, address) + self.network_manager.deallocate_fixed_ip(context, address) host = instance_ref['host'] if host: From 17bc5e0866f3bfcbd56d075eab8b3d318bfb1afe Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 06:29:13 -0700 Subject: [PATCH 23/34] fixed reversed admin logic on describe instances --- nova/endpoint/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 0eedd9fe..a25598dc 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -366,7 +366,7 @@ class CloudController(object): instances = db.instance_get_by_reservation(context, reservation_id) else: - if not context.user.is_admin(): + if context.user.is_admin(): instances = db.instance_get_all(context) else: instances = db.instance_get_by_project(context, From 7289ca245328fb83fc47f8d8a878ed9d64a1734a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 08:43:48 -0700 Subject: [PATCH 24/34] added terminated_at to volume and moved setting of terminated_at into cloud --- nova/endpoint/cloud.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 4b82e6d4..faa646b5 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -23,6 +23,7 @@ datastore. """ import base64 +import datetime import logging import os import time @@ -594,6 +595,10 @@ class CloudController(object): % id_str) continue + now = datetime.datetime.utcnow() + self.db.instance_update(context, + instance_ref['id'], + {'terminated_at': now}) # FIXME(ja): where should network deallocate occur? address = db.instance_get_floating_address(context, instance_ref['id']) @@ -643,6 +648,10 @@ class CloudController(object): def delete_volume(self, context, volume_id, **kwargs): # TODO: return error if not authorized volume_ref = db.volume_get_by_str(context, volume_id) + now = datetime.datetime.utcnow() + self.db.volume_update(context, + volume_ref['id'], + {'terminated_at': now}) host = volume_ref['host'] rpc.cast(db.queue_get_for(context, FLAGS.volume_topic, host), {"method": "delete_volume", From 8a4efea1b9f5d3e70061cd8cc845a8c0df4f2bfd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 17:12:43 -0700 Subject: [PATCH 25/34] db not self.db --- nova/endpoint/cloud.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index faa646b5..9d8e45f3 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -596,9 +596,9 @@ class CloudController(object): continue now = datetime.datetime.utcnow() - self.db.instance_update(context, - instance_ref['id'], - {'terminated_at': now}) + db.instance_update(context, + instance_ref['id'], + {'terminated_at': now}) # FIXME(ja): where should network deallocate occur? address = db.instance_get_floating_address(context, instance_ref['id']) @@ -649,9 +649,7 @@ class CloudController(object): # TODO: return error if not authorized volume_ref = db.volume_get_by_str(context, volume_id) now = datetime.datetime.utcnow() - self.db.volume_update(context, - volume_ref['id'], - {'terminated_at': now}) + db.volume_update(context, volume_ref['id'], {'terminated_at': now}) host = volume_ref['host'] rpc.cast(db.queue_get_for(context, FLAGS.volume_topic, host), {"method": "delete_volume", From 926fe27703543e204eba8af6a15861d295270821 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 18:45:15 -0700 Subject: [PATCH 26/34] use gigabytes and cores --- nova/scheduler/simple.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 48be4c1a..6e77debf 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -27,10 +27,10 @@ from nova.scheduler import driver from nova.scheduler import chance FLAGS = flags.FLAGS -flags.DEFINE_integer("max_instances", 16, - "maximum number of instances to allow per host") -flags.DEFINE_integer("max_volumes", 100, - "maximum number of volumes to allow per host") +flags.DEFINE_integer("max_cores", 16, + "maximum number of instance cores to allow per host") +flags.DEFINE_integer("max_gigabytes", 10000, + "maximum number of volume gigabytes to allow per host") flags.DEFINE_integer("max_networks", 1000, "maximum number of networks to allow per host") @@ -42,9 +42,9 @@ class SimpleScheduler(chance.ChanceScheduler): results = db.service_get_all_compute_sorted(context) for result in results: - (service, instance_count) = result - if instance_count >= FLAGS.max_instances: - raise driver.NoValidHost("All hosts have too many instances") + (service, instance_cores) = result + if instance_cores >= FLAGS.max_cores: + raise driver.NoValidHost("All hosts have too many cores") if self.service_is_up(service): db.instance_update(context, instance_id, @@ -57,13 +57,13 @@ class SimpleScheduler(chance.ChanceScheduler): results = db.service_get_all_volume_sorted(context) for result in results: - (service, instance_count) = result - if instance_count >= FLAGS.max_volumes: - raise driver.NoValidHost("All hosts have too many volumes") + (service, volume_gigabytes) = result + if volume_gigabytes >= FLAGS.max_gigabytes: + raise driver.NoValidHost("All hosts have too many gigabytes") if self.service_is_up(service): - db.instance_update(context, - volume_id, - {'host': service['host']}) + db.volume_update(context, + volume_id, + {'host': service['host']}) return service['host'] raise driver.NoValidHost("No hosts found") From 9cbed574e8a1368e896c6f9becc154c0d790dddc Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 18:57:15 -0700 Subject: [PATCH 27/34] update query and test --- nova/scheduler/simple.py | 1 + nova/tests/scheduler_unittest.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 6e77debf..3feeca84 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -43,6 +43,7 @@ class SimpleScheduler(chance.ChanceScheduler): results = db.service_get_all_compute_sorted(context) for result in results: (service, instance_cores) = result + print service, instance_cores if instance_cores >= FLAGS.max_cores: raise driver.NoValidHost("All hosts have too many cores") if self.service_is_up(service): diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index 27e100fa..b9371e86 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -33,7 +33,7 @@ from nova.scheduler import driver FLAGS = flags.FLAGS -flags.DECLARE('max_instances', 'nova.scheduler.simple') +flags.DECLARE('max_cores', 'nova.scheduler.simple') class TestDriver(driver.Scheduler): """Scheduler Driver for Tests""" @@ -75,7 +75,7 @@ class SimpleDriverTestCase(test.TrialTestCase): def setUp(self): # pylint: disable-msg=C0103 super(SimpleDriverTestCase, self).setUp() self.flags(connection_type='fake', - max_instances=4, + max_cores=4, scheduler_driver='nova.scheduler.simple.SimpleScheduler') self.scheduler = manager.SchedulerManager() self.context = None @@ -109,6 +109,7 @@ class SimpleDriverTestCase(test.TrialTestCase): inst['instance_type'] = 'm1.tiny' inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 + inst['vcpus'] = 1 return db.instance_create(self.context, inst)['id'] def test_hosts_are_up(self): @@ -125,10 +126,10 @@ class SimpleDriverTestCase(test.TrialTestCase): self.assertEqual(host, 'host2') self.service1.terminate_instance(self.context, instance_id) - def test_too_many_instances(self): + def test_too_many_cores(self): instance_ids1 = [] instance_ids2 = [] - for index in xrange(FLAGS.max_instances): + for index in xrange(FLAGS.max_cores): instance_id = self._create_instance() self.service1.run_instance(self.context, instance_id) instance_ids1.append(instance_id) @@ -139,7 +140,6 @@ class SimpleDriverTestCase(test.TrialTestCase): self.assertRaises(driver.NoValidHost, self.scheduler.driver.schedule_run_instance, self.context, - 'compute', instance_id) for instance_id in instance_ids1: self.service1.terminate_instance(self.context, instance_id) From 8950c9b656a26183719847bd3ce78da2f0c5cc92 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 19:40:38 -0700 Subject: [PATCH 28/34] tests for volumes work --- nova/scheduler/simple.py | 9 +-- nova/tests/scheduler_unittest.py | 126 +++++++++++++++++++++++++------ 2 files changed, 109 insertions(+), 26 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 3feeca84..c4ba17ca 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -39,12 +39,11 @@ class SimpleScheduler(chance.ChanceScheduler): def schedule_run_instance(self, context, instance_id, *_args, **_kwargs): """Picks a host that is up and has the fewest running instances.""" - + instance_ref = db.instance_get(context, instance_id) results = db.service_get_all_compute_sorted(context) for result in results: (service, instance_cores) = result - print service, instance_cores - if instance_cores >= FLAGS.max_cores: + if instance_cores + instance_ref['vcpus'] > FLAGS.max_cores: raise driver.NoValidHost("All hosts have too many cores") if self.service_is_up(service): db.instance_update(context, @@ -55,11 +54,11 @@ class SimpleScheduler(chance.ChanceScheduler): def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" - + volume_ref = db.volume_get(context, volume_id) results = db.service_get_all_volume_sorted(context) for result in results: (service, volume_gigabytes) = result - if volume_gigabytes >= FLAGS.max_gigabytes: + if volume_gigabytes + volume_ref['size'] > FLAGS.max_gigabytes: raise driver.NoValidHost("All hosts have too many gigabytes") if self.service_is_up(service): db.volume_update(context, diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index b9371e86..fde30f81 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -19,8 +19,6 @@ Tests For Scheduler """ -import mox - from nova import db from nova import flags from nova import service @@ -76,6 +74,8 @@ class SimpleDriverTestCase(test.TrialTestCase): super(SimpleDriverTestCase, self).setUp() self.flags(connection_type='fake', max_cores=4, + max_gigabytes=4, + volume_driver='nova.volume.driver.FakeAOEDriver', scheduler_driver='nova.scheduler.simple.SimpleScheduler') self.scheduler = manager.SchedulerManager() self.context = None @@ -83,27 +83,16 @@ class SimpleDriverTestCase(test.TrialTestCase): self.user = self.manager.create_user('fake', 'fake', 'fake') self.project = self.manager.create_project('fake', 'fake', 'fake') self.context = None - self.service1 = service.Service('host1', - 'nova-compute', - 'compute', - FLAGS.compute_manager) - self.service2 = service.Service('host2', - 'nova-compute', - 'compute', - FLAGS.compute_manager) def tearDown(self): # pylint: disable-msg=C0103 self.manager.delete_user(self.user) self.manager.delete_project(self.project) - self.service1.kill() - self.service2.kill() def _create_instance(self): """Create a test instance""" inst = {} inst['image_id'] = 'ami-test' inst['reservation_id'] = 'r-fakeres' - inst['launch_time'] = '10' inst['user_id'] = self.user.id inst['project_id'] = self.project.id inst['instance_type'] = 'm1.tiny' @@ -112,29 +101,70 @@ class SimpleDriverTestCase(test.TrialTestCase): inst['vcpus'] = 1 return db.instance_create(self.context, inst)['id'] + def _create_volume(self): + """Create a test volume""" + vol = {} + vol['image_id'] = 'ami-test' + vol['reservation_id'] = 'r-fakeres' + vol['size'] = 1 + return db.volume_create(self.context, vol)['id'] + def test_hosts_are_up(self): + """Ensures driver can find the hosts that are up""" # NOTE(vish): constructing service without create method # because we are going to use it without queue + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) hosts = self.scheduler.driver.hosts_up(self.context, 'compute') self.assertEqual(len(hosts), 2) + compute1.kill() + compute2.kill() def test_least_busy_host_gets_instance(self): - instance_id = self._create_instance() - self.service1.run_instance(self.context, instance_id) + """Ensures the host with less cores gets the next one""" + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + instance_id1 = self._create_instance() + compute1.run_instance(self.context, instance_id1) + instance_id2 = self._create_instance() host = self.scheduler.driver.schedule_run_instance(self.context, - instance_id) + instance_id2) self.assertEqual(host, 'host2') - self.service1.terminate_instance(self.context, instance_id) + compute1.terminate_instance(self.context, instance_id1) + db.instance_destroy(self.context, instance_id2) + compute1.kill() + compute2.kill() def test_too_many_cores(self): + """Ensures we don't go over max cores""" + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) instance_ids1 = [] instance_ids2 = [] for index in xrange(FLAGS.max_cores): instance_id = self._create_instance() - self.service1.run_instance(self.context, instance_id) + compute1.run_instance(self.context, instance_id) instance_ids1.append(instance_id) instance_id = self._create_instance() - self.service2.run_instance(self.context, instance_id) + compute2.run_instance(self.context, instance_id) instance_ids2.append(instance_id) instance_id = self._create_instance() self.assertRaises(driver.NoValidHost, @@ -142,6 +172,60 @@ class SimpleDriverTestCase(test.TrialTestCase): self.context, instance_id) for instance_id in instance_ids1: - self.service1.terminate_instance(self.context, instance_id) + compute1.terminate_instance(self.context, instance_id) for instance_id in instance_ids2: - self.service2.terminate_instance(self.context, instance_id) + compute2.terminate_instance(self.context, instance_id) + compute1.kill() + compute2.kill() + + def test_least_busy_host_gets_volume(self): + """Ensures the host with less gigabytes gets the next one""" + volume1 = service.Service('host1', + 'nova-volume', + 'volume', + FLAGS.volume_manager) + volume2 = service.Service('host2', + 'nova-volume', + 'volume', + FLAGS.volume_manager) + volume_id1 = self._create_volume() + volume1.create_volume(self.context, volume_id1) + volume_id2 = self._create_volume() + host = self.scheduler.driver.schedule_create_volume(self.context, + volume_id2) + self.assertEqual(host, 'host2') + volume1.delete_volume(self.context, volume_id1) + db.volume_destroy(self.context, volume_id2) + volume1.kill() + volume2.kill() + + def test_too_many_gigabytes(self): + """Ensures we don't go over max gigabytes""" + volume1 = service.Service('host1', + 'nova-volume', + 'volume', + FLAGS.volume_manager) + volume2 = service.Service('host2', + 'nova-volume', + 'volume', + FLAGS.volume_manager) + volume_ids1 = [] + volume_ids2 = [] + for index in xrange(FLAGS.max_gigabytes): + volume_id = self._create_volume() + volume1.create_volume(self.context, volume_id) + volume_ids1.append(volume_id) + volume_id = self._create_volume() + volume2.create_volume(self.context, volume_id) + volume_ids2.append(volume_id) + volume_id = self._create_volume() + self.assertRaises(driver.NoValidHost, + self.scheduler.driver.schedule_create_volume, + self.context, + volume_id) + for volume_id in volume_ids1: + volume1.delete_volume(self.context, volume_id) + for volume_id in volume_ids2: + volume2.delete_volume(self.context, volume_id) + volume1.kill() + volume2.kill() From 5aaa5ddda91457f43edeec5e7816631353914dac Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 19:43:02 -0700 Subject: [PATCH 29/34] move volume to the scheduler --- nova/endpoint/cloud.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 4fda484e..584c9c64 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -300,9 +300,11 @@ class CloudController(object): vol['attach_status'] = "detached" volume_ref = db.volume_create(context, vol) - rpc.cast(FLAGS.volume_topic, {"method": "create_volume", - "args": {"context": None, - "volume_id": volume_ref['id']}}) + rpc.cast(FLAGS.scheduler_topic, + {"method": "create_volume", + "args": {"context": None, + "topic": FLAGS.volume_topic, + "volume_id": volume_ref['id']}}) return {'volumeSet': [self._format_volume(context, volume_ref)]} From 318d6e7f6bb494b893328ab229b5015255c26d29 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 20:00:02 -0700 Subject: [PATCH 30/34] fix instance time --- nova/tests/compute_unittest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index de2bf3d3..c983e05c 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -83,21 +83,21 @@ class ComputeTestCase(test.TrialTestCase): @defer.inlineCallbacks def test_run_terminate_timestamps(self): - """Make sure it is possible to run and terminate instance""" + """Make sure timestamps are set for launched and destroyed""" instance_id = self._create_instance() instance_ref = db.instance_get(self.context, instance_id) self.assertEqual(instance_ref['launched_at'], None) - self.assertEqual(instance_ref['terminated_at'], None) + self.assertEqual(instance_ref['deleted_at'], None) launch = datetime.datetime.utcnow() yield self.compute.run_instance(self.context, instance_id) instance_ref = db.instance_get(self.context, instance_id) self.assert_(instance_ref['launched_at'] > launch) - self.assertEqual(instance_ref['terminated_at'], None) + self.assertEqual(instance_ref['deleted_at'], None) terminate = datetime.datetime.utcnow() yield self.compute.terminate_instance(self.context, instance_id) instance_ref = db.instance_get({'deleted': True}, instance_id) self.assert_(instance_ref['launched_at'] < terminate) - self.assert_(instance_ref['terminated_at'] > terminate) + self.assert_(instance_ref['deleted_at'] > terminate) @defer.inlineCallbacks def test_reboot(self): From 65c5e450459ade71a010e00cc9cf15c3a3ae21ae Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 11 Sep 2010 22:48:59 -0700 Subject: [PATCH 31/34] don't allow deletion or attachment of volume unless it is available --- nova/endpoint/cloud.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 94a04fb1..6e2fedd6 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -314,6 +314,8 @@ class CloudController(object): def attach_volume(self, context, volume_id, instance_id, device, **kwargs): volume_ref = db.volume_get_by_str(context, volume_id) # TODO(vish): abstract status checking? + if volume_ref['status'] != "available": + raise exception.ApiError("Volume status must be available") if volume_ref['attach_status'] == "attached": raise exception.ApiError("Volume is already attached") instance_ref = db.instance_get_by_str(context, instance_id) @@ -336,10 +338,10 @@ class CloudController(object): volume_ref = db.volume_get_by_str(context, volume_id) instance_ref = db.volume_get_instance(context, volume_ref['id']) if not instance_ref: - raise exception.Error("Volume isn't attached to anything!") + raise exception.ApiError("Volume isn't attached to anything!") # TODO(vish): abstract status checking? if volume_ref['status'] == "available": - raise exception.Error("Volume is already detached") + raise exception.ApiError("Volume is already detached") try: host = instance_ref['host'] rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), @@ -691,6 +693,8 @@ class CloudController(object): def delete_volume(self, context, volume_id, **kwargs): # TODO: return error if not authorized volume_ref = db.volume_get_by_str(context, volume_id) + if volume_ref['status'] != "available": + raise exception.ApiError("Volume status must be available") now = datetime.datetime.utcnow() db.volume_update(context, volume_ref['id'], {'terminated_at': now}) host = volume_ref['host'] From 68c0aaae457e5f5f69feaba7f77f6c1c8dce52e3 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 15 Sep 2010 17:17:20 -0400 Subject: [PATCH 32/34] Support querying version list --- run_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/run_tests.py b/run_tests.py index 77aa9088..cf37b820 100644 --- a/run_tests.py +++ b/run_tests.py @@ -50,8 +50,10 @@ from nova import flags from nova import twistd from nova.tests.access_unittest import * -from nova.tests.auth_unittest import * from nova.tests.api_unittest import * +from nova.tests.api import * +from nova.tests.api.rackspace import * +from nova.tests.auth_unittest import * from nova.tests.cloud_unittest import * from nova.tests.compute_unittest import * from nova.tests.flags_unittest import * From 6a20679377993db7b41be91ca86cf4a505ad5437 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 20 Sep 2010 17:02:32 -0400 Subject: [PATCH 34/34] Undo run_tests.py modification in the hopes of making this merge --- run_tests.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/run_tests.py b/run_tests.py index cf37b820..77aa9088 100644 --- a/run_tests.py +++ b/run_tests.py @@ -50,10 +50,8 @@ from nova import flags from nova import twistd from nova.tests.access_unittest import * -from nova.tests.api_unittest import * -from nova.tests.api import * -from nova.tests.api.rackspace import * from nova.tests.auth_unittest import * +from nova.tests.api_unittest import * from nova.tests.cloud_unittest import * from nova.tests.compute_unittest import * from nova.tests.flags_unittest import *