Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades.py @ f2ea1314

History | View | Annotate | Download (30.5 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, remove_from_items
37
from kamaki.cli.errors import raiseCLIError, CLISyntaxError, CLIBaseUrlError
38
from kamaki.clients.cyclades import CycladesClient, ClientError
39
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
40
from kamaki.cli.argument import ProgressBarArgument, DateArgument, IntArgument
41
from kamaki.cli.commands import _command_init, errors, addLogSettings
42
from kamaki.cli.commands import _optional_output_cmd, _optional_json
43

    
44
from base64 import b64encode
45
from os.path import exists
46

    
47

    
48
server_cmds = CommandTree('server', 'Cyclades/Compute API server commands')
49
flavor_cmds = CommandTree('flavor', 'Cyclades/Compute API flavor commands')
50
network_cmds = CommandTree('network', 'Cyclades/Compute API network commands')
51
floatingip_cmds = CommandTree(
52
    'floatingip', 'Cyclades/Compute API floating ip commands')
53
_commands = [server_cmds, flavor_cmds, network_cmds, floatingip_cmds]
54

    
55

    
56
about_authentication = '\nUser Authentication:\
57
    \n* to check authentication: /user authenticate\
58
    \n* to set authentication token: /config set cloud.<cloud>.token <token>'
59

    
60
howto_personality = [
61
    'Defines a file to be injected to VMs personality.',
62
    'Personality value syntax: PATH,[SERVER_PATH,[OWNER,[GROUP,[MODE]]]]',
63
    '  PATH: of local file to be injected',
64
    '  SERVER_PATH: destination location inside server Image',
65
    '  OWNER: user id of destination file owner',
66
    '  GROUP: group id or name to own destination file',
67
    '  MODEL: permition in octal (e.g. 0777 or o+rwx)']
68

    
69

    
70
class _server_wait(object):
71

    
72
    wait_arguments = dict(
73
        progress_bar=ProgressBarArgument(
74
            'do not show progress bar',
75
            ('-N', '--no-progress-bar'),
76
            False
77
        )
78
    )
79

    
80
    def _wait(self, server_id, currect_status):
81
        (progress_bar, wait_cb) = self._safe_progress_bar(
82
            'Server %s still in %s mode' % (server_id, currect_status))
83

    
84
        try:
85
            new_mode = self.client.wait_server(
86
                server_id,
87
                currect_status,
88
                wait_cb=wait_cb)
89
        except Exception:
90
            raise
91
        finally:
92
            self._safe_progress_bar_finish(progress_bar)
93
        if new_mode:
94
            print('Server %s is now in %s mode' % (server_id, new_mode))
95
        else:
96
            raiseCLIError(None, 'Time out')
97

    
98

    
99
class _network_wait(object):
100

    
101
    wait_arguments = dict(
102
        progress_bar=ProgressBarArgument(
103
            'do not show progress bar',
104
            ('-N', '--no-progress-bar'),
105
            False
106
        )
107
    )
108

    
109
    def _wait(self, net_id, currect_status):
110
        (progress_bar, wait_cb) = self._safe_progress_bar(
111
            'Network %s still in %s mode' % (net_id, currect_status))
112

    
113
        try:
114
            new_mode = self.client.wait_network(
115
                net_id,
116
                currect_status,
117
                wait_cb=wait_cb)
118
        except Exception:
119
            raise
120
        finally:
121
            self._safe_progress_bar_finish(progress_bar)
122
        if new_mode:
123
            print('Network %s is now in %s mode' % (net_id, new_mode))
124
        else:
125
            raiseCLIError(None, 'Time out')
126

    
127

    
128
class _init_cyclades(_command_init):
129
    @errors.generic.all
130
    @addLogSettings
131
    def _run(self, service='compute'):
132
        if getattr(self, 'cloud', None):
133
            base_url = self._custom_url(service)\
134
                or self._custom_url('cyclades')
135
            if base_url:
136
                token = self._custom_token(service)\
137
                    or self._custom_token('cyclades')\
138
                    or self.config.get_cloud('token')
139
                self.client = CycladesClient(
140
                    base_url=base_url, token=token)
141
                return
142
        else:
143
            self.cloud = 'default'
144
        if getattr(self, 'auth_base', False):
145
            cyclades_endpoints = self.auth_base.get_service_endpoints(
146
                self._custom_type('cyclades') or 'compute',
147
                self._custom_version('cyclades') or '')
148
            base_url = cyclades_endpoints['publicURL']
149
            token = self.auth_base.token
150
            self.client = CycladesClient(base_url=base_url, token=token)
151
        else:
152
            raise CLIBaseUrlError(service='cyclades')
153

    
154
    def main(self):
155
        self._run()
156

    
157

    
158
@command(server_cmds)
159
class server_list(_init_cyclades, _optional_json):
160
    """List Virtual Machines accessible by user"""
161

    
162
    __doc__ += about_authentication
163

    
164
    arguments = dict(
165
        detail=FlagArgument('show detailed output', ('-l', '--details')),
166
        since=DateArgument(
167
            'show only items since date (\' d/m/Y H:M:S \')',
168
            '--since'),
169
        limit=IntArgument('limit number of listed VMs', ('-n', '--number')),
170
        more=FlagArgument(
171
            'output results in pages (-n to set items per page, default 10)',
172
            '--more'),
173
        enum=FlagArgument('Enumerate results', '--enumerate')
174
    )
175

    
176
    @errors.generic.all
177
    @errors.cyclades.connection
178
    @errors.cyclades.date
179
    def _run(self):
180
        servers = self.client.list_servers(self['detail'], self['since'])
181
        if not (self['detail'] or self['json_output']):
182
            remove_from_items(servers, 'links')
183

    
184
        kwargs = dict(with_enumeration=self['enum'])
185
        if self['more']:
186
            kwargs['page_size'] = self['limit'] if self['limit'] else 10
187
        elif self['limit']:
188
            servers = servers[:self['limit']]
189
        self._print(servers, **kwargs)
190

    
191
    def main(self):
192
        super(self.__class__, self)._run()
193
        self._run()
194

    
195

    
196
@command(server_cmds)
197
class server_info(_init_cyclades, _optional_json):
198
    """Detailed information on a Virtual Machine
199
    Contains:
200
    - name, id, status, create/update dates
201
    - network interfaces
202
    - metadata (e.g. os, superuser) and diagnostics
203
    - hardware flavor and os image ids
204
    """
205

    
206
    @errors.generic.all
207
    @errors.cyclades.connection
208
    @errors.cyclades.server_id
209
    def _run(self, server_id):
210
        self._print(self.client.get_server_details(server_id), print_dict)
211

    
212
    def main(self, server_id):
213
        super(self.__class__, self)._run()
214
        self._run(server_id=server_id)
215

    
216

    
217
class PersonalityArgument(KeyValueArgument):
218
    @property
219
    def value(self):
220
        return self._value if hasattr(self, '_value') else []
221

    
222
    @value.setter
223
    def value(self, newvalue):
224
        if newvalue == self.default:
225
            return self.value
226
        self._value = []
227
        for i, terms in enumerate(newvalue):
228
            termlist = terms.split(',')
229
            if len(termlist) > 5:
230
                msg = 'Wrong number of terms (should be 1 to 5)'
231
                raiseCLIError(CLISyntaxError(msg), details=howto_personality)
232
            path = termlist[0]
233
            if not exists(path):
234
                raiseCLIError(
235
                    None,
236
                    '--personality: File %s does not exist' % path,
237
                    importance=1,
238
                    details=howto_personality)
239
            self._value.append(dict(path=path))
240
            with open(path) as f:
241
                self._value[i]['contents'] = b64encode(f.read())
242
            try:
243
                self._value[i]['path'] = termlist[1]
244
                self._value[i]['owner'] = termlist[2]
245
                self._value[i]['group'] = termlist[3]
246
                self._value[i]['mode'] = termlist[4]
247
            except IndexError:
248
                pass
249

    
250

    
251
@command(server_cmds)
252
class server_create(_init_cyclades, _optional_json, _server_wait):
253
    """Create a server (aka Virtual Machine)
254
    Parameters:
255
    - name: (single quoted text)
256
    - flavor id: Hardware flavor. Pick one from: /flavor list
257
    - image id: OS images. Pick one from: /image list
258
    """
259

    
260
    arguments = dict(
261
        personality=PersonalityArgument(
262
            (80 * ' ').join(howto_personality), ('-p', '--personality')),
263
        wait=FlagArgument('Wait server to build', ('-w', '--wait'))
264
    )
265

    
266
    @errors.generic.all
267
    @errors.cyclades.connection
268
    @errors.plankton.id
269
    @errors.cyclades.flavor_id
270
    def _run(self, name, flavor_id, image_id):
271
        r = self.client.create_server(
272
            name, int(flavor_id), image_id, self['personality'])
273
        self._print(r, print_dict)
274
        if self['wait']:
275
            self._wait(r['id'], r['status'])
276

    
277
    def main(self, name, flavor_id, image_id):
278
        super(self.__class__, self)._run()
279
        self._run(name=name, flavor_id=flavor_id, image_id=image_id)
280

    
281

    
282
@command(server_cmds)
283
class server_rename(_init_cyclades, _optional_output_cmd):
284
    """Set/update a server (VM) name
285
    VM names are not unique, therefore multiple servers may share the same name
286
    """
287

    
288
    @errors.generic.all
289
    @errors.cyclades.connection
290
    @errors.cyclades.server_id
291
    def _run(self, server_id, new_name):
292
        self._optional_output(
293
            self.client.update_server_name(int(server_id), new_name))
294

    
295
    def main(self, server_id, new_name):
296
        super(self.__class__, self)._run()
297
        self._run(server_id=server_id, new_name=new_name)
298

    
299

    
300
@command(server_cmds)
301
class server_delete(_init_cyclades, _optional_output_cmd, _server_wait):
302
    """Delete a server (VM)"""
303

    
304
    arguments = dict(
305
        wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))
