~nova-coresec/nova/ppa-lucid

« back to all changes in this revision

Viewing changes to nova/virt/libvirt_conn.py

  • Committer: Soren Hansen
  • Date: 2010-10-29 13:04:56 UTC
  • mfrom: (181.1.207 nova)
  • Revision ID: soren.hansen@rackspace.com-20101029130456-ygryth9050csgar4
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
48
48
 
49
49
 
50
50
FLAGS = flags.FLAGS
 
51
flags.DEFINE_string('libvirt_rescue_xml_template',
 
52
                    utils.abspath('virt/libvirt.rescue.qemu.xml.template'),
 
53
                    'Libvirt RESCUE XML Template for QEmu/KVM')
 
54
flags.DEFINE_string('libvirt_rescue_xen_xml_template',
 
55
                    utils.abspath('virt/libvirt.rescue.xen.xml.template'),
 
56
                    'Libvirt RESCUE XML Template for xen')
 
57
flags.DEFINE_string('libvirt_rescue_uml_xml_template',
 
58
                    utils.abspath('virt/libvirt.rescue.uml.xml.template'),
 
59
                    'Libvirt RESCUE XML Template for user-mode-linux')
 
60
# TODO(vish): These flags should probably go into a shared location
 
61
flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image')
 
62
flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image')
 
63
flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image')
51
64
flags.DEFINE_string('libvirt_xml_template',
52
65
                    utils.abspath('virt/libvirt.qemu.xml.template'),
53
66
                    'Libvirt XML Template for QEmu/KVM')
62
75
                    'Template file for injected network')
63
76
flags.DEFINE_string('libvirt_type',
64
77
                    'kvm',
65
 
                    'Libvirt domain type (valid options are: kvm, qemu, uml, xen)')
 
78
                    'Libvirt domain type (valid options are: '
 
79
                    'kvm, qemu, uml, xen)')
66
80
flags.DEFINE_string('libvirt_uri',
67
81
                    '',
68
82
                    'Override the default libvirt URI (which is dependent'
86
100
 
87
101
class LibvirtConnection(object):
88
102
    def __init__(self, read_only):
89
 
        self.libvirt_uri, template_file = self.get_uri_and_template()
 
103
        (self.libvirt_uri,
 
104
         template_file,
 
105
         rescue_file) = self.get_uri_and_templates()
90
106
 
91
107
        self.libvirt_xml = open(template_file).read()
 
108
        self.rescue_xml = open(rescue_file).read()
92
109
        self._wrapped_conn = None
93
110
        self.read_only = read_only
94
111
 
96
113
    def _conn(self):
97
114
        if not self._wrapped_conn or not self._test_connection():
98
115
            logging.debug('Connecting to libvirt: %s' % self.libvirt_uri)
99
 
            self._wrapped_conn = self._connect(self.libvirt_uri, self.read_only)
 
116
            self._wrapped_conn = self._connect(self.libvirt_uri,
 
117
                                               self.read_only)
100
118
        return self._wrapped_conn
101
119
 
102
120
    def _test_connection(self):
110
128
                return False
111
129
            raise
112
130
 
113
 
    def get_uri_and_template(self):
 
131
    def get_uri_and_templates(self):
114
132
        if FLAGS.libvirt_type == 'uml':
115
133
            uri = FLAGS.libvirt_uri or 'uml:///system'
116
134
            template_file = FLAGS.libvirt_uml_xml_template
 
135
            rescue_file = FLAGS.libvirt_rescue_uml_xml_template
117
136
        elif FLAGS.libvirt_type == 'xen':
118
137
            uri = FLAGS.libvirt_uri or 'xen:///'
119
138
            template_file = FLAGS.libvirt_xen_xml_template
 
139
            rescue_file = FLAGS.libvirt_rescue_xen_xml_template
120
140
        else:
121
141
            uri = FLAGS.libvirt_uri or 'qemu:///system'
122
142
            template_file = FLAGS.libvirt_xml_template
123
 
        return uri, template_file
 
143
            rescue_file = FLAGS.libvirt_rescue_xml_template
 
144
        return uri, template_file, rescue_file
124
145
 
125
146
    def _connect(self, uri, read_only):
126
147
        auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],
