Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades.py @ 77df3d4a

History | View | Annotate | Download (30 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_firewall(_init_cyclades):
435
    """Manage server (VM) firewall profiles for public networks"""
436

    
437

    
438
@command(server_cmds)
439
class server_firewall_set(_init_cyclades, _optional_output_cmd):
440
    """Set the server (VM) firewall profile on VMs public network
441
    Values for profile:
442
    - DISABLED: Shutdown firewall
443
    - ENABLED: Firewall in normal mode
444
    - PROTECTED: Firewall in secure mode
445
    """
446

    
447
    @errors.generic.all
448
    @errors.cyclades.connection
449
    @errors.cyclades.server_id
450
    @errors.cyclades.firewall
451
    def _run(self, server_id, profile):
452
        self._optional_output(self.client.set_firewall_profile(
453
            server_id=int(server_id), profile=('%s' % profile).upper()))
454

    
455
    def main(self, server_id, profile):
456
        super(self.__class__, self)._run()
457
        self._run(server_id=server_id, profile=profile)
458

    
459

    
460
@command(server_cmds)
461
class server_firewall_get(_init_cyclades):
462
    """Get the server (VM) firewall profile for its public network"""
463

    
464
    @errors.generic.all
465
    @errors.cyclades.connection
466
    @errors.cyclades.server_id
467
    def _run(self, server_id):
468
        print(self.client.get_firewall_profile(server_id))
469

    
470
    def main(self, server_id):
471
        super(self.__class__, self)._run()
472
        self._run(server_id=server_id)
473

    
474

    
475
@command(server_cmds)
476
class server_addr(_init_cyclades, _optional_json):
477
    """List the addresses of all network interfaces on a server (VM)"""
478

    
479
    arguments = dict(
480
        enum=FlagArgument('Enumerate results', '--enumerate')
481
    )
482

    
483
    @errors.generic.all
484
    @errors.cyclades.connection
485
    @errors.cyclades.server_id
486
    def _run(self, server_id):
487
        reply = self.client.list_server_nics(int(server_id))
488
        self._print(
489
            reply, with_enumeration=self['enum'] and len(reply) > 1)
490

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

    
495

    
496
@command(server_cmds)
497
class server_metadata(_init_cyclades):
498
    """Manage Server metadata (key:value pairs of server attributes)"""
499

    
500

    
501
@command(server_cmds)
502
class server_metadata_list(_init_cyclades, _optional_json):
503
    """Get server metadata"""
504

    
505
    @errors.generic.all
506
    @errors.cyclades.connection
507
    @errors.cyclades.server_id
508
    @errors.cyclades.metadata
509
    def _run(self, server_id, key=''):
510
        self._print(
511
            self.client.get_server_metadata(int(server_id), key), print_dict)
512

    
513
    def main(self, server_id, key=''):
514
        super(self.__class__, self)._run()
515
        self._run(server_id=server_id, key=key)
516

    
517

    
518
@command(server_cmds)
519
class server_metadata_set(_init_cyclades, _optional_json):
520
    """Set / update server(VM) metadata
521
    Metadata should be given in key/value pairs in key=value format
522
    For example: /server metadata set <server id> key1=value1 key2=value2
523
    Old, unreferenced metadata will remain intact
524
    """
525

    
526
    @errors.generic.all
527
    @errors.cyclades.connection
528
    @errors.cyclades.server_id
529
    def _run(self, server_id, keyvals):
530
        assert keyvals, 'Please, add some metadata ( key=value)'
531
        metadata = dict()
532
        for keyval in keyvals:
533
            k, sep, v = keyval.partition('=')
534
            if sep and k:
535
                metadata[k] = v
536
            else:
537
                raiseCLIError(
538
                    'Invalid piece of metadata %s' % keyval,
539
                    importance=2, details=[
540
                        'Correct metadata format: key=val',
541
                        'For example:',
542
                        '/server metadata set <server id>'
543
                        'key1=value1 key2=value2'])
544
        self._print(
545
            self.client.update_server_metadata(int(server_id), **metadata),
546
            print_dict)
547

    
548
    def main(self, server_id, *key_equals_val):
549
        super(self.__class__, self)._run()
550
        self._run(server_id=server_id, keyvals=key_equals_val)
551

    
552

    
553
@command(server_cmds)
554
class server_metadata_delete(_init_cyclades, _optional_output_cmd):
555
    """Delete server (VM) metadata"""
556

    
557
    @errors.generic.all
558
    @errors.cyclades.connection
559
    @errors.cyclades.server_id
560
    @errors.cyclades.metadata
561
    def _run(self, server_id, key):
562
        self._optional_output(
563
            self.client.delete_server_metadata(int(server_id), key))
564

    
565
    def main(self, server_id, key):
566
        super(self.__class__, self)._run()
567
        self._run(server_id=server_id, key=key)
568

    
569

    
570
@command(server_cmds)
571
class server_stats(_init_cyclades, _optional_json):
572
    """Get server (VM) statistics"""
573

    
574
    @errors.generic.all
575
    @errors.cyclades.connection
576
    @errors.cyclades.server_id
577
    def _run(self, server_id):
578
        self._print(self.client.get_server_stats(int(server_id)), print_dict)
579

    
580
    def main(self, server_id):
581
        super(self.__class__, self)._run()
582
        self._run(server_id=server_id)
583

    
584

    
585
@command(server_cmds)
586
class server_wait(_init_cyclades, _server_wait):
587
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
588

    
589
    @errors.generic.all
590
    @errors.cyclades.connection
591
    @errors.cyclades.server_id
592
    def _run(self, server_id, currect_status):
593
        self._wait(server_id, currect_status)
594

    
595
    def main(self, server_id, currect_status='BUILD'):
596
        super(self.__class__, self)._run()
597
        self._run(server_id=server_id, currect_status=currect_status)
598

    
599

    
600
@command(flavor_cmds)
601
class flavor_list(_init_cyclades, _optional_json):
602
    """List available hardware flavors"""
603

    
604
    arguments = dict(
605
        detail=FlagArgument('show detailed output', ('-l', '--details')),
606
        limit=IntArgument('limit # of listed flavors', ('-n', '--number')),
607
        more=FlagArgument(
608
            'output results in pages (-n to set items per page, default 10)',
609
            '--more'),
610
        enum=FlagArgument('Enumerate results', '--enumerate')
611
    )
612

    
613
    @errors.generic.all
614
    @errors.cyclades.connection
615
    def _run(self):
616
        flavors = self.client.list_flavors(self['detail'])
617
        if not (self['detail'] or self['json_output']):
618
            remove_from_items(flavors, 'links')
619
        pg_size = 10 if self['more'] and not self['limit'] else self['limit']
620
        self._print(
621
            flavors,
622
            with_redundancy=self['detail'],
623
            page_size=pg_size,
624
            with_enumeration=self['enum'])
625

    
626
    def main(self):
627
        super(self.__class__, self)._run()
628
        self._run()
629

    
630

    
631
@command(flavor_cmds)
632
class flavor_info(_init_cyclades, _optional_json):
633
    """Detailed information on a hardware flavor
634
    To get a list of available flavors and flavor ids, try /flavor list
635
    """
636

    
637
    @errors.generic.all
638
    @errors.cyclades.connection
639
    @errors.cyclades.flavor_id
640
    def _run(self, flavor_id):
641
        self._print(
642
            self.client.get_flavor_details(int(flavor_id)), print_dict)
643

    
644
    def main(self, flavor_id):
645
        super(self.__class__, self)._run()
646
        self._run(flavor_id=flavor_id)
647

    
648

    
649
@command(network_cmds)
650
class network_info(_init_cyclades, _optional_json):
651
    """Detailed information on a network
652
    To get a list of available networks and network ids, try /network list
653
    """
654

    
655
    @errors.generic.all
656
    @errors.cyclades.connection
657
    @errors.cyclades.network_id
658
    def _run(self, network_id):
659
        network = self.client.get_network_details(int(network_id))
660
        self._print(network, print_dict, exclude=('id'))
661

    
662
    def main(self, network_id):
663
        super(self.__class__, self)._run()
664
        self._run(network_id=network_id)
665

    
666

    
667
@command(network_cmds)
668
class network_list(_init_cyclades, _optional_json):
669
    """List networks"""
670

    
671
    arguments = dict(
672
        detail=FlagArgument('show detailed output', ('-l', '--details')),
673
        limit=IntArgument('limit # of listed networks', ('-n', '--number')),
674
        more=FlagArgument(
675
            'output results in pages (-n to set items per page, default 10)',
676
            '--more'),
677
        enum=FlagArgument('Enumerate results', '--enumerate')
678
    )
679

    
680
    @errors.generic.all
681
    @errors.cyclades.connection
682
    def _run(self):
683
        networks = self.client.list_networks(self['detail'])
684
        if not (self['detail'] or self['json_output']):
685
            remove_from_items(networks, 'links')
686
        kwargs = dict(with_enumeration=self['enum'])
687
        if self['more']:
688
            kwargs['page_size'] = self['limit'] or 10
689
        elif self['limit']:
690
            networks = networks[:self['limit']]
691
        self._print(networks, **kwargs)
692

    
693
    def main(self):
694
        super(self.__class__, self)._run()
695
        self._run()
696

    
697

    
698
@command(network_cmds)
699
class network_create(_init_cyclades, _optional_json, _network_wait):
700
    """Create an (unconnected) network"""
701

    
702
    arguments = dict(
703
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
704
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
705
        dhcp=FlagArgument('Use dhcp (default: off)', '--with-dhcp'),
706
        type=ValueArgument(
707
            'Valid network types are '
708
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
709
            '--with-type',
710
            default='MAC_FILTERED'),
711
        wait=FlagArgument('Wait network to build', ('-w', '--wait'))
712
    )
713

    
714
    @errors.generic.all
715
    @errors.cyclades.connection
716
    @errors.cyclades.network_max
717
    def _run(self, name):
718
        r = self.client.create_network(
719
            name,
720
            cidr=self['cidr'],
721
            gateway=self['gateway'],
722
            dhcp=self['dhcp'],
723
            type=self['type'])
724
        self._print(r, print_dict)
725

    
726
        if self['wait']:
727
            self._wait(r['id'], 'PENDING')
728

    
729
    def main(self, name):
730
        super(self.__class__, self)._run()
731
        self._run(name)
732

    
733

    
734
@command(network_cmds)
735
class network_rename(_init_cyclades, _optional_output_cmd):
736
    """Set the name of a network"""
737

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

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

    
749

    
750
@command(network_cmds)
751
class network_delete(_init_cyclades, _optional_output_cmd, _network_wait):
752
    """Delete a network"""
753

    
754
    arguments = dict(
755
        wait=FlagArgument('Wait network to build', ('-w', '--wait'))
756
    )
757

    
758
    @errors.generic.all
759
    @errors.cyclades.connection
760
    @errors.cyclades.network_id
761
    @errors.cyclades.network_in_use
762
    def _run(self, network_id):
763
        status = 'DELETED'
764
        if self['wait']:
765
            r = self.client.get_network_details(network_id)
766
            status = r['status']
767
            if status in ('DELETED', ):
768
                return
769

    
770
        r = self.client.delete_network(int(network_id))
771
        self._optional_output(r)
772

    
773
        if self['wait']:
774
            self._wait(network_id, status)
775

    
776
    def main(self, network_id):
777
        super(self.__class__, self)._run()
778
        self._run(network_id=network_id)
779

    
780

    
781
@command(network_cmds)
782
class network_connect(_init_cyclades, _optional_output_cmd):
783
    """Connect a server to a network"""
784

    
785
    @errors.generic.all
786
    @errors.cyclades.connection
787
    @errors.cyclades.server_id
788
    @errors.cyclades.network_id
789
    def _run(self, server_id, network_id):
790
        self._optional_output(
791
                self.client.connect_server(int(server_id), int(network_id)))
792

    
793
    def main(self, server_id, network_id):
794
        super(self.__class__, self)._run()
795
        self._run(server_id=server_id, network_id=network_id)
796

    
797

    
798
@command(network_cmds)
799
class network_disconnect(_init_cyclades):
800
    """Disconnect a nic that connects a server to a network
801
    Nic ids are listed as "attachments" in detailed network information
802
    To get detailed network information: /network info <network id>
803
    """
804

    
805
    @errors.cyclades.nic_format
806
    def _server_id_from_nic(self, nic_id):
807
        return nic_id.split('-')[1]
808

    
809
    @errors.generic.all
810
    @errors.cyclades.connection
811
    @errors.cyclades.server_id
812
    @errors.cyclades.nic_id
813
    def _run(self, nic_id, server_id):
814
        num_of_disconnected = self.client.disconnect_server(server_id, nic_id)
815
        if not num_of_disconnected:
816
            raise ClientError(
817
                'Network Interface %s not found on server %s' % (
818
                    nic_id,
819
                    server_id),
820
                status=404)
821
        print('Disconnected %s connections' % num_of_disconnected)
822

    
823
    def main(self, nic_id):
824
        super(self.__class__, self)._run()
825
        server_id = self._server_id_from_nic(nic_id=nic_id)
826
        self._run(nic_id=nic_id, server_id=server_id)
827

    
828

    
829
@command(network_cmds)
830
class network_wait(_init_cyclades, _network_wait):
831
    """Wait for server to finish [PENDING, ACTIVE, DELETED]"""
832

    
833
    @errors.generic.all
834
    @errors.cyclades.connection
835
    @errors.cyclades.network_id
836
    def _run(self, network_id, currect_status):
837
        self._wait(network_id, currect_status)
838

    
839
    def main(self, network_id, currect_status='PENDING'):
840
        super(self.__class__, self)._run()
841
        self._run(network_id=network_id, currect_status=currect_status)
842

    
843

    
844
@command(floatingip_cmds)
845
class floatingip_pools(_init_cyclades, _optional_json):
846
    """List all floating pools of floating ips"""
847

    
848
    @errors.generic.all
849
    @errors.cyclades.connection
850
    def _run(self):
851
        r = self.client.get_floating_ip_pools()
852
        self._print(r if self['json_output'] else r['floating_ip_pools'])
853

    
854
    def main(self):
855
        super(self.__class__, self)._run()
856
        self._run()
857

    
858

    
859
@command(floatingip_cmds)
860
class floatingip_list(_init_cyclades, _optional_json):
861
    """List all floating ips"""
862

    
863
    @errors.generic.all
864
    @errors.cyclades.connection
865
    def _run(self):
866
        r = self.client.get_floating_ips()
867
        self._print(r if self['json_output'] else r['floating_ips'])
868

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

    
873

    
874
@command(floatingip_cmds)
875
class floatingip_info(_init_cyclades, _optional_json):
876
    """A floating IPs' details"""
877

    
878
    @errors.generic.all
879
    @errors.cyclades.connection
880
    def _run(self, ip):
881
        self._print(self.client.get_floating_ip(ip), print_dict)
882

    
883
    def main(self, ip):
884
        super(self.__class__, self)._run()
885
        self._run(ip=ip)
886

    
887

    
888
@command(floatingip_cmds)
889
class floatingip_create(_init_cyclades, _optional_json):
890
    """Create a new floating IP"""
891

    
892
    arguments = dict(
893
        pool=ValueArgument('Source IP pool', ('--pool'), None)
894
    )
895

    
896
    @errors.generic.all
897
    @errors.cyclades.connection
898
    def _run(self, ip=None):
899
        self._print(
900
            self.client.alloc_floating_ip(self['pool'], ip), print_dict)
901

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

    
906

    
907
@command(floatingip_cmds)
908
class floatingip_delete(_init_cyclades, _optional_output_cmd):
909
    """Delete a floating ip"""
910

    
911
    @errors.generic.all
912
    @errors.cyclades.connection
913
    def _run(self, ip):
914
        self._optional_output(self.client.delete_floating_ip(ip))
915

    
916
    def main(self, ip):
917
        super(self.__class__, self)._run()
918
        self._run(ip=ip)
919

    
920

    
921
@command(server_cmds)
922
class server_ip_attach(_init_cyclades, _optional_output_cmd):
923
    """Attach a floating ip to a server with server_id
924
    """
925

    
926
    @errors.generic.all
927
    @errors.cyclades.connection
928
    @errors.cyclades.server_id
929
    def _run(self, server_id, ip):
930
        self._optional_output(self.client.attach_floating_ip(server_id, ip))
931

    
932
    def main(self, server_id, ip):
933
        super(self.__class__, self)._run()
934
        self._run(server_id=server_id, ip=ip)
935

    
936

    
937
@command(server_cmds)
938
class server_ip_detach(_init_cyclades):
939
    """detach_floating_ip_to_server
940
    """
941

    
942
    @errors.generic.all
943
    @errors.cyclades.connection
944
    @errors.cyclades.server_id
945
    def _run(self, server_id, ip):
946
        self._optional_output(self.client.detach_floating_ip(server_id, ip))
947

    
948
    def main(self, server_id, ip):
949
        super(self.__class__, self)._run()
950
        self._run(server_id=server_id, ip=ip)