~hudson-openstack/nova/trunk

« back to all changes in this revision

Viewing changes to nova/compute/manager.py

  • Committer: Tarmac
  • Author(s): Brian Lamar, Dan Prince
  • Date: 2011-08-31 22:55:34 UTC
  • mfrom: (1443.3.61 instance_states)
  • Revision ID: tarmac-20110831225534-upfhtsvcsafyql6x
Fixed and improved the way instance "states" are set. Instead of relying on solely the power_state of a VM, there are now explicitly defined VM states and VM task states which respectively define the current state of the VM and the task which is currently being performed by the VM.

Show diffs side-by-side

added added

removed removed

Lines of Context:
56
56
from nova import utils
57
57
from nova import volume
58
58
from nova.compute import power_state
 
59
from nova.compute import task_states
 
60
from nova.compute import vm_states
59
61
from nova.notifier import api as notifier
60
62
from nova.compute.utils import terminate_volumes
61
63
from nova.virt import driver
146
148
        super(ComputeManager, self).__init__(service_name="compute",
147
149
                                             *args, **kwargs)
148
150
 
 
151
    def _instance_update(self, context, instance_id, **kwargs):
 
152
        """Update an instance in the database using kwargs as value."""
 
153
        return self.db.instance_update(context, instance_id, kwargs)
 
154
 
149
155
    def init_host(self):
150
156
        """Initialization for a standalone compute service."""
151
157
        self.driver.init_host(host=self.host)
153
159
        instances = self.db.instance_get_all_by_host(context, self.host)
154
160
        for instance in instances:
155
161
            inst_name = instance['name']
156
 
            db_state = instance['state']
157
 
            drv_state = self._update_state(context, instance['id'])
 
162
            db_state = instance['power_state']
 
163
            drv_state = self._get_power_state(context, instance)
158
164
 
159
165
            expect_running = db_state == power_state.RUNNING \
160
166
                             and drv_state != db_state
177
183
                    LOG.warning(_('Hypervisor driver does not '
178
184
                            'support firewall rules'))
179
185
 
180
 
    def _update_state(self, context, instance_id, state=None):
181
 
        """Update the state of an instance from the driver info."""
182
 
        instance_ref = self.db.instance_get(context, instance_id)
183
 
 
184
 
        if state is None:
185
 
            try:
186
 
                LOG.debug(_('Checking state of %s'), instance_ref['name'])
187
 
                info = self.driver.get_info(instance_ref['name'])
188
 
            except exception.NotFound:
189
 
                info = None
190
 
 
191
 
            if info is not None:
192
 
                state = info['state']
193
 
            else:
194
 
                state = power_state.FAILED
195
 
 
196
 
        self.db.instance_set_state(context, instance_id, state)
197
 
        return state
198
 
 
199
 
    def _update_launched_at(self, context, instance_id, launched_at=None):
200
 
        """Update the launched_at parameter of the given instance."""
201
 
        data = {'launched_at': launched_at or utils.utcnow()}
202
 
        self.db.instance_update(context, instance_id, data)
 
186
    def _get_power_state(self, context, instance):
 
187
        """Retrieve the power state for the given instance."""
 
188
        LOG.debug(_('Checking state of %s'), instance['name'])
 
189
        try:
 
190
            return self.driver.get_info(instance['name'])["state"]
 
191
        except exception.NotFound:
 
192
            return power_state.FAILED
203
193
 
204
194
    def get_console_topic(self, context, **kwargs):
