Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 852a22e7

History | View | Annotate | Download (19.9 kB)

1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from kamaki.cli import command
35
from kamaki.cli.command_tree import CommandTree
36
from kamaki.cli.utils import print_dict, print_items, print_list, bold
37
from kamaki.cli.errors import CLIError, raiseCLIError, CLISyntaxError
38
from kamaki.clients.cyclades import CycladesClient, ClientError
39
from kamaki.cli.argument import FlagArgument, ValueArgument
40
from kamaki.cli.argument import ProgressBarArgument
41
from kamaki.cli.commands import _command_init
42

    
43
from base64 import b64encode
44
from os.path import exists
45

    
46

    
47
server_cmds = CommandTree('server',
48
    'Compute/Cyclades API server commands')
49
flavor_cmds = CommandTree('flavor',
50
    'Compute/Cyclades API flavor commands')
51
image_cmds = CommandTree('image',
52
    'Compute/Cyclades or Glance API image commands')
53
network_cmds = CommandTree('network',
54
    'Compute/Cyclades API network commands')
55
_commands = [server_cmds, flavor_cmds, image_cmds, network_cmds]
56

    
57

    
58
class _init_cyclades(_command_init):
59
    def main(self, service='compute'):
60
        token = self.config.get(service, 'token')\
61
            or self.config.get('global', 'token')
62
        base_url = self.config.get(service, 'url')\
63
            or self.config.get('global', 'url')
64
        self.client = CycladesClient(base_url=base_url, token=token)
65

    
66

    
67
@command(server_cmds)
68
class server_list(_init_cyclades):
69
    """List servers"""
70

    
71
    def __init__(self, arguments={}):
72
        super(server_list, self).__init__(arguments)
73
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
74

    
75
    def _info_print(self, server):
76
        addr_dict = {}
77
        if 'attachments' in server:
78
            for addr in server['attachments']['values']:
79
                ips = addr.pop('values', [])
80
                for ip in ips:
81
                    addr['IPv%s' % ip['version']] = ip['addr']
82
                if 'firewallProfile' in addr:
83
                    addr['firewall'] = addr.pop('firewallProfile')
84
                addr_dict[addr.pop('id')] = addr
85
            server['attachments'] = addr_dict if addr_dict is not {} else None
86
        if 'metadata' in server:
87
            server['metadata'] = server['metadata']['values']
88
        print_dict(server, ident=2)
89

    
90
    def _print(self, servers):
91
        for server in servers:
92
            sname = server.pop('name')
93
            sid = server.pop('id')
94
            print('%s (%s)' % (sid, bold(sname)))
95
            if self.get_argument('detail'):
96
                self._info_print(server)
97
                print(' ')
98

    
99
    def main(self):
100
        super(self.__class__, self).main()
101
        try:
102
            servers = self.client.list_servers(self.get_argument('detail'))
103
            self._print(servers)
104
        except ClientError as err:
105
            raiseCLIError(err)
106

    
107

    
108
@command(server_cmds)
109
class server_info(_init_cyclades):
110
    """Get server details"""
111

    
112
    @classmethod
113
    def _print(self, server):
114
        addr_dict = {}
115
        if 'attachments' in server:
116
            atts = server.pop('attachments')
117
            for addr in atts['values']:
118
                ips = addr.pop('values', [])
119
                for ip in ips:
120
                    addr['IPv%s' % ip['version']] = ip['addr']
121
                if 'firewallProfile' in addr:
122
                    addr['firewall'] = addr.pop('firewallProfile')
123
                addr_dict[addr.pop('id')] = addr
124
            server['attachments'] = addr_dict if addr_dict else None
125
        if 'metadata' in server:
126
            server['metadata'] = server['metadata']['values']
127
        print_dict(server, ident=2)
128

    
129
    def main(self, server_id):
130
        super(self.__class__, self).main()
131
        try:
132
            server = self.client.get_server_details(int(server_id))
133
        except ClientError as err:
