Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades.py @ 9b47150e

History | View | Annotate | Download (26.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]
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 _init_cyclades(_command_init):
71
    @errors.generic.all
72
    @addLogSettings
73
    def _run(self, service='compute'):
74
        if getattr(self, 'cloud', None):
75
            base_url = self._custom_url(service)\
76
                or self._custom_url('cyclades')
77
            if base_url:
78
                token = self._custom_token(service)\
79
                    or self._custom_token('cyclades')\
80
                    or self.config.get_cloud('token')
81
                self.client = CycladesClient(
82
                    base_url=base_url, token=token)
83
                return
84
        else:
85
            self.cloud = 'default'
86
        if getattr(self, 'auth_base', False):
87
            cyclades_endpoints = self.auth_base.get_service_endpoints(
88
                self._custom_type('cyclades') or 'compute',
89
                self._custom_version('cyclades') or '')
90
            base_url = cyclades_endpoints['publicURL']
91
            token = self.auth_base.token
92
            self.client = CycladesClient(base_url=base_url, token=token)
93
        else:
94
            raise CLIBaseUrlError(service='cyclades')
95

    
96
    def main(self):
97
        self._run()
98

    
99

    
100
@command(server_cmds)
101
class server_list(_init_cyclades, _optional_json):
102
    """List Virtual Machines accessible by user"""
103

    
104
    __doc__ += about_authentication
105

    
106
    arguments = dict(
107
        detail=FlagArgument('show detailed output', ('-l', '--details')),
108
        since=DateArgument(
109
            'show only items since date (\' d/m/Y H:M:S \')',
110
            '--since'),
111
        limit=IntArgument('limit number of listed VMs', ('-n', '--number')),
112
        more=FlagArgument(
113
            'output results in pages (-n to set items per page, default 10)',
114
            '--more'),
115
        enum=FlagArgument('Enumerate results', '--enumerate')
116
    )
117

    
118
    @errors.generic.all
119
    @errors.cyclades.connection
120
    @errors.cyclades.date
121
    def _run(self):
122
        servers = self.client.list_servers(self['detail'], self['since'])
123
        if not (self['detail'] or self['json_output']):
124
            remove_from_items(servers, 'links')
125

    
126
        kwargs = dict(with_enumeration=self['enum'])
127
        if self['more']:
128
            kwargs['page_size'] = self['limit'] if self['limit'] else 10
129
        elif self['limit']:
130
            servers = servers[:self['limit']]
131
        self._print(servers, **kwargs)
132

    
133
    def main(self):
134
        super(self.__class__, self)._run()
135
        self._run()
136

    
137

    
138
@command(server_cmds)
139
class server_info(_init_cyclades, _optional_json):
140
    """Detailed information on a Virtual Machine
141
    Contains:
142
    - name, id, status, create/update dates
143
    - network interfaces
144
    - metadata (e.g. os, superuser) and diagnostics
145
    - hardware flavor and os image ids
146
    """
147

    
148
    @errors.generic.all
149
    @errors.cyclades.connection
150
    @errors.cyclades.server_id
151
    def _run(self, server_id):
152
        self._print(self.client.get_server_details(server_id), print_dict)
153

    
154
    def main(self, server_id):
155
        super(self.__class__, self)._run()
156
        self._run(server_id=server_id)
157

    
158

    
159
class PersonalityArgument(KeyValueArgument):
160
    @property
161
    def value(self):
162
        return self._value if hasattr(self, '_value') else []
163

    
164
    @value.setter
165
    def value(self, newvalue):
166
        if newvalue == self.default:
167
            return self.value
168
        self._value = []
169
        for i, terms in enumerate(newvalue):
170
            termlist = terms.split(',')
171
            if len(termlist) > 5:
172
                msg = 'Wrong number of terms (should be 1 to 5)'
173
                raiseCLIError(CLISyntaxError(msg), details=howto_personality)
174
            path = termlist[0]
175
            if not exists(path):
176
                raiseCLIError(
177
                    None,
178
                    '--personality: File %s does not exist' % path,
179
                    importance=1,
180
                    details=howto_personality)
181
            self._value.append(dict(path=path))
182
            with open(path) as f:
183
                self._value[i]['contents'] = b64encode(f.read())
184
            try:
185
                self._value[i]['path'] = termlist[1]
186
                self._value[i]['owner'] = termlist[2]
187
                self._value[i]['group'] = termlist[3]
