Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades_cli.py @ 03fd7ddb

History | View | Annotate | Download (16.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
API_DESCRIPTION = {'server':'Compute/Cyclades API server commands',
35
    'flavor':'Compute/Cyclades API flavor commands',
36
    'image':'Compute/Cyclades or Glance API image commands',
37
    'network': 'Compute/Cyclades API network commands'}
38

    
39
from kamaki.cli import command
40
from kamaki.cli.utils import print_dict, print_items, print_list, format_size, bold
41
from kamaki.cli.errors import CLIError, raiseCLIError
42
from kamaki.clients.cyclades import CycladesClient, ClientError
43
from kamaki.cli.argument import FlagArgument, ValueArgument
44
from kamaki.cli.commands import _command_init
45

    
46
from base64 import b64encode
47
from os.path import abspath, exists
48

    
49
class _init_cyclades(_command_init):
50
    def main(self, service='compute'):
51
        token = self.config.get(service, 'token') or self.config.get('global', 'token')
52
        base_url = self.config.get(service, 'url') or self.config.get('global', 'url')
53
        self.client = CycladesClient(base_url=base_url, token=token)
54

    
55
@command()
56
class server_list(_init_cyclades):
57
    """List servers"""
58

    
59
    def __init__(self, arguments={}):
60
        super(server_list, self).__init__(arguments)
61
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
62

    
63
    def _print(self, servers):
64
        for server in servers:
65
            sname = server.pop('name')
66
            sid = server.pop('id')
67
            print('%s (%s)'%(bold(sname), bold(unicode(sid))))
68
            if self.get_argument('detail'):
69
                server_info._print(server)
70
                print('- - -')
71

    
72
    def main(self):
73
        super(self.__class__, self).main()
74
        try:
75
            servers = self.client.list_servers(self.get_argument('detail'))
76
            self._print(servers)
77
            #print_items(servers)
78
        except ClientError as err:
79
            raiseCLIError(err)
80

    
81
@command()
82
class server_info(_init_cyclades):
83
    """Get server details"""
84

    
85
    @classmethod
86
    def _print(self,server):
87
        addr_dict = {}
88
        if server.has_key('attachments'):
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 addr.has_key('firewallProfile'):
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 server.has_key('metadata'):
98
            server['metadata'] = server['metadata']['values']
99
        print_dict(server, ident=14)
100

    
101
    def main(self, server_id):
102
        super(self.__class__, self).main()
103
        try:
104
            server = self.client.get_server_details(int(server_id))
105
        except ClientError as err:
106
            raiseCLIError(err)
107
        except ValueError as err:
108
            raise CLIError(message='Server id must be positive integer',
109
                importance=1)
110
        self._print(server)
111

    
112
class PersonalityArgument(ValueArgument):
113
    @property 
114
    def value(self):
115
        return [self._value] if hasattr(self, '_value') else []
116
    @value.setter 
117
    def value(self, newvalue):
118
        if newvalue == self.default:
119
            return self.value
120
        termlist = newvalue.split()
121
        if len(termlist) > 4:
122
                raise CLISyntaxError(details='Wrong number of personality terms ("PATH [OWNER [GROUP [MODE]]]"')
123
        path = termlist[0]
124
        self._value = dict(path=path)
125
        if not exists(path):
126
            raise CLIError(message="File %s does not exist" % path, importance=1)
127
        with open(path) as f:
128
            self._value['contents'] = b64encode(f.read())
129
        try:
130
            self._value['owner'] = termlist[1]
131
            self._value['group'] = termlist[2]
132
            self._value['mode'] = termlist[3]
133
        except IndexError:
134
            pass
135

    
136
@command()
137
class server_create(_init_cyclades):
138
    """Create a server"""
139

    
140
    def __init__(self, arguments={}):
141
        super(server_create, self).__init__(arguments)
142
        self.arguments['personality'] = PersonalityArgument(parsed_name='--personality',
143
            help='add a personality file ( "PATH [OWNER [GROUP [MODE]]]" )')
144

    
145
    def update_parser(self, parser):
146
        parser.add_argument('--personality', dest='personalities',
147
                          action='append', default=[],
148
                          metavar='PATH[,SERVER PATH[,OWNER[,GROUP,[MODE]]]]',
149
                          help='add a personality file')
150

    
151
    def main(self, name, flavor_id, image_id):
152
        super(self.__class__, self).main()
153
        try:
154
            reply = self.client.create_server(name, int(flavor_id), image_id,
155
                self.get_argument('personality'))
156
        except ClientError as err:
157
            raiseCLIError(err)
158
        print_dict(reply)
159

    
160
@command()
161
class server_rename(_init_cyclades):
162
    """Update a server's name"""
163

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

    
173
@command()
174
class server_delete(_init_cyclades):
175
    """Delete a server"""
176

    
177
    def main(self, server_id):
178
        super(self.__class__, self).main()
179
        try:
180
            self.client.delete_server(int(server_id))
181
        except ClientError as err:
182
            raiseCLIError(err)
183
        except ValueError:
184
            raise CLIError(message='Server id must be positive integer', importance=1)
185

    
186
@command()
187
class server_reboot(_init_cyclades):
188
    """Reboot a server"""
189

    
190
    def __init__(self, arguments={}):
191
        super(server_reboot, self).__init__(arguments)
192
        self.arguments['hard'] = FlagArgument('perform a hard reboot', '-f')
193

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

    
203
@command()
204
class server_start(_init_cyclades):
205
    """Start a server"""
206

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

    
216
@command()
217
class server_shutdown(_init_cyclades):
218
    """Shutdown a server"""
219

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

    
229
@command()
230
class server_console(_init_cyclades):
231
    """Get a VNC console"""
232

    
233
    def main(self, server_id):
234
        super(self.__class__, self).main()
235
        try:
236
            reply = self.client.get_server_console(int(server_id))
237
        except ClientError as err:
238
            raiseCLIError(err)
239
        except ValueError:
240
            raise CLIError(message='Server id must be positive integer', importance=1)
241
        print_dict(reply)
242

    
243
@command()
244
class server_firewall(_init_cyclades):
245
    """Set the server's firewall profile"""
246

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

    
256
@command()
257
class server_addr(_init_cyclades):
258
    """List a server's nic address"""
259

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

    
270
@command()
271
class server_meta(_init_cyclades):
272
    """Get a server's metadata"""
273

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

    
284
@command()
285
class server_addmeta(_init_cyclades):
286
    """Add server metadata"""
287

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

    
298
@command()
299
class server_setmeta(_init_cyclades):
300
    """Update server's metadata"""
301

    
302
    def main(self, server_id, key, val):
303
        super(self.__class__, self).main()
304
        metadata = {key: val}
305
        try:
306
            reply = self.client.update_server_metadata(int(server_id), **metadata)
307
        except ClientError as err:
308
            raiseCLIError(err)
309
        except ValueError:
310
            raise CLIError(message='Server id must be positive integer', importance=1)
311
        print_dict(reply)
312

    
313
@command()
314
class server_delmeta(_init_cyclades):
315
    """Delete server metadata"""
316

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

    
326
@command()
327
class server_stats(_init_cyclades):
328
    """Get server statistics"""
329

    
330
    def main(self, server_id):
331
        super(self.__class__, self).main()
332
        try:
333
            reply = self.client.get_server_stats(int(server_id))
334
        except ClientError as err:
335
            raiseCLIError(err)
336
        except ValueError:
337
            raise CLIError(message='Server id must be positive integer', importance=1)
338
        print_dict(reply, exclude=('serverRef',))
339

    
340
@command()
341
class flavor_list(_init_cyclades):
342
    """List flavors"""
343

    
344
    def __init__(self, arguments={}):
345
        super(flavor_list, self).__init__(arguments)
346
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
347

    
348
    def main(self):
349
        super(self.__class__, self).main()
350
        try:
351
            flavors = self.client.list_flavors(self.get_argument('detail'))
352
        except ClientError as err:
353
            raiseCLIError(err)
354
        print_items(flavors)
355

    
356
@command()
357
class flavor_info(_init_cyclades):
358
    """Get flavor details"""
359

    
360
    def main(self, flavor_id):
361
        super(self.__class__, self).main()
362
        try:
363
            flavor = self.client.get_flavor_details(int(flavor_id))
364
        except ClientError as err:
365
            raiseCLIError(err)
366
        except ValueError:
367
            raise CLIError(message='Server id must be positive integer', importance=1)
368
        print_dict(flavor)
369

    
370
@command()
371
class network_list(_init_cyclades):
372
    """List networks"""
373

    
374
    def __init__(self, arguments={}):
375
        super(network_list, self).__init__(arguments)
376
        self.arguments['detail'] = FlagArgument('show detailed output', '-l')
377

    
378
    def print_networks(self, nets):
379
        for net in nets:
380
            netname = bold(net.pop('name'))
381
            netid = bold(unicode(net.pop('id')))
382
            print('%s (%s)'%(netname, netid))
383
            if self.get_argument('detail'):
384
                network_info.print_network(net)
385

    
386
    def main(self):
387
        super(self.__class__, self).main()
388
        try:
389
            networks = self.client.list_networks(self.get_argument('detail'))
390
        except ClientError as err:
391
            raiseCLIError(err)
392
        self.print_networks(networks)
393

    
394
@command()
395
class network_create(_init_cyclades):
396
    """Create a network"""
397

    
398
    def __init__(self, arguments={}):
399
        super(network_create, self).__init__(arguments)
400
        self.arguments['cidr'] = ValueArgument('specific cidr for new network', '--with-cidr')
401
        self.arguments['gateway'] = ValueArgument('specific gateway for new network',
402
            '--with-gateway')
403
        self.arguments['dhcp'] = ValueArgument('specific dhcp for new network', '--with-dhcp')
404
        self.arguments['type'] = ValueArgument('specific type for new network', '--with-type')
405

    
406
    def main(self, name):
407
        super(self.__class__, self).main()
408
        try:
409
            reply = self.client.create_network(name, cidr= self.get_argument('cidr'),
410
                gateway=self.get_argument('gateway'), dhcp=self.get_argument('dhcp'),
411
                type=self.get_argument('type'))
412
        except ClientError as err:
413
            raiseCLIError(err)
414
        print_dict(reply)
415

    
416
@command()
417
class network_info(_init_cyclades):
418
    """Get network details"""
419

    
420
    @classmethod
421
    def print_network(self, net):
422
        if net.has_key('attachments'):
423
            att = net['attachments']['values']
424
            net['attachments'] = att if len(att) > 0 else None
425
        print_dict(net, ident=14)
426

    
427
    def main(self, network_id):
428
        super(self.__class__, self).main()
429
        try:
430
            network = self.client.get_network_details(network_id)
431
        except ClientError as err:
432
            raiseCLIError(err)
433
        network_info.print_network(network)
434

    
435
@command()
436
class network_rename(_init_cyclades):
437
    """Update network name"""
438

    
439
    def main(self, network_id, new_name):
440
        super(self.__class__, self).main()
441
        try:
442
            self.client.update_network_name(network_id, new_name)
443
        except ClientError as err:
444
            raiseCLIError(err)
445

    
446
@command()
447
class network_delete(_init_cyclades):
448
    """Delete a network"""
449

    
450
    def main(self, network_id):
451
        super(self.__class__, self).main()
452
        try:
453
            self.client.delete_network(network_id)
454
        except ClientError as err:
455
            raiseCLIError(err)
456

    
457
@command()
458
class network_connect(_init_cyclades):
459
    """Connect a server to a network"""
460

    
461
    def main(self, server_id, network_id):
462
        super(self.__class__, self).main()
463
        try:
464
            self.client.connect_server(server_id, network_id)
465
        except ClientError as err:
466
            raiseCLIError(err)
467

    
468
@command()
469
class network_disconnect(_init_cyclades):
470
    """Disconnect a nic that connects a server to a network"""
471

    
472
    def main(self, nic_id):
473
        super(self.__class__, self).main()
474
        try:
475
            server_id = nic_id.split('-')[1]
476
            self.client.disconnect_server(server_id, nic_id)
477
        except IndexError:
478
            raise CLIError(message='Incorrect nic format', importance=1,
479
                details='nid_id format: nic-<server_id>-<nic_index>')
480
        except ClientError as err:
481
            raiseCLIError(err)