Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades.py @ 60c42f9f

History | View | Annotate | Download (28.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, 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
            self._safe_progress_bar_finish(progress_bar)
91
            raise
92
        finally:
93
            self._safe_progress_bar_finish(progress_bar)
94
        if new_mode:
95
            print('Server %s is now in %s mode' % (server_id, new_mode))
96
        else:
97
            raiseCLIError(None, 'Time out')
98

    
99

    
100
class _init_cyclades(_command_init):
101
    @errors.generic.all
102
    @addLogSettings
103
    def _run(self, service='compute'):
104
        if getattr(self, 'cloud', None):
105
            base_url = self._custom_url(service)\
106
                or self._custom_url('cyclades')
107
            if base_url:
108
                token = self._custom_token(service)\
109
                    or self._custom_token('cyclades')\
110
                    or self.config.get_cloud('token')
111
                self.client = CycladesClient(
112
                    base_url=base_url, token=token)
113
                return
114
        else:
115
            self.cloud = 'default'
116
        if getattr(self, 'auth_base', False):
117
            cyclades_endpoints = self.auth_base.get_service_endpoints(
118
                self._custom_type('cyclades') or 'compute',
119
                self._custom_version('cyclades') or '')
120
            base_url = cyclades_endpoints['publicURL']
121
            token = self.auth_base.token
122
            self.client = CycladesClient(base_url=base_url, token=token)
123
        else:
124
            raise CLIBaseUrlError(service='cyclades')
125

    
126
    def main(self):
127
        self._run()
128

    
129

    
130
@command(server_cmds)
131
class server_list(_init_cyclades, _optional_json):
132
    """List Virtual Machines accessible by user"""
133

    
134
    __doc__ += about_authentication
135

    
136
    arguments = dict(
137
        detail=FlagArgument('show detailed output', ('-l', '--details')),
138
        since=DateArgument(
139
            'show only items since date (\' d/m/Y H:M:S \')',
140
            '--since'),
141
        limit=IntArgument('limit number of listed VMs', ('-n', '--number')),
142
        more=FlagArgument(
143
            'output results in pages (-n to set items per page, default 10)',
144
            '--more'),
145
        enum=FlagArgument('Enumerate results', '--enumerate')
146
    )
147

    
148
    @errors.generic.all
149
    @errors.cyclades.connection
150
    @errors.cyclades.date
151
    def _run(self):
152
        servers = self.client.list_servers(self['detail'], self['since'])
153
        if not (self['detail'] or self['json_output']):
154
            remove_from_items(servers, 'links')
155

    
156
        kwargs = dict(with_enumeration=self['enum'])
157
        if self['more']:
158
            kwargs['page_size'] = self['limit'] if self['limit'] else 10
159
        elif self['limit']:
160
            servers = servers[:self['limit']]
161
        self._print(servers, **kwargs)
162

    
163
    def main(self):
164
        super(self.__class__, self)._run()
165
        self._run()
166

    
167

    
168
@command(server_cmds)
169
class server_info(_init_cyclades, _optional_json):
170
    """Detailed information on a Virtual Machine
171
    Contains:
172
    - name, id, status, create/update dates
173
    - network interfaces
174
    - metadata (e.g. os, superuser) and diagnostics
175
    - hardware flavor and os image ids
176
    """
177

    
178
    @errors.generic.all
179
    @errors.cyclades.connection
180
    @errors.cyclades.server_id
181
    def _run(self, server_id):
182
        self._print(self.client.get_server_details(server_id), print_dict)
183

    
184
    def main(self, server_id):
185
        super(self.__class__, self)._run()
186
        self._run(server_id=server_id)
187

    
188

    
189
class PersonalityArgument(KeyValueArgument):
190
    @property
191
    def value(self):
192
        return self._value if hasattr(self, '_value') else []
193

    
194
    @value.setter
195
    def value(self, newvalue):
196
        if newvalue == self.default:
197
            return self.value
198
        self._value = []
199
        for i, terms in enumerate(newvalue):
200
            termlist = terms.split(',')
201
            if len(termlist) > 5:
202
                msg = 'Wrong number of terms (should be 1 to 5)'
203
                raiseCLIError(CLISyntaxError(msg), details=howto_personality)
204
            path = termlist[0]
205
            if not exists(path):
206
                raiseCLIError(
207
                    None,
208
                    '--personality: File %s does not exist' % path,
209
                    importance=1,
210
                    details=howto_personality)
211
            self._value.append(dict(path=path))
212
            with open(path) as f:
213
                self._value[i]['contents'] = b64encode(f.read())
214
            try:
215
                self._value[i]['path'] = termlist[1]
216
                self._value[i]['owner'] = termlist[2]
217
                self._value[i]['group'] = termlist[3]
218
                self._value[i]['mode'] = termlist[4]
219
            except IndexError:
220
                pass
221

    
222

    
223
@command(server_cmds)
224
class server_create(_init_cyclades, _optional_json, _server_wait):
225
    """Create a server (aka Virtual Machine)
226
    Parameters:
227
    - name: (single quoted text)
228
    - flavor id: Hardware flavor. Pick one from: /flavor list
229
    - image id: OS images. Pick one from: /image list
230
    """
231

    
232
    arguments = dict(
233
        personality=PersonalityArgument(
234
            (80 * ' ').join(howto_personality), ('-p', '--personality')),
235
        wait=FlagArgument('Wait server to build', ('-w', '--wait'))
236
    )
237

    
238
    @errors.generic.all
239
    @errors.cyclades.connection
240
    @errors.plankton.id
241
    @errors.cyclades.flavor_id
242
    def _run(self, name, flavor_id, image_id):
243
        r = self.client.create_server(
244
            name, int(flavor_id), image_id, self['personality'])
245
        self._print(r, print_dict)
246
        if self['wait']:
247
            self._wait(r['id'], r['status'])
248

    
249
    def main(self, name, flavor_id, image_id):
250
        super(self.__class__, self)._run()
251
        self._run(name=name, flavor_id=flavor_id, image_id=image_id)
252

    
253

    
254
@command(server_cmds)
255
class server_rename(_init_cyclades, _optional_output_cmd):
256
    """Set/update a server (VM) name
257
    VM names are not unique, therefore multiple servers may share the same name
258
    """
259

    
260
    @errors.generic.all
261
    @errors.cyclades.connection
262
    @errors.cyclades.server_id
263
    def _run(self, server_id, new_name):
264
        self._optional_output(
265
            self.client.update_server_name(int(server_id), new_name))
266

    
267
    def main(self, server_id, new_name):
268
        super(self.__class__, self)._run()
269
        self._run(server_id=server_id, new_name=new_name)
270

    
271

    
272
@command(server_cmds)
273
class server_delete(_init_cyclades, _optional_output_cmd, _server_wait):
274
    """Delete a server (VM)"""
275

    
276
    arguments = dict(
277
        wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))