134
            raiseCLIError(err)
135
        except ValueError as err:
136
            raise CLIError(message='Server id must be positive integer',
137
                importance=1)
138
        self._print(server)
139

    
140

    
141
class PersonalityArgument(ValueArgument):
142
    @property
143
    def value(self):
144
        return [self._value] if hasattr(self, '_value') else []
145

    
146
    @value.setter
147
    def value(self, newvalue):
148
        if newvalue == self.default:
149
            return self.value
150
        termlist = newvalue.split()
151
        if len(termlist) > 4:
152
                raise CLISyntaxError(details='Wrong number of terms'\
153
                    + ' ("PATH [OWNER [GROUP [MODE]]]"')
154
        path = termlist[0]
155
        self._value = dict(path=path)
156
        if not exists(path):
157
            raise CLIError(message="File %s does not exist" % path,
158
                importance=1)
159
        with open(path) as f:
160
            self._value['contents'] = b64encode(f.read())
161
        try:
162
            self._value['owner'] = termlist[1]
163
            self._value['group'] = termlist[2]
164
            self._value['mode'] = termlist[3]
165
        except IndexError:
166
            pass
167

    
168

    
169
@command(server_cmds)
170
class server_create(_init_cyclades):
171
    """Create a server"""
172

    
173
    def __init__(self, arguments={}):
174
        super(server_create, self).__init__(arguments)
175
        self.arguments['personality'] = PersonalityArgument(\
176
            help='add a personality file ( "PATH [OWNER [GROUP [MODE]]]" )',
177
            parsed_name='--personality')
178

    
179
    def update_parser(self, parser):
180
        parser.add_argument('--personality', dest='personalities',
181
                          action='append', default=[],
182
                          metavar='PATH[,SERVER PATH[,OWNER[,GROUP,[MODE]]]]',
183
                          help='add a personality file')
184

    
185
    def main(self, name, flavor_id, image_id):
186
        super(self.__class__, self).main()
187
        try:
188
            reply = self.client.create_server(name,
189
                int(flavor_id),
190
                image_id,
191
                self.get_argument('personality'))
192
        except ClientError as err:
193
            raiseCLIError(err)
194
        except ValueError as err:
195
            raise CLIError('Invalid flavor id %s ' % flavor_id,
196
                details='Flavor id must be a positive integer',
197
                importance=1)
198
        except Exception as err:
199
            raise CLIError('Syntax error: %s\n' % err, importance=1)
200
        print_dict(reply)
201

    
202

    
203
@command(server_cmds)
204
class server_rename(_init_cyclades):
205
    """Update a server's name"""
206

    
207
    def main(self, server_id, new_name):
208
        super(self.__class__, self).main()
209
        try:
210
            self.client.update_server_name(int(server_id), new_name)
211
        except ClientError as err:
212
            raiseCLIError(err)
213
        except ValueError:
214
            raise CLIError('Invalid server id %s ' % server_id,
215
                details='Server id must be positive integer\n',
216
                importance=1)
217

    
218

    
219
@command(server_cmds)
220
class server_delete(_init_cyclades):
221
    """Delete a server"""
222

    
223
    def main(self, server_id):
224
        super(self.__class__, self).main()
225
        try:
226
            self.client.delete_server(int(server_id))
227
        except ClientError as err:
228
            raiseCLIError(err)
229
        except ValueError:
230
            raise CLIError(message='Server id must be positive integer',
231
                importance=1)
232

    
233

    
234
@command(server_cmds)
235
class server_reboot(_init_cyclades):
236
    """Reboot a server"""
237

    
238
    def __init__(self, arguments={}):
239
        super(server_reboot, self).__init__(arguments)
240
        self.arguments['hard'] = FlagArgument('perform a hard reboot', '-f')
241

    
242
    def main(self, server_id):
243
        super(self.__class__, self).main()
244
        try:
245
            self.client.reboot_server(int(server_id),
246
                self.get_argument('hard'))