205
195
        """Retrieves the console host for a project on this host.
251
241
 
252
242
    def _setup_block_device_mapping(self, context, instance_id):
253
243
        """setup volumes for block device mapping"""
254
 
        self.db.instance_set_state(context,
255
 
                                   instance_id,
256
 
                                   power_state.NOSTATE,
257
 
                                   'block_device_mapping')
258
 
 
259
244
        volume_api = volume.API()
260
245
        block_device_mapping = []
261
246
        swap = None
389
374
        updates = {}
390
375
        updates['host'] = self.host
391
376
        updates['launched_on'] = self.host
392
 
        instance = self.db.instance_update(context,
393
 
                                           instance_id,
394
 
                                           updates)
 
377
        updates['vm_state'] = vm_states.BUILDING
 
378
        updates['task_state'] = task_states.NETWORKING
 
379
        instance = self.db.instance_update(context, instance_id, updates)
395
380
        instance['injected_files'] = kwargs.get('injected_files', [])
396
381
        instance['admin_pass'] = kwargs.get('admin_password', None)
397
382
 
398
 
        self.db.instance_set_state(context,
399
 
                                   instance_id,
400
 
                                   power_state.NOSTATE,
401
 
                                   'networking')
402
 
 
403
383
        is_vpn = instance['image_ref'] == str(FLAGS.vpn_image_id)
404
384
        try:
405
385
            # NOTE(vish): This could be a cast because we don't do anything
418
398
                # all vif creation and network injection, maybe this is correct
419
399
                network_info = []
420
400
 
 
401
            self._instance_update(context,
 
402
                                  instance_id,
 
403
                                  vm_state=vm_states.BUILDING,
 
404
                                  task_state=task_states.BLOCK_DEVICE_MAPPING)
 
405
 
421
406
            (swap, ephemerals,
422
407
             block_device_mapping) = self._setup_block_device_mapping(
423
408
                context, instance_id)
427
412
                'ephemerals': ephemerals,
428
413
                'block_device_mapping': block_device_mapping}
429
414
 
 
415
            self._instance_update(context,
 
416
                                  instance_id,
 
417
                                  vm_state=vm_states.BUILDING,
 
418
                                  task_state=task_states.SPAWNING)
 
419
 
430
420
            # TODO(vish) check to make sure the availability zone matches
431
 
            self._update_state(context, instance_id, power_state.BUILDING)
432
 
 
433
421
            try:
434
422
                self.driver.spawn(context, instance,
435
423
                                  network_info, block_device_info)
438
426
                        "virtualization enabled in the BIOS? Details: "
439
427
                        "%(ex)s") % locals()
440
428
                LOG.exception(msg)
441
 
 
442
 
            self._update_launched_at(context, instance_id)
443
 
            self._update_state(context, instance_id)
 
429
                return
 
430
 
 
431
            current_power_state = self._get_power_state(context, instance)
 
432
            self._instance_update(context,
 
433
                                  instance_id,
 
434
                                  power_state=current_power_state,
 
435
                                  vm_state=vm_states.ACTIVE,
 
436
                                  task_state=None,
 
437
                                  launched_at=utils.utcnow())
 
438
 
444
439
            usage_info = utils.usage_from_instance(instance)
445
440
            notifier.notify('compute.%s' % self.host,
446
441
                            'compute.instance.create',
447
442
                            notifier.INFO, usage_info)
 
443
 
448
444
        except exception.InstanceNotFound:
449
445
            # FIXME(wwolf): We are just ignoring InstanceNotFound
450
446
            # exceptions here in case the instance was immediately
480
476
        for volume in volumes:
481
477
            self._detach_volume(context, instance_id, volume['id'], False)
482
478
 
483
 
        if (instance['state'] == power_state.SHUTOFF and
484
 
            instance['state_description'] != 'stopped'):
 
479
        if instance['power_state'] == power_state.SHUTOFF:
485
480
            self.db.instance_destroy(context, instance_id)
486
481
            raise exception.Error(_('trying to destroy already destroyed'
487
482
                                    ' instance: %s') % instance_id)
496
491
        """Terminate an instance on this host."""
497
492
        self._shutdown_instance(context, instance_id, 'Terminating')
498
493
        instance = self.db.instance_get(context.elevated(), instance_id)
 
494
        self._instance_update(context,
 
495
                              instance_id,
 
496
                              vm_state=vm_states.DELETED,
 
497
                              task_state=None,
 
498
                              terminated_at=utils.utcnow())
499
499
 
500
 
        # TODO(ja): should we keep it in a terminated state for a bit?
501
500
        self.db.instance_destroy(context, instance_id)
 
501
 
502
502
        usage_info = utils.usage_from_instance(instance)
503
503
        notifier.notify('compute.%s' % self.host,
504
504
                        'compute.instance.delete',
509
509
    def stop_instance(self, context, instance_id):
510
510
        """Stopping an instance on this host."""
511
511
        self._shutdown_instance(context, instance_id, 'Stopping')
512
 
        # instance state will be updated to stopped by _poll_instance_states()
 
512
        self._instance_update(context,
 
513
                              instance_id,
 
514
                              vm_state=vm_states.STOPPED,
 
515
                              task_state=None)
513
516
 
514
517
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
515
518
    @checks_instance_lock
529
532
        instance_ref = self.db.instance_get(context, instance_id)
530
533
        LOG.audit(_("Rebuilding instance %s"), instance_id, context=context)
531
534
 
532
 
        self._update_state(context, instance_id, power_state.BUILDING)
 
535
        current_power_state = self._get_power_state(context, instance_ref)
 
536
        self._instance_update(context,
 
537
                              instance_id,
 
538
                              power_state=current_power_state,
 
539
                              vm_state=vm_states.REBUILDING,
 
540
                              task_state=None)
533
541
 
534
542
        network_info = self._get_instance_nw_info(context, instance_ref)
535
 
 
536
543
        self.driver.destroy(instance_ref, network_info)
 
544
 
 
545
        self._instance_update(context,
 
546
                              instance_id,
 
547
                              vm_state=vm_states.REBUILDING,
 
548
                              task_state=task_states.BLOCK_DEVICE_MAPPING)
 
549
 
537
550
        instance_ref.injected_files = kwargs.get('injected_files', [])
538
551
        network_info = self.network_api.get_instance_nw_info(context,
539
552
                                                              instance_ref)
540
553
        bd_mapping = self._setup_block_device_mapping(context, instance_id)
541
554
 
 
555
        self._instance_update(context,
 
556
                              instance_id,
 
557
                              vm_state=vm_states.REBUILDING,
 
558
                              task_state=task_states.SPAWNING)
 
559
 
542
560
        # pull in new password here since the original password isn't in the db
543
561
        instance_ref.admin_pass = kwargs.get('new_pass',
544
562
                utils.generate_password(FLAGS.password_length))
545
563
 
546
564
        self.driver.spawn(context, instance_ref, network_info, bd_mapping)
547
565
 
548
 
        self._update_launched_at(context, instance_id)
549
 
        self._update_state(context, instance_id)
 
566
        current_power_state = self._get_power_state(context, instance_ref)
 
567
        self._instance_update(context,
 
568
                              instance_id,
 
569
                              power_state=current_power_state,
 
570
                              vm_state=vm_states.ACTIVE,
 
571
                              task_state=None,
 
572
                              launched_at=utils.utcnow())
 
573
 
550
574
        usage_info = utils.usage_from_instance(instance_ref)
551
 
 
552
575
        notifier.notify('compute.%s' % self.host,
553
576
                            'compute.instance.rebuild',
554
577
                            notifier.INFO,
558
581
    @checks_instance_lock
559
582
    def reboot_instance(self, context, instance_id):
560
583
        """Reboot an instance on this host."""
561
 
        context = context.elevated()
562
 
        self._update_state(context, instance_id)
563
 
        instance_ref = self.db.instance_get(context, instance_id)
564
584
        LOG.audit(_("Rebooting instance %s"), instance_id, context=context)
565
 
 
566
 
        if instance_ref['state'] != power_state.RUNNING:
567
 
            state = instance_ref['state']
 
585
        context = context.elevated()
 
586
        instance_ref = self.db.instance_get(context, instance_id)
 
587
 
 
588
        current_power_state = self._get_power_state(context, instance_ref)
 
589
        self._instance_update(context,
 
590
                              instance_id,
 
591
                              power_state=current_power_state,
 
592
                              vm_state=vm_states.ACTIVE,
 
593
                              task_state=task_states.REBOOTING)
 
594
 
 
595
        if instance_ref['power_state'] != power_state.RUNNING:
 
596
            state = instance_ref['power_state']
568
597
            running = power_state.RUNNING
569
598
            LOG.warn(_('trying to reboot a non-running '
570
599
                     'instance: %(instance_id)s (state: %(state)s '
571
600
                     'expected: %(running)s)') % locals(),
572
601
                     context=context)
573
602
 
574
 
        self.db.instance_set_state(context,
575
 
                                   instance_id,
576
 
                                   power_state.NOSTATE,
577
 
                                   'rebooting')
578
603
        network_info = self._get_instance_nw_info(context, instance_ref)
579
604
        self.driver.reboot(instance_ref, network_info)
580
 
        self._update_state(context, instance_id)
 
605
 
 
606
        current_power_state = self._get_power_state(context, instance_ref)
 
607
        self._instance_update(context,
 
608
                              instance_id,
 
609
                              power_state=current_power_state,
 
610
                              vm_state=vm_states.ACTIVE,
 
611
                              task_state=None)
581
612
 
582
613
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
583
614
    def snapshot_instance(self, context, instance_id, image_id,
593
624
        :param rotation: int representing how many backups to keep around;
594
625
            None if rotation shouldn't be used (as in the case of snapshots)
595
626
        """
 