136
157
        return [self._conn.lookupByID(x).name()
137
158
                for x in self._conn.listDomainsID()]
138
159
 
139
 
    def destroy(self, instance):
 
160
    def destroy(self, instance, cleanup=True):
140
161
        try:
141
162
            virt_dom = self._conn.lookupByName(instance['name'])
142
163
            virt_dom.destroy()
144
165
            pass
145
166
            # If the instance is already terminated, we're still happy
146
167
        d = defer.Deferred()
147
 
        d.addCallback(lambda _: self._cleanup(instance))
 
168
        if cleanup:
 
169
            d.addCallback(lambda _: self._cleanup(instance))
148
170
        # FIXME: What does this comment mean?
149
171
        # TODO(termie): short-circuit me for tests
150
 
       # WE'LL save this for when we do shutdown,
 
172
        # WE'LL save this for when we do shutdown,
151
173
        # instead of destroy - but destroy returns immediately
152
174
        timer = task.LoopingCall(f=None)
 
175
 
153
176
        def _wait_for_shutdown():
154
177
            try:
155
178
                state = self.get_info(instance['name'])['state']
164
187
                                      power_state.SHUTDOWN)
165
188
                timer.stop()
166
189
                d.callback(None)
 
190
 
167
191
        timer.f = _wait_for_shutdown
168
192
        timer.start(interval=0.5, now=True)
169
193
        return d
195
219
    @defer.inlineCallbacks
196
220
    @exception.wrap_exception
197
221
    def reboot(self, instance):
 
222
        yield self.destroy(instance, False)
198
223
        xml = self.to_xml(instance)
199
 
        yield self._conn.lookupByName(instance['name']).destroy()
200
224
        yield self._conn.createXML(xml, 0)
201
225
 
202
226
        d = defer.Deferred()
203
227
        timer = task.LoopingCall(f=None)
 
228
 
204
229
        def _wait_for_reboot():
205
230
            try:
206
231
                state = self.get_info(instance['name'])['state']
217
242
                                      power_state.SHUTDOWN)
218
243
                timer.stop()
219
244
                d.callback(None)
 
245
 
220
246
        timer.f = _wait_for_reboot
221
247
        timer.start(interval=0.5, now=True)
222
248
        yield d
223
249
 
224
250
    @defer.inlineCallbacks
225
251
    @exception.wrap_exception
 
252
    def rescue(self, instance):
 
253
        yield self.destroy(instance, False)
 
254
 
 
255
        xml = self.to_xml(instance, rescue=True)
 
256
        rescue_images = {'image_id': FLAGS.rescue_image_id,
 
257
                         'kernel_id': FLAGS.rescue_kernel_id,
 
258
                         'ramdisk_id': FLAGS.rescue_ramdisk_id}
 
259
        yield self._create_image(instance, xml, 'rescue-', rescue_images)
 
260
        yield self._conn.createXML(xml, 0)
 
261
 
 
262
        d = defer.Deferred()
 
263
        timer = task.LoopingCall(f=None)
 
264
 
 
265
        def _wait_for_rescue():
 
266
            try:
 
267
                state = self.get_info(instance['name'])['state']
 
268
                db.instance_set_state(None, instance['id'], state)
 
269
                if state == power_state.RUNNING:
 
270
                    logging.debug('instance %s: rescued', instance['name'])
 
271
                    timer.stop()
 
272
                    d.callback(None)
 
273
            except Exception, exn:
 
274
                logging.error('_wait_for_rescue failed: %s', exn)
 
275
                db.instance_set_state(None,
 
276
                                      instance['id'],
 
277
                                      power_state.SHUTDOWN)
 
278
                timer.stop()
 
279
                d.callback(None)
 
280
 
 
281
        timer.f = _wait_for_rescue
 