247
        except ClientError as err:
248
            raiseCLIError(err)
249
        except ValueError:
250
            raise CLIError(message='Server id must be positive integer',
251
                importance=1)
252

    
253

    
254
@command(server_cmds)
255
class server_start(_init_cyclades):
256
    """Start a server"""
257

    
258
    def main(self, server_id):
259
        super(self.__class__, self).main()
260
        try:
261
            self.client.start_server(int(server_id))
262
        except ClientError as err:
263
            raiseCLIError(err)
264
        except ValueError:
265
            raise CLIError(message='Server id must be positive integer',
266
                importance=1)
267

    
268

    
269
@command(server_cmds)
270
class server_shutdown(_init_cyclades):
271
    """Shutdown a server"""
272

    
273
    def main(self, server_id):
274
        super(self.__class__, self).main()
275
        try:
276
            self.client.shutdown_server(int(server_id))
277
        except ClientError as err:
278
            raiseCLIError(err)
279
        except ValueError:
280
            raise CLIError(message='Server id must be positive integer',
281
                importance=1)
282

    
283

    
284
@command(server_cmds)
285
class server_console(_init_cyclades):
286
    """Get a VNC console"""
287

    
288
    def main(self, server_id):
289
        super(self.__class__, self).main()
290
        try:
291
            reply = self.client.get_server_console(int(server_id))
292
        except ClientError as err:
293
            raiseCLIError(err)
294
        except ValueError:
295
            raise CLIError(message='Server id must be positive integer',
296
                importance=1)
297
        print_dict(reply)
298

    
299

    
300
@command(server_cmds)
301
class server_firewall(_init_cyclades):
302
    """Set the server's firewall profile"""
303

    
304
    def main(self, server_id, profile):
305
        super(self.__class__, self).main()
306
        try:
307
            self.client.set_firewall_profile(int(server_id), profile)
308
        except ClientError as err:
309
            raiseCLIError(err)
310
        except ValueError:
311
            raise CLIError(message='Server id must be positive integer',
312
                importance=1)
313

    
314

    
315
@command(server_cmds)
316
class server_addr(_init_cyclades):
317
    """List a server's nic address"""
318

    
319
    def main(self, server_id):
320
        super(self.__class__, self).main()
321
        try:
322
            reply = self.client.list_server_nics(int(server_id))
323
        except ClientError as err:
324
            raiseCLIError(err)
325
        except ValueError:
326
            raise CLIError(message='Server id must be positive integer',
327
                importance=1)
328
        print_list(reply)
329

    
330

    
331
@command(server_cmds)
332
class server_meta(_init_cyclades):
333
    """Get a server's metadata"""
334

    
335
    def main(self, server_id, key=''):
336
        super(self.__class__, self).main()
337
        try:
338
            reply = self.client.get_server_metadata(int(server_id), key)
339
        except ValueError:
340
            raise CLIError(message='Server id must be positive integer',
341
                importance=1)
342
        except ClientError as err:
343
            raiseCLIError(err)
344
        print_dict(reply)
345

    
346

    
347
@command(server_cmds)
348
class server_addmeta(_init_cyclades):
349
    """Add server metadata"""
350

    
351
    def main(self, server_id, key, val):
352
        super(self.__class__, self).main()
353
        try:
354
            reply = self.client.create_server_metadata(\
355
                int(server_id), key, val)
356
        except ClientError as err:
357
            raiseCLIError(err)
358
        except ValueError:
359
            raise CLIError(message='Server id must be positive integer',
360
                importance=1)
361
        print_dict(reply)
362

    
363

    
364
@command(server_cmds)
365
class server_setmeta(_init_cyclades):
366
    """Update server's metadata"""
367

    
368
    def main(self, server_id, key, val):
369
        super(self.__class__, self).main()
370
        metadata = {key: val}
371
        try:
372
            reply = self.client.update_server_metadata(int(server_id),
373
                **metadata)