188
                self._value[i]['mode'] = termlist[4]
189
            except IndexError:
190
                pass
191

    
192

    
193
@command(server_cmds)
194
class server_create(_init_cyclades, _optional_json):
195
    """Create a server (aka Virtual Machine)
196
    Parameters:
197
    - name: (single quoted text)
198
    - flavor id: Hardware flavor. Pick one from: /flavor list
199
    - image id: OS images. Pick one from: /image list
200
    """
201

    
202
    arguments = dict(
203
        personality=PersonalityArgument(
204
            (80 * ' ').join(howto_personality), ('-p', '--personality'))
205
    )
206

    
207
    @errors.generic.all
208
    @errors.cyclades.connection
209
    @errors.plankton.id
210
    @errors.cyclades.flavor_id
211
    def _run(self, name, flavor_id, image_id):
212
        self._print(
213
            self.client.create_server(
214
                name, int(flavor_id), image_id, self['personality']),
215
            print_dict)
216

    
217
    def main(self, name, flavor_id, image_id):
218
        super(self.__class__, self)._run()
219
        self._run(name=name, flavor_id=flavor_id, image_id=image_id)
220

    
221

    
222
@command(server_cmds)
223
class server_rename(_init_cyclades, _optional_output_cmd):
224
    """Set/update a server (VM) name
225
    VM names are not unique, therefore multiple servers may share the same name
226
    """
227

    
228
    @errors.generic.all
229
    @errors.cyclades.connection
230
    @errors.cyclades.server_id
231
    def _run(self, server_id, new_name):
232
        self._optional_output(
233
            self.client.update_server_name(int(server_id), new_name))
234

    
235
    def main(self, server_id, new_name):
236
        super(self.__class__, self)._run()
237
        self._run(server_id=server_id, new_name=new_name)
238

    
239

    
240
@command(server_cmds)
241
class server_delete(_init_cyclades, _optional_output_cmd):
242
    """Delete a server (VM)"""
243

    
244
    @errors.generic.all
245
    @errors.cyclades.connection
246
    @errors.cyclades.server_id
247
    def _run(self, server_id):
248
            self._optional_output(self.client.delete_server(int(server_id)))
249

    
250
    def main(self, server_id):
251
        super(self.__class__, self)._run()
252
        self._run(server_id=server_id)
253

    
254

    
255
@command(server_cmds)
256
class server_reboot(_init_cyclades, _optional_output_cmd):
257
    """Reboot a server (VM)"""
258

    
259
    arguments = dict(
260
        hard=FlagArgument('perform a hard reboot', ('-f', '--force'))
261
    )
262

    
263
    @errors.generic.all
264
    @errors.cyclades.connection
265
    @errors.cyclades.server_id
266
    def _run(self, server_id):
267
        self._optional_output(
268
            self.client.reboot_server(int(server_id), self['hard']))
269

    
270
    def main(self, server_id):
271
        super(self.__class__, self)._run()
272
        self._run(server_id=server_id)
273

    
274

    
275
@command(server_cmds)
276
class server_start(_init_cyclades, _optional_output_cmd):
277
    """Start an existing server (VM)"""
278

    
279
    @errors.generic.all
280
    @errors.cyclades.connection
281
    @errors.cyclades.server_id
282
    def _run(self, server_id):
283
        self._optional_output(self.client.start_server(int(server_id)))
284

    
285
    def main(self, server_id):
286
        super(self.__class__, self)._run()
287
        self._run(server_id=server_id)
288

    
289

    
290
@command(server_cmds)
291
class server_shutdown(_init_cyclades, _optional_output_cmd):
292
    """Shutdown an active server (VM)"""
293

    
294
    @errors.generic.all
295
    @errors.cyclades.connection
296
    @errors.cyclades.server_id
297
    def _run(self, server_id):
298
        self._optional_output(self.client.shutdown_server(int(server_id)))
299

    
300
    def main(self, server_id):
301
        super(self.__class__, self)._run()
302
        self._run(server_id=server_id)
303

    
304

    
305
@command(server_cmds)
306
class server_console(_init_cyclades, _optional_json):
307
    """Get a VNC console to access an existing server (VM)
308
    Console connection information provided (at least):
309
    - host: (url or address) a VNC host
310
    - port: (int) the gateway to enter VM on host
311
    - password: for VNC authorization
312
    """
313

    
314
    @errors.generic.all