306
    )
307

    
308
    @errors.generic.all
309
    @errors.cyclades.connection
310
    @errors.cyclades.server_id
311
    def _run(self, server_id):
312
            status = 'DELETED'
313
            if self['wait']:
314
                details = self.client.get_server_details(server_id)
315
                status = details['status']
316

    
317
            r = self.client.delete_server(int(server_id))
318
            self._optional_output(r)
319

    
320
            if self['wait']:
321
                self._wait(server_id, status)
322

    
323
    def main(self, server_id):
324
        super(self.__class__, self)._run()
325
        self._run(server_id=server_id)
326

    
327

    
328
@command(server_cmds)
329
class server_reboot(_init_cyclades, _optional_output_cmd, _server_wait):
330
    """Reboot a server (VM)"""
331

    
332
    arguments = dict(
333
        hard=FlagArgument('perform a hard reboot', ('-f', '--force')),
334
        wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))
335
    )
336

    
337
    @errors.generic.all
338
    @errors.cyclades.connection
339
    @errors.cyclades.server_id
340
    def _run(self, server_id):
341
        r = self.client.reboot_server(int(server_id), self['hard'])
342
        self._optional_output(r)
343

    
344
        if self['wait']:
345
            self._wait(server_id, 'REBOOT')
346

    
347
    def main(self, server_id):
