~ubuntu-cloud-archive/ubuntu/precise/nova/trunk

« back to all changes in this revision

Viewing changes to nova/compute/manager.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short, Adam Gandleman
  • Date: 2012-01-13 09:51:10 UTC
  • mfrom: (1.1.40)
  • Revision ID: package-import@ubuntu.com-20120113095110-ffd6163drcg77wez
Tags: 2012.1~e3~20120113.12049-0ubuntu1
[Chuck Short]
* New upstream version.
* debian/nova_sudoers, debian/nova-common.install, 
  Switch out to nova-rootwrap. (LP: #681774)
* Add "get-origsource-git" which allows developers to 
  generate a tarball from github, by doing:
  fakeroot debian/rules get-orig-source-git
* debian/debian/nova-objectstore.logrotate: Dont determine
  if we are running Debian or Ubuntu. (LP: #91379)

[Adam Gandleman]
* Removed python-nova.postinst, let dh_python2 generate instead since
  python-support is not a dependency. (LP: #907543)

Show diffs side-by-side

added added

removed removed

Lines of Context:
33
33
 
34
34
"""
35
35
 
36
 
import datetime
37
36
import functools
38
37
import os
39
38
import socket
86
85
                     " Set to 0 to disable.")
87
86
flags.DEFINE_integer('host_state_interval', 120,
88
87
                     'Interval in seconds for querying the host status')
 
88
flags.DEFINE_integer("running_deleted_instance_timeout", 0,
 
89
                     "Number of seconds after being deleted when a"
 
90
                     " still-running instance should be considered"
 
91
                     " eligible for cleanup.")
 
92
flags.DEFINE_integer("running_deleted_instance_poll_interval", 30,
 
93
                     "Number of periodic scheduler ticks to wait between"
 
94
                     " runs of the cleanup task.")
 
95
flags.DEFINE_string("running_deleted_instance_action", "noop",
 
96
                     "Action to take if a running deleted instance is"
 
97
                     " detected. Valid options are 'noop', 'log', and"
 
98
                     " 'reap'. Set to 'noop' to disable.")
89
99
 
90
100
LOG = logging.getLogger('nova.compute.manager')
91
101
 
122
132
    return decorated_function
123
133
 
124
134
 
 
135
def wrap_instance_fault(function):
 
136
    """Wraps a method to catch exceptions related to instances.
 
137
 
 
138
    This decorator wraps a method to catch any exceptions having to do with
 
139
    an instance that may get thrown. It then logs an instance fault in the db.
 
140
    """
 
141
    @functools.wraps(function)
 
142
    def decorated_function(self, context, instance_uuid, *args, **kwargs):
 
143
        try:
 
144
            return function(self, context, instance_uuid, *args, **kwargs)
 
145
        except exception.InstanceNotFound:
 
146
            raise
 
147
        except Exception, e:
 
148
            with utils.save_and_reraise_exception():
 
149
                self.add_instance_fault_from_exc(context, instance_uuid, e)
 
150
 
 
151
    return decorated_function
 
152
 
 
153
 
125
154
def _get_image_meta(context, image_ref):
126
155
    image_service, image_id = nova.image.get_image_service(context, image_ref)
127
156
    return image_service.show(context, image_id)
136
165
        #             and re-document the module docstring
137
166
        if not compute_driver:
138
167
            compute_driver = FLAGS.compute_driver
139
 
 
140
168
        try:
141
169
            self.driver = utils.check_isinstance(
142
170
                                        utils.import_object(compute_driver),
146
174
            sys.exit(1)
147
175
 
148
176
        self.network_api = network.API()
 
177
        self.volume_api = volume.API()
149
178
        self.network_manager = utils.import_object(FLAGS.network_manager)
150
179
        self._last_host_check = 0
151
180
        self._last_bw_usage_poll = 0
162
191
        context = nova.context.get_admin_context()
163
192
        instances = self.db.instance_get_all_by_host(context, self.host)
164
193
        for instance in instances:
165
 
            inst_name = instance['name']
 
194
            instance_uuid = instance['uuid']
166
195
            db_state = instance['power_state']
167
196
            drv_state = self._get_power_state(context, instance)
168
197
 
169
198
            expect_running = db_state == power_state.RUNNING \
170
199
                             and drv_state != db_state
171
200
 
172
 
            LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, '
 
201
            LOG.debug(_('Current state of %(instance_uuid)s is %(drv_state)s, '
173
202
                        'state in DB is %(db_state)s.'), locals())
174
203
 
175
204
            if (expect_running and FLAGS.resume_guests_state_on_host_boot)\
176
205
               or FLAGS.start_guests_on_host_boot:
177
 
                LOG.info(_('Rebooting instance %(inst_name)s after '
 
206
                LOG.info(_('Rebooting instance %(instance_uuid)s after '
178
207
                            'nova-compute restart.'), locals())
179
 
                self.reboot_instance(context, instance['id'])
 
208
                self.reboot_instance(context, instance['uuid'])
180
209
            elif drv_state == power_state.RUNNING:
181
210
                # Hyper-V and VMWareAPI drivers will raise an exception
182
211
                try:
189
218
 
190
219
    def _get_power_state(self, context, instance):
191
220
        """Retrieve the power state for the given instance."""
192
 
        LOG.debug(_('Checking state of %s'), instance['name'])
 
221
        LOG.debug(_('Checking state of %s'), instance['uuid'])
193
222
        try:
194
223
            return self.driver.get_info(instance['name'])["state"]
195
224
        except exception.NotFound:
245
274
 
246
275
    def _setup_block_device_mapping(self, context, instance):
247
276
        """setup volumes for block device mapping"""
248
 
        volume_api = volume.API()
249
277
        block_device_mapping = []
250
278
        swap = None
251
279
        ephemerals = []
273
301
            if ((bdm['snapshot_id'] is not None) and
274
302
                (bdm['volume_id'] is None)):
275
303
                # TODO(yamahata): default name and description
276
 
                vol = volume_api.create(context, bdm['volume_size'],
277
 
                                        bdm['snapshot_id'], '', '')
 
304
                vol = self.volume_api.create(context, bdm['volume_size'],
 
305
                                             bdm['snapshot_id'], '', '')
278
306
                # TODO(yamahata): creating volume simultaneously
279
307
                #                 reduces creation time?
280
 
                volume_api.wait_creation(context, vol['id'])
 
308
                self.volume_api.wait_creation(context, vol['id'])
281
309
                self.db.block_device_mapping_update(
282
310
                    context, bdm['id'], {'volume_id': vol['id']})
283
311
                bdm['volume_id'] = vol['id']
284
312
 
285
313
            if bdm['volume_id'] is not None:
286
 
                volume_api.check_attach(context,
287
 
                                        volume_id=bdm['volume_id'])
 
314
                self.volume_api.check_attach(context,
 
315
                                             volume_id=bdm['volume_id'])
288
316
                cinfo = self._attach_volume_boot(context, instance,
289
317
                                                    bdm['volume_id'],
290
318
                                                    bdm['device_name'])
305
333
            if instance['task_state'] == task_states.DELETING:
306
334
                return True
307
335
            return False
308
 
        except:
 
336
        except Exception:
309
337
            return True
310
338
 
311
339
    def _shutdown_instance_even_if_deleted(self, context, instance_uuid):
323
351
                self.terminate_instance(ctxt, instance_uuid)
324
352
        except Exception as ex:
325
353
            LOG.info(_("exception terminating the instance "
326
 
                     "%(instance_id)s") % locals())
 
354
                     "%(instance_uuid)s") % locals())
327
355
 
328
356
    def _run_instance(self, context, instance_uuid,
329
357
                      requested_networks=None,
344
372
                instance = self._spawn(context, instance, image_meta,
345
373
                                       network_info, block_device_info,
346
374
                                       injected_files, admin_password)
347
 
            except:
 
375
            except Exception:
348
376
                with utils.save_and_reraise_exception():
349
377
                    self._deallocate_network(context, instance)
350
378
            self._notify_about_instance_usage(instance)
360
388
            with utils.save_and_reraise_exception():
361
389
                self._instance_update(context, instance_uuid,
362
390
                                      vm_state=vm_states.ERROR)
363
 
                self.add_instance_fault_from_exc(context, instance_uuid, e)
364
391
 
365
392
    def _check_instance_not_already_created(self, context, instance):
366
393
        """Ensure an instance with the same name is not already present."""
443
470
            network_info = self.network_api.allocate_for_instance(
444
471
                                context, instance, vpn=is_vpn,
445
472
                                requested_networks=requested_networks)
446
 
        except:
 
473
        except Exception:
447
474
            msg = _("Instance %s failed network setup")
448
475
            LOG.exception(msg % instance['uuid'])
449
476
            raise
458
485
        try:
459
486
            mapping = self._setup_block_device_mapping(context, instance)
460
487
            swap, ephemerals, block_device_mapping = mapping
461
 
        except:
 
488
        except Exception:
462
489
            msg = _("Instance %s failed block device setup")
463
490
            LOG.exception(msg % instance['uuid'])
464
491
            raise
478
505
        try:
479
506
            self.driver.spawn(context, instance, image_meta,
480
507
                              network_info, block_device_info)
481
 
        except:
 
508
        except Exception:
482
509
            msg = _("Instance %s failed to spawn")
483
510
            LOG.exception(msg % instance['uuid'])
484
511
            raise
507
534
                                                                instance_id)
508
535
        return [bdm for bdm in bdms if bdm['volume_id']]
509
536
 
 
537
    def _get_instance_volume_bdm(self, context, instance_id, volume_id):
 
538
        bdms = self._get_instance_volume_bdms(context, instance_id)
 
539
        for bdm in bdms:
 
540
            # NOTE(vish): Comparing as strings because the os_api doesn't
 
541
            #             convert to integer and we may wish to support uuids
 
542
            #             in the future.
 
543
            if str(bdm['volume_id']) == str(volume_id):
 
544
                return bdm
 
545
 
510
546
    def _get_instance_volume_block_device_info(self, context, instance_id):
511
547
        bdms = self._get_instance_volume_bdms(context, instance_id)
512
548
        block_device_mapping = []
515
551
            block_device_mapping.append({'connection_info': cinfo,
516
552
                                         'mount_device':
517
553
                                         bdm['device_name']})
518
 
        ## NOTE(vish): The mapping is passed in so the driver can disconnect
519
 
        ##             from remote volumes if necessary
 
554
        # NOTE(vish): The mapping is passed in so the driver can disconnect
 
555
        #             from remote volumes if necessary
520
556
        return {'block_device_mapping': block_device_mapping}
521
557
 
522
558
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
559
    @wrap_instance_fault
523
560
    def run_instance(self, context, instance_uuid, **kwargs):
524
561
        self._run_instance(context, instance_uuid, **kwargs)
525
562
 
526
563
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
527
564
    @checks_instance_lock
 
565
    @wrap_instance_fault
528
566
    def start_instance(self, context, instance_uuid):
529
567
        """Starting an instance on this host."""
530
568
        # TODO(yamahata): injected_files isn't supported.
533
571
        #              I think start will fail due to the files still
534
572
        self._run_instance(context, instance_uuid)
535
573
 
536
 
    def _shutdown_instance(self, context, instance, action_str, cleanup):
 
574
    def _shutdown_instance(self, context, instance, action_str):
537
575
        """Shutdown an instance on this host."""
538
576
        context = context.elevated()
539
577
        instance_id = instance['id']
546
584
        if not FLAGS.stub_network:
547
585
            self.network_api.deallocate_for_instance(context, instance)
548
586
 
549
 
        for bdm in self._get_instance_volume_bdms(context, instance_id):
550
 
            volume_id = bdm['volume_id']
551
 
            try:
552
 
                self._detach_volume(context, instance_uuid, volume_id)
553
 
            except exception.DiskNotFound as exc:
554
 
                LOG.warn(_("Ignoring DiskNotFound: %s") % exc)
555
 
 
556
587
        if instance['power_state'] == power_state.SHUTOFF:
557
588
            self.db.instance_destroy(context, instance_id)
558
589
            raise exception.Error(_('trying to destroy already destroyed'
559
590
                                    ' instance: %s') % instance_uuid)
 
591
        # NOTE(vish) get bdms before destroying the instance
 
592
        bdms = self._get_instance_volume_bdms(context, instance_id)
560
593
        block_device_info = self._get_instance_volume_block_device_info(
561
594
            context, instance_id)
562
 
        self.driver.destroy(instance, network_info, block_device_info, cleanup)
 
595
        self.driver.destroy(instance, network_info, block_device_info)
 
596
        for bdm in bdms:
 
597
            try:
 
598
                # NOTE(vish): actual driver detach done in driver.destroy, so
 
599
                #             just tell nova-volume that we are done with it.
 
600
                self.volume_api.terminate_connection(context,
 
601
                                                     bdm['volume_id'],
 
602
                                                     FLAGS.my_ip)
 
603
                self.volume_api.detach(context, bdm['volume_id'])
 
604
            except exception.DiskNotFound as exc:
 
605
                LOG.warn(_("Ignoring DiskNotFound: %s") % exc)
563
606
 
564
607
    def _cleanup_volumes(self, context, instance_id):
565
 
        volume_api = volume.API()
566
608
        bdms = self.db.block_device_mapping_get_all_by_instance(context,
567
609
                                                                instance_id)
568
610
        for bdm in bdms:
569
611
            LOG.debug(_("terminating bdm %s") % bdm)
570
612
            if bdm['volume_id'] and bdm['delete_on_termination']:
571
 
                volume_api.delete(context, bdm['volume_id'])
 
613
                self.volume_api.delete(context, bdm['volume_id'])
572
614
            # NOTE(vish): bdms will be deleted on instance destroy
573
615
 
574
616
    def _delete_instance(self, context, instance):
575
617
        """Delete an instance on this host."""
576
618
        instance_id = instance['id']
577
 
        self._shutdown_instance(context, instance, 'Terminating', True)
 
619
        self._shutdown_instance(context, instance, 'Terminating')
578
620
        self._cleanup_volumes(context, instance_id)
579
621
        self._instance_update(context,
580
622
                              instance_id,
591
633
 
592
634
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
593
635
    @checks_instance_lock
 
636
    @wrap_instance_fault
594
637
    def terminate_instance(self, context, instance_uuid):
595
638
        """Terminate an instance on this host."""
596
639
        elevated = context.elevated()
600
643
 
601
644
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
602
645
    @checks_instance_lock
 
646
    @wrap_instance_fault
603
647
    def stop_instance(self, context, instance_uuid):
604
648
        """Stopping an instance on this host."""
605
 
        # FIXME(vish): I've kept the files during stop instance, but
606
 
        #              I think start will fail due to the files still
607
 
        #              existing.  I don't really know what the purpose of
608
 
        #              stop and start are when compared to pause and unpause
609
649
        instance = self.db.instance_get_by_uuid(context, instance_uuid)
610
 
        self._shutdown_instance(context, instance, 'Stopping', False)
 
650
        self._shutdown_instance(context, instance, 'Stopping')
611
651
        self._instance_update(context,
612
652
                              instance_uuid,
613
653
                              vm_state=vm_states.STOPPED,
615
655
 
616
656
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
617
657
    @checks_instance_lock
 
658
    @wrap_instance_fault
618
659
    def power_off_instance(self, context, instance_uuid):
619
660
        """Power off an instance on this host."""
620
661
        instance = self.db.instance_get_by_uuid(context, instance_uuid)
627
668
 
628
669
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
629
670
    @checks_instance_lock
 
671
    @wrap_instance_fault
630
672
    def power_on_instance(self, context, instance_uuid):
631
673
        """Power on an instance on this host."""
632
674
        instance = self.db.instance_get_by_uuid(context, instance_uuid)
639
681
 
640
682
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
641
683
    @checks_instance_lock
 
684
    @wrap_instance_fault
642
685
    def rebuild_instance(self, context, instance_uuid, **kwargs):
643
686
        """Destroy and re-make this instance.
644
687
 
704
747
 
705
748
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
706
749
    @checks_instance_lock
 
750
    @wrap_instance_fault
707
751
    def reboot_instance(self, context, instance_uuid, reboot_type="SOFT"):
708
752
        """Reboot an instance on this host."""
709
753
        LOG.audit(_("Rebooting instance %s"), instance_uuid, context=context)
735
779
                              task_state=None)
736
780
 
737
781
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
782
    @wrap_instance_fault
738
783
    def snapshot_instance(self, context, instance_uuid, image_id,
739
784
                          image_type='snapshot', backup_type=None,
740
785
                          rotation=None):
789
834
        elif image_type == 'backup':
790
835
            raise exception.RotationRequiredForBackup()
791
836
 
 
837
    @wrap_instance_fault
792
838
    def rotate_backups(self, context, instance_uuid, backup_type, rotation):
793
839
        """Delete excess backups associated to an instance.
794
840
 
837
883
 
838
884
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
839
885
    @checks_instance_lock
 
886
    @wrap_instance_fault
840
887
    def set_admin_password(self, context, instance_uuid, new_pass=None):
841
888
        """Set the root/admin password for an instance on this host.
842
889
 
865
912
                try:
866
913
                    self.driver.set_admin_password(instance_ref, new_pass)
867
914
                    LOG.audit(_("Instance %s: Root password set"),
868
 
                                instance_ref["name"])
 
915
                                instance_ref["uuid"])
869
916
                    self._instance_update(context,
870
917
                                          instance_id,
871
918
                                          task_state=None)
883
930
                    # Catch all here because this could be anything.
884
931
                    LOG.exception(e)
885
932
                    if i == max_tries - 1:
886
 
                        # At some point this exception may make it back
887
 
                        # to the API caller, and we don't want to reveal
888
 
                        # too much.  The real exception is logged above
889
933
                        self._instance_update(context,
890
934
                                              instance_id,
 
935
                                              task_state=None,
891
936
                                              vm_state=vm_states.ERROR)
892
 
                        raise exception.Error(_('Internal error'))
 
937
                        # We create a new exception here so that we won't
 
938
                        # potentially reveal password information to the
 
939
                        # API caller.  The real exception is logged above
 
940
                        _msg = _('Error setting admin password')
 
941
                        raise exception.Error(_msg)
893
942
                    time.sleep(1)
894
943
                    continue
895
944
 
896
945
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
897
946
    @checks_instance_lock
 
947
    @wrap_instance_fault
898
948
    def inject_file(self, context, instance_uuid, path, file_contents):
899
949
        """Write a file to the specified path in an instance on this host."""
900
950
        context = context.elevated()
905
955
            LOG.warn(_('trying to inject a file into a non-running '
906
956
                    'instance: %(instance_uuid)s (state: %(instance_state)s '
907
957
                    'expected: %(expected_state)s)') % locals())
908
 
        nm = instance_ref['name']
909
 
        msg = _('instance %(nm)s: injecting file to %(path)s') % locals()
910
 
        LOG.audit(msg)
 
958
        instance_uuid = instance_ref['uuid']
 
959
        msg = _('instance %(instance_uuid)s: injecting file to %(path)s')
 
960
        LOG.audit(msg % locals())
911
961
        self.driver.inject_file(instance_ref, path, file_contents)
912
962
 
913
963
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
914
964
    @checks_instance_lock
 
965
    @wrap_instance_fault
915
966
    def agent_update(self, context, instance_uuid, url, md5hash):
916
967
        """Update agent running on an instance on this host."""
917
968
        context = context.elevated()
922
973
            LOG.warn(_('trying to update agent on a non-running '
923
974
                    'instance: %(instance_uuid)s (state: %(instance_state)s '
924
975
                    'expected: %(expected_state)s)') % locals())
925
 
        nm = instance_ref['name']
926
 
        msg = _('instance %(nm)s: updating agent to %(url)s') % locals()
927
 
        LOG.audit(msg)
 
976
        instance_uuid = instance_ref['uuid']
 
977
        msg = _('instance %(instance_uuid)s: updating agent to %(url)s')
 
978
        LOG.audit(msg % locals())
928
979
        self.driver.agent_update(instance_ref, url, md5hash)
929
980
 
930
981
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
931
982
    @checks_instance_lock
 
983
    @wrap_instance_fault
932
984
    def rescue_instance(self, context, instance_uuid, **kwargs):
933
985
        """
934
986
        Rescue an instance on this host.
955
1007
 
956
1008
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
957
1009
    @checks_instance_lock
 
1010
    @wrap_instance_fault
958
1011
    def unrescue_instance(self, context, instance_uuid):
959
1012
        """Rescue an instance on this host."""
960
1013
        LOG.audit(_('instance %s: unrescuing'), instance_uuid, context=context)
974
1027
 
975
1028
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
976
1029
    @checks_instance_lock
 
1030
    @wrap_instance_fault
977
1031
    def confirm_resize(self, context, instance_uuid, migration_id):
978
1032
        """Destroys the source instance."""
979
1033
        migration_ref = self.db.migration_get(context, migration_id)
992
1046
 
993
1047
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
994
1048
    @checks_instance_lock
 
1049
    @wrap_instance_fault
995
1050
    def revert_resize(self, context, instance_uuid, migration_id):
996
1051
        """Destroys the new instance on the destination machine.
997
1052
 
1015
1070
 
1016
1071
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1017
1072
    @checks_instance_lock
 
1073
    @wrap_instance_fault
1018
1074
    def finish_revert_resize(self, context, instance_uuid, migration_id):
1019
1075
        """Finishes the second half of reverting a resize.
1020
1076
 
1050
1106
 
1051
1107
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1052
1108
    @checks_instance_lock
 
1109
    @wrap_instance_fault
1053
1110
    def prep_resize(self, context, instance_uuid, instance_type_id):
1054
1111
        """Initiates the process of moving a running instance to another host.
1055
1112
 
1065
1122
            self._instance_update(context,
1066
1123
                                  instance_uuid,
1067
1124
                                  vm_state=vm_states.ERROR)
1068
 
            msg = _('Migration error: destination same as source!')
1069
 
            raise exception.Error(msg)
 
1125
            msg = _('destination same as source!')
 
1126
            raise exception.MigrationError(msg)
1070
1127
 
1071
1128
        old_instance_type_id = instance_ref['instance_type_id']
1072
1129
        old_instance_type = instance_types.get_instance_type(
1101
1158
 
1102
1159
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1103
1160
    @checks_instance_lock
 
1161
    @wrap_instance_fault
1104
1162
    def resize_instance(self, context, instance_uuid, migration_id):
1105
1163
        """Starts the migration of a running instance to another host."""
1106
1164
        migration_ref = self.db.migration_get(context, migration_id)
1142
1200
 
1143
1201
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1144
1202
    @checks_instance_lock
 
1203
    @wrap_instance_fault
1145
1204
    def finish_resize(self, context, instance_uuid, migration_id, disk_info):
1146
1205
        """Completes the migration process.
1147
1206
 
1197
1256
 
1198
1257
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1199
1258
    @checks_instance_lock
 
1259
    @wrap_instance_fault
1200
1260
    def add_fixed_ip_to_instance(self, context, instance_uuid, network_id):
1201
1261
        """Calls network_api to add new fixed_ip to instance
1202
1262
        then injects the new network info and resets instance networking.
1216
1276
 
1217
1277
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1218
1278
    @checks_instance_lock
 
1279
    @wrap_instance_fault
1219
1280
    def remove_fixed_ip_from_instance(self, context, instance_uuid, address):
1220
1281
        """Calls network_api to remove existing fixed_ip from instance
1221
1282
        by injecting the altered network info and resetting
1235
1296
 
1236
1297
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1237
1298
    @checks_instance_lock
 
1299
    @wrap_instance_fault
1238
1300
    def pause_instance(self, context, instance_uuid):
1239
1301
        """Pause an instance on this host."""
1240
1302
        LOG.audit(_('instance %s: pausing'), instance_uuid, context=context)
1252
1314
 
1253
1315
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1254
1316
    @checks_instance_lock
 
1317
    @wrap_instance_fault
1255
1318
    def unpause_instance(self, context, instance_uuid):
1256
1319
        """Unpause a paused instance on this host."""
1257
1320
        LOG.audit(_('instance %s: unpausing'), instance_uuid, context=context)
1278
1341
        return self.driver.set_host_enabled(host, enabled)
1279
1342
 
1280
1343
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
1344
    @wrap_instance_fault
1281
1345
    def get_diagnostics(self, context, instance_uuid):
1282
1346
        """Retrieve diagnostics for an instance on this host."""
1283
1347
        instance_ref = self.db.instance_get_by_uuid(context, instance_uuid)
1288
1352
 
1289
1353
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1290
1354
    @checks_instance_lock
 
1355
    @wrap_instance_fault
1291
1356
    def suspend_instance(self, context, instance_uuid):
1292
1357
        """Suspend the given instance."""
1293
1358
        LOG.audit(_('instance %s: suspending'), instance_uuid, context=context)
1303
1368
                              vm_state=vm_states.SUSPENDED,
1304
1369
                              task_state=None)
1305
1370
 
 
1371
        usage_info = utils.usage_from_instance(instance_ref)
 
1372
        notifier.notify('compute.%s' % self.host, 'compute.instance.suspend',
 
1373
                notifier.INFO, usage_info)
 
1374
 
1306
1375
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1307
1376
    @checks_instance_lock
 
1377
    @wrap_instance_fault
1308
1378
    def resume_instance(self, context, instance_uuid):
1309
1379
        """Resume the given suspended instance."""
1310
1380
        LOG.audit(_('instance %s: resuming'), instance_uuid, context=context)
1320
1390
                              vm_state=vm_states.ACTIVE,
1321
1391
                              task_state=None)
1322
1392
 
 
1393
        usage_info = utils.usage_from_instance(instance_ref)
 
1394
        notifier.notify('compute.%s' % self.host, 'compute.instance.resume',
 
1395
                notifier.INFO, usage_info)
 
1396
 
1323
1397
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
1398
    @wrap_instance_fault
1324
1399
    def lock_instance(self, context, instance_uuid):
1325
1400
        """Lock the given instance."""
1326
1401
        context = context.elevated()
1329
1404
        self.db.instance_update(context, instance_uuid, {'locked': True})
1330
1405
 
1331
1406
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
1407
    @wrap_instance_fault
1332
1408
    def unlock_instance(self, context, instance_uuid):
1333
1409
        """Unlock the given instance."""
1334
1410
        context = context.elevated()
1337
1413
        self.db.instance_update(context, instance_uuid, {'locked': False})
1338
1414
 
1339
1415
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
1416
    @wrap_instance_fault
1340
1417
    def get_lock(self, context, instance_uuid):
1341
1418
        """Return the boolean state of the given instance's lock."""
1342
1419
        context = context.elevated()
1346
1423
        return instance_ref['locked']
1347
1424
 
1348
1425
    @checks_instance_lock
 
1426
    @wrap_instance_fault
1349
1427
    def reset_network(self, context, instance_uuid):
1350
1428
        """Reset networking on the given instance."""
1351
1429
        instance = self.db.instance_get_by_uuid(context, instance_uuid)
1354
1432
        self.driver.reset_network(instance)
1355
1433
 
1356
1434
    @checks_instance_lock
 
1435
    @wrap_instance_fault
1357
1436
    def inject_network_info(self, context, instance_uuid):
1358
1437
        """Inject network info for the given instance."""
1359
1438
        LOG.debug(_('instance %s: inject network info'), instance_uuid,
1365
1444
        self.driver.inject_network_info(instance, network_info)
1366
1445
 
1367
1446
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1368
 
    def get_console_output(self, context, instance_uuid):
 
1447
    @wrap_instance_fault
 
1448
    def get_console_output(self, context, instance_uuid, tail_length=None):
1369
1449
        """Send the console output for the given instance."""
1370
1450
        context = context.elevated()
1371
1451
        instance_ref = self.db.instance_get_by_uuid(context, instance_uuid)
1372
1452
        LOG.audit(_("Get console output for instance %s"), instance_uuid,
1373
1453
                  context=context)
1374
1454
        output = self.driver.get_console_output(instance_ref)
 
1455
 
 
1456
        if tail_length is not None:
 
1457
            output = self._tail_log(output, tail_length)
 
1458
 
1375
1459
        return output.decode('utf-8', 'replace').encode('ascii', 'replace')
1376
1460
 
 
1461
    def _tail_log(self, log, length):
 
1462
        try:
 
1463
            length = int(length)
 
1464
        except ValueError:
 
1465
            length = 0
 
1466
 
 
1467
        if length == 0:
 
1468
            return ''
 
1469
        else:
 
1470
            return '\n'.join(log.split('\n')[-int(length):])
 
1471
 
1377
1472
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
1473
    @wrap_instance_fault
1378
1474
    def get_ajax_console(self, context, instance_uuid):
1379
1475
        """Return connection information for an ajax console."""
1380
1476
        context = context.elevated()
1383
1479
        return self.driver.get_ajax_console(instance_ref)
1384
1480
 
1385
1481
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
1482
    @wrap_instance_fault
1386
1483
    def get_vnc_console(self, context, instance_uuid):
1387
1484
        """Return connection information for a vnc console."""
1388
1485
        context = context.elevated()
1395
1492
        is done by instance creation"""
1396
1493
 
1397
1494
        instance_id = instance['id']
 
1495
        instance_uuid = instance['uuid']
1398
1496
        context = context.elevated()
1399
 
        LOG.audit(_("instance %(instance_id)s: booting with "
 
1497
        LOG.audit(_("instance %(instance_uuid)s: booting with "
1400
1498
                    "volume %(volume_id)s at %(mountpoint)s") %
1401
1499
                  locals(), context=context)
1402
1500
        address = FLAGS.my_ip
1403
 
        volume_api = volume.API()
1404
 
        connection_info = volume_api.initialize_connection(context,
1405
 
                                                           volume_id,
1406
 
                                                           address)
1407
 
        volume_api.attach(context, volume_id, instance_id, mountpoint)
 
1501
        connection_info = self.volume_api.initialize_connection(context,
 
1502
                                                                volume_id,
 
1503
                                                                address)
 
1504
        self.volume_api.attach(context, volume_id, instance_id, mountpoint)
1408
1505
        return connection_info
1409
1506
 
 
1507
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1410
1508
    @checks_instance_lock
 
1509
    @wrap_instance_fault
1411
1510
    def attach_volume(self, context, instance_uuid, volume_id, mountpoint):
1412
1511
        """Attach a volume to an instance."""
1413
1512
        context = context.elevated()
1416
1515
        LOG.audit(
1417
1516
            _("instance %(instance_uuid)s: attaching volume %(volume_id)s"
1418
1517
              " to %(mountpoint)s") % locals(), context=context)
1419
 
        volume_api = volume.API()
1420
1518
        address = FLAGS.my_ip
1421
 
        connection_info = volume_api.initialize_connection(context,
1422
 
                                                           volume_id,
1423
 
                                                           address)
 
1519
        connection_info = self.volume_api.initialize_connection(context,
 
1520
                                                                volume_id,
 
1521
                                                                address)
1424
1522
        try:
1425
1523
            self.driver.attach_volume(connection_info,
1426
1524
                                      instance_ref['name'],
1427
1525
                                      mountpoint)
1428
1526
        except Exception:  # pylint: disable=W0702
1429
 
            exc = sys.exc_info()
1430
 
            # NOTE(vish): The inline callback eats the exception info so we
1431
 
            #             log the traceback here and reraise the same
1432
 
            #             ecxception below.
1433
 
            LOG.exception(_("instance %(instance_uuid)s: attach failed"
1434
 
                    " %(mountpoint)s, removing") % locals(), context=context)
1435
 
            volume_api.terminate_connection(context, volume_id, address)
1436
 
            raise exc
 
1527
            with utils.save_and_reraise_exception():
 
1528
                LOG.exception(_("instance %(instance_uuid)s: attach failed"
 
1529
                                " %(mountpoint)s, removing") % locals(),
 
1530
                              context=context)
 
1531
                self.volume_api.terminate_connection(context, volume_id,
 
1532
                                                     address)
1437
1533
 
1438
 
        volume_api.attach(context, volume_id, instance_id, mountpoint)
 
1534
        self.volume_api.attach(context, volume_id, instance_id, mountpoint)
1439
1535
        values = {
1440
1536
            'instance_id': instance_id,
1441
1537
            'connection_info': utils.dumps(connection_info),
1449
1545
        self.db.block_device_mapping_create(context, values)
1450
1546
        return True
1451
1547
 
1452
 
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1453
 
    @checks_instance_lock
1454
 
    def _detach_volume(self, context, instance_uuid, volume_id,
1455
 
                       destroy_bdm=False, mark_detached=True,
1456
 
                       force_detach=False):
1457
 
        """Detach a volume from an instance."""
1458
 
        context = context.elevated()
1459
 
        instance_ref = self.db.instance_get_by_uuid(context, instance_uuid)
1460
 
        instance_id = instance_ref['id']
1461
 
        bdms = self.db.block_device_mapping_get_all_by_instance(
1462
 
                context, instance_id)
1463
 
        for item in bdms:
1464
 
            # NOTE(vish): Comparing as strings because the os_api doesn't
1465
 
            #             convert to integer and we may wish to support uuids
1466
 
            #             in the future.
1467
 
            if str(item['volume_id']) == str(volume_id):
1468
 
                bdm = item
1469
 
                break
 
1548
    def _detach_volume(self, context, instance, bdm):
 
1549
        """Do the actual driver detach using block device mapping."""
 
1550
        instance_name = instance['name']
 
1551
        instance_uuid = instance['uuid']
1470
1552
        mp = bdm['device_name']
 
1553
        volume_id = bdm['volume_id']
1471
1554
 
1472
1555
        LOG.audit(_("Detach volume %(volume_id)s from mountpoint %(mp)s"
1473
 
                " on instance %(instance_id)s") % locals(), context=context)
1474
 
        volume_api = volume.API()
1475
 
        if (instance_ref['name'] not in self.driver.list_instances() and
1476
 
            not force_detach):
 
1556
                " on instance %(instance_uuid)s") % locals(), context=context)
 
1557
 
 
1558
        if instance_name not in self.driver.list_instances():
1477
1559
            LOG.warn(_("Detaching volume from unknown instance %s"),
1478
 
                     instance_id, context=context)
1479
 
        else:
1480
 
            self.driver.detach_volume(utils.loads(bdm['connection_info']),
1481
 
                                      instance_ref['name'],
1482
 
                                      bdm['device_name'])
1483
 
        address = FLAGS.my_ip
1484
 
        volume_api.terminate_connection(context, volume_id, address)
1485
 
        if mark_detached:
1486
 
            volume_api.detach(context, volume_id)
1487
 
        if destroy_bdm:
1488
 
            self.db.block_device_mapping_destroy_by_instance_and_volume(
1489
 
                context, instance_id, volume_id)
1490
 
        return True
 
1560
                     instance_uuid, context=context)
 
1561
        self.driver.detach_volume(utils.loads(bdm['connection_info']),
 
1562
                                  instance_name,
 
1563
                                  mp)
1491
1564
 
 
1565
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 
1566
    @checks_instance_lock
 
1567
    @wrap_instance_fault
1492
1568
    def detach_volume(self, context, instance_uuid, volume_id):
1493
1569
        """Detach a volume from an instance."""
1494
 
        return self._detach_volume(context, instance_uuid, volume_id, True)
 
1570
        instance_ref = self.db.instance_get_by_uuid(context, instance_uuid)
 
1571
        instance_id = instance_ref['id']
 
1572
        bdm = self._get_instance_volume_bdm(context, instance_id, volume_id)
 
1573
        self._detach_volume(context, instance_ref, bdm)
 
1574
        self.volume_api.terminate_connection(context, volume_id, FLAGS.my_ip)
 
1575
        self.volume_api.detach(context.elevated(), volume_id)
 
1576
        self.db.block_device_mapping_destroy_by_instance_and_volume(
 
1577
            context, instance_id, volume_id)
 
1578
        return True
1495
1579
 
1496
1580
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1497
1581
    def remove_volume_connection(self, context, instance_id, volume_id):
1498
 
        """Detach a volume from an instance.,"""
 
1582
        """Remove a volume connection using the volume api"""
1499
1583
        # NOTE(vish): We don't want to actually mark the volume
1500
1584
        #             detached, or delete the bdm, just remove the
1501
1585
        #             connection from this host.
1502
1586
        try:
1503
1587
            instance_ref = self.db.instance_get(context, instance_id)
1504
 
            self._detach_volume(context, instance_ref['uuid'], volume_id,
1505
 
                                False, False, True)
 
1588
            bdm = self._get_instance_volume_bdm(context,
 
1589
                                                instance_id,
 
1590
                                                volume_id)
 
1591
            self._detach_volume(context, instance_ref,
 
1592
                                bdm['volume_id'], bdm['device_name'])
 
1593
            self.volume_api.terminate_connection(context,
 
1594
                                                 volume_id,
 
1595
                                                 FLAGS.my_ip)
1506
1596
        except exception.NotFound:
1507
1597
            pass
1508
1598
 
1573
1663
        """
1574
1664
        return self.driver.update_available_resource(context, self.host)
1575
1665
 
 
1666
    def get_instance_disk_info(self, context, instance_name):
 
1667
        """Getting infomation of instance's current disk.
 
1668
 
 
1669
        Implementation nova.virt.libvirt.connection.
 
1670
 
 
1671
        :param context: security context
 
1672
        :param instance_name: instance name
 
1673
 
 
1674
        """
 
1675
        return self.driver.get_instance_disk_info(instance_name)
 
1676
 
1576
1677
    def pre_live_migration(self, context, instance_id, time=None,
1577
1678
                           block_migration=False, disk=None):
1578
1679
        """Preparations for live migration at dest host.
1592
1693
        block_device_info = \
1593
1694
            self._get_instance_volume_block_device_info(context, instance_id)
1594
1695
        if not block_device_info['block_device_mapping']:
1595
 
            LOG.info(_("%s has no volume."), instance_ref.name)
 
1696
            LOG.info(_("%s has no volume."), instance_ref['uuid'])
1596
1697
 
1597
1698
        self.driver.pre_live_migration(block_device_info)
1598
1699
 
1645
1746
        :param context: security context
1646
1747
        :param instance_id: nova.db.sqlalchemy.models.Instance.Id
1647
1748
        :param dest: destination host
1648
 
        :param block_migration: if true, do block migration
 
1749
        :param block_migration: if true, prepare for block migration
1649
1750
 
1650
1751
        """
1651
1752
        # Get instance for error handling.
1661
1762
                           "args": {'instance_id': instance_id}})
1662
1763
 
1663
1764
            if block_migration:
1664
 
                disk = self.driver.get_instance_disk_info(context,
1665
 
                                                          instance_ref)
 
1765
                disk = self.driver.get_instance_disk_info(instance_ref.name)
1666
1766
            else:
1667
1767
                disk = None
1668
1768
 
1674
1774
                               'disk': disk}})