627
        if image_type == "snapshot":
 
628
            task_state = task_states.IMAGE_SNAPSHOT
 
629
        elif image_type == "backup":
 
630
            task_state = task_states.IMAGE_BACKUP
 
631
        else:
 
632
            raise Exception(_('Image type not recognized %s') % image_type)
 
633
 
596
634
        context = context.elevated()
597
635
        instance_ref = self.db.instance_get(context, instance_id)
598
636
 
599
 
        #NOTE(sirp): update_state currently only refreshes the state field
600
 
        # if we add is_snapshotting, we will need this refreshed too,
601
 
        # potentially?
602
 
        self._update_state(context, instance_id)
 
637
        current_power_state = self._get_power_state(context, instance_ref)
 
638
        self._instance_update(context,
 
639
                              instance_id,
 
640
                              power_state=current_power_state,
 
641
                              vm_state=vm_states.ACTIVE,
 
642
                              task_state=task_state)
603
643
 
604
644
        LOG.audit(_('instance %s: snapshotting'), instance_id,
605
645
                  context=context)
606
 
        if instance_ref['state'] != power_state.RUNNING:
607
 
            state = instance_ref['state']
 
646
 
 
647
        if instance_ref['power_state'] != power_state.RUNNING:
 
648
            state = instance_ref['power_state']
