~ubuntu-branches/ubuntu/precise/python-novaclient/precise

« back to all changes in this revision

Viewing changes to novaclient/shell.py

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2011-08-02 14:28:50 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20110802142850-b98ntnbobuwiapu3
Tags: 2.5.9~bzr65-0ubuntu1
* New upstream release.
* debian/watch: Add url for python-novaclient.
* dh_python2 transition.

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
import prettytable
28
28
import sys
29
29
import textwrap
 
30
import uuid
30
31
 
31
32
# Choices for flags.
32
33
DAY_CHOICES = [getattr(novaclient, i).lower()
96
97
            default=env('NOVA_API_KEY'),
97
98
            help='Defaults to env[NOVA_API_KEY].')
98
99
 
 
100
        self.parser.add_argument('--projectid',
 
101
            default=env('NOVA_PROJECT_ID'),
 
102
            help='Defaults to env[NOVA_PROJECT_ID].')
 
103
 
99
104
        auth_url = env('NOVA_URL')
100
105
        if auth_url == '':
101
106
            auth_url = 'https://auth.api.rackspacecloud.com/v1.0'
144
149
        if args.debug:
145
150
            httplib2.debuglevel = 1
146
151
 
147
 
        user, apikey, url = args.username, args.apikey, args.url
 
152
        user, apikey, projectid, url = args.username, args.apikey, \
 
153
                                       args.projectid, args.url
 
154
 
 
155
        #FIXME(usrleon): Here should be restrict for project id same as
 
156
        # for username or apikey but for compatibility it is not.
 
157
 
148
158
        if not user:
149
159
            raise CommandError("You must provide a username, either via "
150
160
                               "--username or via env[NOVA_USERNAME]")
152
162
            raise CommandError("You must provide an API key, either via "
153
163
                               "--apikey or via env[NOVA_API_KEY]")
154
164
 
155
 
        self.cs = self._api_class(user, apikey, url)
 
165
        self.cs = self._api_class(user, apikey, projectid, url)
156
166
        try:
157
167
            self.cs.authenticate()
158
168
        except novaclient.Unauthorized:
218
228
        server = self._find_server(args.server)
219
229
        server.backup_schedule.delete()
220
230
 
221
 
    @arg('--flavor',
222
 
         default=None,
223
 
         metavar='<flavor>',
224
 
         help="Flavor ID (see 'novaclient flavors'). "\
225
 
              "Defaults to 256MB RAM instance.")
226
 
    @arg('--image',
227
 
         default=None,
228
 
         metavar='<image>',
229
 
         help="Image ID (see 'novaclient images'). "\
230
 
              "Defaults to Ubuntu 10.04 LTS.")
231
 
    @arg('--ipgroup',
232
 
         default=None,
233
 
         metavar='<group>',
234
 
         help="IP group name or ID (see 'novaclient ipgroup-list').")
235
 
    @arg('--meta',
236
 
         metavar="<key=value>",
237
 
         action='append',
238
 
         default=[],
239
 
         help="Record arbitrary key/value metadata. "\
240
 
              "May be give multiple times.")
241
 
    @arg('--file',
242
 
         metavar="<dst-path=src-path>",
243
 
         action='append',
244
 
         dest='files',
245
 
         default=[],
246
 
         help="Store arbitrary files from <src-path> locally to <dst-path> "\
247
 
              "on the new server. You may store up to 5 files.")
248
 
    @arg('--key',
249
 
         metavar='<path>',
250
 
         nargs='?',
251
 
         const=AUTO_KEY,
252
 
         help="Key the server with an SSH keypair. "\
253
 
              "Looks in ~/.ssh for a key, "\
254
 
              "or takes an explicit <path> to one.")
255
 
    @arg('name', metavar='<name>', help='Name for the new server')
256
 
    def do_boot(self, args):
 
231
    def _boot(self, args, reservation_id=None, min_count=None, max_count=None):
257
232
        """Boot a new server."""
 
233
        if min_count is None:
 
234
            min_count = 1
 
