~rackspace-titan/nova/openstack-api-markers

« back to all changes in this revision

Viewing changes to nova/compute/manager.py

  • Committer: Naveed Massjouni
  • Date: 2011-03-24 16:04:58 UTC
  • mfrom: (784.1.80 nova)
  • Revision ID: naveedm9@gmail.com-20110324160458-09aw4v7reem37sxa
MergeĀ fromĀ trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 
3
3
# Copyright 2010 United States Government as represented by the
4
4
# Administrator of the National Aeronautics and Space Administration.
 
5
# Copyright 2011 Justin Santa Barbara
5
6
# All Rights Reserved.
6
7
#
7
8
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
39
40
import random
40
41
import string
41
42
import socket
 
43
import sys
42
44
import tempfile
43
 
import time
44
45
import functools
45
46
 
 
47
from eventlet import greenthread
 
48
 
46
49
from nova import exception
47
50
from nova import flags
48
51
from nova import log as logging
50
53
from nova import rpc
51
54
from nova import utils
52
55
from nova.compute import power_state
 
56
from nova.virt import driver
53
57
 
54
58
FLAGS = flags.FLAGS
55
59
flags.DEFINE_string('instances_path', '$state_path/instances',
64
68
                    'Console proxy host to use to connect to instances on'
65
69
                    'this host.')
66
70
flags.DEFINE_integer('live_migration_retry_count', 30,
67
 
                    ("Retry count needed in live_migration."
68
 
                     " sleep 1 sec for each count"))
 
71
                     "Retry count needed in live_migration."
 
72
                     " sleep 1 sec for each count")
 
73
flags.DEFINE_integer("rescue_timeout", 0,
 
74
                     "Automatically unrescue an instance after N seconds."
 
75
                     " Set to 0 to disable.")
69
76
 
70
77
LOG = logging.getLogger('nova.compute.manager')
71
78
 
114
121
        #             and redocument the module docstring
115
122
        if not compute_driver:
116
123
            compute_driver = FLAGS.compute_driver
117
 
        self.driver = utils.import_object(compute_driver)
 
124
 
 
125
        try:
 
126
            self.driver = utils.check_isinstance(
 
127
                                        utils.import_object(compute_driver),
 
128
                                        driver.ComputeDriver)
 
129
        except ImportError as e:
 
130
            LOG.error(_("Unable to load the virtualization driver: %s") % (e))
 
131
            sys.exit(1)
 
132
 
118
133
        self.network_manager = utils.import_object(FLAGS.network_manager)
119
134
        self.volume_manager = utils.import_object(FLAGS.volume_manager)
120
135
        super(ComputeManager, self).__init__(*args, **kwargs)
125
140
        """
126
141
        self.driver.init_host(host=self.host)
127
142
 
 
143
    def periodic_tasks(self, context=None):
 
144
        """Tasks to be run at a periodic interval."""
 
145
        super(ComputeManager, self).periodic_tasks(context)
 
146
        if FLAGS.rescue_timeout > 0:
 
147
            self.driver.poll_rescued_instances(FLAGS.rescue_timeout)
 
148
 
128
149
    def _update_state(self, context, instance_id):
129
150
        """Update the state of an instance from the driver info."""
130
151
        # FIXME(ja): include other fields from state?
220
241
            self.db.instance_update(context,
221
242
                                    instance_id,
222
243
                                    {'launched_at': now})
223
 
        except Exception:  # pylint: disable-msg=W0702
224
 
            LOG.exception(_("instance %s: Failed to spawn"), instance_id,
225
 
                          context=context)
 
244
        except Exception:  # pylint: disable=W0702
 
245
            LOG.exception(_("Instance '%s' failed to spawn. Is virtualization"
 
246
                            " enabled in the BIOS?"), instance_id,
 
247
                                                     context=context)
226
248
            self.db.instance_set_state(context,
227
249
                                       instance_id,
228
250
                                       power_state.SHUTDOWN)
429
451
        instance_ref = self.db.instance_get(context, instance_id)
430
452
        migration_ref = self.db.migration_get(context, migration_id)
431
453
 
432
 
        #TODO(mdietz): we may want to split these into separate methods.
433
 
        if migration_ref['source_compute'] == FLAGS.host:
434
 
            self.driver._start(instance_ref)
435
 
            self.db.migration_update(context, migration_id,
436
 
                    {'status': 'reverted'})
437
 
        else:
438
 
            self.driver.destroy(instance_ref)
439
 
            topic = self.db.queue_get_for(context, FLAGS.compute_topic,
440
 
                    instance_ref['host'])
441
 
            rpc.cast(context, topic,
442
 
                    {'method': 'revert_resize',
443
 
                     'args': {
444
 
                           'migration_id': migration_ref['id'],
445
 
                           'instance_id': instance_id, },
446
 
                    })
447
 
 
448
 
    @exception.wrap_exception
449
 
    @checks_instance_lock
450
 
    def prep_resize(self, context, instance_id):
 
454
        self.driver.destroy(instance_ref)
 
455
        topic = self.db.queue_get_for(context, FLAGS.compute_topic,
 
456
                instance_ref['host'])
 
457
        rpc.cast(context, topic,
 
458
                {'method': 'finish_revert_resize',
 
459
                 'args': {
 
460
                       'migration_id': migration_ref['id'],
 
461
                       'instance_id': instance_id, },
 
462
                })
 
463
 
 
464
    @exception.wrap_exception
 
465
    @checks_instance_lock
 
466
    def finish_revert_resize(self, context, instance_id, migration_id):
 
467
        """Finishes the second half of reverting a resize, powering back on
 
