Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 43ee6ae1

History | View | Annotate | Download (19.5 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_list, bold
37
from kamaki.cli.errors import raiseCLIError, CLISyntaxError
38
from kamaki.clients.cyclades import CycladesClient, ClientError
39
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
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 Exception 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 ValueError as err:
134
            raiseCLIError(err, 'Server id must be positive integer', 1)
135
        except Exception as err:
136
            raiseCLIError(err)
137
        self._print(server)
138

    
139

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

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

    
169

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

    
174
    def __init__(self, arguments={}):
175
        super(server_create, self).__init__(arguments)
176
        self.arguments['personality'] = PersonalityArgument(\
177
            'add one or more personality files ( ' +\
178
            '"PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]" )',
179
            parsed_name='--personality')
180

    
181
    def main(self, name, flavor_id, image_id):
182
        super(self.__class__, self).main()
183

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

    
199

    
200
@command(server_cmds)
201
class server_rename(_init_cyclades):
202
    """Update a server's name"""
203

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

    
215

    
216
@command(server_cmds)
217
class server_delete(_init_cyclades):
218
    """Delete a server"""
219

    
220
    def main(self, server_id):
221
        super(self.__class__, self).main()
222
        try:
223
            self.client.delete_server(int(server_id))
224
        except ValueError as err:
225
            raiseCLIError(err, 'Server id must be positive integer', 1)
226
        except Exception as err:
227
            raiseCLIError(err)
228

    
229

    
230
@command(server_cmds)
231
class server_reboot(_init_cyclades):
232
    """Reboot a server"""
233

    
234
    def __init__(self, arguments={}):
235
        super(server_reboot, self).__init__(arguments)
236
        self.arguments['hard'] = FlagArgument('perform a hard reboot', '-f')
237

    
238
    def main(self, server_id):
239
        super(self.__class__, self).main()
240
        try:
241
            self.client.reboot_server(int(server_id),
242
                self.get_argument('hard'))
243
        except ValueError as err:
244
            raiseCLIError(err, 'Server id must be positive integer', 1)
245
        except Exception as err:
246
            raiseCLIError(err)
247

    
248

    
249
@command(server_cmds)
250
class server_start(_init_cyclades):
251
    """Start a server"""
252

    
253
    def main(self, server_id):
254
        super(self.__class__, self).main()
255
        try:
256
            self.client.start_server(int(server_id))
257
        except ValueError as err:
258
            raiseCLIError(err, 'Server id must be positive integer', 1)
259
        except Exception as err:
260
            raiseCLIError(err)
261

    
262

    
263
@command(server_cmds)
264
class server_shutdown(_init_cyclades):
265
    """Shutdown a server"""
266

    
267
    def main(self, server_id):
268
        super(self.__class__, self).main()
269
        try:
270
            self.client.shutdown_server(int(server_id))
271
        except ValueError as err:
272
            raiseCLIError(err, 'Server id must be positive integer', 1)
273
        except Exception as err:
274
            raiseCLIError(err)
275

    
276

    
277
@command(server_cmds)
278
class server_console(_init_cyclades):
279
    """Get a VNC console"""
280

    
281
    def main(self, server_id):
282
        super(self.__class__, self).main()
283
        try:
284
            reply = self.client.get_server_console(int(server_id))
285
        except ValueError as err:
286
            raiseCLIError(err, 'Server id must be positive integer', 1)
287
        except Exception as err:
288
            raiseCLIError(err)
289
        print_dict(reply)
290

    
291

    
292
@command(server_cmds)
293
class server_firewall(_init_cyclades):
294
    """Set the server's firewall profile"""
295

    
296
    def main(self, server_id, profile):
297
        super(self.__class__, self).main()
298
        try:
299
            self.client.set_firewall_profile(int(server_id), profile)
300
        except ValueError as err:
301
            raiseCLIError(err, 'Server id must be positive integer', 1)
302
        except Exception as err:
303
            raiseCLIError(err)
304

    
305

    
306
@command(server_cmds)
307
class server_addr(_init_cyclades):
308
    """List a server's nic address"""
309

    
310
    def main(self, server_id):
311
        super(self.__class__, self).main()
312
        try:
313
            reply = self.client.list_server_nics(int(server_id))
314
        except ValueError as err:
315
            raiseCLIError(err, 'Server id must be positive integer', 1)
316
        except Exception as err:
317
            raiseCLIError(err)
318
        print_list(reply)
319

    
320

    
321
@command(server_cmds)
322
class server_meta(_init_cyclades):
323
    """Get a server's metadata"""
324

    
325
    def main(self, server_id, key=''):
326
        super(self.__class__, self).main()
327
        try:
328
            reply = self.client.get_server_metadata(int(server_id), key)
329
        except ValueError as err:
330
            raiseCLIError(err, 'Server id must be positive integer', 1)
331
        except Exception as err:
332
            raiseCLIError(err)
333
        print_dict(reply)
334

    
335

    
336
@command(server_cmds)
337
class server_addmeta(_init_cyclades):
338
    """Add server metadata"""
339

    
340
    def main(self, server_id, key, val):
341
        super(self.__class__, self).main()
342
        try:
343
            reply = self.client.create_server_metadata(\
344
                int(server_id), key, val)
345
        except ValueError as err:
346
            raiseCLIError(err, 'Server id must be positive integer', 1)
347
        except Exception as err:
348
            raiseCLIError(err)
349
        print_dict(reply)
350

    
351

    
352
@command(server_cmds)
353
class server_setmeta(_init_cyclades):
354
    """Update server's metadata"""
355

    
356
    def main(self, server_id, key, val):
357
        super(self.__class__, self).main()
358
        metadata = {key: val}
359
        try:
360
            reply = self.client.update_server_metadata(int(server_id),
361
                **metadata)
362
        except ValueError as err:
363
            raiseCLIError(err, 'Server id must be positive integer', 1)
364
        except Exception as err:
365
            raiseCLIError(err)
366
        print_dict(reply)
367

    
368

    
369
@command(server_cmds)
370
class server_delmeta(_init_cyclades):
371
    """Delete server metadata"""
372

    
373
    def main(self, server_id, key):
374
        super(self.__class__, self).main()
375
        try:
376
            self.client.delete_server_metadata(int(server_id), key)
377
        except ValueError as err:
378
            raiseCLIError(err, 'Server id must be positive integer', 1)
379
        except Exception as err:
380
            raiseCLIError(err)
381

    
382

    
383
@command(server_cmds)
384
class server_stats(_init_cyclades):
385
    """Get server statistics"""
386

    
387
    def main(self, server_id):
388
        super(self.__class__, self).main()
389
        try:
390
            reply = self.client.get_server_stats(int(server_id))
391
        except ValueError as err:
392
            raiseCLIError(err, 'Server id must be positive integer', 1)
393
        except Exception as err:
394
            raiseCLIError(err)
395
        print_dict(reply, exclude=('serverRef',))
396

    
397

    
398
@command(server_cmds)
399
class server_wait(_init_cyclades):
400
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
401

    
402
    def __init__(self, arguments={}):
403
        super(self.__class__, self).__init__(arguments)
404
        self.arguments['progress_bar'] = ProgressBarArgument(\
405
            'do not show progress bar', '--no-progress-bar', False)
406

    
407
    def main(self, server_id, currect_status='BUILD'):
408
        super(self.__class__, self).main()
409
        try:
410
            progress_bar = self.arguments['progress_bar']
411
            wait_cb = progress_bar.get_generator(\
412
                'Server %s still in %s mode' % (server_id, currect_status))
413
        except Exception:
414
            wait_cb = None
415
        try:
416
            new_mode = self.client.wait_server(server_id,
417
                currect_status,
418
                wait_cb=wait_cb)
419
            progress_bar.finish()
420
        except KeyboardInterrupt:
421
            print('\nCanceled')
422
            progress_bar.finish()
423
            return
424
        except ClientError as err:
425
            progress_bar.finish()
426
            raiseCLIError(err)
427
        if new_mode:
428
            print('Server %s is now in %s mode' % (server_id, new_mode))
429
        else:
430
            raiseCLIError(None, 'Time out')
431

    
432

    
433
@command(flavor_cmds)
434
class flavor_list(_init_cyclades):
435
    """List flavors"""
436

    
437
    def __init__(self, arguments={}):
438
        super(flavor_list, self).__init__(arguments)
439
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
440

    
441
    @classmethod
442
    def _print(self, flist):
443
        for i, flavor in enumerate(flist):
444
            print(bold('%s. %s' % (i, flavor['name'])))
445
            print_dict(flavor, exclude=('name'), ident=2)
446
            print(' ')
447

    
448
    def main(self):
449
        super(self.__class__, self).main()
450
        try:
451
            flavors = self.client.list_flavors(self.get_argument('detail'))
452
        except Exception as err:
453
            raiseCLIError(err)
454
        self._print(flavors)
455

    
456

    
457
@command(flavor_cmds)
458
class flavor_info(_init_cyclades):
459
    """Get flavor details"""
460

    
461
    def main(self, flavor_id):
462
        super(self.__class__, self).main()
463
        try:
464
            flavor = self.client.get_flavor_details(int(flavor_id))
465
        except ValueError as err:
466
            raiseCLIError(err, 'Server id must be positive integer', 1)
467
        except Exception as err:
468
            raiseCLIError(err)
469
        print_dict(flavor)
470

    
471

    
472
@command(network_cmds)
473
class network_list(_init_cyclades):
474
    """List networks"""
475

    
476
    def __init__(self, arguments={}):
477
        super(network_list, self).__init__(arguments)
478
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
479

    
480
    def print_networks(self, nets):
481
        for net in nets:
482
            netname = bold(net.pop('name'))
483
            netid = bold(unicode(net.pop('id')))
484
            print('%s (%s)' % (netid, netname))
485
            if self.get_argument('detail'):
486
                network_info.print_network(net)
487

    
488
    def main(self):
489
        super(self.__class__, self).main()
490
        try:
491
            networks = self.client.list_networks(self.get_argument('detail'))
492
        except Exception as err:
493
            raiseCLIError(err)
494
        self.print_networks(networks)
495

    
496

    
497
@command(network_cmds)
498
class network_create(_init_cyclades):
499
    """Create a network"""
500

    
501
    def __init__(self, arguments={}):
502
        super(network_create, self).__init__(arguments)
503
        self.arguments['cidr'] =\
504
            ValueArgument('specific cidr for new network', '--with-cidr')
505
        self.arguments['gateway'] =\
506
            ValueArgument('specific gateway for new network', '--with-gateway')
507
        self.arguments['dhcp'] =\
508
            ValueArgument('specific dhcp for new network', '--with-dhcp')
509
        self.arguments['type'] =\
510
            ValueArgument('specific type for new network', '--with-type')
511

    
512
    def main(self, name):
513
        super(self.__class__, self).main()
514
        try:
515
            reply = self.client.create_network(name,
516
                cidr=self.get_argument('cidr'),
517
                gateway=self.get_argument('gateway'),
518
                dhcp=self.get_argument('dhcp'),
519
                type=self.get_argument('type'))
520
        except Exception as err:
521
            raiseCLIError(err)
522
        print_dict(reply)
523

    
524

    
525
@command(network_cmds)
526
class network_info(_init_cyclades):
527
    """Get network details"""
528

    
529
    @classmethod
530
    def print_network(self, net):
531
        if 'attachments' in net:
532
            att = net['attachments']['values']
533
            net['attachments'] = att if len(att) > 0 else None
534
        print_dict(net, ident=2)
535

    
536
    def main(self, network_id):
537
        super(self.__class__, self).main()
538
        try:
539
            network = self.client.get_network_details(network_id)
540
        except Exception as err:
541
            raiseCLIError(err)
542
        network_info.print_network(network)
543

    
544

    
545
@command(network_cmds)
546
class network_rename(_init_cyclades):
547
    """Update network name"""
548

    
549
    def main(self, network_id, new_name):
550
        super(self.__class__, self).main()
551
        try:
552
            self.client.update_network_name(network_id, new_name)
553
        except Exception as err:
554
            raiseCLIError(err)
555

    
556

    
557
@command(network_cmds)
558
class network_delete(_init_cyclades):
559
    """Delete a network"""
560

    
561
    def main(self, network_id):
562
        super(self.__class__, self).main()
563
        try:
564
            self.client.delete_network(network_id)
565
        except Exception as err:
566
            raiseCLIError(err)
567

    
568

    
569
@command(network_cmds)
570
class network_connect(_init_cyclades):
571
    """Connect a server to a network"""
572

    
573
    def main(self, server_id, network_id):
574
        super(self.__class__, self).main()
575
        try:
576
            self.client.connect_server(server_id, network_id)
577
        except Exception as err:
578
            raiseCLIError(err)
579

    
580

    
581
@command(network_cmds)
582
class network_disconnect(_init_cyclades):
583
    """Disconnect a nic that connects a server to a network"""
584

    
585
    def main(self, nic_id):
586
        super(self.__class__, self).main()
587
        try:
588
            server_id = nic_id.split('-')[1]
589
            self.client.disconnect_server(server_id, nic_id)
590
        except IndexError as err:
591
            raiseCLIError(err, 'Incorrect nic format', importance=1,
592
                details='nid_id format: nic-<server_id>-<nic_index>')
593
        except Exception as err:
594
            raiseCLIError(err)