235
        if max_count is None:
 
236
            max_count = min_count
 
237
        if min_count > max_count:
 
238
            raise CommandError("min_instances should be <= max_instances")
 
239
        if not min_count or not max_count:
 
240
            raise CommandError("min_instances nor max_instances should be 0")
 
241
 
258
242
        flavor = args.flavor or self.cs.flavors.find(ram=256)
259
243
        image = args.image or self.cs.images.find(name="Ubuntu 10.04 LTS "\
260
244
                                                       "(lucid)")
297
281
            except IOError, e:
298
282
                raise CommandError("Can't open '%s': %s" % (keyfile, e))
299
283
 
300
 
        server = self.cs.servers.create(args.name, image, flavor, ipgroup,
301
 
                                        metadata, files)
302
 
        print_dict(server._info)
 
284
        return (args.name, image, flavor, ipgroup, metadata, files,
 
285
                reservation_id, min_count, max_count)
 
286
 
 
287
    @arg('--flavor',
 
288
         default=None,
 
289
         metavar='<flavor>',
 
290
         help="Flavor ID (see 'novaclient flavors'). "\
 
291
              "Defaults to 256MB RAM instance.")
 
292
    @arg('--image',
 
293
         default=None,
 
294
         metavar='<image>',
 
295
         help="Image ID (see 'novaclient images'). "\
 
296
              "Defaults to Ubuntu 10.04 LTS.")
 
297
    @arg('--ipgroup',
 
298
         default=None,
 
299
         metavar='<group>',
 
300
         help="IP group name or ID (see 'novaclient ipgroup-list').")
 
301
    @arg('--meta',
 
302
         metavar="<key=value>",
 
303
         action='append',
 
304
         default=[],
 
305
         help="Record arbitrary key/value metadata. "\
 
306
              "May be give multiple times.")
 
307
    @arg('--file',
 
308
         metavar="<dst-path=src-path>",
 
309
         action='append',
 
310
         dest='files',
 
311
         default=[],
 
312
         help="Store arbitrary files from <src-path> locally to <dst-path> "\
 
313
              "on the new server. You may store up to 5 files.")
 
314
    @arg('--key',
 
315
         metavar='<path>',
 
316
         nargs='?',
 
317
         const=AUTO_KEY,
 
318
         help="Key the server with an SSH keypair. "\
 
319
              "Looks in ~/.ssh for a key, "\
 
320
              "or takes an explicit <path> to one.")
 
321
    @arg('name', metavar='<name>', help='Name for the new server')
 
322
    def do_boot(self, args):
 
323
        """Boot a new server."""
 
324
        name, image, flavor, ipgroup, metadata, files, reservation_id, \
 
325
                    min_count, max_count = self._boot(args)
 
326
 
 
327
        server = self.cs.servers.create(args.name, image, flavor,
 
328
                                        ipgroup=ipgroup,
 
329
                                        meta=metadata,
 
330
                                        files=files,
 
331
                                        min_count=min_count,
 
332
                                        max_count=max_count)
 
333
        print_dict(server._info)
 
334
 
 
335
    @arg('--flavor',
 
336
         default=None,
 
337
         metavar='<flavor>',
 
338
         help="Flavor ID (see 'novaclient flavors'). "\
 
339
              "Defaults to 256MB RAM instance.")
 
340
    @arg('--image',
 
341
         default=None,
 
342
         metavar='<image>',
 
343
         help="Image ID (see 'novaclient images'). "\
 
344
              "Defaults to Ubuntu 10.04 LTS.")
 
345
    @arg('--ipgroup',
 
346
         default=None,
 
347
         metavar='<group>',
 
348
         help="IP group name or ID (see 'novaclient ipgroup-list').")
 
349
    @arg('--meta',
 
350
         metavar="<key=value>",
 
351
         action='append',
 
352
         default=[],
 
353
         help="Record arbitrary key/value metadata. "\
 
354
              "May be give multiple times.")
 