608
649
            running = power_state.RUNNING
609
650
            LOG.warn(_('trying to snapshot a non-running '
610
651
                       'instance: %(instance_id)s (state: %(state)s '
611
652
                       'expected: %(running)s)') % locals())
612
653
 
613
654
        self.driver.snapshot(context, instance_ref, image_id)
614
 
 
615
 
        if image_type == 'snapshot':
616
 
            if rotation:
617
 
                raise exception.ImageRotationNotAllowed()
 
655
        self._instance_update(context, instance_id, task_state=None)
 
656
 
 
657
        if image_type == 'snapshot' and rotation:
 
658
            raise exception.ImageRotationNotAllowed()
 
659
 
 
660
        elif image_type == 'backup' and rotation:
 
661
            instance_uuid = instance_ref['uuid']
 
662
            self.rotate_backups(context, instance_uuid, backup_type, rotation)
 
663
 
618
664
        elif image_type == 'backup':
619
 
            if rotation:
620
 
                instance_uuid = instance_ref['uuid']
621
 
                self.rotate_backups(context, instance_uuid, backup_type,
622
 
                                    rotation)
623
 
            else:
624
 
                raise exception.RotationRequiredForBackup()
625
 
        else:
626
 
            raise Exception(_('Image type not recognized %s') % image_type)
 
665
            raise exception.RotationRequiredForBackup()
627
666
 
628
667
    def rotate_backups(self, context, instance_uuid, backup_type, rotation):