348
        super(self.__class__, self)._run()
349
        self._run(server_id=server_id)
350

    
351

    
352
@command(server_cmds)
353
class server_start(_init_cyclades, _optional_output_cmd, _server_wait):
354
    """Start an existing server (VM)"""
355

    
356
    arguments = dict(
357
        wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))
358
    )
359

    
360
    @errors.generic.all
361
    @errors.cyclades.connection
362
    @errors.cyclades.server_id
363
    def _run(self, server_id):
364
        status = 'ACTIVE'
365
        if self['wait']:
366
            details = self.client.get_server_details(server_id)
367
            status = details['status']
368
            if status in ('ACTIVE', ):
369
                return
370

    
371
        r = self.client.start_server(int(server_id))
372
        self._optional_output(r)
373

    
374
        if self['wait']:
375
            self._wait(server_id, status)
376

    
377
    def main(self, server_id):
378
        super(self.__class__, self)._run()
379
        self._run(server_id=server_id)
380

    
381

    
382
@command(server_cmds)
383
class server_shutdown(_init_cyclades, _optional_output_cmd, _server_wait):
384
    """Shutdown an active server (VM)"""
385

    
386
    arguments = dict(
387
        wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))
388
    )
389

    
390
    @errors.generic.all
391
    @errors.cyclades.connection