282
        timer.start(interval=0.5, now=True)
 
283
        yield d
 
284
 
 
285
    @defer.inlineCallbacks
 
286
    @exception.wrap_exception
 
287
    def unrescue(self, instance):
 
288
        # NOTE(vish): Because reboot destroys and recreates an instance using
 
289
        #             the normal xml file, we can just call reboot here
 
290
        yield self.reboot(instance)
 
291
 
 
292
    @defer.inlineCallbacks
 
293
    @exception.wrap_exception
226
294
    def spawn(self, instance):
227
295
        xml = self.to_xml(instance)
228
296
        db.instance_set_state(context.get_admin_context(),
229
297
                              instance['id'],
230
298
                              power_state.NOSTATE,
231
299
                              'launching')
232
 
        yield NWFilterFirewall(self._conn).setup_nwfilters_for_instance(instance)
 
300
        yield NWFilterFirewall(self._conn).\
 
301
              setup_nwfilters_for_instance(instance)
233
302
        yield self._create_image(instance, xml)
234
303
        yield self._conn.createXML(xml, 0)
235
 
        # TODO(termie): this should actually register
236
 
        # a callback to check for successful boot
237
304
        logging.debug("instance %s: is running", instance['name'])
238
305
 
239
306
        local_d = defer.Deferred()
240
307
        timer = task.LoopingCall(f=None)
 
308
 
241
309
        def _wait_for_boot():
242
310
            try:
243
311
                state = self.get_info(instance['name'])['state']
265
333
 
266
334
        if virsh_output.startswith('/dev/'):
267
335
            logging.info('cool, it\'s a device')
268
 
            d = process.simple_execute("sudo dd if=%s iflag=nonblock" % virsh_output, check_exit_code=False)
269
 
            d.addCallback(lambda r:r[0])
 
336
            d = process.simple_execute("sudo dd if=%s iflag=nonblock" %
 
337
                                       virsh_output, check_exit_code=False)
 
338
            d.addCallback(lambda r: r[0])
270
339
            return d
271
340
        else:
272
341
            return ''
285
354
 
286
355
    @exception.wrap_exception
287
356
    def get_console_output(self, instance):
288
 
        console_log = os.path.join(FLAGS.instances_path, instance['name'], 'console.log')
289
 
        d = process.simple_execute('sudo chown %d %s' % (os.getuid(), console_log))
 
357
        console_log = os.path.join(FLAGS.instances_path, instance['name'],
 
358
                                   'console.log')
 
359
        d = process.simple_execute('sudo chown %d %s' % (os.getuid(),
 
360
                                   console_log))
290
361
        if FLAGS.libvirt_type == 'xen':
291
362
            # Xen is spethial
292
 
            d.addCallback(lambda _: process.simple_execute("virsh ttyconsole %s" % instance['name']))
 
363
            d.addCallback(lambda _:
 
364
                process.simple_execute("virsh ttyconsole %s" %
 
365
                                       instance['name']))
293
366
            d.addCallback(self._flush_xen_console)
294
367
            d.addCallback(self._append_to_file, console_log)
295
368
        else:
297
370
        d.addCallback(self._dump_file)
298
371
        return d
299
372
 
300
 
 
301
373
    @defer.inlineCallbacks
302
 
    def _create_image(self, inst, libvirt_xml):
 
374
    def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None):
303
375
        # syntactic nicety
304
 
        basepath = lambda fname='': os.path.join(FLAGS.instances_path,
 
376
        basepath = lambda fname='', prefix=prefix: os.path.join(
 
377
                                                 FLAGS.instances_path,
305
378
                                                 inst['name'],
306
 
                                                 fname)
 
379
                                                 prefix + fname)
307
380
 
308
381
        # ensure directories exist and are writable
309
 
        yield process.simple_execute('mkdir -p %s' % basepath())
310
 
        yield process.simple_execute('chmod 0777 %s' % basepath())
311
 
 
 