468
        the source instance and reverting the resized attributes in the
 
469
        database"""
 
470
        instance_ref = self.db.instance_get(context, instance_id)
 
471
        migration_ref = self.db.migration_get(context, migration_id)
 
472
        instance_type = self.db.instance_type_get_by_flavor_id(context,
 
473
                migration_ref['old_flavor_id'])
 
474
 
 
475
        # Just roll back the record. There's no need to resize down since
 
476
        # the 'old' VM already has the preferred attributes
 
477
        self.db.instance_update(context, instance_id,
 
478
           dict(memory_mb=instance_type['memory_mb'],
 
479
                vcpus=instance_type['vcpus'],
 
480
                local_gb=instance_type['local_gb']))
 
481
 
 
482
        self.driver.revert_resize(instance_ref)
 
483
        self.db.migration_update(context, migration_id,
 
484
                {'status': 'reverted'})
 
485
 
 
486
    @exception.wrap_exception
 
487
    @checks_instance_lock
 
488
    def prep_resize(self, context, instance_id, flavor_id):
451
489
        """Initiates the process of moving a running instance to another
452
490
        host, possibly changing the RAM and disk size in the process"""
453
491
        context = context.elevated()
456
494
            raise exception.Error(_(
457
495
                    'Migration error: destination same as source!'))
458
496
 
 
497
        instance_type = self.db.instance_type_get_by_flavor_id(context,
 
498
                flavor_id)
459
499
        migration_ref = self.db.migration_create(context,
460
500
                {'instance_id': instance_id,
461
501
                 'source_compute': instance_ref['host'],
462
502
                 'dest_compute': FLAGS.host,
463
503
                 'dest_host':   self.driver.get_host_ip_addr(),
 
504
                 'old_flavor_id': instance_type['flavorid'],
 
505
                 'new_flavor_id': flavor_id,
464
506
                 'status':      'pre-migrating'})
 
507
 
465
508
        LOG.audit(_('instance %s: migrating to '), instance_id,
466
509
                context=context)
467
510
        topic = self.db.queue_get_for(context, FLAGS.compute_topic,
487
530
        self.db.migration_update(context, migration_id,
488
531
                {'status': 'post-migrating', })
489
532
 
490
 
        #TODO(mdietz): This is where we would update the VM record
491
 
        #after resizing
492
533
        service = self.db.service_get_by_host_and_topic(context,
493
534
                migration_ref['dest_compute'], FLAGS.compute_topic)
494
535
        topic = self.db.queue_get_for(context, FLAGS.compute_topic,
509
550
        migration_ref = self.db.migration_get(context, migration_id)
510
551
        instance_ref = self.db.instance_get(context,
511
552
                migration_ref['instance_id'])
 
553
        # TODO(mdietz): apply the rest of the instance_type attributes going
 
554
        # after they're supported
 
555
        instance_type = self.db.instance_type_get_by_flavor_id(context,
 
556
                migration_ref['new_flavor_id'])
 
557
        self.db.instance_update(context, instance_id,
 
558
               dict(instance_type=instance_type['name'],
 
559
                    memory_mb=instance_type['memory_mb'],
 
560
                    vcpus=instance_type['vcpus'],
 
561
                    local_gb=instance_type['local_gb']))
512
562
 
 
563
        # reload the updated instance ref
 
564
        # FIXME(mdietz): is there reload functionality?
 
565
        instance_ref = self.db.instance_get(context, instance_id)
513
566
        self.driver.finish_resize(instance_ref, disk_info)
514
567
 
515
568
        self.db.migration_update(context, migration_id,
692
745
                                    volume_id,
693
746
                                    instance_id,
694
747
                                    mountpoint)
695
 
        except Exception as exc:  # pylint: disable-msg=W0702
 
748
        except Exception as exc:  # pylint: disable=W0702
696
749
            # NOTE(vish): The inline callback eats the exception info so we
697
750
            #             log the traceback here and reraise the same
698
751
            #             ecxception below.
792
845
 
793
846
        return self.driver.update_available_resource(context, self.host)
794
847
 
795
 
    def pre_live_migration(self, context, instance_id):
 
848
    def pre_live_migration(self, context, instance_id, time=None):
796
849
        """Preparations for live migration at dest host.