392
    @errors.cyclades.server_id
393
    def _run(self, server_id):
394
        status = 'STOPPED'
395
        if self['wait']:
396
            details = self.client.get_server_details(server_id)
397
            status = details['status']
398
            if status in ('STOPPED', ):
399
                return
400

    
401
        r = self.client.shutdown_server(int(server_id))
402
        self._optional_output(r)
403

    
404
        if self['wait']:
405
            self._wait(server_id, status)
406

    
407
    def main(self, server_id):
408
        super(self.__class__, self)._run()
409
        self._run(server_id=server_id)
410

    
411

    
412
@command(server_cmds)
413
class server_console(_init_cyclades, _optional_json):
414
    """Get a VNC console to access an existing server (VM)
415
    Console connection information provided (at least):
416
    - host: (url or address) a VNC host
417
    - port: (int) the gateway to enter VM on host
418
    - password: for VNC authorization
419
    """
420

    
421
    @errors.generic.all
422
    @errors.cyclades.connection
423
    @errors.cyclades.server_id
424
    def _run(self, server_id):
425
        self._print(
426
            self.client.get_server_console(int(server_id)), print_dict)
427

    
428
    def main(self, server_id):
429
        super(self.__class__, self)._run()
430
        self._run(server_id=server_id)
431

    
432

    
433
@command(server_cmds)
434
class server_resize(_init_cyclades, _optional_output_cmd):
435
    """Set a different flavor for an existing server
436
    To get server ids and flavor ids:
437
    /server list
438
    /flavor list
439
    """
440

    
441
    @errors.generic.all
442
    @errors.cyclades.connection
443
    @errors.cyclades.server_id
444
    @errors.cyclades.flavor_id
445
    def _run(self, server_id, flavor_id):
446
        self._optional_output(self.client.resize_server(server_id, flavor_id))
447

    
448
    def main(self, server_id, flavor_id):
449
        super(self.__class__, self)._run()
450
        self._run(server_id=server_id, flavor_id=flavor_id)
451

    
452

    
453
@command(server_cmds)
454
class server_firewall(_init_cyclades):
455
    """Manage server (VM) firewall profiles for public networks"""
456

    
457

    
458
@command(server_cmds)
459
class server_firewall_set(_init_cyclades, _optional_output_cmd):
460
    """Set the server (VM) firewall profile on VMs public network
461
    Values for profile:
462
    - DISABLED: Shutdown firewall
463
    - ENABLED: Firewall in normal mode
464
    - PROTECTED: Firewall in secure mode
465
    """
466

    
467
    @errors.generic.all
468
    @errors.cyclades.connection
469
    @errors.cyclades.server_id
470
    @errors.cyclades.firewall
471
    def _run(self, server_id, profile):
472
        self._optional_output(self.client.set_firewall_profile(
473
            server_id=int(server_id), profile=('%s' % profile).upper()))
474

    
475
    def main(self, server_id, profile):
476
        super(self.__class__, self)._run()
477
        self._run(server_id=server_id, profile=profile)
478

    
479

    
480
@command(server_cmds)
481
class server_firewall_get(_init_cyclades):
482
    """Get the server (VM) firewall profile for its public network"""
483

    
484
    @errors.generic.all
485
    @errors.cyclades.connection
486
    @errors.cyclades.server_id
487
    def _run(self, server_id):
488
        print(self.client.get_firewall_profile(server_id))
489

    
490
    def main(self, server_id):
491
        super(self.__class__, self)._run()
492
        self._run(server_id=server_id)
493

    
494

    
495
@command(server_cmds)
496
class server_addr(_init_cyclades, _optional_json):
497
    """List the addresses of all network interfaces on a server (VM)"""
