Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ e15d78e2

History | View | Annotate | Download (19.3 kB)

1
# Copyright 2011-2013 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, print_items, 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
    arguments = dict(
72
        detail=FlagArgument('show detailed output', '-l')
73
    )
74

    
75
    def __init__(self, arguments={}):
76
        super(server_list, self).__init__(arguments)
77

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

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

    
101
    def main(self):
102
        super(self.__class__, self).main()
103
        try:
104
            servers = self.client.list_servers(self['detail'])
105
            self._print(servers)
106
        except Exception as err:
107
            raiseCLIError(err)
108

    
109

    
110
@command(server_cmds)
111
class server_info(_init_cyclades):
112
    """Get server details"""
113

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

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

    
141

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

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

    
171

    
172
@command(server_cmds)
173
class server_create(_init_cyclades):
174
    """Create a server"""
175

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

    
183
    def main(self, name, flavor_id, image_id):
184
        super(self.__class__, self).main()
185

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

    
201

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

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

    
217

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

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

    
231

    
232
@command(server_cmds)
233
class server_reboot(_init_cyclades):
234
    """Reboot a server"""
235

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

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

    
250

    
251
@command(server_cmds)
252
class server_start(_init_cyclades):
253
    """Start a server"""
254

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

    
264

    
265
@command(server_cmds)
266
class server_shutdown(_init_cyclades):
267
    """Shutdown a server"""
268

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

    
278

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

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

    
293

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

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

    
307

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

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

    
322

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

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

    
337

    
338
@command(server_cmds)
339
class server_addmeta(_init_cyclades):
340
    """Add server metadata"""
341

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

    
353

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

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

    
370

    
371
@command(server_cmds)
372
class server_delmeta(_init_cyclades):
373
    """Delete server metadata"""
374

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

    
384

    
385
@command(server_cmds)
386
class server_stats(_init_cyclades):
387
    """Get server statistics"""
388

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

    
399

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

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

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

    
434

    
435
@command(flavor_cmds)
436
class flavor_list(_init_cyclades):
437
    """List flavors"""
438

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

    
443
    def main(self):
444
        super(self.__class__, self).main()
445
        try:
446
            flavors = self.client.list_flavors(self.get_argument('detail'))
447
        except Exception as err:
448
            raiseCLIError(err)
449
        print_items(flavors, with_redundancy=self.get_argument('detail'))
450

    
451

    
452
@command(flavor_cmds)
453
class flavor_info(_init_cyclades):
454
    """Get flavor details"""
455

    
456
    def main(self, flavor_id):
457
        super(self.__class__, self).main()
458
        try:
459
            flavor = self.client.get_flavor_details(int(flavor_id))
460
        except ValueError as err:
461
            raiseCLIError(err, 'Server id must be positive integer', 1)
462
        except Exception as err:
463
            raiseCLIError(err)
464
        print_dict(flavor)
465

    
466

    
467
@command(network_cmds)
468
class network_info(_init_cyclades):
469
    """Get network details"""
470

    
471
    @classmethod
472
    def print_network(self, net):
473
        if 'attachments' in net:
474
            att = net['attachments']['values']
475
            count = len(att)
476
            net['attachments'] = att if count else None
477
        print_dict(net, ident=1)
478

    
479
    def main(self, network_id):
480
        super(self.__class__, self).main()
481
        try:
482
            network = self.client.get_network_details(network_id)
483
        except Exception as err:
484
            raiseCLIError(err)
485
        network_info.print_network(network)
486

    
487

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

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

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

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

    
512

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

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

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

    
540

    
541
@command(network_cmds)
542
class network_rename(_init_cyclades):
543
    """Update network name"""
544

    
545
    def main(self, network_id, new_name):
546
        super(self.__class__, self).main()
547
        try:
548
            self.client.update_network_name(network_id, new_name)
549
        except Exception as err:
550
            raiseCLIError(err)
551

    
552

    
553
@command(network_cmds)
554
class network_delete(_init_cyclades):
555
    """Delete a network"""
556

    
557
    def main(self, network_id):
558
        super(self.__class__, self).main()
559
        try:
560
            self.client.delete_network(network_id)
561
        except Exception as err:
562
            raiseCLIError(err)
563

    
564

    
565
@command(network_cmds)
566
class network_connect(_init_cyclades):
567
    """Connect a server to a network"""
568

    
569
    def main(self, server_id, network_id):
570
        super(self.__class__, self).main()
571
        try:
572
            self.client.connect_server(server_id, network_id)
573
        except Exception as err:
574
            raiseCLIError(err)
575

    
576

    
577
@command(network_cmds)
578
class network_disconnect(_init_cyclades):
579
    """Disconnect a nic that connects a server to a network"""
580

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