Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 7493ccb6

History | View | Annotate | Download (19.1 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, set_api_description, CLIError
35
from kamaki.cli.utils import print_dict, print_items, print_list, format_size, raiseCLIError
36
from colors import bold
37
set_api_description('server', "Compute/Cyclades API server commands")
38
set_api_description('flavor', "Compute/Cyclades API flavor commands")
39
set_api_description('image', "Compute/Cyclades or Glance API image commands")
40
set_api_description('network', "Compute/Cyclades API network commands")
41
from kamaki.clients.cyclades import CycladesClient, ClientError
42

    
43
class _init_cyclades(object):
44
    def main(self):
45
        token = self.config.get('compute', 'token') or self.config.get('global', 'token')
46
        base_url = self.config.get('compute', 'url') or self.config.get('global', 'url')
47
        self.client = CycladesClient(base_url=base_url, token=token)
48

    
49
@command()
50
class server_list(_init_cyclades):
51
    """List servers"""
52

    
53
    def _print(self, servers):
54
        for server in servers:
55
            sname = server.pop('name')
56
            sid = server.pop('id')
57
            print('%s (%s)'%(bold(sname), bold(unicode(sid))))
58
            if getattr(self.args, 'detail'):
59
                server_info._print(server)
60
                print('- - -')
61

    
62
    def update_parser(self, parser):
63
        parser.add_argument('-l', dest='detail', action='store_true',
64
                default=False, help='show detailed output')
65

    
66
    def main(self):
67
        super(self.__class__, self).main()
68
        try:
69
            servers = self.client.list_servers(self.args.detail)
70
            self._print(servers)
71
            #print_items(servers)
72
        except ClientError as err:
73
            raiseCLIError(err)
74

    
75
@command()
76
class server_info(_init_cyclades):
77
    """Get server details"""
78

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

    
95
    def main(self, server_id):
96
        super(self.__class__, self).main()
97
        try:
98
            server = self.client.get_server_details(int(server_id))
99
        except ClientError as err:
100
            raiseCLIError(err)
101
        except ValueError as err:
102
            raise CLIError(message='Server id must be positive integer',
103
                importance=1)
104
        self._print(server)
105

    
106
@command()
107
class server_create(_init_cyclades):
108
    """Create a server"""
109

    
110
    def update_parser(self, parser):
111
        parser.add_argument('--personality', dest='personalities',
112
                          action='append', default=[],
113
                          metavar='PATH[,SERVER PATH[,OWNER[,GROUP,[MODE]]]]',
114
                          help='add a personality file')
115

    
116
    def main(self, name, flavor_id, image_id):
117
        super(self.__class__, self).main()
118
        personalities = []
119
        for personality in self.args.personalities:
120
            p = personality.split(',')
121
            p.extend([None] * (5 - len(p)))     # Fill missing fields with None
122

    
123
            path = p[0]
124

    
125
            if not path:
126
                raise CLIError(message='Invalid personality argument %s'%p, importance=1)
127
            if not exists(path):
128
                raise CLIError(message="File %s does not exist" % path, importance=1)
129

    
130
            with open(path) as f:
131
                contents = b64encode(f.read())
132

    
133
            d = {'path': p[1] or abspath(path), 'contents': contents}
134
            if p[2]:
135
                d['owner'] = p[2]
136
            if p[3]:
137
                d['group'] = p[3]
138
            if p[4]:
139
                d['mode'] = int(p[4])
140
            personalities.append(d)
141

    
142
        try:
143
            reply = self.client.create_server(name, int(flavor_id), image_id,
144
                personalities)
145
        except ClientError as err:
146
            raiseCLIError(err)
147
        print_dict(reply)
148

    
149
@command()
150
class server_rename(_init_cyclades):
151
    """Update a server's name"""
152

    
153
    def main(self, server_id, new_name):
154
        super(self.__class__, self).main()
155
        try:
156
            self.client.update_server_name(int(server_id), new_name)
157
        except ClientError as err:
158
            raiseCLIError(err)
159
        except ValueError:
160
            raise CLIError(message='Server id must be positive integer', importance=1)
161

    
162
@command()
163
class server_delete(_init_cyclades):
164
    """Delete a server"""
165

    
166
    def main(self, server_id):
167
        super(self.__class__, self).main()
168
        try:
169
            self.client.delete_server(int(server_id))
170
        except ClientError as err:
171
            raiseCLIError(err)
172
        except ValueError:
173
            raise CLIError(message='Server id must be positive integer', importance=1)
174

    
175
@command()
176
class server_reboot(_init_cyclades):
177
    """Reboot a server"""
178

    
179
    def update_parser(self, parser):
180
        parser.add_argument('-f', dest='hard', action='store_true',
181
                default=False, help='perform a hard reboot')
182

    
183
    def main(self, server_id):
184
        super(self.__class__, self).main()
185
        try:
186
            self.client.reboot_server(int(server_id), self.args.hard)
187
        except ClientError as err:
188
            raiseCLIError(err)
189
        except ValueError:
190
            raise CLIError(message='Server id must be positive integer', importance=1)
191

    
192
@command()
193
class server_start(_init_cyclades):
194
    """Start a server"""
195

    
196
    def main(self, server_id):
197
        super(self.__class__, self).main()
198
        try:
199
            self.client.start_server(int(server_id))
200
        except ClientError as err:
201
            raiseCLIError(err)
202
        except ValueError:
203
            raise CLIError(message='Server id must be positive integer', importance=1)
204

    
205
@command()
206
class server_shutdown(_init_cyclades):
207
    """Shutdown a server"""
208

    
209
    def main(self, server_id):
210
        super(self.__class__, self).main()
211
        try:
212
            self.client.shutdown_server(int(server_id))
213
        except ClientError as err:
214
            raiseCLIError(err)
215
        except ValueError:
216
            raise CLIError(message='Server id must be positive integer', importance=1)
217

    
218
@command()
219
class server_console(_init_cyclades):
220
    """Get a VNC console"""
221

    
222
    def main(self, server_id):
223
        super(self.__class__, self).main()
224
        try:
225
            reply = self.client.get_server_console(int(server_id))
226
        except ClientError as err:
227
            raiseCLIError(err)
228
        except ValueError:
229
            raise CLIError(message='Server id must be positive integer', importance=1)
230
        print_dict(reply)
231

    
232
@command()
233
class server_firewall(_init_cyclades):
234
    """Set the server's firewall profile"""
235

    
236
    def main(self, server_id, profile):
237
        super(self.__class__, self).main()
238
        try:
239
            self.client.set_firewall_profile(int(server_id), profile)
240
        except ClientError as err:
241
            raiseCLIError(err)
242
        except ValueError:
243
            raise CLIError(message='Server id must be positive integer', importance=1)
244
@command()
245
class server_addr(_init_cyclades):
246
    """List a server's nic address"""
247

    
248
    def main(self, server_id):
249
        super(self.__class__, self).main()
250
        try:
251
            reply = self.client.list_server_nics(int(server_id))
252
        except ClientError as err:
253
            raiseCLIError(err)
254
        except ValueError:
255
            raise CLIError(message='Server id must be positive integer', importance=1)
256
        print_list(reply)
257

    
258
@command()
259
class server_meta(_init_cyclades):
260
    """Get a server's metadata"""
261

    
262
    def main(self, server_id, key=None):
263
        super(self.__class__, self).main()
264
        try:
265
            reply = self.client.get_server_metadata(int(server_id), key)
266
        except ClientError as err:
267
            raiseCLIError(err)
268
        except ValueError:
269
            raise CLIError(message='Server id must be positive integer', importance=1)
270
        print_dict(reply)
271

    
272
@command()
273
class server_addmeta(_init_cyclades):
274
    """Add server metadata"""
275

    
276
    def main(self, server_id, key, val):
277
        super(self.__class__, self).main()
278
        try:
279
            reply = self.client.create_server_metadata(int(server_id), key, val)
280
        except ClientError as err:
281
            raiseCLIError(err)
282
        except ValueError:
283
            raise CLIError(message='Server id must be positive integer', importance=1)
284
        print_dict(reply)
285

    
286
@command()
287
class server_setmeta(_init_cyclades):
288
    """Update server's metadata"""
289

    
290
    def main(self, server_id, key, val):
291
        super(self.__class__, self).main()
292
        metadata = {key: val}
293
        try:
294
            reply = self.client.update_server_metadata(int(server_id), **metadata)
295
        except ClientError as err:
296
            raiseCLIError(err)
297
        except ValueError:
298
            raise CLIError(message='Server id must be positive integer', importance=1)
299
        print_dict(reply)
300

    
301
@command()
302
class server_delmeta(_init_cyclades):
303
    """Delete server metadata"""
304

    
305
    def main(self, server_id, key):
306
        super(self.__class__, self).main()
307
        try:
308
            self.client.delete_server_metadata(int(server_id), key)
309
        except ClientError as err:
310
            raiseCLIError(err)
311
        except ValueError:
312
            raise CLIError(message='Server id must be positive integer', importance=1)
313

    
314
@command()
315
class server_stats(_init_cyclades):
316
    """Get server statistics"""
317

    
318
    def main(self, server_id):
319
        super(self.__class__, self).main()
320
        try:
321
            reply = self.client.get_server_stats(int(server_id))
322
        except ClientError as err:
323
            raiseCLIError(err)
324
        except ValueError:
325
            raise CLIError(message='Server id must be positive integer', importance=1)
326
        print_dict(reply, exclude=('serverRef',))
327

    
328
@command()
329
class flavor_list(_init_cyclades):
330
    """List flavors"""
331

    
332
    def update_parser(self, parser):
333
        parser.add_argument('-l', dest='detail', action='store_true',
334
                default=False, help='show detailed output')
335

    
336
    def main(self):
337
        super(self.__class__, self).main()
338
        try:
339
            flavors = self.client.list_flavors(self.args.detail)
340
        except ClientError as err:
341
            raiseCLIError(err)
342
        print_items(flavors)
343

    
344
@command()
345
class flavor_info(_init_cyclades):
346
    """Get flavor details"""
347

    
348
    def main(self, flavor_id):
349
        super(self.__class__, self).main()
350
        try:
351
            flavor = self.client.get_flavor_details(int(flavor_id))
352
        except ClientError as err:
353
            raiseCLIError(err)
354
        except ValueError:
355
            raise CLIError(message='Server id must be positive integer', importance=1)
356
        print_dict(flavor)
357

    
358
@command()
359
class image_list(_init_cyclades):
360
    """List images"""
361

    
362
    def update_parser(self, parser):
363
        parser.add_argument('-l', dest='detail', action='store_true',
364
                default=False, help='show detailed output')
365

    
366
    def _print(self, images):
367
        for img in images:
368
            iname = img.pop('name')
369
            iid = img.pop('id')
370
            print('%s (%s)'%(bold(iname), bold(unicode(iid))))
371
            if getattr(self.args, 'detail'):
372
                image_info._print(img)
373
                print('- - -')
374

    
375
    def main(self):
376
        super(self.__class__, self).main()
377
        try:
378
            images = self.client.list_images(self.args.detail)
379
        except ClientError as err:
380
            raiseCLIError(err)
381
        #print_items(images)
382
        self._print(images)
383

    
384
@command()
385
class image_info(_init_cyclades):
386
    """Get image details"""
387

    
388
    @classmethod
389
    def _print(self, image):
390
        if image.has_key('metadata'):
391
            image['metadata'] = image['metadata']['values']
392
        print_dict(image, ident=14)
393

    
394
    def main(self, image_id):
395
        super(self.__class__, self).main()
396
        try:
397
            image = self.client.get_image_details(image_id)
398
        except ClientError as err:
399
            raiseCLIError(err)
400
        self._print(image)
401

    
402
@command()
403
class image_delete(_init_cyclades):
404
    """Delete image"""
405

    
406
    def main(self, image_id):
407
        super(self.__class__, self).main()
408
        try:
409
            self.client.delete_image(image_id)
410
        except ClientError as err:
411
            raiseCLIError(err)
412

    
413
@command()
414
class image_properties(_init_cyclades):
415
    """Get image properties"""
416

    
417
    def main(self, image_id, key=None):
418
        super(self.__class__, self).main()
419
        try:
420
            reply = self.client.get_image_metadata(image_id, key)
421
        except ClientError as err:
422
            raiseCLIError(err)
423
        print_dict(reply)
424

    
425
@command()
426
class image_addproperty(_init_cyclades):
427
    """Add an image property"""
428

    
429
    def main(self, image_id, key, val):
430
        super(self.__class__, self).main()
431
        try:
432
            reply = self.client.create_image_metadata(image_id, key, val)
433
        except ClientError as err:
434
            raiseCLIError(err)
435
        print_dict(reply)
436

    
437
@command()
438
class image_setproperty(_init_cyclades):
439
    """Update an image property"""
440

    
441
    def main(self, image_id, key, val):
442
        super(self.__class__, self).main()
443
        metadata = {key: val}
444
        try:
445
            reply = self.client.update_image_metadata(image_id, **metadata)
446
        except ClientError as err:
447
            raiseCLIError(err)
448
        print_dict(reply)
449

    
450
@command()
451
class image_delproperty(_init_cyclades):
452
    """Delete an image property"""
453

    
454
    def main(self, image_id, key):
455
        super(self.__class__, self).main()
456
        try:
457
            self.client.delete_image_metadata(image_id, key)
458
        except ClientError as err:
459
            raiseCLIError(err)
460

    
461
@command()
462
class network_list(_init_cyclades):
463
    """List networks"""
464

    
465
    def update_parser(self, parser):
466
        parser.add_argument('-l', dest='detail', action='store_true',
467
                default=False, help='show detailed output')
468

    
469
    def print_networks(self, nets):
470
        for net in nets:
471
            netname = bold(net.pop('name'))
472
            netid = bold(unicode(net.pop('id')))
473
            print('%s (%s)'%(netname, netid))
474
            if getattr(self.args, 'detail'):
475
                network_info.print_network(net)
476
                print('- - -')
477

    
478
    def main(self):
479
        super(self.__class__, self).main()
480
        try:
481
            networks = self.client.list_networks(self.args.detail)
482
        except ClientError as err:
483
            raiseCLIError(err)
484
        self.print_networks(networks)
485

    
486
@command()
487
class network_create(_init_cyclades):
488
    """Create a network"""
489

    
490
    def update_parser(self, parser):
491
        try:
492
            super(self.__class__, self).update_parser(parser)
493
        except AttributeError:
494
            pass
495
        parser.add_argument('--with-cidr', action='store', dest='cidr', default=False,
496
            help='specific cidr for new network')
497
        parser.add_argument('--with-gateway', action='store', dest='gateway', default=False,
498
            help='specific getaway for new network')
499
        parser.add_argument('--with-dhcp', action='store', dest='dhcp', default=False,
500
            help='specific dhcp for new network')
501
        parser.add_argument('--with-type', action='store', dest='type', default=False,
502
            help='specific type for new network')
503

    
504
    def main(self, name):
505
        super(self.__class__, self).main()
506
        try:
507
            reply = self.client.create_network(name, cidr=getattr(self.args, 'cidr'),
508
                gateway=getattr(self.args, 'gateway'), dhcp=getattr(self.args, 'gateway'),
509
                type=getattr(self.args, 'type'))
510
        except ClientError as err:
511
            raiseCLIError(err)
512
        print_dict(reply)
513

    
514
@command()
515
class network_info(_init_cyclades):
516
    """Get network details"""
517

    
518
    @classmethod
519
    def print_network(self, net):
520
        if net.has_key('attachments'):
521
            att = net['attachments']['values']
522
            net['attachments'] = att if len(att) > 0 else None
523
        print_dict(net, ident=14)
524

    
525
    def main(self, network_id):
526
        super(self.__class__, self).main()
527
        try:
528
            network = self.client.get_network_details(network_id)
529
        except ClientError as err:
530
            raiseCLIError(err)
531
        network_info.print_network(network)
532

    
533
@command()
534
class network_rename(_init_cyclades):
535
    """Update network name"""
536

    
537
    def main(self, network_id, new_name):
538
        super(self.__class__, self).main()
539
        try:
540
            self.client.update_network_name(network_id, new_name)
541
        except ClientError as err:
542
            raiseCLIError(err)
543

    
544
@command()
545
class network_delete(_init_cyclades):
546
    """Delete a network"""
547

    
548
    def main(self, network_id):
549
        super(self.__class__, self).main()
550
        try:
551
            self.client.delete_network(network_id)
552
        except ClientError as err:
553
            raiseCLIError(err)
554

    
555
@command()
556
class network_connect(_init_cyclades):
557
    """Connect a server to a network"""
558

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

    
566
@command()
567
class network_disconnect(_init_cyclades):
568
    """Disconnect a nic that connects a server to a network"""
569

    
570
    def main(self, nic_id):
571
        super(self.__class__, self).main()
572
        try:
573
            server_id = nic_id.split('-')[1]
574
            self.client.disconnect_server(server_id, nic_id)
575
        except IndexError:
576
            raise CLIError(message='Incorrect nic format', importance=1,
577
                details='nid_id format: nic-<server_id>-<nic_index>')
578
        except ClientError as err:
579
            raiseCLIError(err)