498

    
499
    arguments = dict(
500
        enum=FlagArgument('Enumerate results', '--enumerate')
501
    )
502

    
503
    @errors.generic.all
504
    @errors.cyclades.connection
505
    @errors.cyclades.server_id
506
    def _run(self, server_id):
507
        reply = self.client.list_server_nics(int(server_id))
508
        self._print(
509
            reply, with_enumeration=self['enum'] and len(reply) > 1)
510

    
511
    def main(self, server_id):
512
        super(self.__class__, self)._run()
513
        self._run(server_id=server_id)
514

    
515

    
516
@command(server_cmds)
517
class server_metadata(_init_cyclades):
518
    """Manage Server metadata (key:value pairs of server attributes)"""
519

    
520

    
521
@command(server_cmds)
522
class server_metadata_list(_init_cyclades, _optional_json):
523
    """Get server metadata"""
524

    
525
    @errors.generic.all
526
    @errors.cyclades.connection
527
    @errors.cyclades.server_id
528
    @errors.cyclades.metadata
529
    def _run(self, server_id, key=''):
530
        self._print(
531
            self.client.get_server_metadata(int(server_id), key), print_dict)
532

    
533
    def main(self, server_id, key=''):
534
        super(self.__class__, self)._run()
535
        self._run(server_id=server_id, key=key)
536

    
537

    
538
@command(server_cmds)
539
class server_metadata_set(_init_cyclades, _optional_json):
540
    """Set / update server(VM) metadata
541
    Metadata should be given in key/value pairs in key=value format
542
    For example: /server metadata set <server id> key1=value1 key2=value2
543
    Old, unreferenced metadata will remain intact
544
    """
545

    
546
    @errors.generic.all
547
    @errors.cyclades.connection
548
    @errors.cyclades.server_id
549
    def _run(self, server_id, keyvals):
550
        assert keyvals, 'Please, add some metadata ( key=value)'
551
        metadata = dict()
552
        for keyval in keyvals:
553
            k, sep, v = keyval.partition('=')
554
            if sep and k:
555
                metadata[k] = v
556
            else:
557
                raiseCLIError(
558
                    'Invalid piece of metadata %s' % keyval,
559
                    importance=2, details=[
560
                        'Correct metadata format: key=val',
561
                        'For example:',
562
                        '/server metadata set <server id>'
563
                        'key1=value1 key2=value2'])
564
        self._print(
565
            self.client.update_server_metadata(int(server_id), **metadata),
566
            print_dict)
567

    
568
    def main(self, server_id, *key_equals_val):
569
        super(self.__class__, self)._run()
570
        self._run(server_id=server_id, keyvals=key_equals_val)
571

    
572

    
573
@command(server_cmds)
574
class server_metadata_delete(_init_cyclades, _optional_output_cmd):
575
    """Delete server (VM) metadata"""
576

    
577
    @errors.generic.all
578
    @errors.cyclades.connection
579
    @errors.cyclades.server_id
580
    @errors.cyclades.metadata
581
    def _run(self, server_id, key):
582
        self._optional_output(
583
            self.client.delete_server_metadata(int(server_id), key))
584

    
585
    def main(self, server_id, key):
586
        super(self.__class__, self)._run()
587
        self._run(server_id=server_id, key=key)
588

    
589

    
590
@command(server_cmds)
591
class server_stats(_init_cyclades, _optional_json):
592
    """Get server (VM) statistics"""
593

    
594
    @errors.generic.all
595
    @errors.cyclades.connection
596
    @errors.cyclades.server_id
597
    def _run(self, server_id):
598
        self._print(self.client.get_server_stats(int(server_id)), print_dict)
599

    
600
    def main(self, server_id):
601
        super(self.__class__, self)._run()
602
        self._run(server_id=server_id)
603

    
604

    
605
@command(server_cmds)
606
class server_wait(_init_cyclades, _server_wait):
607
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
608

    
609
    @errors.generic.all
610
    @errors.cyclades.connection
611
    @errors.cyclades.server_id
612
    def _run(self, server_id, currect_status):
613
        self._wait(server_id, currect_status)
614

    
615
    def main(self, server_id, currect_status='BUILD'):
