Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 75c3fc42

History | View | Annotate | Download (19.8 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
        except KeyboardInterrupt:
435
            print('\nCanceled')
436
            return
437
        except ClientError as err:
438
            raiseCLIError(err)
439
        if new_mode:
440
            print('\nServer %s is now in %s mode' % (server_id, new_mode))
441
        else:
442
            print('\nTime out')
443

    
444

    
445
@command(flavor_cmds)
446
class flavor_list(_init_cyclades):
447
    """List flavors"""
448

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

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

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

    
469

    
470
@command(flavor_cmds)
471
class flavor_info(_init_cyclades):
472
    """Get flavor details"""
473

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

    
485

    
486
@command(network_cmds)
487
class network_list(_init_cyclades):
488
    """List networks"""
489

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

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

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

    
510

    
511
@command(network_cmds)
512
class network_create(_init_cyclades):
513
    """Create a network"""
514

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

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

    
538

    
539
@command(network_cmds)
540
class network_info(_init_cyclades):
541
    """Get network details"""
542

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

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

    
558

    
559
@command(network_cmds)
560
class network_rename(_init_cyclades):
561
    """Update network name"""
562

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

    
570

    
571
@command(network_cmds)
572
class network_delete(_init_cyclades):
573
    """Delete a network"""
574

    
575
    def main(self, network_id):
576
        super(self.__class__, self).main()
577
        try:
578
            self.client.delete_network(network_id)
579
        except ClientError as err:
580
            raiseCLIError(err)
581

    
582

    
583
@command(network_cmds)
584
class network_connect(_init_cyclades):
585
    """Connect a server to a network"""
586

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

    
594

    
595
@command(network_cmds)
596
class network_disconnect(_init_cyclades):
597
    """Disconnect a nic that connects a server to a network"""
598

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