1675
1775
 
1676
1776
        except Exception:
1677
 
            exc = sys.exc_info()
1678
 
            i_name = instance_ref.name
1679
 
            msg = _("Pre live migration for %(i_name)s failed at %(dest)s")
1680
 
            LOG.exception(msg % locals())
1681
 
            self.rollback_live_migration(context, instance_ref,
1682
 
                                         dest, block_migration)
1683
 
            raise exc
 
1777
            with utils.save_and_reraise_exception():
 
1778
                instance_uuid = instance_ref['uuid']
 
1779
                msg = _("Pre live migration for %(instance_uuid)s failed at"
 
1780
                        " %(dest)s")
 
1781
                LOG.exception(msg % locals())
 
1782
                self.rollback_live_migration(context, instance_ref, dest,
 
1783
                                             block_migration)
1684
1784
 
1685
1785
        # Executing live migration
1686
1786
        # live_migration might raises exceptions, but
1700
1800
        :param ctxt: security context
1701
1801
        :param instance_id: nova.db.sqlalchemy.models.Instance.Id
1702
1802
        :param dest: destination host
1703
 
        :param block_migration: if true, do block migration
 
1803
        :param block_migration: if true, prepare for block migration
1704
1804
 
1705
1805
        """
1706
1806
 
1707
1807
        LOG.info(_('post_live_migration() is started..'))
1708
1808
        instance_id = instance_ref['id']
 
1809
        instance_uuid = instance_ref['uuid']
1709
1810
 
1710
1811
        # Detaching volumes.
1711
1812
        for bdm in self._get_instance_volume_bdms(ctxt, instance_id):
1723
1824
        self.driver.unfilter_instance(instance_ref, network_info)
1724
1825
 
1725
1826
        # Database updating.
1726
 
        i_name = instance_ref.name
1727
1827
        try:
1728
1828
            # Not return if floating_ip is not found, otherwise,
1729
1829
            # instance never be accessible..
1730
1830
            floating_ip = self.db.instance_get_floating_address(ctxt,
1731
1831
                                                         instance_id)
1732
1832
            if not floating_ip:
1733
 
                LOG.info(_('No floating_ip is found for %s.'), i_name)
 
1833
                LOG.info(_('No floating_ip is found for %s.'), instance_uuid)
1734
1834
            else:
1735
1835
                floating_ip_ref = self.db.floating_ip_get_by_address(ctxt,
1736
1836
                                                              floating_ip)
1738
1838
                                           floating_ip_ref['address'],
1739
1839
                                           {'host': dest})
1740
1840
        except exception.NotFound:
1741
 
            LOG.info(_('No floating_ip is found for %s.'), i_name)
 
1841
            LOG.info(_('No floating_ip is found for %s.'), instance_uuid)
1742
1842
        except Exception, e:
1743
1843
            LOG.error(_("Live migration: Unexpected error: "
1744
 
                        "%(i_name)s cannot inherit floating "
 
1844
                        "%(instance_uuid)s cannot inherit floating "
1745
1845
                        "ip.\n%(e)s") % (locals()))
1746
1846
 
1747
1847
        # Define domain at destination host, without doing it,
1749
1849
        rpc.call(ctxt,
1750
1850
                 self.db.queue_get_for(ctxt, FLAGS.compute_topic, dest),
1751
1851
                     {"method": "post_live_migration_at_destination",
1752
 
                      "args": {'instance_id': instance_ref.id,
 
1852
                      "args": {'instance_id': instance_ref['id'],
1753
1853
                               'block_migration': block_migration}})
1754
1854
 
1755
1855
        # Restore instance state
1777
1877
            # torn down
1778
1878
            self.driver.unplug_vifs(instance_ref, network_info)
1779
1879
 
1780
 
        LOG.info(_('Migrating %(i_name)s to %(dest)s finished successfully.')
1781
 
                 % locals())
 
1880
        LOG.info(_('Migrating %(instance_uuid)s to %(dest)s finished'
 
1881
                   ' successfully.') % locals())
1782
1882
        LOG.info(_("You may see the error \"libvirt: QEMU error: "
1783
1883
                   "Domain not found: no domain with matching name.\" "
1784
1884
                   "This error can be safely ignored."))
1789
1889
 
1790
1890
        :param context: security context
1791
1891
        :param instance_id: nova.db.sqlalchemy.models.Instance.Id
1792
 
        :param block_migration: block_migration
 
1892
        :param block_migration: if true, prepare for block migration
1793
1893
 
1794
1894
        """