616
        super(self.__class__, self)._run()
617
        self._run(server_id=server_id, currect_status=currect_status)
618

    
619

    
620
@command(flavor_cmds)
621
class flavor_list(_init_cyclades, _optional_json):
622
    """List available hardware flavors"""
623

    
624
    arguments = dict(
625
        detail=FlagArgument('show detailed output', ('-l', '--details')),
626
        limit=IntArgument('limit # of listed flavors', ('-n', '--number')),
627
        more=FlagArgument(
628
            'output results in pages (-n to set items per page, default 10)',
629
            '--more'),
630
        enum=FlagArgument('Enumerate results', '--enumerate')
631
    )
632

    
633
    @errors.generic.all
634
    @errors.cyclades.connection
635
    def _run(self):
636
        flavors = self.client.list_flavors(self['detail'])
637
        if not (self['detail'] or self['json_output']):
638
            remove_from_items(flavors, 'links')
639
        pg_size = 10 if self['more'] and not self['limit'] else self['limit']
640
        self._print(
641
            flavors,
642
            with_redundancy=self['detail'],
643
            page_size=pg_size,
644
            with_enumeration=self['enum'])
645

    
646
    def main(self):
647
        super(self.__class__, self)._run()
648
        self._run()
649

    
650

    
651
@command(flavor_cmds)
652
class flavor_info(_init_cyclades, _optional_json):
653
    """Detailed information on a hardware flavor
654
    To get a list of available flavors and flavor ids, try /flavor list
655
    """
656

    
657
    @errors.generic.all
658
    @errors.cyclades.connection
659
    @errors.cyclades.flavor_id
660
    def _run(self, flavor_id):
661
        self._print(
662
            self.client.get_flavor_details(int(flavor_id)), print_dict)
663

    
664
    def main(self, flavor_id):
665
        super(self.__class__, self)._run()
666
        self._run(flavor_id=flavor_id)
667

    
668

    
669
@command(network_cmds)
670
class network_info(_init_cyclades, _optional_json):
671
    """Detailed information on a network
672
    To get a list of available networks and network ids, try /network list
673
    """
674

    
675
    @errors.generic.all
676
    @errors.cyclades.connection
677
    @errors.cyclades.network_id
678
    def _run(self, network_id):
679
        network = self.client.get_network_details(int(network_id))
680
        self._print(network, print_dict, exclude=('id'))
681

    
682
    def main(self, network_id):
683
        super(self.__class__, self)._run()
684
        self._run(network_id=network_id)
685

    
686

    
687
@command(network_cmds)
688
class network_list(_init_cyclades, _optional_json):
689
    """List networks"""
690

    
691
    arguments = dict(
692
        detail=FlagArgument('show detailed output', ('-l', '--details')),
693
        limit=IntArgument('limit # of listed networks', ('-n', '--number')),
694
        more=FlagArgument(
695
            'output results in pages (-n to set items per page, default 10)',
696
            '--more'),
697
        enum=FlagArgument('Enumerate results', '--enumerate')
698
    )
699

    
700
    @errors.generic.all
701
    @errors.cyclades.connection
702
    def _run(self):
703
        networks = self.client.list_networks(self['detail'])
704
        if not (self['detail'] or self['json_output']):
705
            remove_from_items(networks, 'links')
706
        kwargs = dict(with_enumeration=self['enum'])
707
        if self['more']:
708
            kwargs['page_size'] = self['limit'] or 10
709
        elif self['limit']:
710
            networks = networks[:self['limit']]
711
        self._print(networks, **kwargs)
712

    
713
    def main(self):
714
        super(self.__class__, self)._run()
715
        self._run()
716

    
717

    
718
@command(network_cmds)
719
class network_create(_init_cyclades, _optional_json, _network_wait):
720
    """Create an (unconnected) network"""
721

    
722
    arguments = dict(
723
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
724
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
725
        dhcp=FlagArgument('Use dhcp (default: off)', '--with-dhcp'),
726
        type=ValueArgument(
727
            'Valid network types are '
728
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
729
            '--with-type',
730
            default='MAC_FILTERED'),
731
        wait=FlagArgument('Wait network to build', ('-w', '--wait'))
732
    )