374
        except ClientError as err:
375
            raiseCLIError(err)
376
        except ValueError:
377
            raise CLIError(message='Server id must be positive integer',
378
                importance=1)
379
        print_dict(reply)
380

    
381

    
382
@command(server_cmds)
383
class server_delmeta(_init_cyclades):
384
    """Delete server metadata"""
385

    
386
    def main(self, server_id, key):
387
        super(self.__class__, self).main()
388
        try:
389
            self.client.delete_server_metadata(int(server_id), key)
390
        except ClientError as err:
391
            raiseCLIError(err)
392
        except ValueError:
393
            raise CLIError(message='Server id must be positive integer',
394
                importance=1)
395

    
396

    
397
@command(server_cmds)
398
class server_stats(_init_cyclades):
399
    """Get server statistics"""
400

    
401
    def main(self, server_id):
402
        super(self.__class__, self).main()
403
        try:
404
            reply = self.client.get_server_stats(int(server_id))
405
        except ClientError as err:
406
            raiseCLIError(err)
407
        except ValueError:
408
            raise CLIError(message='Server id must be positive integer',
409
                importance=1)
410
        print_dict(reply, exclude=('serverRef',))
411

    
412

    
413
@command(server_cmds)
414
class server_wait(_init_cyclades):
415
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
416

    
417
    def __init__(self, arguments={}):
418
        super(self.__class__, self).__init__(arguments)
419
        self.arguments['progress_bar'] = ProgressBarArgument(\
420
            'do not show progress bar', '--no-progress-bar', False)
421

    
422
    def main(self, server_id, currect_status='BUILD'):
423
        super(self.__class__, self).main()
424
        try:
425
            progress_bar = self.arguments['progress_bar']
426
            wait_cb = progress_bar.get_generator(\
427
                'Server %s still in %s mode' % (server_id, currect_status))
428
        except Exception:
429
            wait_cb = None
430
        try:
431
            new_mode = self.client.wait_server(server_id,
432
                currect_status,
433
                wait_cb=wait_cb)
434
            progress_bar.finish()
435
        except KeyboardInterrupt:
436
            print('\nCanceled')
437
            progress_bar.finish()
438
            return
439
        except ClientError as err:
440
            progress_bar.finish()
441
            raiseCLIError(err)
442
        if new_mode:
443
            print('\nServer %s is now in %s mode' % (server_id, new_mode))
444
        else:
445
            print('\nTime out')
446

    
447

    
448
@command(flavor_cmds)
449
class flavor_list(_init_cyclades):
450
    """List flavors"""
451

    
452
    def __init__(self, arguments={}):
453
        super(flavor_list, self).__init__(arguments)
454
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
455

    
456
    @classmethod
457
    def _print(self, flist):
458
        for i, flavor in enumerate(flist):
459
            print(bold('%s. %s' % (i, flavor['name'])))
460
            print_dict(flavor, exclude=('name'), ident=2)
461
            print(' ')
462

    
463
    def main(self):
464
        super(self.__class__, self).main()
465
        try:
466
            flavors = self.client.list_flavors(self.get_argument('detail'))
467
        except ClientError as err:
468
            raiseCLIError(err)
469
        #print_list(flavors)
470
        self._print(flavors)
471

    
472

    
473
@command(flavor_cmds)
474
class flavor_info(_init_cyclades):
475
    """Get flavor details"""
476

    
477
    def main(self, flavor_id):
478
        super(self.__class__, self).main()
479
        try:
480
            flavor = self.client.get_flavor_details(int(flavor_id))
481
        except ClientError as err:
482
            raiseCLIError(err)
483
        except ValueError:
484
            raise CLIError(message='Server id must be positive integer',
485
                importance=1)
486
        print_dict(flavor)
487

    
488

    
489
@command(network_cmds)
490
class network_list(_init_cyclades):
491
    """List networks"""
492

    
493
    def __init__(self, arguments={}):
494
        super(network_list, self).__init__(arguments)