797
850
 
798
851
        :param context: security context
800
853
 
801
854
        """
802
855
 
 
856
        if not time:
 
857
            time = greenthread
 
858
 
803
859
        # Getting instance info
804
860
        instance_ref = self.db.instance_get(context, instance_id)
805
861
        ec2_id = instance_ref['hostname']
968
1024
 
969
1025
        for volume in instance_ref['volumes']:
970
1026
            self.db.volume_update(ctxt, volume['id'], {'status': 'in-use'})
 
1027
 
 
1028
    def periodic_tasks(self, context=None):
 
1029
        """Tasks to be run at a periodic interval."""
 
1030
        error_list = super(ComputeManager, self).periodic_tasks(context)
 
1031
        if error_list is None:
 
1032
            error_list = []
 
1033
 
 
1034
        try:
 
1035
            self._poll_instance_states(context)
 
1036
        except Exception as ex:
 
1037
            LOG.warning(_("Error during instance poll: %s"),
 
1038
                        unicode(ex))
 
1039
            error_list.append(ex)
 
1040
        return error_list
 
1041
 
 
1042
    def _poll_instance_states(self, context):
 
1043
        vm_instances = self.driver.list_instances_detail()
 
1044
        vm_instances = dict((vm.name, vm) for vm in vm_instances)
 
1045
 
 
1046
        # Keep a list of VMs not in the DB, cross them off as we find them
 
1047
        vms_not_found_in_db = list(vm_instances.keys())
 
1048
 
 
1049
        db_instances = self.db.instance_get_all_by_host(context, self.host)
 
1050
 
 
1051
        for db_instance in db_instances:
 
1052
            name = db_instance['name']
 
1053
            vm_instance = vm_instances.get(name)
 
1054
            if vm_instance is None:
 
1055
                LOG.info(_("Found instance '%(name)s' in DB but no VM. "
 
1056
                           "Setting state to shutoff.") % locals())
 
1057
                vm_state = power_state.SHUTOFF
 
1058
            else:
 
1059
                vm_state = vm_instance.state
 
1060
                vms_not_found_in_db.remove(name)
 
1061
 
 
1062
            db_state = db_instance['state']
 
1063
            if vm_state != db_state:
 
1064
                LOG.info(_("DB/VM state mismatch. Changing state from "
 
1065
                           "'%(db_state)s' to '%(vm_state)s'") % locals())
 
1066
                self.db.instance_set_state(context,
 
1067
                                           db_instance['id'],
 
1068
                                           vm_state)
 
1069
 
 
1070
            if vm_state == power_state.SHUTOFF:
 
1071
                # TODO(soren): This is what the compute manager does when you
 
1072
                # terminate an instance. At some point I figure we'll have a
 
1073
                # "terminated" state and some sort of cleanup job that runs
 
1074
                # occasionally, cleaning them out.
 
1075
                self.db.instance_destroy(context, db_instance['id'])
 
1076
 
 
1077
        # Are there VMs not in the DB?
 
1078
        for vm_not_found_in_db in vms_not_found_in_db:
 
1079
            name = vm_not_found_in_db
 
1080
            # TODO(justinsb): What to do here?  Adopt it?  Shut it down?
 
1081
            LOG.warning(_("Found VM not in DB: '%(name)s'.  Ignoring")
 
1082
                        % locals())