Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades.py @ 0f383dcc

History | View | Annotate | Download (30.5 kB)

1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from kamaki.cli import command
35
from kamaki.cli.command_tree import CommandTree
36
from kamaki.cli.utils import print_dict, remove_from_items
37
from kamaki.cli.errors import raiseCLIError, CLISyntaxError, CLIBaseUrlError
38
from kamaki.clients.cyclades import CycladesClient, ClientError
39
from kamaki.cli.argument import FlagArgument, ValueArgument, KeyValueArgument
40
from kamaki.cli.argument import ProgressBarArgument, DateArgument, IntArgument
41
from kamaki.cli.commands import _command_init, errors, addLogSettings
42
from kamaki.cli.commands import _optional_output_cmd, _optional_json
43

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

    
47

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

    
53

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

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

    
67

    
68
class _server_wait(object):
69

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

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

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

    
96

    
97
class _network_wait(object):
98

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

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

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

    
125

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

    
152
    def main(self):
153
        self._run()
154

    
155

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

    
160
    __doc__ += about_authentication
161

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

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

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

    
189
    def main(self):
190
        super(self.__class__, self)._run()
191
        self._run()
192

    
193

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

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

    
210
    def main(self, server_id):
211
        super(self.__class__, self)._run()
212
        self._run(server_id=server_id)
213

    
214

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

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

    
248

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

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

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

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

    
279

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

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

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

    
297

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

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

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

    
315
            r = self.client.delete_server(int(server_id))
316
            self._optional_output(r)
317

    
318
            if self['wait']:
319
                self._wait(server_id, status)
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_reboot(_init_cyclades, _optional_output_cmd, _server_wait):
328
    """Reboot a server (VM)"""
329

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

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

    
342
        if self['wait']:
343
            self._wait(server_id, 'REBOOT')
344

    
345
    def main(self, server_id):
346
        super(self.__class__, self)._run()
347
        self._run(server_id=server_id)
348

    
349

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

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

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

    
369
        r = self.client.start_server(int(server_id))
370
        self._optional_output(r)
371

    
372
        if self['wait']:
373
            self._wait(server_id, status)
374

    
375
    def main(self, server_id):
376
        super(self.__class__, self)._run()
377
        self._run(server_id=server_id)
378

    
379

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

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

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

    
399
        r = self.client.shutdown_server(int(server_id))
400
        self._optional_output(r)
401

    
402
        if self['wait']:
403
            self._wait(server_id, status)
404

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

    
409

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

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

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

    
430

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

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

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

    
450

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

    
455

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

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

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

    
477

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

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

    
488
    def main(self, server_id):
489
        super(self.__class__, self)._run()
490
        self._run(server_id=server_id)
491

    
492

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

    
497
    arguments = dict(
498
        enum=FlagArgument('Enumerate results', '--enumerate')
499
    )
500

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

    
509
    def main(self, server_id):
510
        super(self.__class__, self)._run()
511
        self._run(server_id=server_id)
512

    
513

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

    
518

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

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

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

    
535

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

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

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

    
570

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

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

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

    
587

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

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

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

    
602

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

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

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

    
617

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

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

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

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

    
648

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

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

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

    
666

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

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

    
680
    def main(self, network_id):
681
        super(self.__class__, self)._run()
682
        self._run(network_id=network_id)
683

    
684

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

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

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

    
711
    def main(self):
712
        super(self.__class__, self)._run()
713
        self._run()
714

    
715

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

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

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

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

    
747
    def main(self, name):
748
        super(self.__class__, self)._run()
749
        self._run(name)
750

    
751

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

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

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

    
767

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

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

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

    
788
        r = self.client.delete_network(int(network_id))
789
        self._optional_output(r)
790

    
791
        if self['wait']:
792
            self._wait(network_id, status)
793

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

    
798

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

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

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

    
815

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

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

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

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

    
846

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

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

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

    
861

    
862
@command(server_cmds)
863
class server_ip(_init_cyclades):
864
    """Manage floating IPs for the servers"""
865

    
866

    
867
@command(server_cmds)
868
class server_ip_pools(_init_cyclades, _optional_json):
869
    """List all floating pools of floating ips"""
870

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

    
877
    def main(self):
878
        super(self.__class__, self)._run()
879
        self._run()
880

    
881

    
882
@command(server_cmds)
883
class server_ip_list(_init_cyclades, _optional_json):
884
    """List all floating ips"""
885

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

    
892
    def main(self):
893
        super(self.__class__, self)._run()
894
        self._run()
895

    
896

    
897
@command(server_cmds)
898
class server_ip_info(_init_cyclades, _optional_json):
899
    """A floating IPs' details"""
900

    
901
    @errors.generic.all
902
    @errors.cyclades.connection
903
    def _run(self, ip):
904
        self._print(self.client.get_floating_ip(ip), print_dict)
905

    
906
    def main(self, ip):
907
        super(self.__class__, self)._run()
908
        self._run(ip=ip)
909

    
910

    
911
@command(server_cmds)
912
class server_ip_create(_init_cyclades, _optional_json):
913
    """Create a new floating IP"""
914

    
915
    arguments = dict(
916
        pool=ValueArgument('Source IP pool', ('--pool'), None)
917
    )
918

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

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

    
928

    
929
@command(server_cmds)
930
class server_ip_delete(_init_cyclades, _optional_output_cmd):
931
    """Delete a floating ip"""
932

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

    
938
    def main(self, ip):
939
        super(self.__class__, self)._run()
940
        self._run(ip=ip)
941

    
942

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

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

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

    
958

    
959
@command(server_cmds)
960
class server_ip_detach(_init_cyclades, _optional_output_cmd):
961
    """Detach floating IP from server
962
    """
963

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

    
970
    def main(self, server_id, ip):
971
        super(self.__class__, self)._run()
972
        self._run(server_id=server_id, ip=ip)