315
    @errors.cyclades.connection
316
    @errors.cyclades.server_id
317
    def _run(self, server_id):
318
        self._print(
319
            self.client.get_server_console(int(server_id)), print_dict)
320

    
321
    def main(self, server_id):
322
        super(self.__class__, self)._run()
323
        self._run(server_id=server_id)
324

    
325

    
326
@command(server_cmds)
327
class server_firewall(_init_cyclades):
328
    """Manage server (VM) firewall profiles for public networks"""
329

    
330

    
331
@command(server_cmds)
332
class server_firewall_set(_init_cyclades, _optional_output_cmd):
333
    """Set the server (VM) firewall profile on VMs public network
334
    Values for profile:
335
    - DISABLED: Shutdown firewall
336
    - ENABLED: Firewall in normal mode
337
    - PROTECTED: Firewall in secure mode
338
    """
339

    
340
    @errors.generic.all
341
    @errors.cyclades.connection
342
    @errors.cyclades.server_id
343
    @errors.cyclades.firewall
344
    def _run(self, server_id, profile):
345
        self._optional_output(self.client.set_firewall_profile(
346
            server_id=int(server_id), profile=('%s' % profile).upper()))
347

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

    
352

    
353
@command(server_cmds)
354
class server_firewall_get(_init_cyclades):
355
    """Get the server (VM) firewall profile for its public network"""
356

    
357
    @errors.generic.all
358
    @errors.cyclades.connection
359
    @errors.cyclades.server_id
360
    def _run(self, server_id):
361
        print(self.client.get_firewall_profile(server_id))
362

    
363
    def main(self, server_id):
364
        super(self.__class__, self)._run()
365
        self._run(server_id=server_id)
366

    
367

    
368
@command(server_cmds)
369
class server_addr(_init_cyclades, _optional_json):
370
    """List the addresses of all network interfaces on a server (VM)"""
371

    
372
    arguments = dict(
373
        enum=FlagArgument('Enumerate results', '--enumerate')
374
    )
375

    
376
    @errors.generic.all
377
    @errors.cyclades.connection
378
    @errors.cyclades.server_id
379
    def _run(self, server_id):
380
        reply = self.client.list_server_nics(int(server_id))
381
        self._print(
382
            reply, with_enumeration=self['enum'] and len(reply) > 1)
383

    
384
    def main(self, server_id):
385
        super(self.__class__, self)._run()
386
        self._run(server_id=server_id)
387

    
388

    
389
@command(server_cmds)
390
class server_metadata(_init_cyclades):
391
    """Manage Server metadata (key:value pairs of server attributes)"""
392

    
393

    
394
@command(server_cmds)
395
class server_metadata_list(_init_cyclades, _optional_json):
396
    """Get server metadata"""
397

    
398
    @errors.generic.all
399
    @errors.cyclades.connection
400
    @errors.cyclades.server_id
401
    @errors.cyclades.metadata
402
    def _run(self, server_id, key=''):
403
        self._print(
404
            self.client.get_server_metadata(int(server_id), key), print_dict)
405

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

    
410

    
411
@command(server_cmds)
412
class server_metadata_set(_init_cyclades, _optional_json):
413
    """Set / update server(VM) metadata
414
    Metadata should be given in key/value pairs in key=value format
415
    For example: /server metadata set <server id> key1=value1 key2=value2
416
    Old, unreferenced metadata will remain intact
417
    """
418

    
419
    @errors.generic.all
420
    @errors.cyclades.connection
421
    @errors.cyclades.server_id
422
    def _run(self, server_id, keyvals):
423
        assert keyvals, 'Please, add some metadata ( key=value)'
424
        metadata = dict()
425
        for keyval in keyvals:
426
            k, sep, v = keyval.partition('=')
427
            if sep and k:
428
                metadata[k] = v
429
            else:
430
                raiseCLIError(
431
                    'Invalid piece of metadata %s' % keyval,
432
                    importance=2, details=[
433
                        'Correct metadata format: key=val',
434
                        'For example:',
435
                        '/server metadata set <server id>'
436
                        'key1=value1 key2=value2'])
437
        self._print(
438
            self.client.update_server_metadata(int(server_id), **metadata),
439
            print_dict)
440

    
441
    def main(self, server_id, *key_equals_val):
442
        super(self.__class__, self)._run()
443
        self._run(server_id=server_id, keyvals=key_equals_val)