278
    )
279

    
280
    @errors.generic.all
281
    @errors.cyclades.connection
282
    @errors.cyclades.server_id
283
    def _run(self, server_id):
284
            status = 'DELETED'
285
            if self['wait']:
286
                details = self.client.get_server_details(server_id)
287
                status = details['status']
288

    
289
            r = self.client.delete_server(int(server_id))
290
            self._optional_output(r)
291

    
292
            if self['wait']:
293
                self._wait(server_id, status)
294

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

    
299

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

    
304
    arguments = dict(
305
        hard=FlagArgument('perform a hard reboot', ('-f', '--force')),
306
        wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))
307
    )
308

    
309
    @errors.generic.all
310
    @errors.cyclades.connection
311
    @errors.cyclades.server_id
312
    def _run(self, server_id):
313
        r = self.client.reboot_server(int(server_id), self['hard'])
314
        self._optional_output(r)
315

    
316
        if self['wait']:
317
            self._wait(server_id, 'REBOOT')
318

    
319
    def main(self, server_id):
320
        super(self.__class__, self)._run()
321
        self._run(server_id=server_id)
322

    
323

    
324
@command(server_cmds)
325
class server_start(_init_cyclades, _optional_output_cmd, _server_wait):
326
    """Start an existing server (VM)"""
327

    
328
    arguments = dict(
329
        wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait'))
330
    )
331

    
332
    @errors.generic.all
333
    @errors.cyclades.connection
334
    @errors.cyclades.server_id
335
    def _run(self, server_id):
336
        status = 'ACTIVE'
337
        if self['wait']:
338
            details = self.client.get_server_details(server_id)
339
            status = details['status']
340
            if status in ('ACTIVE', ):
341
                return
342

    
343
        r = self.client.start_server(int(server_id))
344
        self._optional_output(r)
345

    
346
        if self['wait']:
347
            self._wait(server_id, status)
348

    
349
    def main(self, server_id):
350
        super(self.__class__, self)._run()
351
        self._run(server_id=server_id)