629
668
        """Delete excess backups associated to an instance.
691
730
        for i in xrange(max_tries):
692
731
            instance_ref = self.db.instance_get(context, instance_id)
693
732
            instance_id = instance_ref["id"]
694
 
            instance_state = instance_ref["state"]
 
733
            instance_state = instance_ref["power_state"]
695
734
            expected_state = power_state.RUNNING
696
735
 
697
736
            if instance_state != expected_state:
726
765
        context = context.elevated()
727
766
        instance_ref = self.db.instance_get(context, instance_id)
728
767
        instance_id = instance_ref['id']
729
 
        instance_state = instance_ref['state']
 
768
        instance_state = instance_ref['power_state']
730
769
        expected_state = power_state.RUNNING
731
770
        if instance_state != expected_state:
732
771
            LOG.warn(_('trying to inject a file into a non-running '
744
783
        context = context.elevated()
745
784
        instance_ref = self.db.instance_get(context, instance_id)
746
785
        instance_id = instance_ref['id']
747
 
        instance_state = instance_ref['state']
 
786
        instance_state = instance_ref['power_state']
748
787
        expected_state = power_state.RUNNING
749
788
        if instance_state != expected_state:
750
789
            LOG.warn(_('trying to update agent on a non-running '
759
798
    @checks_instance_lock
760
799
    def rescue_instance(self, context, instance_id):
761
800
        """Rescue an instance on this host."""
762
 
        context = context.elevated()
763
 
        instance_ref = self.db.instance_get(context, instance_id)
764
801
        LOG.audit(_('instance %s: rescuing'), instance_id, context=context)
765
 
        self.db.instance_set_state(context,
766
 
                                   instance_id,
767
 
                                   power_state.NOSTATE,
768
 
                                   'rescuing')
769
 
        _update_state = lambda result: self._update_state_callback(
770
 
                self, context, instance_id, result)
 
802
        context = context.elevated()
 
803
 
 
804
        instance_ref = self.db.instance_get(context, instance_id)
771
805
        network_info = self._get_instance_nw_info(context, instance_ref)
772
 
        self.driver.rescue(context, instance_ref, _update_state, network_info)
773
 
        self._update_state(context, instance_id)
 
806
 
 
807
        # NOTE(blamar): None of the virt drivers use the 'callback' param
 
808
        self.driver.rescue(context, instance_ref, None, network_info)
 
809
 
 
810
        current_power_state = self._get_power_state(context, instance_ref)
 
811
        self._instance_update(context,
 
812
                              instance_id,
 
813
                              vm_state=vm_states.RESCUED,
 
814
                              task_state=None,
 
815
                              power_state=current_power_state)
774
816
 
775
817
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
776
818
    @checks_instance_lock
777
819
    def unrescue_instance(self, context, instance_id):
778
820
        """Rescue an instance on this host."""
779
 
        context = context.elevated()
780
 
        instance_ref = self.db.instance_get(context, instance_id)
781
821
        LOG.audit(_('instance %s: unrescuing'), instance_id, context=context)
782
 
        self.db.instance_set_state(context,
783
 
                                   instance_id,
784
 
                                   power_state.NOSTATE,
785
 
                                   'unrescuing')
786
 
        _update_state = lambda result: self._update_state_callback(
787
 
                self, context, instance_id, result)
 
822
        context = context.elevated()
 
823
 
 
824
        instance_ref = self.db.instance_get(context, instance_id)
788
825
        network_info = self._get_instance_nw_info(context, instance_ref)
789
 
        self.driver.unrescue(instance_ref, _update_state, network_info)
790
 
        self._update_state(context, instance_id)
791
 
 
792
 
    @staticmethod
793
 
    def _update_state_callback(self, context, instance_id, result):
794
 
        """Update instance state when async task completes."""
795
 
        self._update_state(context, instance_id)
 
826
 
 
827
        # NOTE(blamar): None of the virt drivers use the 'callback' param
 
828
        self.driver.unrescue(instance_ref, None, network_info)
 
829
 
 
830
        current_power_state = self._get_power_state(context, instance_ref)
 
831
        self._instance_update(context,
 
832
                              instance_id,
 
833
                              vm_state=vm_states.ACTIVE,
 
834
                              task_state=None,
 
835
                              power_state=current_power_state)
796
836
 
797
837
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
798
838
    @checks_instance_lock
851
891
 
852
892
        # Just roll back the record. There's no need to resize down since
853
893
        # the 'old' VM already has the preferred attributes
854
 
        self.db.instance_update(context, instance_ref['uuid'],
855
 
           dict(memory_mb=instance_type['memory_mb'],
856
 
                vcpus=instance_type['vcpus'],
857
 
                local_gb=instance_type['local_gb'],
858
 
                instance_type_id=instance_type['id']))
 
894
        self._instance_update(context,
 
895
                              instance_ref["uuid"],
 
896
                              memory_mb=instance_type['memory_mb'],
 
897
                              vcpus=instance_type['vcpus'],
 
898
                              local_gb=instance_type['local_gb'],
 
899
                              instance_type_id=instance_type['id'])
859
900
 
860
901
        self.driver.revert_migration(instance_ref)
861
902
        self.db.migration_update(context, migration_id,
882
923
        instance_ref = self.db.instance_get_by_uuid(context, instance_id)
883
924
 
884
925
        if instance_ref['host'] == FLAGS.host:
885
 
            raise exception.Error(_(
886
 
                    'Migration error: destination same as source!'))
 
926
            self._instance_update(context,
 
927
                                  instance_id,
 
928
                                  vm_state=vm_states.ERROR)
 
929
            msg = _('Migration error: destination same as source!')
 
930
            raise exception.Error(msg)
887
931
 
888
932
        old_instance_type = self.db.instance_type_get(context,
889
933
                instance_ref['instance_type_id'])
977
1021
        self.driver.finish_migration(context, instance_ref, disk_info,
978
1022
                                     network_info, resize_instance)
979
1023
 
 
1024
        self._instance_update(context,
 
1025
                              instance_id,
 
1026
                              vm_state=vm_states.ACTIVE,
 
1027
                              task_state=task_states.RESIZE_VERIFY)
 
1028
 
980
1029
        self.db.migration_update(context, migration_id,
981
1030
                {'status': 'finished', })
982
1031
 
1008
1057
    @checks_instance_lock
1009
1058
    def pause_instance(self, context, instance_id):
1010
1059
        """Pause an instance on this host."""
1011
 
        context = context.elevated()
1012
 
        instance_ref = self.db.instance_get(context, instance_id)
1013
1060
        LOG.audit(_('instance %s: pausing'), instance_id, context=context)
1014
 
        self.db.instance_set_state(context,
1015
 
                                   instance_id,
1016
 
                                   power_state.NOSTATE,
1017
 
                                   'pausing')
1018
 
        self.driver.pause(instance_ref,
1019
 
            lambda result: self._update_state_callback(self,
1020
 
                                                       context,
1021
 
                                                       instance_id,
1022
 
                                                       result))
 
1061
        context = context.elevated()
 
1062
 
 
1063
        instance_ref = self.db.instance_get(context, instance_id)
 
1064
        self.driver.pause(instance_ref, lambda result: None)
 
1065
 
 
1066
        current_power_state = self._get_power_state(context, instance_ref)
 
1067
        self._instance_update(context,
 
1068
                              instance_id,
 
1069
                              power_state=current_power_state,
 
1070
                              vm_state=vm_states.PAUSED,
 
1071
                              task_state=None)
1023
1072
 
1024
1073
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1025
1074
    @checks_instance_lock
1026
1075
    def unpause_instance(self, context, instance_id):
1027
1076
        """Unpause a paused instance on this host."""
1028
 
        context = context.elevated()
1029
 
        instance_ref = self.db.instance_get(context, instance_id)
1030
1077
        LOG.audit(_('instance %s: unpausing'), instance_id, context=context)
1031
 
        self.db.instance_set_state(context,
1032
 
                                   instance_id,
1033
 
                                   power_state.NOSTATE,
1034
 
                                   'unpausing')
1035
 
        self.driver.unpause(instance_ref,
1036
 
            lambda result: self._update_state_callback(self,
1037
 
                                                       context,
1038
 
                                                       instance_id,
1039
 
                                                       result))
 
1078
        context = context.elevated()
 
1079
 
 
1080
        instance_ref = self.db.instance_get(context, instance_id)
 
1081
        self.driver.unpause(instance_ref, lambda result: None)
 
1082
 
 
1083
        current_power_state = self._get_power_state(context, instance_ref)
 
1084
        self._instance_update(context,
 
1085
                              instance_id,
 
1086
                              power_state=current_power_state,
 
1087
                              vm_state=vm_states.ACTIVE,
 
1088
                              task_state=None)
1040
1089
 
1041
1090
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1042
1091
    def host_power_action(self, context, host=None, action=None):
1052
1101
    def get_diagnostics(self, context, instance_id):
1053
1102
        """Retrieve diagnostics for an instance on this host."""
1054
1103
        instance_ref = self.db.instance_get(context, instance_id)
1055
 
        if instance_ref["state"] == power_state.RUNNING:
 
1104
        if instance_ref["power_state"] == power_state.RUNNING:
1056
1105
            LOG.audit(_("instance %s: retrieving diagnostics"), instance_id,
1057
1106
                      context=context)
1058
1107
            return self.driver.get_diagnostics(instance_ref)
1061
1110
    @checks_instance_lock
1062
1111
    def suspend_instance(self, context, instance_id):
1063
1112
        """Suspend the given instance."""
1064
 
        context = context.elevated()
1065
 
        instance_ref = self.db.instance_get(context, instance_id)
1066
1113
        LOG.audit(_('instance %s: suspending'), instance_id, context=context)
1067
 
        self.db.instance_set_state(context, instance_id,
1068
 
                                            power_state.NOSTATE,
1069
 
                                            'suspending')
1070
 
        self.driver.suspend(instance_ref,
1071
 
            lambda result: self._update_state_callback(self,
1072
 
                                                       context,
1073
 
                                                       instance_id,
1074
 
                                                       result))
 
1114
        context = context.elevated()
 
1115
 
 
1116
        instance_ref = self.db.instance_get(context, instance_id)
 
1117
        self.driver.suspend(instance_ref, lambda result: None)
 
1118
 
 
1119
        current_power_state = self._get_power_state(context, instance_ref)
 
1120
        self._instance_update(context,
 
1121
                              instance_id,
 
1122
                              power_state=current_power_state,
 
1123
                              vm_state=vm_states.SUSPENDED,
 
1124
                              task_state=None)
1075
1125
 
1076
1126
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1077
1127
    @checks_instance_lock
1078
1128
    def resume_instance(self, context, instance_id):
1079
1129
        """Resume the given suspended instance."""
1080
 
        context = context.elevated()
1081
 
        instance_ref = self.db.instance_get(context, instance_id)
1082
1130
        LOG.audit(_('instance %s: resuming'), instance_id, context=context)
1083
 
        self.db.instance_set_state(context, instance_id,
1084
 
                                            power_state.NOSTATE,
1085
 
                                            'resuming')
1086
 
        self.driver.resume(instance_ref,
1087
 
            lambda result: self._update_state_callback(self,
1088
 
                                                       context,
1089
 
                                                       instance_id,
1090
 
                                                       result))
 
1131
        context = context.elevated()
 
1132
 
 
1133
        instance_ref = self.db.instance_get(context, instance_id)
 
1134
        self.driver.resume(instance_ref, lambda result: None)
 
1135
 
 
1136
        current_power_state = self._get_power_state(context, instance_ref)
 
1137
        self._instance_update(context,
 
1138
                              instance_id,
 
1139
                              power_state=current_power_state,
 
1140
                              vm_state=vm_states.ACTIVE,
 
1141
                              task_state=None)
1091
1142
 
1092
1143
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1093
1144
    def lock_instance(self, context, instance_id):
1498
1549
                               'block_migration': block_migration}})
1499
1550
 
1500
1551
        # Restore instance state
1501
 
        self.db.instance_update(ctxt,
1502
 
                                instance_ref['id'],
1503
 
                                {'state_description': 'running',
1504
 
                                 'state': power_state.RUNNING,
1505
 
                                 'host': dest})
 
1552
        current_power_state = self._get_power_state(ctxt, instance_ref)
 
1553
        self._instance_update(ctxt,
 
1554
                              instance_ref["id"],
 
1555
                              host=dest,
 
1556
                              power_state=current_power_state,
 
1557
                              vm_state=vm_states.ACTIVE,
 
1558
                              task_state=None)
 
1559
 
1506
1560
        # Restore volume state
1507
1561
        for volume_ref in instance_ref['volumes']:
1508
1562
            volume_id = volume_ref['id']
1548
1602
            This param specifies destination host.
1549
1603
        """