355
    @arg('--file',
 
356
         metavar="<dst-path=src-path>",
 
357
         action='append',
 
358
         dest='files',
 
359
         default=[],
 
360
         help="Store arbitrary files from <src-path> locally to <dst-path> "\
 
361
              "on the new server. You may store up to 5 files.")
 
362
    @arg('--key',
 
363
         metavar='<path>',
 
364
         nargs='?',
 
365
         const=AUTO_KEY,
 
366
         help="Key the server with an SSH keypair. "\
 
367
              "Looks in ~/.ssh for a key, "\
 
368
              "or takes an explicit <path> to one.")
 
369
    @arg('account', metavar='<account>', help='Account to build this'\
 
370
         ' server for')
 
371
    @arg('name', metavar='<name>', help='Name for the new server')
 
372
    def do_boot_for_account(self, args):
 
373
        """Boot a new server in an account."""
 
374
        name, image, flavor, ipgroup, metadata, files, reservation_id, \
 
375
                min_count, max_count = self._boot(args)
 
376
 
 
377
        server = self.cs.accounts.create_instance_for(args.account, args.name,
 
378
                    image, flavor,
 
379
                    ipgroup=ipgroup,
 
380
                    meta=metadata,
 
381
                    files=files)
 
382
        print_dict(server._info)
 
383
 
 
384
    @arg('--flavor',
 
385
         default=None,
 
386
         metavar='<flavor>',
 
387
         help="Flavor ID (see 'novaclient flavors'). "\
 
388
              "Defaults to 256MB RAM instance.")
 
389
    @arg('--image',
 
390
         default=None,
 
391
         metavar='<image>',
 
392
         help="Image ID (see 'novaclient images'). "\
 
393
              "Defaults to Ubuntu 10.04 LTS.")
 
394
    @arg('--ipgroup',
 
395
         default=None,
 
396
         metavar='<group>',
 
397
         help="IP group name or ID (see 'novaclient ipgroup-list').")
 
398
    @arg('--meta',
 
399
         metavar="<key=value>",
 
400
         action='append',
 
401
         default=[],
 
402
         help="Record arbitrary key/value metadata. "\
 
403
              "May be give multiple times.")
 
404
    @arg('--file',
 
405
         metavar="<dst-path=src-path>",
 
406
         action='append',
 
407
         dest='files',
 
408
         default=[],
 
409
         help="Store arbitrary files from <src-path> locally to <dst-path> "\
 
410
              "on the new server. You may store up to 5 files.")
 
411
    @arg('--key',
 
412
         metavar='<path>',
 
413
         nargs='?',
 
414
         const=AUTO_KEY,
 
415
         help="Key the server with an SSH keypair. "\
 
416
              "Looks in ~/.ssh for a key, "\
 
417
              "or takes an explicit <path> to one.")
 
418
    @arg('--reservation_id',
 
419
         default=None,
 
420
         metavar='<reservation_id>',
 
421
         help="Reservation ID (a UUID). "\
 
422
              "If unspecified will be generated by the server.")
 
423
    @arg('--min_instances',
 
424
         default=None,
 
425
         type=int,
 
426
         metavar='<number>',
 
427
         help="The minimum number of instances to build. "\
 
428
                 "Defaults to 1.")
 
429
    @arg('--max_instances',
 
430
         default=None,
 
431
         type=int,
 
432
         metavar='<number>',
 
433
         help="The maximum number of instances to build. "\
 
434
                 "Defaults to 'min_instances' setting.")
 
435
    @arg('name', metavar='<name>', help='Name for the new server')
 
436
    def do_zone_boot(self, args):
 
437
        """Boot a new server, potentially across Zones."""
 
438
        reservation_id = args.reservation_id
 
439
        min_count = args.min_instances
 
440
        max_count = args.max_instances
 
441
        name, image, flavor, ipgroup, metadata, \
 
442
                files, reservation_id, min_count, max_count = \
 
443
                                 self._boot(args,
 
444
                                            reservation_id=reservation_id,
 
445
                                            min_count=min_count,
 
446
                                            max_count=max_count)
 