352

    
353

    
354
@command(server_cmds)
355
class server_shutdown(_init_cyclades, _optional_output_cmd, _server_wait):
356
    """Shutdown an active server (VM)"""
357

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

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

    
373
        r = self.client.shutdown_server(int(server_id))
374
        self._optional_output(r)
375

    
376
        if self['wait']:
377
            self._wait(server_id, status)
378

    
379
    def main(self, server_id):
380
        super(self.__class__, self)._run()
381
        self._run(server_id=server_id)
382

    
383

    
384
@command(server_cmds)
385
class server_console(_init_cyclades, _optional_json):
386
    """Get a VNC console to access an existing server (VM)
387
    Console connection information provided (at least):
388
    - host: (url or address) a VNC host
389
    - port: (int) the gateway to enter VM on host
390
    - password: for VNC authorization
391
    """
392

    
393
    @errors.generic.all
394
    @errors.cyclades.connection
395
    @errors.cyclades.server_id
396
    def _run(self, server_id):
397
        self._print(
398
            self.client.get_server_console(int(server_id)), print_dict)
399

    
400
    def main(self, server_id):
401
        super(self.__class__, self)._run()
402
        self._run(server_id=server_id)
403

    
404

    
405
@command(server_cmds)
406
class server_firewall(_init_cyclades):
407
    """Manage server (VM) firewall profiles for public networks"""
408

    
409

    
410
@command(server_cmds)
411
class server_firewall_set(_init_cyclades, _optional_output_cmd):
412
    """Set the server (VM) firewall profile on VMs public network
413
    Values for profile:
414
    - DISABLED: Shutdown firewall
415
    - ENABLED: Firewall in normal mode
416
    - PROTECTED: Firewall in secure mode
417
    """
418

    
419
    @errors.generic.all
420
    @errors.cyclades.connection
421
    @errors.cyclades.server_id
422
    @errors.cyclades.firewall
423
    def _run(self, server_id, profile):
424
        self._optional_output(self.client.set_firewall_profile(
425
            server_id=int(server_id), profile=('%s' % profile).upper()))
426

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

    
431

    
432
@command(server_cmds)
433
class server_firewall_get(_init_cyclades):
434
    """Get the server (VM) firewall profile for its public network"""
435

    
436
    @errors.generic.all
437
    @errors.cyclades.connection
438
    @errors.cyclades.server_id
439
    def _run(self, server_id):
440
        print(self.client.get_firewall_profile(server_id))
441

    
442
    def main(self, server_id):
443
        super(self.__class__, self)._run()
444
        self._run(server_id=server_id)
445

    
446

    
447
@command(server_cmds)
448
class server_addr(_init_cyclades, _optional_json):
449
    """List the addresses of all network interfaces on a server (VM)"""
450

    
451
    arguments = dict(
452
        enum=FlagArgument('Enumerate results', '--enumerate')
453
    )
454

    
455
    @errors.generic.all
456
    @errors.cyclades.connection
457
    @errors.cyclades.server_id
458
    def _run(self, server_id):
459
        reply = self.client.list_server_nics(int(server_id))
460
        self._print(
461
            reply, with_enumeration=self['enum'] and len(reply) > 1)
462

    
463
    def main(self, server_id):
464
        super(self.__class__, self)._run()
465
        self._run(server_id=server_id)
466

    
467

    
468
@command(server_cmds)
469
class server_metadata(_init_cyclades):
470
    """Manage Server metadata (key:value pairs of server attributes)"""
471

    
472

    
473
@command(server_cmds)
474
class server_metadata_list(_init_cyclades, _optional_json):
475
    """Get server metadata"""
476

    
477
    @errors.generic.all
478
    @errors.cyclades.connection
479
    @errors.cyclades.server_id
480
    @errors.cyclades.metadata
481
    def _run(self, server_id, key=''):
482
        self._print(
483
            self.client.get_server_metadata(int(server_id), key), print_dict)
484

    
485
    def main(self, server_id, key=''):
486
        super(self.__class__, self)._run()
487
        self._run(server_id=server_id, key=key)
488

    
489

    
490
@command(server_cmds)
491
class server_metadata_set(_init_cyclades, _optional_json):
492
    """Set / update server(VM) metadata
493
    Metadata should be given in key/value pairs in key=value format
494
    For example: /server metadata set <server id> key1=value1 key2=value2
495
    Old, unreferenced metadata will remain intact
496
    """
497

    
498
    @errors.generic.all
499
    @errors.cyclades.connection
500
    @errors.cyclades.server_id