444

    
445

    
446
@command(server_cmds)
447
class server_metadata_delete(_init_cyclades, _optional_output_cmd):
448
    """Delete server (VM) metadata"""
449

    
450
    @errors.generic.all
451
    @errors.cyclades.connection
452
    @errors.cyclades.server_id
453
    @errors.cyclades.metadata
454
    def _run(self, server_id, key):
455
        self._optional_output(
456
            self.client.delete_server_metadata(int(server_id), key))
457

    
458
    def main(self, server_id, key):
459
        super(self.__class__, self)._run()
460
        self._run(server_id=server_id, key=key)
461

    
462

    
463
@command(server_cmds)
464
class server_stats(_init_cyclades, _optional_json):
465
    """Get server (VM) statistics"""
466

    
467
    @errors.generic.all
468
    @errors.cyclades.connection
469
    @errors.cyclades.server_id
470
    def _run(self, server_id):
471
        self._print(self.client.get_server_stats(int(server_id)), print_dict)
472

    
473
    def main(self, server_id):
474
        super(self.__class__, self)._run()
475
        self._run(server_id=server_id)
476

    
477

    
478
@command(server_cmds)
479
class server_wait(_init_cyclades):
480
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
481

    
482
    arguments = dict(
483
        progress_bar=ProgressBarArgument(
484
            'do not show progress bar',
485
            ('-N', '--no-progress-bar'),
486
            False
487
        )
488
    )
489

    
490
    @errors.generic.all
491
    @errors.cyclades.connection
492
    @errors.cyclades.server_id
493
    def _run(self, server_id, currect_status):
494
        (progress_bar, wait_cb) = self._safe_progress_bar(
495
            'Server %s still in %s mode' % (server_id, currect_status))
496

    
497
        try:
498
            new_mode = self.client.wait_server(
499
                server_id,
500
                currect_status,
501
                wait_cb=wait_cb)
502
        except Exception:
503
            self._safe_progress_bar_finish(progress_bar)
504
            raise
505
        finally:
506
            self._safe_progress_bar_finish(progress_bar)
507
        if new_mode:
508
            print('Server %s is now in %s mode' % (server_id, new_mode))
509
        else:
510
            raiseCLIError(None, 'Time out')
511

    
512
    def main(self, server_id, currect_status='BUILD'):
513
        super(self.__class__, self)._run()
514
        self._run(server_id=server_id, currect_status=currect_status)
515

    
516

    
517
@command(flavor_cmds)
518
class flavor_list(_init_cyclades, _optional_json):
519
    """List available hardware flavors"""
520

    
521
    arguments = dict(
522
        detail=FlagArgument('show detailed output', ('-l', '--details')),
523
        limit=IntArgument('limit # of listed flavors', ('-n', '--number')),
524
        more=FlagArgument(
525
            'output results in pages (-n to set items per page, default 10)',
526
            '--more'),
527
        enum=FlagArgument('Enumerate results', '--enumerate')
528
    )
529

    
530
    @errors.generic.all
531
    @errors.cyclades.connection
532
    def _run(self):
533
        flavors = self.client.list_flavors(self['detail'])
534
        if not (self['detail'] or self['json_output']):
535
            remove_from_items(flavors, 'links')
536
        pg_size = 10 if self['more'] and not self['limit'] else self['limit']
537
        self._print(
538
            flavors,
539
            with_redundancy=self['detail'],
540
            page_size=pg_size,
541
            with_enumeration=self['enum'])
542

    
543
    def main(self):
544
        super(self.__class__, self)._run()
545
        self._run()
546

    
547

    
548
@command(flavor_cmds)
549
class flavor_info(_init_cyclades, _optional_json):
550
    """Detailed information on a hardware flavor
551
    To get a list of available flavors and flavor ids, try /flavor list
552
    """
553

    
554
    @errors.generic.all
555
    @errors.cyclades.connection
556
    @errors.cyclades.flavor_id
557
    def _run(self, flavor_id):
558
        self._print(
559
            self.client.get_flavor_details(int(flavor_id)), print_dict)
560

    
561
    def main(self, flavor_id):
562
        super(self.__class__, self)._run()
563
        self._run(flavor_id=flavor_id)
564

    
565

    
566
@command(network_cmds)
567
class network_info(_init_cyclades, _optional_json):
568
    """Detailed information on a network
569
    To get a list of available networks and network ids, try /network list
570
    """
