Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 18edacfe

History | View | Annotate | Download (19.2 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
about_authentication = '\n  User Authentication:\
59
    \n    to check authentication: /astakos authenticate\
60
    \n    to set authentication token: /config set token <token>'
61

    
62

    
63
class _init_cyclades(_command_init):
64
    def main(self, service='compute'):
65
        token = self.config.get(service, 'token')\
66
            or self.config.get('global', 'token')
67
        base_url = self.config.get(service, 'url')\
68
            or self.config.get('global', 'url')
69
        self.client = CycladesClient(base_url=base_url, token=token)
70

    
71

    
72
@command(server_cmds)
73
class server_list(_init_cyclades):
74
    """List Virtual Machines accessible by user
75
    """
76

    
77
    __doc__ += about_authentication
78

    
79
    arguments = dict(
80
        detail=FlagArgument('show detailed output', '-l'),
81
        since=ValueArgument(
82
            'show only items since date (\' d/m/Y H:M:S \')',
83
            '--since')
84
    )
85

    
86
    def _info_print(self, server):
87
        addr_dict = {}
88
        if 'attachments' in server:
89
            for addr in server['attachments']['values']:
90
                ips = addr.pop('values', [])
91
                for ip in ips:
92
                    addr['IPv%s' % ip['version']] = ip['addr']
93
                if 'firewallProfile' in addr:
94
                    addr['firewall'] = addr.pop('firewallProfile')
95
                addr_dict[addr.pop('id')] = addr
96
            server['attachments'] = addr_dict if addr_dict is not {} else None
97
        if 'metadata' in server:
98
            server['metadata'] = server['metadata']['values']
99
        print_dict(server, ident=1)
100

    
101
    def _print(self, servers):
102
        for server in servers:
103
            sname = server.pop('name')
104
            sid = server.pop('id')
105
            print('%s (%s)' % (sid, bold(sname)))
106
            if self['detail']:
107
                self._info_print(server)
108

    
109
    def main(self):
110
        super(self.__class__, self).main()
111
        try:
112
            servers = self.client.list_servers(self['detail'], self['since'])
113
            self._print(servers)
114
        except ClientError as ce:
115
            if ce.status == 400 and 'changes-since' in ('%s' % ce):
116
                raiseCLIError(None,
117
                    'Incorrect date format for --since',
118
                    details=['Accepted date format: d/m/y'])
119
            raiseCLIError(ce)
120
        except Exception as err:
121
            raiseCLIError(err)
122

    
123

    
124
@command(server_cmds)
125
class server_info(_init_cyclades):
126
    """Get server details"""
127

    
128
    @classmethod
129
    def _print(self, server):
130
        addr_dict = {}
131
        if 'attachments' in server:
132
            atts = server.pop('attachments')
133
            for addr in atts['values']:
134
                ips = addr.pop('values', [])
135
                for ip in ips:
136
                    addr['IPv%s' % ip['version']] = ip['addr']
137
                if 'firewallProfile' in addr:
138
                    addr['firewall'] = addr.pop('firewallProfile')
139
                addr_dict[addr.pop('id')] = addr
140
            server['attachments'] = addr_dict if addr_dict else None
141
        if 'metadata' in server:
142
            server['metadata'] = server['metadata']['values']
143
        print_dict(server, ident=1)
144

    
145
    def main(self, server_id):
146
        super(self.__class__, self).main()
147
        try:
148
            server = self.client.get_server_details(int(server_id))
149
        except ValueError as err:
150
            raiseCLIError(err, 'Server id must be positive integer', 1)
151
        except Exception as err:
152
            raiseCLIError(err)
153
        self._print(server)
154

    
155

    
156
class PersonalityArgument(KeyValueArgument):
157
    @property
158
    def value(self):
159
        return self._value if hasattr(self, '_value') else []
160

    
161
    @value.setter
162
    def value(self, newvalue):
163
        if newvalue == self.default:
164
            return self.value
165
        self._value = []
166
        for i, terms in enumerate(newvalue):
167
            termlist = terms.split(',')
168
            if len(termlist) > 5:
169
                raiseCLIError(CLISyntaxError(details='Wrong number of terms'\
170
                + ' ("PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]"'))
171
            path = termlist[0]
172
            if not exists(path):
173
                raiseCLIError(None, "File %s does not exist" % path, 1)
174
            self._value.append(dict(path=path))
175
            with open(path) as f:
176
                self._value[i]['contents'] = b64encode(f.read())
177
            try:
178
                self._value[i]['path'] = termlist[1]
179
                self._value[i]['owner'] = termlist[2]
180
                self._value[i]['group'] = termlist[3]
181
                self._value[i]['mode'] = termlist[4]
182
            except IndexError:
183
                pass
184

    
185

    
186
@command(server_cmds)
187
class server_create(_init_cyclades):
188
    """Create a server"""
189

    
190
    arguments = dict(
191
        personality=PersonalityArgument(
192
            'add one or more personality files ( ' +\
193
                '"PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]" )',
194
            parsed_name='--personality')
195
    )
196

    
197
    def main(self, name, flavor_id, image_id):
198
        super(self.__class__, self).main()
199

    
200
        try:
201
            reply = self.client.create_server(
202
                        name,
203
                        int(flavor_id),
204
                        image_id,
205
                        self['personality']
206
                    )
207
        except ClientError as err:
208
            raiseCLIError(err)
209
        except ValueError as err:
210
            raiseCLIError(err, 'Invalid flavor id %s ' % flavor_id,
211
                details='Flavor id must be a positive integer',
212
                importance=1)
213
        except Exception as err:
214
            raiseCLIError(err, 'Syntax error: %s\n' % err, importance=1)
215
        print_dict(reply)
216

    
217

    
218
@command(server_cmds)
219
class server_rename(_init_cyclades):
220
    """Update a server's name"""
221

    
222
    def main(self, server_id, new_name):
223
        super(self.__class__, self).main()
224
        try:
225
            self.client.update_server_name(int(server_id), new_name)
226
        except ClientError as err:
227
            raiseCLIError(err)
228
        except ValueError as err:
229
            raiseCLIError(err, 'Invalid server id %s ' % server_id,
230
                details='Server id must be positive integer\n',
231
                importance=1)
232

    
233

    
234
@command(server_cmds)
235
class server_delete(_init_cyclades):
236
    """Delete a server"""
237

    
238
    def main(self, server_id):
239
        super(self.__class__, self).main()
240
        try:
241
            self.client.delete_server(int(server_id))
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_reboot(_init_cyclades):
250
    """Reboot a server"""
251

    
252
    arguments = dict(
253
        hard=FlagArgument('perform a hard reboot', '-f')
254
    )
255

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

    
265

    
266
@command(server_cmds)
267
class server_start(_init_cyclades):
268
    """Start a server"""
269

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

    
279

    
280
@command(server_cmds)
281
class server_shutdown(_init_cyclades):
282
    """Shutdown a server"""
283

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

    
293

    
294
@command(server_cmds)
295
class server_console(_init_cyclades):
296
    """Get a VNC console"""
297

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

    
308

    
309
@command(server_cmds)
310
class server_firewall(_init_cyclades):
311
    """Set the server's firewall profile"""
312

    
313
    def main(self, server_id, profile):
314
        super(self.__class__, self).main()
315
        try:
316
            self.client.set_firewall_profile(int(server_id), profile)
317
        except ValueError as err:
318
            raiseCLIError(err, 'Server id must be positive integer', 1)
319
        except Exception as err:
320
            raiseCLIError(err)
321

    
322

    
323
@command(server_cmds)
324
class server_addr(_init_cyclades):
325
    """List a server's nic address"""
326

    
327
    def main(self, server_id):
328
        super(self.__class__, self).main()
329
        try:
330
            reply = self.client.list_server_nics(int(server_id))
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_list(reply, with_enumeration=len(reply) > 1)
336

    
337

    
338
@command(server_cmds)
339
class server_meta(_init_cyclades):
340
    """Get a server's metadata"""
341

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

    
352

    
353
@command(server_cmds)
354
class server_addmeta(_init_cyclades):
355
    """Add server metadata"""
356

    
357
    def main(self, server_id, key, val):
358
        super(self.__class__, self).main()
359
        try:
360
            reply = self.client.create_server_metadata(\
361
                int(server_id), key, val)
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_setmeta(_init_cyclades):
371
    """Update server's metadata"""
372

    
373
    def main(self, server_id, key, val):
374
        super(self.__class__, self).main()
375
        metadata = {key: val}
376
        try:
377
            reply = self.client.update_server_metadata(int(server_id),
378
                **metadata)
379
        except ValueError as err:
380
            raiseCLIError(err, 'Server id must be positive integer', 1)
381
        except Exception as err:
382
            raiseCLIError(err)
383
        print_dict(reply)
384

    
385

    
386
@command(server_cmds)
387
class server_delmeta(_init_cyclades):
388
    """Delete server metadata"""
389

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

    
399

    
400
@command(server_cmds)
401
class server_stats(_init_cyclades):
402
    """Get server statistics"""
403

    
404
    def main(self, server_id):
405
        super(self.__class__, self).main()
406
        try:
407
            reply = self.client.get_server_stats(int(server_id))
408
        except ValueError as err:
409
            raiseCLIError(err, 'Server id must be positive integer', 1)
410
        except Exception as err:
411
            raiseCLIError(err)
412
        print_dict(reply, exclude=('serverRef',))
413

    
414

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

    
419
    arguments = dict(
420
        progress_bar=ProgressBarArgument(
421
            'do not show progress bar',
422
            '--no-progress-bar',
423
            False
424
        )
425
    )
426

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

    
452

    
453
@command(flavor_cmds)
454
class flavor_list(_init_cyclades):
455
    """List flavors"""
456

    
457
    arguments = dict(
458
        detail=FlagArgument('show detailed output', '-l')
459
    )
460

    
461
    def main(self):
462
        super(self.__class__, self).main()
463
        try:
464
            flavors = self.client.list_flavors(self['detail'])
465
        except Exception as err:
466
            raiseCLIError(err)
467
        print_items(flavors, with_redundancy=self['detail'])
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 ValueError as err:
479
            raiseCLIError(err, 'Server id must be positive integer', 1)
480
        except Exception as err:
481
            raiseCLIError(err)
482
        print_dict(flavor)
483

    
484

    
485
@command(network_cmds)
486
class network_info(_init_cyclades):
487
    """Get network details"""
488

    
489
    @classmethod
490
    def print_network(self, net):
491
        if 'attachments' in net:
492
            att = net['attachments']['values']
493
            count = len(att)
494
            net['attachments'] = att if count else None
495
        print_dict(net, ident=1)
496

    
497
    def main(self, network_id):
498
        super(self.__class__, self).main()
499
        try:
500
            network = self.client.get_network_details(network_id)
501
        except Exception as err:
502
            raiseCLIError(err)
503
        network_info.print_network(network)
504

    
505

    
506
@command(network_cmds)
507
class network_list(_init_cyclades):
508
    """List networks"""
509

    
510
    arguments = dict(
511
        detail=FlagArgument('show detailed output', '-l')
512
    )
513

    
514
    def print_networks(self, nets):
515
        for net in nets:
516
            netname = bold(net.pop('name'))
517
            netid = bold(unicode(net.pop('id')))
518
            print('%s (%s)' % (netid, netname))
519
            if self['detail']:
520
                network_info.print_network(net)
521

    
522
    def main(self):
523
        super(self.__class__, self).main()
524
        try:
525
            networks = self.client.list_networks(self['detail'])
526
        except Exception as err:
527
            raiseCLIError(err)
528
        self.print_networks(networks)
529

    
530

    
531
@command(network_cmds)
532
class network_create(_init_cyclades):
533
    """Create a network"""
534

    
535
    arguments = dict(
536
        cidr=ValueArgument('specify cidr', '--with-cidr'),
537
        gateway=ValueArgument('specify gateway', '--with-gateway'),
538
        dhcp=ValueArgument('specify dhcp', '--with-dhcp'),
539
        type=ValueArgument('specify type', '--with-type')
540
    )
541

    
542
    def main(self, name):
543
        super(self.__class__, self).main()
544
        try:
545
            reply = self.client.create_network(name,
546
                cidr=self['cidr'],
547
                gateway=self['gateway'],
548
                dhcp=self['dhcp'],
549
                type=self['type'])
550
        except Exception as err:
551
            raiseCLIError(err)
552
        print_dict(reply)
553

    
554

    
555
@command(network_cmds)
556
class network_rename(_init_cyclades):
557
    """Update network name"""
558

    
559
    def main(self, network_id, new_name):
560
        super(self.__class__, self).main()
561
        try:
562
            self.client.update_network_name(network_id, new_name)
563
        except Exception as err:
564
            raiseCLIError(err)
565

    
566

    
567
@command(network_cmds)
568
class network_delete(_init_cyclades):
569
    """Delete a network"""
570

    
571
    def main(self, network_id):
572
        super(self.__class__, self).main()
573
        try:
574
            self.client.delete_network(network_id)
575
        except Exception as err:
576
            raiseCLIError(err)
577

    
578

    
579
@command(network_cmds)
580
class network_connect(_init_cyclades):
581
    """Connect a server to a network"""
582

    
583
    def main(self, server_id, network_id):
584
        super(self.__class__, self).main()
585
        try:
586
            self.client.connect_server(server_id, network_id)
587
        except Exception as err:
588
            raiseCLIError(err)
589

    
590

    
591
@command(network_cmds)
592
class network_disconnect(_init_cyclades):
593
    """Disconnect a nic that connects a server to a network"""
594

    
595
    def main(self, nic_id):
596
        super(self.__class__, self).main()
597
        try:
598
            server_id = nic_id.split('-')[1]
599
            self.client.disconnect_server(server_id, nic_id)
600
        except IndexError as err:
601
            raiseCLIError(err, 'Incorrect nic format', importance=1,
602
                details='nid_id format: nic-<server_id>-<nic_index>')
603
        except Exception as err:
604
            raiseCLIError(err)