495
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
496

    
497
    def print_networks(self, nets):
498
        for net in nets:
499
            netname = bold(net.pop('name'))
500
            netid = bold(unicode(net.pop('id')))
501
            print('%s (%s)' % (netname, netid))
502
            if self.get_argument('detail'):
503
                network_info.print_network(net)
504

    
505
    def main(self):
506
        super(self.__class__, self).main()
507
        try:
508
            networks = self.client.list_networks(self.get_argument('detail'))
509
        except ClientError as err:
510
            raiseCLIError(err)
511
        self.print_networks(networks)
512

    
513

    
514
@command(network_cmds)
515
class network_create(_init_cyclades):
516
    """Create a network"""
517

    
518
    def __init__(self, arguments={}):
519
        super(network_create, self).__init__(arguments)
520
        self.arguments['cidr'] =\
521
            ValueArgument('specific cidr for new network', '--with-cidr')
522
        self.arguments['gateway'] =\
523
            ValueArgument('specific gateway for new network', '--with-gateway')
524
        self.arguments['dhcp'] =\
525
            ValueArgument('specific dhcp for new network', '--with-dhcp')
526
        self.arguments['type'] =\
527
            ValueArgument('specific type for new network', '--with-type')
528

    
529
    def main(self, name):
530
        super(self.__class__, self).main()
531
        try:
532
            reply = self.client.create_network(name,
533
                cidr=self.get_argument('cidr'),
534
                gateway=self.get_argument('gateway'),
535
                dhcp=self.get_argument('dhcp'),
536
                type=self.get_argument('type'))
537
        except ClientError as err:
538
            raiseCLIError(err)
539
        print_dict(reply)
540

    
541

    
542
@command(network_cmds)
543
class network_info(_init_cyclades):
544
    """Get network details"""
545

    
546
    @classmethod
547
    def print_network(self, net):
548
        if 'attachments' in net:
549
            att = net['attachments']['values']
550
            net['attachments'] = att if len(att) > 0 else None
551
        print_dict(net, ident=2)
552

    
553
    def main(self, network_id):
554
        super(self.__class__, self).main()
555
        try:
556
            network = self.client.get_network_details(network_id)
557
        except ClientError as err:
558
            raiseCLIError(err)
559
        network_info.print_network(network)
560

    
561

    
562
@command(network_cmds)
563
class network_rename(_init_cyclades):
564
    """Update network name"""
565

    
566
    def main(self, network_id, new_name):
567
        super(self.__class__, self).main()
568
        try:
569
            self.client.update_network_name(network_id, new_name)
570
        except ClientError as err:
571
            raiseCLIError(err)
572

    
573

    
574
@command(network_cmds)
575
class network_delete(_init_cyclades):
576
    """Delete a network"""
577

    
578
    def main(self, network_id):
579
        super(self.__class__, self).main()
580
        try:
581
            self.client.delete_network(network_id)
582
        except ClientError as err:
583
            raiseCLIError(err)
584

    
585

    
586
@command(network_cmds)
587
class network_connect(_init_cyclades):
588
    """Connect a server to a network"""
589

    
590
    def main(self, server_id, network_id):
591
        super(self.__class__, self).main()
592
        try:
593
            self.client.connect_server(server_id, network_id)
594
        except ClientError as err:
595
            raiseCLIError(err)
596

    
597

    
598
@command(network_cmds)
599
class network_disconnect(_init_cyclades):
600
    """Disconnect a nic that connects a server to a network"""
601

    
602
    def main(self, nic_id):
603
        super(self.__class__, self).main()
604
        try:
605
            server_id = nic_id.split('-')[1]
606
            self.client.disconnect_server(server_id, nic_id)
607
        except IndexError:
608
            raise CLIError(message='Incorrect nic format', importance=1,
609
                details='nid_id format: nic-<server_id>-<nic_index>')
610
        except ClientError as err:
611
            raiseCLIError(err)