571

    
572
    @errors.generic.all
573
    @errors.cyclades.connection
574
    @errors.cyclades.network_id
575
    def _run(self, network_id):
576
        network = self.client.get_network_details(int(network_id))
577
        self._print(network, print_dict, exclude=('id'))
578

    
579
    def main(self, network_id):
580
        super(self.__class__, self)._run()
581
        self._run(network_id=network_id)
582

    
583

    
584
@command(network_cmds)
585
class network_list(_init_cyclades, _optional_json):
586
    """List networks"""
587

    
588
    arguments = dict(
589
        detail=FlagArgument('show detailed output', ('-l', '--details')),
590
        limit=IntArgument('limit # of listed networks', ('-n', '--number')),
591
        more=FlagArgument(
592
            'output results in pages (-n to set items per page, default 10)',
593
            '--more'),
594
        enum=FlagArgument('Enumerate results', '--enumerate')
595
    )
596

    
597
    @errors.generic.all
598
    @errors.cyclades.connection
599
    def _run(self):
600
        networks = self.client.list_networks(self['detail'])
601
        if not (self['detail'] or self['json_output']):
602
            remove_from_items(networks, 'links')
603
        kwargs = dict(with_enumeration=self['enum'])
604
        if self['more']:
605
            kwargs['page_size'] = self['limit'] or 10
606
        elif self['limit']:
607
            networks = networks[:self['limit']]
608
        self._print(networks, **kwargs)
609

    
610
    def main(self):
611
        super(self.__class__, self)._run()
612
        self._run()
613

    
614

    
615
@command(network_cmds)
616
class network_create(_init_cyclades, _optional_json):
617
    """Create an (unconnected) network"""
618

    
619
    arguments = dict(
620
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
621
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
622
        dhcp=FlagArgument('Use dhcp (default: off)', '--with-dhcp'),
623
        type=ValueArgument(
624
            'Valid network types are '
625
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
626
            '--with-type',
627
            default='MAC_FILTERED')
628
    )
629

    
630
    @errors.generic.all
631
    @errors.cyclades.connection
632
    @errors.cyclades.network_max
633
    def _run(self, name):
634
        self._print(self.client.create_network(
635
            name,
636
            cidr=self['cidr'],
637
            gateway=self['gateway'],
638
            dhcp=self['dhcp'],
639
            type=self['type']), print_dict)
640

    
641
    def main(self, name):
642
        super(self.__class__, self)._run()
643
        self._run(name)
644

    
645

    
646
@command(network_cmds)
647
class network_rename(_init_cyclades, _optional_output_cmd):
648
    """Set the name of a network"""
649

    
650
    @errors.generic.all
651
    @errors.cyclades.connection
652
    @errors.cyclades.network_id
653
    def _run(self, network_id, new_name):
654
        self._optional_output(
655
                self.client.update_network_name(int(network_id), new_name))
656

    
657
    def main(self, network_id, new_name):
658
        super(self.__class__, self)._run()
659
        self._run(network_id=network_id, new_name=new_name)
660

    
661

    
662
@command(network_cmds)
663
class network_delete(_init_cyclades, _optional_output_cmd):
664
    """Delete a network"""
665

    
666
    @errors.generic.all
667
    @errors.cyclades.connection
668
    @errors.cyclades.network_id
669
    @errors.cyclades.network_in_use
670
    def _run(self, network_id):
671
        self._optional_output(self.client.delete_network(int(network_id)))
672

    
673
    def main(self, network_id):
674
        super(self.__class__, self)._run()
675
        self._run(network_id=network_id)
676

    
677

    
678
@command(network_cmds)
679
class network_connect(_init_cyclades, _optional_output_cmd):
680
    """Connect a server to a network"""
681

    
682
    @errors.generic.all
683
    @errors.cyclades.connection
684
    @errors.cyclades.server_id
685
    @errors.cyclades.network_id
686
    def _run(self, server_id, network_id):
687
        self._optional_output(
688
                self.client.connect_server(int(server_id), int(network_id)))
689

    
690
    def main(self, server_id, network_id):
691
        super(self.__class__, self)._run()
692
        self._run(server_id=server_id, network_id=network_id)
693

    
694

    
695
@command(network_cmds)
696
class network_disconnect(_init_cyclades):
697
    """Disconnect a nic that connects a server to a network
698
    Nic ids are listed as "attachments" in detailed network information
699
    To get detailed network information: /network info <network id>
700
    """