733

    
734
    @errors.generic.all
735
    @errors.cyclades.connection
736
    @errors.cyclades.network_max
737
    def _run(self, name):
738
        r = self.client.create_network(
739
            name,
740
            cidr=self['cidr'],
741
            gateway=self['gateway'],
742
            dhcp=self['dhcp'],
743
            type=self['type'])
744
        self._print(r, print_dict)
745

    
746
        if self['wait']:
747
            self._wait(r['id'], 'PENDING')
748

    
749
    def main(self, name):
750
        super(self.__class__, self)._run()
751
        self._run(name)
752

    
753

    
754
@command(network_cmds)
755
class network_rename(_init_cyclades, _optional_output_cmd):
756
    """Set the name of a network"""
757

    
758
    @errors.generic.all
759
    @errors.cyclades.connection
760
    @errors.cyclades.network_id
761
    def _run(self, network_id, new_name):
762
        self._optional_output(
763
                self.client.update_network_name(int(network_id), new_name))
764

    
765
    def main(self, network_id, new_name):
766
        super(self.__class__, self)._run()
767
        self._run(network_id=network_id, new_name=new_name)
768

    
769

    
770
@command(network_cmds)
771
class network_delete(_init_cyclades, _optional_output_cmd, _network_wait):
772
    """Delete a network"""
773

    
774
    arguments = dict(
775
        wait=FlagArgument('Wait network to build', ('-w', '--wait'))
776
    )
777

    
778
    @errors.generic.all
779
    @errors.cyclades.connection
780
    @errors.cyclades.network_id
781
    @errors.cyclades.network_in_use
782
    def _run(self, network_id):
783
        status = 'DELETED'
784
        if self['wait']:
785
            r = self.client.get_network_details(network_id)
786
            status = r['status']
787
            if status in ('DELETED', ):
788
                return
789

    
790
        r = self.client.delete_network(int(network_id))
791
        self._optional_output(r)
792

    
793
        if self['wait']:
794
            self._wait(network_id, status)
795

    
796
    def main(self, network_id):
797
        super(self.__class__, self)._run()
798
        self._run(network_id=network_id)
799

    
800

    
801
@command(network_cmds)
802
class network_connect(_init_cyclades, _optional_output_cmd):
803
    """Connect a server to a network"""
804

    
805
    @errors.generic.all
806
    @errors.cyclades.connection
807
    @errors.cyclades.server_id
808
    @errors.cyclades.network_id
809
    def _run(self, server_id, network_id):
810
        self._optional_output(
811
                self.client.connect_server(int(server_id), int(network_id)))
812

    
813
    def main(self, server_id, network_id):
814
        super(self.__class__, self)._run()
815
        self._run(server_id=server_id, network_id=network_id)
816

    
817

    
818
@command(network_cmds)
819
class network_disconnect(_init_cyclades):
820
    """Disconnect a nic that connects a server to a network
821
    Nic ids are listed as "attachments" in detailed network information
822
    To get detailed network information: /network info <network id>
823
    """
824

    
825
    @errors.cyclades.nic_format
826
    def _server_id_from_nic(self, nic_id):
827
        return nic_id.split('-')[1]
828

    
829
    @errors.generic.all
830
    @errors.cyclades.connection
831
    @errors.cyclades.server_id
832
    @errors.cyclades.nic_id
833
    def _run(self, nic_id, server_id):
834
        num_of_disconnected = self.client.disconnect_server(server_id, nic_id)
835
        if not num_of_disconnected:
836
            raise ClientError(
837
                'Network Interface %s not found on server %s' % (
838
                    nic_id,
839
                    server_id),
840
                status=404)
841
        print('Disconnected %s connections' % num_of_disconnected)
842

    
843
    def main(self, nic_id):
844
        super(self.__class__, self)._run()
845
        server_id = self._server_id_from_nic(nic_id=nic_id)
846
        self._run(nic_id=nic_id, server_id=server_id)
847

    
848

    
849
@command(network_cmds)
850
class network_wait(_init_cyclades, _network_wait):
851
    """Wait for server to finish [PENDING, ACTIVE, DELETED]"""