1550
1604
        host = instance_ref['host']
1551
 
        self.db.instance_update(context,
1552
 
                                instance_ref['id'],
1553
 
                                {'state_description': 'running',
1554
 
                                 'state': power_state.RUNNING,
1555
 
                                 'host': host})
 
1605
        self._instance_update(context,
 
1606
                              instance_ref['id'],
 
1607
                              host=host,
 
1608
                              vm_state=vm_states.ACTIVE,
 
1609
                              task_state=None)
1556
1610
 
1557
1611
        for volume_ref in instance_ref['volumes']:
1558
1612
            volume_id = volume_ref['id']
1600
1654
            error_list.append(ex)
1601
1655
 
1602
1656
        try:
1603
 
            self._poll_instance_states(context)
 
1657
            self._sync_power_states(context)
1604
1658
        except Exception as ex:
1605
 
            LOG.warning(_("Error during instance poll: %s"),
1606
 
                        unicode(ex))
 
1659
            LOG.warning(_("Error during power_state sync: %s"), unicode(ex))
1607
1660
            error_list.append(ex)
1608
1661
 
1609
1662
        return error_list
1618
1671
            self.update_service_capabilities(
1619
1672
                self.driver.get_host_stats(refresh=True))
1620
1673
 
1621
 
    def _poll_instance_states(self, context):
 