501
    def _run(self, server_id, keyvals):
502
        assert keyvals, 'Please, add some metadata ( key=value)'
503
        metadata = dict()
504
        for keyval in keyvals:
505
            k, sep, v = keyval.partition('=')
506
            if sep and k:
507
                metadata[k] = v
508
            else:
509
                raiseCLIError(
510
                    'Invalid piece of metadata %s' % keyval,
511
                    importance=2, details=[
512
                        'Correct metadata format: key=val',
513
                        'For example:',
514
                        '/server metadata set <server id>'
515
                        'key1=value1 key2=value2'])
516
        self._print(
517
            self.client.update_server_metadata(int(server_id), **metadata),
518
            print_dict)
519

    
520
    def main(self, server_id, *key_equals_val):
521
        super(self.__class__, self)._run()
522
        self._run(server_id=server_id, keyvals=key_equals_val)
523

    
524

    
525
@command(server_cmds)
526
class server_metadata_delete(_init_cyclades, _optional_output_cmd):
527
    """Delete server (VM) metadata"""
528

    
529
    @errors.generic.all
530
    @errors.cyclades.connection
531
    @errors.cyclades.server_id
532
    @errors.cyclades.metadata
533
    def _run(self, server_id, key):
534
        self._optional_output(
535
            self.client.delete_server_metadata(int(server_id), key))
536

    
537
    def main(self, server_id, key):
538
        super(self.__class__, self)._run()
539
        self._run(server_id=server_id, key=key)
540

    
541

    
542
@command(server_cmds)
543
class server_stats(_init_cyclades, _optional_json):
544
    """Get server (VM) statistics"""
545

    
546
    @errors.generic.all
547
    @errors.cyclades.connection
548
    @errors.cyclades.server_id
549
    def _run(self, server_id):
550
        self._print(self.client.get_server_stats(int(server_id)), print_dict)
551

    
552
    def main(self, server_id):
553
        super(self.__class__, self)._run()
554
        self._run(server_id=server_id)
555

    
556

    
557
@command(server_cmds)
558
class server_wait(_init_cyclades, _server_wait):
559
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
560

    
561
    @errors.generic.all
562
    @errors.cyclades.connection
563
    @errors.cyclades.server_id
564
    def _run(self, server_id, currect_status):
565
        self._wait(server_id, currect_status)
566

    
567
    def main(self, server_id, currect_status='BUILD'):
568
        super(self.__class__, self)._run()
569
        self._run(server_id=server_id, currect_status=currect_status)
570

    
571

    
572
@command(flavor_cmds)
573
class flavor_list(_init_cyclades, _optional_json):
574
    """List available hardware flavors"""
575

    
576
    arguments = dict(
577
        detail=FlagArgument('show detailed output', ('-l', '--details')),
578
        limit=IntArgument('limit # of listed flavors', ('-n', '--number')),
579
        more=FlagArgument(
580
            'output results in pages (-n to set items per page, default 10)',
581
            '--more'),
582
        enum=FlagArgument('Enumerate results', '--enumerate')
583
    )
584

    
585
    @errors.generic.all
586
    @errors.cyclades.connection
587
    def _run(self):
588
        flavors = self.client.list_flavors(self['detail'])
589
        if not (self['detail'] or self['json_output']):
590
            remove_from_items(flavors, 'links')
591
        pg_size = 10 if self['more'] and not self['limit'] else self['limit']
592
        self._print(
593
            flavors,
594
            with_redundancy=self['detail'],
595
            page_size=pg_size,
596
            with_enumeration=self['enum'])
597

    
598
    def main(self):
599
        super(self.__class__, self)._run()
600
        self._run()
601

    
602

    
603
@command(flavor_cmds)
604
class flavor_info(_init_cyclades, _optional_json):
605
    """Detailed information on a hardware flavor
606
    To get a list of available flavors and flavor ids, try /flavor list
607
    """
608

    
609
    @errors.generic.all
610
    @errors.cyclades.connection
611
    @errors.cyclades.flavor_id
612
    def _run(self, flavor_id):
613
        self._print(
614
            self.client.get_flavor_details(int(flavor_id)), print_dict)
615

    
616
    def main(self, flavor_id):
617
        super(self.__class__, self)._run()
618
        self._run(flavor_id=flavor_id)
619

    
620

    
621
@command(network_cmds)
622
class network_info(_init_cyclades, _optional_json):
623
    """Detailed information on a network
624
    To get a list of available networks and network ids, try /network list
625
    """
