Statistics
| Branch: | Tag: | Revision:

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

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
    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=1)
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

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

    
106

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

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

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

    
138

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

    
144
    @value.setter
145
    def value(self, newvalue):
146
        if newvalue == self.default:
147
            return self.value
148
        self._value = []
149
        for i, terms in enumerate(newvalue):
150
            termlist = terms.split(',')
151
            if len(termlist) > 5:
152
                raiseCLIError(CLISyntaxError(details='Wrong number of terms'\
153
                + ' ("PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]"'))
154
            path = termlist[0]
155
            if not exists(path):
156
                raiseCLIError(None, "File %s does not exist" % path, 1)
157
            self._value.append(dict(path=path))
158
            with open(path) as f:
159
                self._value[i]['contents'] = b64encode(f.read())
160
            try:
161
                self._value[i]['path'] = termlist[1]
162
                self._value[i]['owner'] = termlist[2]
163
                self._value[i]['group'] = termlist[3]
164
                self._value[i]['mode'] = termlist[4]
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
            'add one or more personality files ( ' +\
177
            '"PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]" )',
178
            parsed_name='--personality')
179

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

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

    
198

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

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

    
214

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

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

    
228

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

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

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

    
247

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

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

    
261

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

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

    
275

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

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

    
290

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

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

    
304

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

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

    
319

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

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

    
334

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

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

    
350

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

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

    
367

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

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

    
381

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

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

    
396

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

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

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

    
431

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

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

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

    
448

    
449
@command(flavor_cmds)
450
class flavor_info(_init_cyclades):
451
    """Get flavor details"""
452

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

    
463

    
464
@command(network_cmds)
465
class network_info(_init_cyclades):
466
    """Get network details"""
467

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

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

    
484

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

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

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

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

    
509

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

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

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

    
537

    
538
@command(network_cmds)
539
class network_rename(_init_cyclades):
540
    """Update network name"""
541

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

    
549

    
550
@command(network_cmds)
551
class network_delete(_init_cyclades):
552
    """Delete a network"""
553

    
554
    def main(self, network_id):
555
        super(self.__class__, self).main()
556
        try:
557
            self.client.delete_network(network_id)
558
        except Exception as err:
559
            raiseCLIError(err)
560

    
561

    
562
@command(network_cmds)
563
class network_connect(_init_cyclades):
564
    """Connect a server to a network"""
565

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

    
573

    
574
@command(network_cmds)
575
class network_disconnect(_init_cyclades):
576
    """Disconnect a nic that connects a server to a network"""
577

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