447
 
 
448
        reservation_id = self.cs.zones.boot(args.name, image, flavor,
 
449
                                            ipgroup=ipgroup,
 
450
                                            meta=metadata,
 
451
                                            files=files,
 
452
                                            reservation_id=reservation_id,
 
453
                                            min_count=min_count,
 
454
                                            max_count=max_count)
 
455
        print "Reservation ID=", reservation_id
 
456
 
 
457
    def _translate_flavor_keys(self, collection):
 
458
        convert = [('ram', 'memory_mb'), ('disk', 'local_gb')]
 
459
        for item in collection:
 
460
            keys = item.__dict__.keys()
 
461
            for from_key, to_key in convert:
 
462
                if from_key in keys and to_key not in keys:
 
463
                    setattr(item, to_key, item._info[from_key])
303
464
 
304
465
    def do_flavor_list(self, args):
305
466
        """Print a list of available 'flavors' (sizes of servers)."""
306
 
        print_list(self.cs.flavors.list(), [
 
467
        flavors = self.cs.flavors.list()
 
468
        self._translate_flavor_keys(flavors)
 
469
        print_list(flavors, [
307
470
            'ID',
308
471
            'Name',
309
472
            'Memory_MB',
318
481
        print_list(self.cs.images.list(), ['ID', 'Name', 'Status'])
319
482
 
320
483
    @arg('server', metavar='<server>', help='Name or ID of server.')
321
 
    @arg('name', metavar='<name>', help='Name for the new image.')
 
484
    @arg('name', metavar='<name>', help='Name of snapshot.')
322
485
    def do_image_create(self, args):
323
486
        """Create a new image by taking a snapshot of a running server."""
324
487
        server = self._find_server(args.server)
325
 
        image = self.cs.images.create(args.name, server)
 
488
        image = self.cs.images.create(server, args.name)
326
489
        print_dict(image._info)
327
490
 
328
491
    @arg('image', metavar='<image>', help='Name or ID of image.')
386
549
        """Delete an IP group."""
387
550
        self._find_ipgroup(args.group).delete()
388
551
 
 
552
    @arg('--fixed_ip',
 
553
        dest='fixed_ip',
 
554
        metavar='<fixed_ip>',
 
555
        default=None,
 
556
        help='Only match against fixed IP.')
 
557
    @arg('--reservation_id',
 
558
        dest='reservation_id',
 
559
        metavar='<reservation_id>',
 
560
        default=None,
 
561
        help='Only return instances that match reservation_id.')
 
562
    @arg('--recurse_zones',
 
563
        dest='recurse_zones',
 
564
        metavar='<0|1>',
 
565
        nargs='?',
 
566
        type=int,
 
567
        const=1,
 
568
        default=0,
 
569
        help='Recurse through all zones if set.')
 
570
    @arg('--ip',
 
571
        dest='ip',
 
572
        metavar='<ip_regexp>',
 
573
        default=None,
 
574
        help='Search with regular expression match by IP address')
 
575
    @arg('--ip6',
 
576
        dest='ip6',
 
577
        metavar='<ip6_regexp>',
 
578
        default=None,
 
579
        help='Search with regular expression match by IPv6 address')
 
580
    @arg('--server_name',
 
581
        dest='server_name',
 
582
        metavar='<name_regexp>',
 
583
        default=None,
 
584
        help='Search with regular expression match by server name')
 
585
    @arg('--name',
 
586
        dest='display_name',
 
587
        metavar='<name_regexp>',
 
588
        default=None,
 
589
        help='Search with regular expression match by display name')
 
590
    @arg('--instance_name',
 
591
        dest='name',
 
592
        metavar='<name_regexp>',
 
593
        default=None,
 
594
        help='Search with regular expression match by instance name')
389
595
    def do_list(self, args):
390
596
        """List active servers."""
391
 
        print_list(self.cs.servers.list(), ['ID', 'Name', 'Status',
392
 
                                            'Public IP', 'Private IP'])
 
597
        recurse_zones = args.recurse_zones
 
598
        search_opts = {
 
599
                'reservation_id': args.reservation_id,
 
600
                'fixed_ip': args.fixed_ip,
 
601
                'recurse_zones': recurse_zones,
 
602
                'ip': args.ip,
 
603
                'ip6': args.ip6,
 
604
                'name': args.name,
 
605
                'server_name': args.server_name,
 
606
                'display_name': args.display_name}
 
607
        if recurse_zones:
 
608
            to_print = ['UUID', 'Name', 'Status', 'Public IP', 'Private IP']
 
609
        else:
 
610
            to_print = ['ID', 'Name', 'Status', 'Public IP', 'Private IP']
 
611
        print_list(self.cs.servers.list(search_opts=search_opts),
 
612
                to_print)
393
613
 
394
614
    @arg('--hard',
395
615
        dest='reboot_type',
425
645
        server.resize(flavor)
426
646
 
427
647
    @arg('server', metavar='<server>', help='Name or ID of server.')
 
648
    @arg('name', metavar='<name>', help='Name of snapshot.')
 
649
    @arg('backup_type', metavar='<daily|weekly>', help='type of backup')
 
650
    @arg('rotation', type=int, metavar='<rotation>',
 
651
         help="Number of backups to retain. Used for backup image_type.")
 
652
    def do_backup(self, args):
 
653
        """Resize a server."""
 
654
        server = self._find_server(args.server)
 
655
        server.backup(args.name, args.backup_type, args.rotation)
 
656
 
 
657
    @arg('server', metavar='<server>', help='Name or ID of server.')
 
658
    def do_migrate(self, args):
 
659
        """Migrate a server."""
 
660
        self._find_server(args.server).migrate()
 
661
 
 
662
    @arg('server', metavar='<server>', help='Name or ID of server.')
428
663
    def do_pause(self, args):
429
664
        """Pause a server."""
430
665
        self._find_server(args.server).pause()
491
726
    @arg('server', metavar='<server>', help='Name or ID of server.')
492
727
    def do_show(self, args):
493
728
        """Show details about the given server."""
494
 
        s = self.cs.servers.get(self._find_server(args.server))
 
729
        s = self._find_server(args.server)
495
730
 
496
731
        info = s._info.copy()
497
732
        addresses = info.pop('addresses')
498
733
        for addrtype in addresses:
499
 
            info['%s ip' % addrtype] = ', '.join(addresses[addrtype])
 
734
            for address in  addresses[addrtype]: 
 
735
                info['%s ipv%s' % (addrtype, address['version'])] = address['addr']
500
736
 
501
 
        info['flavor'] = self._find_flavor(info.pop('flavorId')).name
502
 
        info['image'] = self._find_image(info.pop('imageId')).name
 
737
        flavorId = info.get('flavorId', None)
 
738
        if flavorId:
 
739
            info['flavor'] = self._find_flavor(info.pop('flavorId')).name
 
740
        imageId = info.get('imageId', None)
 
741
        if imageId:
 
742
            info['image'] = self._find_image(info.pop('imageId')).name
503
743
 
504
744
        print_dict(info)
505
745
 
514
754
    @arg('--zone_username', dest='zone_username', default=None,
515
755
                            help='New zone username.')
516
756
    @arg('--password', dest='password', default=None, help='New password.')
 
757
    @arg('--weight_offset', dest='weight_offset', default=None,
 
758
                            help='Child Zone weight offset.')
 
759
    @arg('--weight_scale', dest='weight_scale', default=None,
 
760
                            help='Child Zone weight scale.')
517
761
    def do_zone(self, args):
518
762
        """Show or edit a child zone. No zone arg for this zone."""
519
763
        zone = self.cs.zones.get(args.zone)
520
 
 
 
764
 
521
765
        # If we have some flags, update the zone
522
766
        zone_delta = {}
523
767
        if args.api_url:
526
770
            zone_delta['username'] = args.zone_username
527
771
        if args.password:
528
772
            zone_delta['password'] = args.password
 
773
        if args.weight_offset:
 
774
            zone_delta['weight_offset'] = args.weight_offset
 
775
        if args.weight_scale:
 
776
            zone_delta['weight_scale'] = args.weight_scale
529
777
        if zone_delta:
530
778
            zone.update(**zone_delta)
531
779
        else:
537
785
        print_dict(zone._info)
538
786
 
539
787
    @arg('api_url', metavar='<api_url>', help="URL for the Zone's API")
540
 
    @arg('zone_username', metavar='<zone_username>', 
 
788
    @arg('zone_username', metavar='<zone_username>',
541
789
                          help='Authentication username.')
542
790
    @arg('password', metavar='<password>', help='Authentication password.')
 
791
    @arg('weight_offset', metavar='<weight_offset>',
 
792
                            help='Child Zone weight offset (typically 0.0).')
 
793
    @arg('weight_scale', metavar='<weight_scale>',
 
794
                            help='Child Zone weight scale (typically 1.0).')
543
795
    def do_zone_add(self, args):
544
796
        """Add a new child zone."""
545
 
        zone = self.cs.zones.create(args.api_url, args.zone_username, 
546
 
                                                  args.password)
 
797
        zone = self.cs.zones.create(args.api_url, args.zone_username,
 
798
                                    args.password, args.weight_offset,
 
799
                                    args.weight_scale)
547
800
        print_dict(zone._info)
548
801
 
549
 
    @arg('zone', metavar='<zone name>', help='Name or ID of the zone')
 
802
    @arg('zone', metavar='<zone>', help='Name or ID of the zone')
550
803
    def do_zone_delete(self, args):
551
804
        """Delete a zone."""
552
805
        self.cs.zones.delete(args.zone)
553
806
 
554
807
    def do_zone_list(self, args):
555
808
        """List the children of a zone."""
556
 
        print_list(self.cs.zones.list(), ['ID', 'Name', 'Is Active',
557
 
                                            'Capabilities', 'API URL'])
 
809
        print_list(self.cs.zones.list(), ['ID', 'Name', 'Is Active', \
 
810
                            'API URL', 'Weight Offset', 'Weight Scale'])
 
811
 
 
812
    @arg('server', metavar='<server>', help='Name or ID of server.')
 
813
    @arg('network_id', metavar='<network_id>', help='Network ID.')
 
814
    def do_add_fixed_ip(self, args):
 
815
        """Add new IP address to network."""
 
816
        server = self._find_server(args.server)
 
817
        server.add_fixed_ip(args.network_id)
 
818
 
 
819
    @arg('server', metavar='<server>', help='Name or ID of server.')
 
820
    @arg('address', metavar='<address>', help='IP Address.')
 
821
    def do_remove_fixed_ip(self, args):
 
822
        """Remove an IP address from a server."""
 
823
        server = self._find_server(args.server)
 
824
        server.remove_fixed_ip(args.address)
558
825
 
559
826
    def _find_server(self, server):
560
827
        """Get a server by name or ID."""
580
847
        try:
581
848
            if isinstance(name_or_id, int) or name_or_id.isdigit():
582
849
                return manager.get(int(name_or_id))
583
 
            else:
 
850
 
 
851
            try:
 
852
                uuid.UUID(name_or_id)
 
853
                return manager.get(name_or_id)
 
854
            except ValueError:
584
855
                return manager.find(name=name_or_id)
585
856
        except novaclient.NotFound:
586
857
            raise CommandError("No %s with a name or ID of '%s' exists." %
606
877
            if field in formatters:
607
878
                row.append(formatters[field](o))
608
879
            else:
609
 
                row.append(getattr(o, field.lower().replace(' ', '_'), ''))
 
880
                field_name = field.lower().replace(' ', '_')
 
881
                data = getattr(o, field_name, '')
 
882
                row.append(data)
610
883
        pt.add_row(row)
611
884
 
612
885
    pt.printt(sortby=fields[0])