626

    
627
    @errors.generic.all
628
    @errors.cyclades.connection
629
    @errors.cyclades.network_id
630
    def _run(self, network_id):
631
        network = self.client.get_network_details(int(network_id))
632
        self._print(network, print_dict, exclude=('id'))
633

    
634
    def main(self, network_id):
635
        super(self.__class__, self)._run()
636
        self._run(network_id=network_id)
637

    
638

    
639
@command(network_cmds)
640
class network_list(_init_cyclades, _optional_json):
641
    """List networks"""
642

    
643
    arguments = dict(
644
        detail=FlagArgument('show detailed output', ('-l', '--details')),
645
        limit=IntArgument('limit # of listed networks', ('-n', '--number')),
646
        more=FlagArgument(
647
            'output results in pages (-n to set items per page, default 10)',
648
            '--more'),
649
        enum=FlagArgument('Enumerate results', '--enumerate')
650
    )
651

    
652
    @errors.generic.all
653
    @errors.cyclades.connection
654
    def _run(self):
655
        networks = self.client.list_networks(self['detail'])
656
        if not (self['detail'] or self['json_output']):
657
            remove_from_items(networks, 'links')
658
        kwargs = dict(with_enumeration=self['enum'])
659
        if self['more']:
660
            kwargs['page_size'] = self['limit'] or 10
661
        elif self['limit']:
662
            networks = networks[:self['limit']]
663
        self._print(networks, **kwargs)
664

    
665
    def main(self):
666
        super(self.__class__, self)._run()
667
        self._run()
668

    
669

    
670
@command(network_cmds)
671
class network_create(_init_cyclades, _optional_json):
672
    """Create an (unconnected) network"""
673

    
674
    arguments = dict(
675
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
676
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
677
        dhcp=FlagArgument('Use dhcp (default: off)', '--with-dhcp'),
678
        type=ValueArgument(
679
            'Valid network types are '
680
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
681
            '--with-type',
682
            default='MAC_FILTERED')
683
    )
684

    
685
    @errors.generic.all
686
    @errors.cyclades.connection
687
    @errors.cyclades.network_max
688
    def _run(self, name):
689
        self._print(self.client.create_network(
690
            name,
691
            cidr=self['cidr'],
692
            gateway=self['gateway'],
693
            dhcp=self['dhcp'],
694
            type=self['type']), print_dict)
695

    
696
    def main(self, name):
697
        super(self.__class__, self)._run()
698
        self._run(name)
699

    
700

    
701
@command(network_cmds)
702
class network_rename(_init_cyclades, _optional_output_cmd):
703
    """Set the name of a network"""
704

    
705
    @errors.generic.all
706
    @errors.cyclades.connection
707
    @errors.cyclades.network_id
708
    def _run(self, network_id, new_name):
709
        self._optional_output(
710
                self.client.update_network_name(int(network_id), new_name))
711

    
712
    def main(self, network_id, new_name):
713
        super(self.__class__, self)._run()
714
        self._run(network_id=network_id, new_name=new_name)
715

    
716

    
717
@command(network_cmds)
718
class network_delete(_init_cyclades, _optional_output_cmd):
719
    """Delete a network"""
720

    
721
    @errors.generic.all
722
    @errors.cyclades.connection
723
    @errors.cyclades.network_id
724
    @errors.cyclades.network_in_use
725
    def _run(self, network_id):
726
        self._optional_output(self.client.delete_network(int(network_id)))
727

    
728
    def main(self, network_id):
729
        super(self.__class__, self)._run()
730
        self._run(network_id=network_id)
731

    
732

    
733
@command(network_cmds)
734
class network_connect(_init_cyclades, _optional_output_cmd):
735
    """Connect a server to a network"""
736

    
737
    @errors.generic.all
738
    @errors.cyclades.connection
739
    @errors.cyclades.server_id
740
    @errors.cyclades.network_id
741
    def _run(self, server_id, network_id):
742
        self._optional_output(
743
                self.client.connect_server(int(server_id), int(network_id)))
744

    
745
    def main(self, server_id, network_id):
746
        super(self.__class__, self)._run()
747
        self._run(server_id=server_id, network_id=network_id)
748

    
749

    
750
@command(network_cmds)
751
class network_disconnect(_init_cyclades):
752
    """Disconnect a nic that connects a server to a network
753
    Nic ids are listed as "attachments" in detailed network information
754
    To get detailed network information: /network info <network id>
755
    """
756

    
757
    @errors.cyclades.nic_format
