Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades.py @ 545c6c29

History | View | Annotate | Download (24.4 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, print_items, print_json
37
from kamaki.cli.errors import raiseCLIError, CLISyntaxError
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
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 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 _init_cyclades(_command_init):
69
    @errors.generic.all
70
    def _run(self, service='compute'):
71
        token = self.config.get(service, 'token')\
72
            or self.config.get('global', 'token')
73
        base_url = self.config.get(service, 'url')\
74
            or self.config.get('global', 'url')
75
        self.client = CycladesClient(base_url=base_url, token=token)
76
        self._set_log_params()
77
        self._update_max_threads()
78

    
79
    def main(self):
80
        self._run()
81

    
82

    
83
@command(server_cmds)
84
class server_list(_init_cyclades, _optional_json):
85
    """List Virtual Machines accessible by user"""
86

    
87
    __doc__ += about_authentication
88

    
89
    arguments = dict(
90
        detail=FlagArgument('show detailed output', ('-l', '--details')),
91
        since=DateArgument(
92
            'show only items since date (\' d/m/Y H:M:S \')',
93
            '--since'),
94
        limit=IntArgument('limit number of listed VMs', ('-n', '--number')),
95
        more=FlagArgument(
96
            'output results in pages (-n to set items per page, default 10)',
97
            '--more'),
98
        enum=FlagArgument('Enumerate results', '--enumerate')
99
    )
100

    
101
    def _make_results_pretty(self, servers):
102
        for server in servers:
103
            addr_dict = {}
104
            if 'attachments' in server:
105
                for addr in server['attachments']['values']:
106
                    ips = addr.pop('values', [])
107
                    for ip in ips:
108
                        addr['IPv%s' % ip['version']] = ip['addr']
109
                    if 'firewallProfile' in addr:
110
                        addr['firewall'] = addr.pop('firewallProfile')
111
                    addr_dict[addr.pop('id')] = addr
112
                server['attachments'] = addr_dict if addr_dict else None
113
            if 'metadata' in server:
114
                server['metadata'] = server['metadata']['values']
115

    
116
    @errors.generic.all
117
    @errors.cyclades.connection
118
    @errors.cyclades.date
119
    def _run(self):
120
        servers = self.client.list_servers(self['detail'], self['since'])
121

    
122
        if self['detail'] and not self['json_output']:
123
            self._make_results_pretty(servers)
124

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

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

    
136

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

    
147
    def _pretty(self, server):
148
        addr_dict = {}
149
        if 'attachments' in server:
150
            atts = server.pop('attachments')
151
            for addr in atts['values']:
152
                ips = addr.pop('values', [])
153
                for ip in ips:
154
                    addr['IPv%s' % ip['version']] = ip['addr']
155
                if 'firewallProfile' in addr:
156
                    addr['firewall'] = addr.pop('firewallProfile')
157
                addr_dict[addr.pop('id')] = addr
158
            server['attachments'] = addr_dict if addr_dict else None
159
        if 'metadata' in server:
160
            server['metadata'] = server['metadata']['values']
161
        print_dict(server, ident=1)
162

    
163
    @errors.generic.all
164
    @errors.cyclades.connection
165
    @errors.cyclades.server_id
166
    def _run(self, server_id):
167
        self._print(self.client.get_server_details(server_id), self._pretty)
168

    
169
    def main(self, server_id):
170
        super(self.__class__, self)._run()
171
        self._run(server_id=server_id)
172

    
173

    
174
class PersonalityArgument(KeyValueArgument):
175
    @property
176
    def value(self):
177
        return self._value if hasattr(self, '_value') else []
178

    
179
    @value.setter
180
    def value(self, newvalue):
181
        if newvalue == self.default:
182
            return self.value
183
        self._value = []
184
        for i, terms in enumerate(newvalue):
185
            termlist = terms.split(',')
186
            if len(termlist) > 5:
187
                msg = 'Wrong number of terms (should be 1 to 5)'
188
                raiseCLIError(CLISyntaxError(msg), details=howto_personality)
189
            path = termlist[0]
190
            if not exists(path):
191
                raiseCLIError(
192
                    None,
193
                    '--personality: File %s does not exist' % path,
194
                    importance=1,
195
                    details=howto_personality)
196
            self._value.append(dict(path=path))
197
            with open(path) as f:
198
                self._value[i]['contents'] = b64encode(f.read())
199
            try:
200
                self._value[i]['path'] = termlist[1]
201
                self._value[i]['owner'] = termlist[2]
202
                self._value[i]['group'] = termlist[3]
203
                self._value[i]['mode'] = termlist[4]
204
            except IndexError:
205
                pass
206

    
207

    
208
@command(server_cmds)
209
class server_create(_init_cyclades, _optional_json):
210
    """Create a server (aka Virtual Machine)
211
    Parameters:
212
    - name: (single quoted text)
213
    - flavor id: Hardware flavor. Pick one from: /flavor list
214
    - image id: OS images. Pick one from: /image list
215
    """
216

    
217
    arguments = dict(
218
        personality=PersonalityArgument(
219
            (80 * ' ').join(howto_personality), ('-p', '--personality'))
220
    )
221

    
222
    @errors.generic.all
223
    @errors.cyclades.connection
224
    @errors.plankton.id
225
    @errors.cyclades.flavor_id
226
    def _run(self, name, flavor_id, image_id):
227
        self._print([self.client.create_server(
228
            name, int(flavor_id), image_id, self['personality'])])
229

    
230
    def main(self, name, flavor_id, image_id):
231
        super(self.__class__, self)._run()
232
        self._run(name=name, flavor_id=flavor_id, image_id=image_id)
233

    
234

    
235
@command(server_cmds)
236
class server_rename(_init_cyclades, _optional_output_cmd):
237
    """Set/update a server (VM) name
238
    VM names are not unique, therefore multiple servers may share the same name
239
    """
240

    
241
    @errors.generic.all
242
    @errors.cyclades.connection
243
    @errors.cyclades.server_id
244
    def _run(self, server_id, new_name):
245
        self._optional_output(
246
            self.client.update_server_name(int(server_id), new_name))
247

    
248
    def main(self, server_id, new_name):
249
        super(self.__class__, self)._run()
250
        self._run(server_id=server_id, new_name=new_name)
251

    
252

    
253
@command(server_cmds)
254
class server_delete(_init_cyclades, _optional_output_cmd):
255
    """Delete a server (VM)"""
256

    
257
    @errors.generic.all
258
    @errors.cyclades.connection
259
    @errors.cyclades.server_id
260
    def _run(self, server_id):
261
            self._optional_output(self.client.delete_server(int(server_id)))
262

    
263
    def main(self, server_id):
264
        super(self.__class__, self)._run()
265
        self._run(server_id=server_id)
266

    
267

    
268
@command(server_cmds)
269
class server_reboot(_init_cyclades, _optional_output_cmd):
270
    """Reboot a server (VM)"""
271

    
272
    arguments = dict(
273
        hard=FlagArgument('perform a hard reboot', ('-f', '--force'))
274
    )
275

    
276
    @errors.generic.all
277
    @errors.cyclades.connection
278
    @errors.cyclades.server_id
279
    def _run(self, server_id):
280
        self._optional_output(
281
            self.client.reboot_server(int(server_id), self['hard']))
282

    
283
    def main(self, server_id):
284
        super(self.__class__, self)._run()
285
        self._run(server_id=server_id)
286

    
287

    
288
@command(server_cmds)
289
class server_start(_init_cyclades, _optional_output_cmd):
290
    """Start an existing server (VM)"""
291

    
292
    @errors.generic.all
293
    @errors.cyclades.connection
294
    @errors.cyclades.server_id
295
    def _run(self, server_id):
296
        self._optional_output(self.client.start_server(int(server_id)))
297

    
298
    def main(self, server_id):
299
        super(self.__class__, self)._run()
300
        self._run(server_id=server_id)
301

    
302

    
303
@command(server_cmds)
304
class server_shutdown(_init_cyclades, _optional_output_cmd):
305
    """Shutdown an active server (VM)"""
306

    
307
    @errors.generic.all
308
    @errors.cyclades.connection
309
    @errors.cyclades.server_id
310
    def _run(self, server_id):
311
        self._optional_output(self.client.shutdown_server(int(server_id)))
312

    
313
    def main(self, server_id):
314
        super(self.__class__, self)._run()
315
        self._run(server_id=server_id)
316

    
317

    
318
@command(server_cmds)
319
class server_console(_init_cyclades, _optional_json):
320
    """Get a VNC console to access an existing server (VM)
321
    Console connection information provided (at least):
322
    - host: (url or address) a VNC host
323
    - port: (int) the gateway to enter VM on host
324
    - password: for VNC authorization
325
    """
326

    
327
    @errors.generic.all
328
    @errors.cyclades.connection
329
    @errors.cyclades.server_id
330
    def _run(self, server_id):
331
        self._print([self.client.get_server_console(int(server_id))])
332

    
333
    def main(self, server_id):
334
        super(self.__class__, self)._run()
335
        self._run(server_id=server_id)
336

    
337

    
338
@command(server_cmds)
339
class server_firewall(_init_cyclades):
340
    """Manage server (VM) firewall profiles for public networks"""
341

    
342

    
343
@command(server_cmds)
344
class server_firewall_set(_init_cyclades, _optional_output_cmd):
345
    """Set the server (VM) firewall profile on VMs public network
346
    Values for profile:
347
    - DISABLED: Shutdown firewall
348
    - ENABLED: Firewall in normal mode
349
    - PROTECTED: Firewall in secure mode
350
    """
351

    
352
    @errors.generic.all
353
    @errors.cyclades.connection
354
    @errors.cyclades.server_id
355
    @errors.cyclades.firewall
356
    def _run(self, server_id, profile):
357
        self._optional_output(self.client.set_firewall_profile(
358
            server_id=int(server_id), profile=('%s' % profile).upper()))
359

    
360
    def main(self, server_id, profile):
361
        super(self.__class__, self)._run()
362
        self._run(server_id=server_id, profile=profile)
363

    
364

    
365
@command(server_cmds)
366
class server_firewall_get(_init_cyclades):
367
    """Get the server (VM) firewall profile for its public network"""
368

    
369
    @errors.generic.all
370
    @errors.cyclades.connection
371
    @errors.cyclades.server_id
372
    def _run(self, server_id):
373
        print(self.client.get_firewall_profile(server_id))
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_addr(_init_cyclades, _optional_json):
382
    """List the addresses of all network interfaces on a server (VM)"""
383

    
384
    arguments = dict(
385
        enum=FlagArgument('Enumerate results', '--enumerate')
386
    )
387

    
388
    @errors.generic.all
389
    @errors.cyclades.connection
390
    @errors.cyclades.server_id
391
    def _run(self, server_id):
392
        reply = self.client.list_server_nics(int(server_id))
393
        self._print(
394
            reply, with_enumeration=self['enum'] and len(reply) > 1)
395

    
396
    def main(self, server_id):
397
        super(self.__class__, self)._run()
398
        self._run(server_id=server_id)
399

    
400

    
401
@command(server_cmds)
402
class server_metadata(_init_cyclades):
403
    """Manage Server metadata (key:value pairs of server attributes)"""
404

    
405

    
406
@command(server_cmds)
407
class server_metadata_list(_init_cyclades, _optional_json):
408
    """Get server metadata"""
409

    
410
    @errors.generic.all
411
    @errors.cyclades.connection
412
    @errors.cyclades.server_id
413
    @errors.cyclades.metadata
414
    def _run(self, server_id, key=''):
415
        self._print(
416
            [self.client.get_server_metadata(int(server_id), key)], title=())
417

    
418
    def main(self, server_id, key=''):
419
        super(self.__class__, self)._run()
420
        self._run(server_id=server_id, key=key)
421

    
422

    
423
@command(server_cmds)
424
class server_metadata_set(_init_cyclades, _optional_json):
425
    """Set / update server(VM) metadata
426
    Metadata should be given in key/value pairs in key=value format
427
    For example:
428
        /server metadata set <server id> key1=value1 key2=value2
429
    Old, unreferenced metadata will remain intact
430
    """
431

    
432
    @errors.generic.all
433
    @errors.cyclades.connection
434
    @errors.cyclades.server_id
435
    def _run(self, server_id, keyvals):
436
        assert keyvals, 'Please, add some metadata ( key=value)'
437
        metadata = dict()
438
        for keyval in keyvals:
439
            k, sep, v = keyval.partition('=')
440
            if sep and k:
441
                metadata[k] = v
442
            else:
443
                raiseCLIError(
444
                    'Invalid piece of metadata %s' % keyval,
445
                    importance=2, details=[
446
                        'Correct metadata format: key=val',
447
                        'For example:',
448
                        '/server metadata set <server id>'
449
                        'key1=value1 key2=value2'])
450
        self._print(
451
            [self.client.update_server_metadata(int(server_id), **metadata)],
452
            title=())
453

    
454
    def main(self, server_id, *key_equals_val):
455
        super(self.__class__, self)._run()
456
        self._run(server_id=server_id, keyvals=key_equals_val)
457

    
458

    
459
@command(server_cmds)
460
class server_metadata_delete(_init_cyclades, _optional_output_cmd):
461
    """Delete server (VM) metadata"""
462

    
463
    @errors.generic.all
464
    @errors.cyclades.connection
465
    @errors.cyclades.server_id
466
    @errors.cyclades.metadata
467
    def _run(self, server_id, key):
468
        self._optional_output(
469
            self.client.delete_server_metadata(int(server_id), key))
470

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

    
475

    
476
@command(server_cmds)
477
class server_stats(_init_cyclades, _optional_json):
478
    """Get server (VM) statistics"""
479

    
480
    @errors.generic.all
481
    @errors.cyclades.connection
482
    @errors.cyclades.server_id
483
    def _run(self, server_id):
484
        self._print([self.client.get_server_stats(int(server_id))])
485

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

    
490

    
491
@command(server_cmds)
492
class server_wait(_init_cyclades):
493
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
494

    
495
    arguments = dict(
496
        progress_bar=ProgressBarArgument(
497
            'do not show progress bar',
498
            ('-N', '--no-progress-bar'),
499
            False
500
        )
501
    )
502

    
503
    @errors.generic.all
504
    @errors.cyclades.connection
505
    @errors.cyclades.server_id
506
    def _run(self, server_id, currect_status):
507
        (progress_bar, wait_cb) = self._safe_progress_bar(
508
            'Server %s still in %s mode' % (server_id, currect_status))
509

    
510
        try:
511
            new_mode = self.client.wait_server(
512
                server_id,
513
                currect_status,
514
                wait_cb=wait_cb)
515
        except Exception:
516
            self._safe_progress_bar_finish(progress_bar)
517
            raise
518
        finally:
519
            self._safe_progress_bar_finish(progress_bar)
520
        if new_mode:
521
            print('Server %s is now in %s mode' % (server_id, new_mode))
522
        else:
523
            raiseCLIError(None, 'Time out')
524

    
525
    def main(self, server_id, currect_status='BUILD'):
526
        super(self.__class__, self)._run()
527
        self._run(server_id=server_id, currect_status=currect_status)
528

    
529

    
530
@command(flavor_cmds)
531
class flavor_list(_init_cyclades, _optional_json):
532
    """List available hardware flavors"""
533

    
534
    arguments = dict(
535
        detail=FlagArgument('show detailed output', ('-l', '--details')),
536
        limit=IntArgument('limit # of listed flavors', ('-n', '--number')),
537
        more=FlagArgument(
538
            'output results in pages (-n to set items per page, default 10)',
539
            '--more'),
540
        enum=FlagArgument('Enumerate results', '--enumerate')
541
    )
542

    
543
    @errors.generic.all
544
    @errors.cyclades.connection
545
    def _run(self):
546
        flavors = self.client.list_flavors(self['detail'])
547
        pg_size = 10 if self['more'] and not self['limit'] else self['limit']
548
        self._print(
549
            flavors,
550
            with_redundancy=self['detail'],
551
            page_size=pg_size,
552
            with_enumeration=self['enum'])
553

    
554
    def main(self):
555
        super(self.__class__, self)._run()
556
        self._run()
557

    
558

    
559
@command(flavor_cmds)
560
class flavor_info(_init_cyclades, _optional_json):
561
    """Detailed information on a hardware flavor
562
    To get a list of available flavors and flavor ids, try /flavor list
563
    """
564

    
565
    @errors.generic.all
566
    @errors.cyclades.connection
567
    @errors.cyclades.flavor_id
568
    def _run(self, flavor_id):
569
        self._print([self.client.get_flavor_details(int(flavor_id))])
570

    
571
    def main(self, flavor_id):
572
        super(self.__class__, self)._run()
573
        self._run(flavor_id=flavor_id)
574

    
575

    
576
@command(network_cmds)
577
class network_info(_init_cyclades, _optional_json):
578
    """Detailed information on a network
579
    To get a list of available networks and network ids, try /network list
580
    """
581

    
582
    @classmethod
583
    def _make_result_pretty(self, net):
584
        if 'attachments' in net:
585
            att = net['attachments']['values']
586
            count = len(att)
587
            net['attachments'] = att if count else None
588

    
589
    @errors.generic.all
590
    @errors.cyclades.connection
591
    @errors.cyclades.network_id
592
    def _run(self, network_id):
593
        network = self.client.get_network_details(int(network_id))
594
        self._make_result_pretty(network)
595
        #print_dict(network, exclude=('id'))
596
        self._print(network, print_dict, exclude=('id'))
597

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

    
602

    
603
@command(network_cmds)
604
class network_list(_init_cyclades, _optional_json):
605
    """List networks"""
606

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

    
616
    def _make_results_pretty(self, nets):
617
        for net in nets:
618
            network_info._make_result_pretty(net)
619

    
620
    @errors.generic.all
621
    @errors.cyclades.connection
622
    def _run(self):
623
        networks = self.client.list_networks(self['detail'])
624
        if self['detail']:
625
            self._make_results_pretty(networks)
626
        kwargs = dict(with_enumeration=self['enum'])
627
        if self['more']:
628
            kwargs['page_size'] = self['limit'] or 10
629
        elif self['limit']:
630
            networks = networks[:self['limit']]
631
        self._print(networks, **kwargs)
632

    
633
    def main(self):
634
        super(self.__class__, self)._run()
635
        self._run()
636

    
637

    
638
@command(network_cmds)
639
class network_create(_init_cyclades, _optional_json):
640
    """Create an (unconnected) network"""
641

    
642
    arguments = dict(
643
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
644
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
645
        dhcp=FlagArgument('Use dhcp (default: off)', '--with-dhcp'),
646
        type=ValueArgument(
647
            'Valid network types are '
648
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
649
            '--with-type',
650
            default='MAC_FILTERED')
651
    )
652

    
653
    @errors.generic.all
654
    @errors.cyclades.connection
655
    @errors.cyclades.network_max
656
    def _run(self, name):
657
        self._print([self.client.create_network(
658
            name,
659
            cidr=self['cidr'],
660
            gateway=self['gateway'],
661
            dhcp=self['dhcp'],
662
            type=self['type'])])
663

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

    
668

    
669
@command(network_cmds)
670
class network_rename(_init_cyclades, _optional_output_cmd):
671
    """Set the name of a network"""
672

    
673
    @errors.generic.all
674
    @errors.cyclades.connection
675
    @errors.cyclades.network_id
676
    def _run(self, network_id, new_name):
677
        self._optional_output(
678
                self.client.update_network_name(int(network_id), new_name))
679

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

    
684

    
685
@command(network_cmds)
686
class network_delete(_init_cyclades, _optional_output_cmd):
687
    """Delete a network"""
688

    
689
    @errors.generic.all
690
    @errors.cyclades.connection
691
    @errors.cyclades.network_id
692
    @errors.cyclades.network_in_use
693
    def _run(self, network_id):
694
        self._optional_output(self.client.delete_network(int(network_id)))
695

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

    
700

    
701
@command(network_cmds)
702
class network_connect(_init_cyclades, _optional_output_cmd):
703
    """Connect a server to a network"""
704

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

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

    
717

    
718
@command(network_cmds)
719
class network_disconnect(_init_cyclades):
720
    """Disconnect a nic that connects a server to a network
721
    Nic ids are listed as "attachments" in detailed network information
722
    To get detailed network information: /network info <network id>
723
    """
724

    
725
    @errors.cyclades.nic_format
726
    def _server_id_from_nic(self, nic_id):
727
        return nic_id.split('-')[1]
728

    
729
    @errors.generic.all
730
    @errors.cyclades.connection
731
    @errors.cyclades.server_id
732
    @errors.cyclades.nic_id
733
    def _run(self, nic_id, server_id):
734
        num_of_disconnected = self.client.disconnect_server(server_id, nic_id)
735
        if not num_of_disconnected:
736
            raise ClientError(
737
                'Network Interface %s not found on server %s' % (
738
                    nic_id,
739
                    server_id),
740
                status=404)
741
        print('Disconnected %s connections' % num_of_disconnected)
742

    
743
    def main(self, nic_id):
744
        super(self.__class__, self)._run()
745
        server_id = self._server_id_from_nic(nic_id=nic_id)
746
        self._run(nic_id=nic_id, server_id=server_id)