1795
1895
        instance_ref = self.db.instance_get(context, instance_id)
1796
1896
        LOG.info(_('Post operation of migraton started for %s .')
1797
 
                 % instance_ref.name)
 
1897
                 % instance_ref['uuid'])
1798
1898
        network_info = self._get_instance_nw_info(context, instance_ref)
1799
1899
        self.driver.post_live_migration_at_destination(context,
1800
1900
                                                       instance_ref,
1810
1910
        :param dest:
1811
1911
            This method is called from live migration src host.
1812
1912
            This param specifies destination host.
 
1913
        :param block_migration: if true, prepare for block migration
 
1914
 
1813
1915
        """
1814
1916
        host = instance_ref['host']
1815
1917
        self._instance_update(context,
1821
1923
        for bdm in self._get_instance_volume_bdms(context, instance_ref['id']):
1822
1924
            volume_id = bdm['volume_id']
1823
1925
            self.db.volume_update(context, volume_id, {'status': 'in-use'})
1824
 
            volume.API().remove_from_compute(context, instance_ref['id'],
1825
 
                                             volume_id, dest)
 
1926
            self.volume_api.remove_from_compute(context, instance_ref['id'],
 
1927
                                                volume_id, dest)
1826
1928
 
1827
1929
        # Block migration needs empty image at destination host
1828
1930
        # before migration starts, so if any failure occurs,
1936
2038
            if vm_power_state == db_power_state:
1937
2039
                continue
1938
2040
 
1939
 
            self._instance_update(context,
1940
 
                                  db_instance["id"],
1941
 
                                  power_state=vm_power_state)
 
2041
            if (vm_power_state in (power_state.NOSTATE, power_state.SHUTOFF)
 
2042
                and db_instance['vm_state'] == vm_states.ACTIVE):
 
2043
                self._instance_update(context,
 
2044
                                      db_instance["id"],
 
2045
                                      power_state=vm_power_state,
 
2046
                                      vm_state=vm_states.SHUTOFF)
 
2047
            else:
 
2048
                self._instance_update(context,
 
2049
                                      db_instance["id"],
 
2050
                                      power_state=vm_power_state)
1942
2051
 
1943
2052
    @manager.periodic_task
1944
2053
    def _reclaim_queued_deletes(self, context):
1955
2064
            soft_deleted = instance.vm_state == vm_states.SOFT_DELETE
1956
2065
 
1957
2066
            if soft_deleted and old_enough:
1958
 
                instance_id = instance.id
1959
 
                LOG.info(_("Reclaiming deleted instance %(instance_id)s"),
 
2067
                instance_uuid = instance['uuid']
 
2068
                LOG.info(_("Reclaiming deleted instance %(instance_uuid)s"),
1960
2069
                         locals())
1961
2070
                self._delete_instance(context, instance)
1962
2071
 
1975
2084
        }
1976
2085
        self.db.instance_fault_create(context, values)
1977
2086
 
1978
 
    def add_instance_fault(self, context, instance_uuid, code=500,
1979
 
                           message='', details=''):
1980
 
        """Adds a fault to the database using the specified values."""
1981
 
        values = {
1982
 
            'instance_uuid': instance_uuid,
1983
 
            'code': code,
1984
 
            'message': message,
1985
 
            'details': details,
1986
 
        }
1987
 
        self.db.instance_fault_create(context, values)
 
2087
    @manager.periodic_task(
 
2088
        ticks_between_runs=FLAGS.running_deleted_instance_poll_interval)
 
2089
    def _cleanup_running_deleted_instances(self, context):
 
2090
        """Cleanup any instances which are erroneously still running after
 
2091
        having been deleted.
 
2092
 
 
2093
        Valid actions to take are:
 
2094
 
 
2095
            1. noop - do nothing
 
2096
            2. log - log which instances are erroneously running
 
2097
            3. reap - shutdown and cleanup any erroneously running instances
 
2098
 
 
2099
        The use-case for this cleanup task is: for various reasons, it may be
 
2100
        possible for the database to show an instance as deleted but for that
 
2101
        instance to still be running on a host machine (see bug
 
2102
        https://bugs.launchpad.net/nova/+bug/911366).
 
2103
 
 
2104
        This cleanup task is a cross-hypervisor utility for finding these
 
2105
        zombied instances and either logging the discrepancy (likely what you
 
2106
        should do in production), or automatically reaping the instances (more
 
2107
        appropriate for dev environments).
 
2108
        """
 
2109
        action = FLAGS.running_deleted_instance_action
 
2110
 
 
2111
        if action == "noop":
 
2112
            return
 
2113
 
 
2114
        present_name_labels = set(self.driver.list_instances())
 
2115
 
 
2116
        # NOTE(sirp): admin contexts don't ordinarily return deleted records
 
2117
        with utils.temporary_mutation(context, read_deleted="yes"):
 
2118
            instances = self.db.instance_get_all_by_host(context, self.host)
 
2119
            for instance in instances:
 
2120
                present = instance.name in present_name_labels
 
2121
                erroneously_running = instance.deleted and present
 
2122
                old_enough = (not instance.deleted_at or utils.is_older_than(
 
2123
                        instance.deleted_at,
 
2124
                        FLAGS.running_deleted_instance_timeout))
 
2125
 
 
2126
                if erroneously_running and old_enough:
 
2127
                    instance_id = instance['id']
 
2128
                    instance_uuid = instance['uuid']
 
2129
                    name_label = instance['name']
 
2130
 
 
2131
                    if action == "log":
 
2132
                        LOG.warning(_("Detected instance %(instance_uuid)s"
 
2133
                                      " with name label '%(name_label)s' which"
 
2134
                                      " is marked as DELETED but still present"
 
2135
                                      " on host."), locals())
 
2136
 
 
2137
                    elif action == 'reap':
 
2138
                        LOG.info(_("Destroying instance %(instance_uuid)s with"
 
2139
                                   " name label '%(name_label)s' which is"
 
2140
                                   " marked as DELETED but still present on"
 
2141
                                   " host."), locals())
 
2142
                        self._shutdown_instance(
 
2143
                                context, instance, 'Terminating', True)
 
2144
                        self._cleanup_volumes(context, instance_id)
 
2145
                    else:
 
2146
                        raise Exception(_("Unrecognized value '%(action)s'"
 
2147
                                          " for FLAGS.running_deleted_"
 
2148
                                          "instance_action"), locals())