758
    def _server_id_from_nic(self, nic_id):
759
        return nic_id.split('-')[1]
760

    
761
    @errors.generic.all
762
    @errors.cyclades.connection
763
    @errors.cyclades.server_id
764
    @errors.cyclades.nic_id
765
    def _run(self, nic_id, server_id):
766
        num_of_disconnected = self.client.disconnect_server(server_id, nic_id)
767
        if not num_of_disconnected:
768
            raise ClientError(
769
                'Network Interface %s not found on server %s' % (
770
                    nic_id,
771
                    server_id),
772
                status=404)
773
        print('Disconnected %s connections' % num_of_disconnected)
774

    
775
    def main(self, nic_id):
776
        super(self.__class__, self)._run()
777
        server_id = self._server_id_from_nic(nic_id=nic_id)
778
        self._run(nic_id=nic_id, server_id=server_id)
779

    
780

    
781
@command(floatingip_cmds)
782
class floatingip_pools(_init_cyclades, _optional_json):
783
    """List all floating pools of floating ips"""
784

    
785
    @errors.generic.all
786
    @errors.cyclades.connection
787
    def _run(self):
788
        r = self.client.get_floating_ip_pools()
789
        self._print(r if self['json_output'] else r['floating_ip_pools'])
790

    
791
    def main(self):
792
        super(self.__class__, self)._run()
793
        self._run()
794

    
795

    
796
@command(floatingip_cmds)
797
class floatingip_list(_init_cyclades, _optional_json):
798
    """List all floating ips"""
799

    
800
    @errors.generic.all
801
    @errors.cyclades.connection
802
    def _run(self):
803
        r = self.client.get_floating_ips()
804
        self._print(r if self['json_output'] else r['floating_ips'])
805

    
806
    def main(self):
807
        super(self.__class__, self)._run()
808
        self._run()
809

    
810

    
811
@command(floatingip_cmds)
812
class floatingip_info(_init_cyclades, _optional_json):
813
    """A floating IPs' details"""
814

    
815
    @errors.generic.all
816
    @errors.cyclades.connection
817
    def _run(self, ip):
818
        self._print(self.client.get_floating_ip(ip), print_dict)
819

    
820
    def main(self, ip):
821
        super(self.__class__, self)._run()
822
        self._run(ip=ip)
823

    
824

    
825
@command(floatingip_cmds)
826
class floatingip_create(_init_cyclades, _optional_json):
827
    """Create a new floating IP"""
828

    
829
    arguments = dict(
830
        pool=ValueArgument('Source IP pool', ('--pool'), None)
831
    )
832

    
833
    @errors.generic.all
834
    @errors.cyclades.connection
835
    def _run(self, ip=None):
836
        self._print(
837
            self.client.alloc_floating_ip(self['pool'], ip), print_dict)
838

    
839
    def main(self, requested_address=None):
840
        super(self.__class__, self)._run()
841
        self._run(ip=requested_address)
842

    
843

    
844
@command(floatingip_cmds)
845
class floatingip_delete(_init_cyclades, _optional_output_cmd):
846
    """Delete a floating ip"""
847

    
848
    @errors.generic.all
849
    @errors.cyclades.connection
850
    def _run(self, ip):
851
        self._optional_output(self.client.delete_floating_ip(ip))
852

    
853
    def main(self, ip):
854
        super(self.__class__, self)._run()
855
        self._run(ip=ip)
856

    
857

    
858
@command(server_cmds)
859
class server_ip_attach(_init_cyclades, _optional_output_cmd):
860
    """Attach a floating ip to a server with server_id
861
    """
862

    
863
    @errors.generic.all
864
    @errors.cyclades.connection
865
    @errors.cyclades.server_id
866
    def _run(self, server_id, ip):
867
        self._optional_output(self.client.attach_floating_ip(server_id, ip))
868

    
869
    def main(self, server_id, ip):
870
        super(self.__class__, self)._run()
871
        self._run(server_id=server_id, ip=ip)
872

    
873

    
874
@command(server_cmds)
875
class server_ip_detach(_init_cyclades):
876
    """detach_floating_ip_to_server
877
    """
878

    
879
    @errors.generic.all
880
    @errors.cyclades.connection
881
    @errors.cyclades.server_id
882
    def _run(self, server_id, ip):
883
        self._optional_output(self.client.detach_floating_ip(server_id, ip))
884

    
885
    def main(self, server_id, ip):
886
        super(self.__class__, self)._run()
887
        self._run(server_id=server_id, ip=ip)