382
        yield process.simple_execute('mkdir -p %s' % basepath(prefix=''))
 
383
        yield process.simple_execute('chmod 0777 %s' % basepath(prefix=''))
312
384
 
313
385
        # TODO(termie): these are blocking calls, it would be great
314
386
        #               if they weren't.
317
389
        f.write(libvirt_xml)
318
390
        f.close()
319
391
 
320
 
        os.close(os.open(basepath('console.log'), os.O_CREAT | os.O_WRONLY, 0660))
 
392
        # NOTE(vish): No need add the prefix to console.log
 
393
        os.close(os.open(basepath('console.log', ''),
 
394
                         os.O_CREAT | os.O_WRONLY, 0660))
321
395
 
322
396
        user = manager.AuthManager().get_user(inst['user_id'])
323
397
        project = manager.AuthManager().get_project(inst['project_id'])
324
398
 
 
399
        if not disk_images:
 
400
            disk_images = {'image_id': inst['image_id'],
 
401
                           'kernel_id': inst['kernel_id'],
 
402
                           'ramdisk_id': inst['ramdisk_id']}
325
403
        if not os.path.exists(basepath('disk')):
326
 
           yield images.fetch(inst.image_id, basepath('disk-raw'), user, project)
 
404
            yield images.fetch(inst.image_id, basepath('disk-raw'), user,
 
405
                               project)
327
406
        if not os.path.exists(basepath('kernel')):
328
 
           yield images.fetch(inst.kernel_id, basepath('kernel'), user, project)
 
407
            yield images.fetch(inst.kernel_id, basepath('kernel'), user,
 
408
                               project)
329
409
        if not os.path.exists(basepath('ramdisk')):
330
 
           yield images.fetch(inst.ramdisk_id, basepath('ramdisk'), user, project)
 
410
            yield images.fetch(inst.ramdisk_id, basepath('ramdisk'), user,
 
411
                               project)
331
412
 
332
413
        execute = lambda cmd, process_input=None, check_exit_code=True: \
333
414
                  process.simple_execute(cmd=cmd,
339
420
        network_ref = db.network_get_by_instance(context.get_admin_context(),
340
421
                                                 inst['id'])
341
422
        if network_ref['injected']:
342
 
            address = db.instance_get_fixed_address(context.get_admin_context(),
343
 
                                                    inst['id'])
 
423
            admin_context = context.get_admin_context()
 
424
            address = db.instance_get_fixed_address(admin_context, inst['id'])
344
425
            with open(FLAGS.injected_network_template) as f:
345
426
                net = f.read() % {'address': address,
346
427
                                  'netmask': network_ref['netmask'],
354
435
            if net:
355
436
                logging.info('instance %s: injecting net into image %s',
356
437
                    inst['name'], inst.image_id)
357
 
            yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute)
 
438
            yield disk.inject_data(basepath('disk-raw'), key, net,
 
439
                                   execute=execute)
358
440
 
359
441
        if os.path.exists(basepath('disk')):
360
442
            yield process.simple_execute('rm -f %s' % basepath('disk'))
363
445
                                                    ['local_gb']
364
446
                                                    * 1024 * 1024 * 1024)
365
447
 
366
 
        resize = inst['instance_type'] != 'm1.tiny'
 
448
        resize = True
 
449
        if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
 
450
            resize = False
367
451
        yield disk.partition(basepath('disk-raw'), basepath('disk'),
368
452
                             local_bytes, resize, execute=execute)
369
453
 
371
455
            yield process.simple_execute('sudo chown root %s' %
372
456
                                         basepath('disk'))
373
457
 
374
 
    def to_xml(self, instance):
 
458
    def to_xml(self, instance, rescue=False):
375
459
        # TODO(termie): cache?
376
460
        logging.debug('instance %s: starting toXML method', instance['name'])
377
461
        network = db.project_get_network(context.get_admin_context(),
378
462
                                         instance['project_id'])
379
463
        # FIXME(vish): stick this in db