701

    
702
    @errors.cyclades.nic_format
703
    def _server_id_from_nic(self, nic_id):
704
        return nic_id.split('-')[1]
705

    
706
    @errors.generic.all
707
    @errors.cyclades.connection
708
    @errors.cyclades.server_id
709
    @errors.cyclades.nic_id
710
    def _run(self, nic_id, server_id):
711
        num_of_disconnected = self.client.disconnect_server(server_id, nic_id)
712
        if not num_of_disconnected:
713
            raise ClientError(
714
                'Network Interface %s not found on server %s' % (
715
                    nic_id,
716
                    server_id),
717
                status=404)
718
        print('Disconnected %s connections' % num_of_disconnected)
719

    
720
    def main(self, nic_id):
721
        super(self.__class__, self)._run()
722
        server_id = self._server_id_from_nic(nic_id=nic_id)
723
        self._run(nic_id=nic_id, server_id=server_id)
724

    
725

    
726
@command(floatingip_cmds)
727
class floatingip_pools(_init_cyclades, _optional_json):
728
    """List all floating pools of floating ips"""
729

    
730
    @errors.generic.all
731
    @errors.cyclades.connection
732
    def _run(self):
733
        r = self.client.get_floating_ip_pools()
734
        self._print(r if self['json_output'] else r['floating_ip_pools'])
735

    
736
    def main(self):
737
        super(self.__class__, self)._run()
738
        self._run()
739

    
740

    
741
@command(floatingip_cmds)
742
class floatingip_list(_init_cyclades, _optional_json):
743
    """List all floating ips"""
744

    
745
    @errors.generic.all
746
    @errors.cyclades.connection
747
    def _run(self):
748
        r = self.client.get_floating_ips()
749
        self._print(r if self['json_output'] else r['floating_ips'])
750

    
751
    def main(self):
752
        super(self.__class__, self)._run()
753
        self._run()
754

    
755

    
756
@command(floatingip_cmds)
757
class floatingip_info(_init_cyclades, _optional_json):
758
    """A floating IPs' details"""
759

    
760
    @errors.generic.all
761
    @errors.cyclades.connection
762
    def _run(self, ip):
763
        self._print(self.client.get_floating_ip(ip), print_dict)
764

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

    
769

    
770
@command(floatingip_cmds)
771
class floatingip_create(_init_cyclades, _optional_json):
772
    """Create a new floating IP"""
773

    
774
    arguments = dict(
775
        pool=ValueArgument('Source IP pool', ('--pool'), None)
776
    )
777

    
778
    @errors.generic.all
779
    @errors.cyclades.connection
780
    def _run(self, ip=None):
781
        self._print(
782
            self.client.alloc_floating_ip(self['pool'], ip), print_dict)
783

    
784
    def main(self, requested_address=None):
785
        super(self.__class__, self)._run()
786
        self._run(ip=requested_address)
787

    
788

    
789
@command(floatingip_cmds)
790
class floatingip_delete(_init_cyclades, _optional_output_cmd):
791
    """Delete a floating ip"""
792

    
793
    @errors.generic.all
794
    @errors.cyclades.connection
795
    def _run(self, ip):
796
        self._optional_output(self.client.delete_floating_ip(ip))
797

    
798
    def main(self, ip):
799
        super(self.__class__, self)._run()
800
        self._run(ip=ip)
801

    
802

    
803
@command(server_cmds)
804
class server_ip_attach(_init_cyclades, _optional_output_cmd):
805
    """Attach a floating ip to a server with server_id
806
    """
807

    
808
    @errors.generic.all
809
    @errors.cyclades.connection
810
    @errors.cyclades.server_id
811
    def _run(self, server_id, ip):
812
        self._optional_output(self.client.attach_floating_ip(server_id, ip))
813

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

    
818

    
819
@command(server_cmds)
820
class server_ip_detach(_init_cyclades):
821
    """detach_floating_ip_to_server
822
    """
823

    
824
    @errors.generic.all
825
    @errors.cyclades.connection
826
    @errors.cyclades.server_id
827
    def _run(self, server_id, ip):
828
        self._optional_output(self.client.detach_floating_ip(server_id, ip))
829

    
830
    def main(self, server_id, ip):
831
        super(self.__class__, self)._run()
832
        self._run(server_id=server_id, ip=ip)