1674
    def _sync_power_states(self, context):
 
1675
        """Align power states between the database and the hypervisor.
 
1676
 
 
1677
        The hypervisor is authoritative for the power_state data, so we
 
1678
        simply loop over all known instances for this host and update the
 
1679
        power_state according to the hypervisor. If the instance is not found
 
1680
        then it will be set to power_state.NOSTATE, because it doesn't exist
 
1681
        on the hypervisor.
 
1682
 
 
1683
        """
1622
1684
        vm_instances = self.driver.list_instances_detail()
1623
1685
        vm_instances = dict((vm.name, vm) for vm in vm_instances)
1624
 
 
1625
 
        # Keep a list of VMs not in the DB, cross them off as we find them
1626
 
        vms_not_found_in_db = list(vm_instances.keys())
1627
 
 
1628
1686
        db_instances = self.db.instance_get_all_by_host(context, self.host)
1629
1687
 
 
1688
        num_vm_instances = len(vm_instances)
 
1689
        num_db_instances = len(db_instances)
 
1690
 
 
1691
        if num_vm_instances != num_db_instances:
 
1692
            LOG.info(_("Found %(num_db_instances)s in the database and "
 
1693
                       "%(num_vm_instances)s on the hypervisor.") % locals())
 
1694
 
1630
1695
        for db_instance in db_instances:
1631
 
            name = db_instance['name']
1632
 
            db_state = db_instance['state']
 
1696
            name = db_instance["name"]
 
1697
            db_power_state = db_instance['power_state']
1633
1698
            vm_instance = vm_instances.get(name)
1634
1699
 
1635
1700
            if vm_instance is None:
1636
 
                # NOTE(justinsb): We have to be very careful here, because a
1637
 
                # concurrent operation could be in progress (e.g. a spawn)
1638
 
                if db_state == power_state.BUILDING:
1639
 
                    # TODO(justinsb): This does mean that if we crash during a
1640
 
                    # spawn, the machine will never leave the spawning state,
1641
 
                    # but this is just the way nova is; this function isn't
1642
 
                    # trying to correct that problem.
1643
 
                    # We could have a separate task to correct this error.
1644
 
                    # TODO(justinsb): What happens during a live migration?
1645
 
                    LOG.info(_("Found instance '%(name)s' in DB but no VM. "
1646
 
                               "State=%(db_state)s, so assuming spawn is in "
1647
 
                               "progress.") % locals())
1648
 
                    vm_state = db_state
1649
 
                else:
1650
 
                    LOG.info(_("Found instance '%(name)s' in DB but no VM. "
1651
 
                               "State=%(db_state)s, so setting state to "
1652
 
                               "shutoff.") % locals())
1653
 
                    vm_state = power_state.SHUTOFF
1654
 
                    if db_instance['state_description'] == 'stopping':
1655
 
                        self.db.instance_stop(context, db_instance['id'])
1656
 
                        continue
 
1701
                vm_power_state = power_state.NOSTATE
1657
1702
            else:
1658
 
                vm_state = vm_instance.state
1659
 
                vms_not_found_in_db.remove(name)
 
1703
                vm_power_state = vm_instance.state
1660
1704
 
1661
 
            if (db_instance['state_description'] in ['migrating', 'stopping']):
1662
 
                # A situation which db record exists, but no instance"
1663
 
                # sometimes occurs while live-migration at src compute,
1664
 
                # this case should be ignored.
1665
 
                LOG.debug(_("Ignoring %(name)s, as it's currently being "
1666
 
                           "migrated.") % locals())
 
1705
            if vm_power_state == db_power_state:
1667
1706
                continue
1668
1707
 
1669
 
            if vm_state != db_state:
1670
 
                LOG.info(_("DB/VM state mismatch. Changing state from "
1671
 
                           "'%(db_state)s' to '%(vm_state)s'") % locals())
1672
 
                self._update_state(context, db_instance['id'], vm_state)
1673
 
 
1674
 
            # NOTE(justinsb): We no longer auto-remove SHUTOFF instances
1675
 
            # It's quite hard to get them back when we do.
1676
 
 
1677
 
        # Are there VMs not in the DB?
1678
 
        for vm_not_found_in_db in vms_not_found_in_db:
1679
 
            name = vm_not_found_in_db
1680
 
 
1681
 
            # We only care about instances that compute *should* know about
1682
 
            if name.startswith("instance-"):
1683
 
                # TODO(justinsb): What to do here?  Adopt it?  Shut it down?
1684
 
                LOG.warning(_("Found VM not in DB: '%(name)s'.  Ignoring")
1685
 
                            % locals())
 
1708
            self._instance_update(context,
 
1709
                                  db_instance["id"],
 
1710
                                  power_state=vm_power_state)