380
 
        instance_type = instance_types.INSTANCE_TYPES[instance['instance_type']]
 
464
        instance_type = instance['instance_type']
 
465
        instance_type = instance_types.INSTANCE_TYPES[instance_type]
381
466
        ip_address = db.instance_get_fixed_address(context.get_admin_context(),
382
467
                                                   instance['id'])
383
468
        # Assume that the gateway also acts as the dhcp server.
391
476
                    'bridge_name': network['bridge'],
392
477
                    'mac_address': instance['mac_address'],
393
478
                    'ip_address': ip_address,
394
 
                    'dhcp_server': dhcp_server }
395
 
        libvirt_xml = self.libvirt_xml % xml_info
 
479
                    'dhcp_server': dhcp_server}
 
480
        if rescue:
 
481
            libvirt_xml = self.rescue_xml % xml_info
 
482
        else:
 
483
            libvirt_xml = self.libvirt_xml % xml_info
396
484
        logging.debug('instance %s: finished toXML method', instance['name'])
397
485
 
398
486
        return libvirt_xml
399
487
 
400
488
    def get_info(self, instance_name):
401
 
        virt_dom = self._conn.lookupByName(instance_name)
 
489
        try:
 
490
            virt_dom = self._conn.lookupByName(instance_name)
 
491
        except:
 
492
            raise exception.NotFound("Instance %s not found" % instance_name)