852

    
853
    @errors.generic.all
854
    @errors.cyclades.connection
855
    @errors.cyclades.network_id
856
    def _run(self, network_id, currect_status):
857
        self._wait(network_id, currect_status)
858

    
859
    def main(self, network_id, currect_status='PENDING'):
860
        super(self.__class__, self)._run()
861
        self._run(network_id=network_id, currect_status=currect_status)
862

    
863

    
864
@command(floatingip_cmds)
865
class floatingip_pools(_init_cyclades, _optional_json):
866
    """List all floating pools of floating ips"""
867

    
868
    @errors.generic.all
869
    @errors.cyclades.connection
870
    def _run(self):
871
        r = self.client.get_floating_ip_pools()
872
        self._print(r if self['json_output'] else r['floating_ip_pools'])
873

    
874
    def main(self):
875
        super(self.__class__, self)._run()
876
        self._run()
877

    
878

    
879
@command(floatingip_cmds)
880
class floatingip_list(_init_cyclades, _optional_json):
881
    """List all floating ips"""
882

    
883
    @errors.generic.all
884
    @errors.cyclades.connection
885
    def _run(self):
886
        r = self.client.get_floating_ips()
887
        self._print(r if self['json_output'] else r['floating_ips'])
888

    
889
    def main(self):
890
        super(self.__class__, self)._run()
891
        self._run()
892

    
893

    
894
@command(floatingip_cmds)
895
class floatingip_info(_init_cyclades, _optional_json):
896
    """A floating IPs' details"""
897

    
898
    @errors.generic.all
899
    @errors.cyclades.connection
900
    def _run(self, ip):
901
        self._print(self.client.get_floating_ip(ip), print_dict)
902

    
903
    def main(self, ip):
904
        super(self.__class__, self)._run()
905
        self._run(ip=ip)
906

    
907

    
908
@command(floatingip_cmds)
909
class floatingip_create(_init_cyclades, _optional_json):
910
    """Create a new floating IP"""
911

    
912
    arguments = dict(
913
        pool=ValueArgument('Source IP pool', ('--pool'), None)
914
    )
915

    
916
    @errors.generic.all
917
    @errors.cyclades.connection
918
    def _run(self, ip=None):
919
        self._print(
920
            self.client.alloc_floating_ip(self['pool'], ip), print_dict)
921

    
922
    def main(self, requested_address=None):
923
        super(self.__class__, self)._run()
924
        self._run(ip=requested_address)
925

    
926

    
927
@command(floatingip_cmds)
928
class floatingip_delete(_init_cyclades, _optional_output_cmd):
929
    """Delete a floating ip"""
930

    
931
    @errors.generic.all
932
    @errors.cyclades.connection
933
    def _run(self, ip):
934
        self._optional_output(self.client.delete_floating_ip(ip))
935

    
936
    def main(self, ip):
937
        super(self.__class__, self)._run()
938
        self._run(ip=ip)
939

    
940

    
941
@command(server_cmds)
942
class server_ip_attach(_init_cyclades, _optional_output_cmd):
943
    """Attach a floating ip to a server with server_id
944
    """
945

    
946
    @errors.generic.all
947
    @errors.cyclades.connection
948
    @errors.cyclades.server_id
949
    def _run(self, server_id, ip):
950
        self._optional_output(self.client.attach_floating_ip(server_id, ip))
951

    
952
    def main(self, server_id, ip):
953
        super(self.__class__, self)._run()
954
        self._run(server_id=server_id, ip=ip)
955

    
956

    
957
@command(server_cmds)
958
class server_ip_detach(_init_cyclades):
959
    """detach_floating_ip_to_server
960
    """
961

    
962
    @errors.generic.all
963
    @errors.cyclades.connection
964
    @errors.cyclades.server_id
965
    def _run(self, server_id, ip):
966
        self._optional_output(self.client.detach_floating_ip(server_id, ip))
967

    
968
    def main(self, server_id, ip):
969
        super(self.__class__, self)._run()
970
        self._run(server_id=server_id, ip=ip)