402
493
        (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
403
494
        return {'state': state,
404
495
                'max_mem': max_mem,
506
597
        domain = self._conn.lookupByName(instance_name)
507
598
        return domain.interfaceStats(interface)
508
599
 
509
 
 
510
600
    def refresh_security_group(self, security_group_id):
511
601
        fw = NWFilterFirewall(self._conn)
512
602
        fw.ensure_security_group_filter(security_group_id)
557
647
    def __init__(self, get_connection):
558
648
        self._conn = get_connection
559
649
 
560
 
 
561
650
    nova_base_filter = '''<filter name='nova-base' chain='root'>
562
651
                            <uuid>26717364-50cf-42d1-8185-29bf893ab110</uuid>
563
652
                            <filterref filter='no-mac-spoofing'/>
578
667
                                     srcportstart='68'
579
668
                                     dstportstart='67'/>
580
669
                              </rule>
581
 
                              <rule action='accept' direction='in' priority='100'>
 
670
                              <rule action='accept' direction='in'
 
671
                                    priority='100'>
582
672
                                <udp srcipaddr='$DHCPSERVER'
583
673
                                     srcportstart='67'
584
674
                                     dstportstart='68'/>
588
678
    def nova_base_ipv4_filter(self):
589
679
        retval = "<filter name='nova-base-ipv4' chain='ipv4'>"
590
680
        for protocol in ['tcp', 'udp', 'icmp']:
591
 
            for direction,action,priority in [('out','accept', 399),
592
 
                                     ('inout','drop', 400)]:
 
681
            for direction, action, priority in [('out', 'accept', 399),
 
682
                                                ('inout', 'drop', 400)]:
593
683
                retval += """<rule action='%s' direction='%s' priority='%d'>
594
684
                               <%s />
595
685
                             </rule>""" % (action, direction,
597
687
        retval += '</filter>'
598
688
        return retval
599
689
 
600
 
 
601
690
    def nova_base_ipv6_filter(self):
602
691
        retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
603
692
        for protocol in ['tcp', 'udp', 'icmp']:
604
 
            for direction,action,priority in [('out','accept',399),
605
 
                                     ('inout','drop',400)]:
 
693
            for direction, action, priority in [('out', 'accept', 399),
 
694
                                                ('inout', 'drop', 400)]:
606
695
                retval += """<rule action='%s' direction='%s' priority='%d'>
607
696
                               <%s-ipv6 />
608
697
                             </rule>""" % (action, direction,
610
699
        retval += '</filter>'
611
700
        return retval
612
701
 
613
 
 
614
702
    def nova_project_filter(self, project, net, mask):
615
703
        retval = "<filter name='nova-project-%s' chain='ipv4'>" % project
616
704
        for protocol in ['tcp', 'udp', 'icmp']:
620
708
        retval += '</filter>'
621
709
        return retval
622
710
 
623
 
 
624
711
    def _define_filter(self, xml):
625
712
        if callable(xml):
626
713
            xml = xml()
627
714
        d = threads.deferToThread(self._conn.nwfilterDefineXML, xml)
628
715
        return d
629
716
 
630
 
 
631
717
    @staticmethod
632
718
    def _get_net_and_mask(cidr):
633
719
        net = IPy.IP(cidr)
646
732
        yield self._define_filter(self.nova_dhcp_filter)
647
733
        yield self._define_filter(self.nova_base_filter)
648
734
 
649
 
        nwfilter_xml = ("<filter name='nova-instance-%s' chain='root'>\n" +
650
 
                         "  <filterref filter='nova-base' />\n"
651
 
                       ) % instance['name']
 
735
        nwfilter_xml = "<filter name='nova-instance-%s' chain='root'>\n" \
 
736
                       "  <filterref filter='nova-base' />\n" % \
 
737
                       instance['name']
652
738
 
653
739
        if FLAGS.allow_project_net_traffic:
654
740
            network_ref = db.project_get_network(context.get_admin_context(),
658
744
                                                      net, mask)
659
745
            yield self._define_filter(project_filter)
660
746
 
661
 
            nwfilter_xml += ("  <filterref filter='nova-project-%s' />\n"
662
 
                            ) % instance['project_id']
 
747
            nwfilter_xml += "  <filterref filter='nova-project-%s' />\n" % \
 
748
                            instance['project_id']
663
749
 
664
750
        for security_group in instance.security_groups:
665
751
            yield self.ensure_security_group_filter(security_group['id'])
666
752
 
667
 
            nwfilter_xml += ("  <filterref filter='nova-secgroup-%d' />\n"
668
 
                            ) % security_group['id']
 
753
            nwfilter_xml += "  <filterref filter='nova-secgroup-%d' />\n" % \
 
754
                            security_group['id']
669
755
        nwfilter_xml += "</filter>"
670
756
 
671
757
        yield self._define_filter(nwfilter_xml)
675
761
        return self._define_filter(
676
762
                   self.security_group_to_nwfilter_xml(security_group_id))
677
763
 
678
 
 
679
764
    def security_group_to_nwfilter_xml(self, security_group_id):
680
765
        security_group = db.security_group_get(context.get_admin_context(),
681
766
                                               security_group_id)
684
769
            rule_xml += "<rule action='accept' direction='in' priority='300'>"
685
770
            if rule.cidr:
686
771
                net, mask = self._get_net_and_mask(rule.cidr)
687
 
                rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % (rule.protocol, net, mask)
 
772
                rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
 
773
                            (rule.protocol, net, mask)
688
774
                if rule.protocol in ['tcp', 'udp']:
689
775
                    rule_xml += "dstportstart='%s' dstportend='%s' " % \
690
776
                                (rule.from_port, rule.to_port)
691
777
                elif rule.protocol == 'icmp':
692
 
                    logging.info('rule.protocol: %r, rule.from_port: %r, rule.to_port: %r' % (rule.protocol, rule.from_port, rule.to_port))
 
778
                    logging.info('rule.protocol: %r, rule.from_port: %r, '
 
779
                                 'rule.to_port: %r' %
 
780
                                 (rule.protocol, rule.from_port, rule.to_port))
693
781
                    if rule.from_port != -1:
694
782
                        rule_xml += "type='%s' " % rule.from_port
695
783
                    if rule.to_port != -1:
697
785
 
698
786
                rule_xml += '/>\n'
699
787
            rule_xml += "</rule>\n"
700
 
        xml = '''<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>''' % (security_group_id, rule_xml,)
 
788
        xml = "<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>" % \
 
789
              (security_group_id, rule_xml,)
701
790
        return xml