Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / commands / cyclades.py @ f724cd35

History | View | Annotate | Download (22.7 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
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
        cyclades_endpoints = self.auth_base.get_service_endpoints(
74
            self.config.get('cyclades', 'type'),
75
            self.config.get('cyclades', 'version'))
76
        base_url = cyclades_endpoints['publicURL']
77
        self.client = CycladesClient(base_url=base_url, token=token)
78
        self._set_log_params()
79
        self._update_max_threads()
80

    
81
    def main(self):
82
        self._run()
83

    
84

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

    
89
    __doc__ += about_authentication
90

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

    
103
    @errors.generic.all
104
    @errors.cyclades.connection
105
    @errors.cyclades.date
106
    def _run(self):
107
        servers = self.client.list_servers(self['detail'], self['since'])
108

    
109
        kwargs = dict(with_enumeration=self['enum'])
110
        if self['more']:
111
            kwargs['page_size'] = self['limit'] if self['limit'] else 10
112
        elif self['limit']:
113
            servers = servers[:self['limit']]
114
        self._print(servers, **kwargs)
115

    
116
    def main(self):
117
        super(self.__class__, self)._run()
118
        self._run()
119

    
120

    
121
@command(server_cmds)
122
class server_info(_init_cyclades, _optional_json):
123
    """Detailed information on a Virtual Machine
124
    Contains:
125
    - name, id, status, create/update dates
126
    - network interfaces
127
    - metadata (e.g. os, superuser) and diagnostics
128
    - hardware flavor and os image ids
129
    """
130

    
131
    @errors.generic.all
132
    @errors.cyclades.connection
133
    @errors.cyclades.server_id
134
    def _run(self, server_id):
135
        self._print(self.client.get_server_details(server_id), print_dict)
136

    
137
    def main(self, server_id):
138
        super(self.__class__, self)._run()
139
        self._run(server_id=server_id)
140

    
141

    
142
class PersonalityArgument(KeyValueArgument):
143
    @property
144
    def value(self):
145
        return self._value if hasattr(self, '_value') else []
146

    
147
    @value.setter
148
    def value(self, newvalue):
149
        if newvalue == self.default:
150
            return self.value
151
        self._value = []
152
        for i, terms in enumerate(newvalue):
153
            termlist = terms.split(',')
154
            if len(termlist) > 5:
155
                msg = 'Wrong number of terms (should be 1 to 5)'
156
                raiseCLIError(CLISyntaxError(msg), details=howto_personality)
157
            path = termlist[0]
158
            if not exists(path):
159
                raiseCLIError(
160
                    None,
161
                    '--personality: File %s does not exist' % path,
162
                    importance=1,
163
                    details=howto_personality)
164
            self._value.append(dict(path=path))
165
            with open(path) as f:
166
                self._value[i]['contents'] = b64encode(f.read())
167
            try:
168
                self._value[i]['path'] = termlist[1]
169
                self._value[i]['owner'] = termlist[2]
170
                self._value[i]['group'] = termlist[3]
171
                self._value[i]['mode'] = termlist[4]
172
            except IndexError:
173
                pass
174

    
175

    
176
@command(server_cmds)
177
class server_create(_init_cyclades, _optional_json):
178
    """Create a server (aka Virtual Machine)
179
    Parameters:
180
    - name: (single quoted text)
181
    - flavor id: Hardware flavor. Pick one from: /flavor list
182
    - image id: OS images. Pick one from: /image list
183
    """
184

    
185
    arguments = dict(
186
        personality=PersonalityArgument(
187
            (80 * ' ').join(howto_personality), ('-p', '--personality'))
188
    )
189

    
190
    @errors.generic.all
191
    @errors.cyclades.connection
192
    @errors.plankton.id
193
    @errors.cyclades.flavor_id
194
    def _run(self, name, flavor_id, image_id):
195
        self._print(
196
            self.client.create_server(
197
                name, int(flavor_id), image_id, self['personality']),
198
            print_dict)
199

    
200
    def main(self, name, flavor_id, image_id):
201
        super(self.__class__, self)._run()
202
        self._run(name=name, flavor_id=flavor_id, image_id=image_id)
203

    
204

    
205
@command(server_cmds)
206
class server_rename(_init_cyclades, _optional_output_cmd):
207
    """Set/update a server (VM) name
208
    VM names are not unique, therefore multiple servers may share the same name
209
    """
210

    
211
    @errors.generic.all
212
    @errors.cyclades.connection
213
    @errors.cyclades.server_id
214
    def _run(self, server_id, new_name):
215
        self._optional_output(
216
            self.client.update_server_name(int(server_id), new_name))
217

    
218
    def main(self, server_id, new_name):
219
        super(self.__class__, self)._run()
220
        self._run(server_id=server_id, new_name=new_name)
221

    
222

    
223
@command(server_cmds)
224
class server_delete(_init_cyclades, _optional_output_cmd):
225
    """Delete a server (VM)"""
226

    
227
    @errors.generic.all
228
    @errors.cyclades.connection
229
    @errors.cyclades.server_id
230
    def _run(self, server_id):
231
            self._optional_output(self.client.delete_server(int(server_id)))
232

    
233
    def main(self, server_id):
234
        super(self.__class__, self)._run()
235
        self._run(server_id=server_id)
236

    
237

    
238
@command(server_cmds)
239
class server_reboot(_init_cyclades, _optional_output_cmd):
240
    """Reboot a server (VM)"""
241

    
242
    arguments = dict(
243
        hard=FlagArgument('perform a hard reboot', ('-f', '--force'))
244
    )
245

    
246
    @errors.generic.all
247
    @errors.cyclades.connection
248
    @errors.cyclades.server_id
249
    def _run(self, server_id):
250
        self._optional_output(
251
            self.client.reboot_server(int(server_id), self['hard']))
252

    
253
    def main(self, server_id):
254
        super(self.__class__, self)._run()
255
        self._run(server_id=server_id)
256

    
257

    
258
@command(server_cmds)
259
class server_start(_init_cyclades, _optional_output_cmd):
260
    """Start an existing server (VM)"""
261

    
262
    @errors.generic.all
263
    @errors.cyclades.connection
264
    @errors.cyclades.server_id
265
    def _run(self, server_id):
266
        self._optional_output(self.client.start_server(int(server_id)))
267

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

    
272

    
273
@command(server_cmds)
274
class server_shutdown(_init_cyclades, _optional_output_cmd):
275
    """Shutdown an active server (VM)"""
276

    
277
    @errors.generic.all
278
    @errors.cyclades.connection
279
    @errors.cyclades.server_id
280
    def _run(self, server_id):
281
        self._optional_output(self.client.shutdown_server(int(server_id)))
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_console(_init_cyclades, _optional_json):
290
    """Get a VNC console to access an existing server (VM)
291
    Console connection information provided (at least):
292
    - host: (url or address) a VNC host
293
    - port: (int) the gateway to enter VM on host
294
    - password: for VNC authorization
295
    """
296

    
297
    @errors.generic.all
298
    @errors.cyclades.connection
299
    @errors.cyclades.server_id
300
    def _run(self, server_id):
301
        self._print(
302
            self.client.get_server_console(int(server_id)), print_dict)
303

    
304
    def main(self, server_id):
305
        super(self.__class__, self)._run()
306
        self._run(server_id=server_id)
307

    
308

    
309
@command(server_cmds)
310
class server_firewall(_init_cyclades):
311
    """Manage server (VM) firewall profiles for public networks"""
312

    
313

    
314
@command(server_cmds)
315
class server_firewall_set(_init_cyclades, _optional_output_cmd):
316
    """Set the server (VM) firewall profile on VMs public network
317
    Values for profile:
318
    - DISABLED: Shutdown firewall
319
    - ENABLED: Firewall in normal mode
320
    - PROTECTED: Firewall in secure mode
321
    """
322

    
323
    @errors.generic.all
324
    @errors.cyclades.connection
325
    @errors.cyclades.server_id
326
    @errors.cyclades.firewall
327
    def _run(self, server_id, profile):
328
        self._optional_output(self.client.set_firewall_profile(
329
            server_id=int(server_id), profile=('%s' % profile).upper()))
330

    
331
    def main(self, server_id, profile):
332
        super(self.__class__, self)._run()
333
        self._run(server_id=server_id, profile=profile)
334

    
335

    
336
@command(server_cmds)
337
class server_firewall_get(_init_cyclades):
338
    """Get the server (VM) firewall profile for its public network"""
339

    
340
    @errors.generic.all
341
    @errors.cyclades.connection
342
    @errors.cyclades.server_id
343
    def _run(self, server_id):
344
        print(self.client.get_firewall_profile(server_id))
345

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

    
350

    
351
@command(server_cmds)
352
class server_addr(_init_cyclades, _optional_json):
353
    """List the addresses of all network interfaces on a server (VM)"""
354

    
355
    arguments = dict(
356
        enum=FlagArgument('Enumerate results', '--enumerate')
357
    )
358

    
359
    @errors.generic.all
360
    @errors.cyclades.connection
361
    @errors.cyclades.server_id
362
    def _run(self, server_id):
363
        reply = self.client.list_server_nics(int(server_id))
364
        self._print(
365
            reply, with_enumeration=self['enum'] and len(reply) > 1)
366

    
367
    def main(self, server_id):
368
        super(self.__class__, self)._run()
369
        self._run(server_id=server_id)
370

    
371

    
372
@command(server_cmds)
373
class server_metadata(_init_cyclades):
374
    """Manage Server metadata (key:value pairs of server attributes)"""
375

    
376

    
377
@command(server_cmds)
378
class server_metadata_list(_init_cyclades, _optional_json):
379
    """Get server metadata"""
380

    
381
    @errors.generic.all
382
    @errors.cyclades.connection
383
    @errors.cyclades.server_id
384
    @errors.cyclades.metadata
385
    def _run(self, server_id, key=''):
386
        self._print(
387
            self.client.get_server_metadata(int(server_id), key), print_dict)
388

    
389
    def main(self, server_id, key=''):
390
        super(self.__class__, self)._run()
391
        self._run(server_id=server_id, key=key)
392

    
393

    
394
@command(server_cmds)
395
class server_metadata_set(_init_cyclades, _optional_json):
396
    """Set / update server(VM) metadata
397
    Metadata should be given in key/value pairs in key=value format
398
    For example:
399
        /server metadata set <server id> key1=value1 key2=value2
400
    Old, unreferenced metadata will remain intact
401
    """
402

    
403
    @errors.generic.all
404
    @errors.cyclades.connection
405
    @errors.cyclades.server_id
406
    def _run(self, server_id, keyvals):
407
        assert keyvals, 'Please, add some metadata ( key=value)'
408
        metadata = dict()
409
        for keyval in keyvals:
410
            k, sep, v = keyval.partition('=')
411
            if sep and k:
412
                metadata[k] = v
413
            else:
414
                raiseCLIError(
415
                    'Invalid piece of metadata %s' % keyval,
416
                    importance=2, details=[
417
                        'Correct metadata format: key=val',
418
                        'For example:',
419
                        '/server metadata set <server id>'
420
                        'key1=value1 key2=value2'])
421
        self._print(
422
            self.client.update_server_metadata(int(server_id), **metadata),
423
            print_dict)
424

    
425
    def main(self, server_id, *key_equals_val):
426
        super(self.__class__, self)._run()
427
        self._run(server_id=server_id, keyvals=key_equals_val)
428

    
429

    
430
@command(server_cmds)
431
class server_metadata_delete(_init_cyclades, _optional_output_cmd):
432
    """Delete server (VM) metadata"""
433

    
434
    @errors.generic.all
435
    @errors.cyclades.connection
436
    @errors.cyclades.server_id
437
    @errors.cyclades.metadata
438
    def _run(self, server_id, key):
439
        self._optional_output(
440
            self.client.delete_server_metadata(int(server_id), key))
441

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

    
446

    
447
@command(server_cmds)
448
class server_stats(_init_cyclades, _optional_json):
449
    """Get server (VM) statistics"""
450

    
451
    @errors.generic.all
452
    @errors.cyclades.connection
453
    @errors.cyclades.server_id
454
    def _run(self, server_id):
455
        self._print(self.client.get_server_stats(int(server_id)), print_dict)
456

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

    
461

    
462
@command(server_cmds)
463
class server_wait(_init_cyclades):
464
    """Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]"""
465

    
466
    arguments = dict(
467
        progress_bar=ProgressBarArgument(
468
            'do not show progress bar',
469
            ('-N', '--no-progress-bar'),
470
            False
471
        )
472
    )
473

    
474
    @errors.generic.all
475
    @errors.cyclades.connection
476
    @errors.cyclades.server_id
477
    def _run(self, server_id, currect_status):
478
        (progress_bar, wait_cb) = self._safe_progress_bar(
479
            'Server %s still in %s mode' % (server_id, currect_status))
480

    
481
        try:
482
            new_mode = self.client.wait_server(
483
                server_id,
484
                currect_status,
485
                wait_cb=wait_cb)
486
        except Exception:
487
            self._safe_progress_bar_finish(progress_bar)
488
            raise
489
        finally:
490
            self._safe_progress_bar_finish(progress_bar)
491
        if new_mode:
492
            print('Server %s is now in %s mode' % (server_id, new_mode))
493
        else:
494
            raiseCLIError(None, 'Time out')
495

    
496
    def main(self, server_id, currect_status='BUILD'):
497
        super(self.__class__, self)._run()
498
        self._run(server_id=server_id, currect_status=currect_status)
499

    
500

    
501
@command(flavor_cmds)
502
class flavor_list(_init_cyclades, _optional_json):
503
    """List available hardware flavors"""
504

    
505
    arguments = dict(
506
        detail=FlagArgument('show detailed output', ('-l', '--details')),
507
        limit=IntArgument('limit # of listed flavors', ('-n', '--number')),
508
        more=FlagArgument(
509
            'output results in pages (-n to set items per page, default 10)',
510
            '--more'),
511
        enum=FlagArgument('Enumerate results', '--enumerate')
512
    )
513

    
514
    @errors.generic.all
515
    @errors.cyclades.connection
516
    def _run(self):
517
        flavors = self.client.list_flavors(self['detail'])
518
        pg_size = 10 if self['more'] and not self['limit'] else self['limit']
519
        self._print(
520
            flavors,
521
            with_redundancy=self['detail'],
522
            page_size=pg_size,
523
            with_enumeration=self['enum'])
524

    
525
    def main(self):
526
        super(self.__class__, self)._run()
527
        self._run()
528

    
529

    
530
@command(flavor_cmds)
531
class flavor_info(_init_cyclades, _optional_json):
532
    """Detailed information on a hardware flavor
533
    To get a list of available flavors and flavor ids, try /flavor list
534
    """
535

    
536
    @errors.generic.all
537
    @errors.cyclades.connection
538
    @errors.cyclades.flavor_id
539
    def _run(self, flavor_id):
540
        self._print(
541
            self.client.get_flavor_details(int(flavor_id)), print_dict)
542

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

    
547

    
548
@command(network_cmds)
549
class network_info(_init_cyclades, _optional_json):
550
    """Detailed information on a network
551
    To get a list of available networks and network ids, try /network list
552
    """
553

    
554
    @errors.generic.all
555
    @errors.cyclades.connection
556
    @errors.cyclades.network_id
557
    def _run(self, network_id):
558
        network = self.client.get_network_details(int(network_id))
559
        self._print(network, print_dict, exclude=('id'))
560

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

    
565

    
566
@command(network_cmds)
567
class network_list(_init_cyclades, _optional_json):
568
    """List networks"""
569

    
570
    arguments = dict(
571
        detail=FlagArgument('show detailed output', ('-l', '--details')),
572
        limit=IntArgument('limit # of listed networks', ('-n', '--number')),
573
        more=FlagArgument(
574
            'output results in pages (-n to set items per page, default 10)',
575
            '--more'),
576
        enum=FlagArgument('Enumerate results', '--enumerate')
577
    )
578

    
579
    @errors.generic.all
580
    @errors.cyclades.connection
581
    def _run(self):
582
        networks = self.client.list_networks(self['detail'])
583
        kwargs = dict(with_enumeration=self['enum'])
584
        if self['more']:
585
            kwargs['page_size'] = self['limit'] or 10
586
        elif self['limit']:
587
            networks = networks[:self['limit']]
588
        self._print(networks, **kwargs)
589

    
590
    def main(self):
591
        super(self.__class__, self)._run()
592
        self._run()
593

    
594

    
595
@command(network_cmds)
596
class network_create(_init_cyclades, _optional_json):
597
    """Create an (unconnected) network"""
598

    
599
    arguments = dict(
600
        cidr=ValueArgument('explicitly set cidr', '--with-cidr'),
601
        gateway=ValueArgument('explicitly set gateway', '--with-gateway'),
602
        dhcp=FlagArgument('Use dhcp (default: off)', '--with-dhcp'),
603
        type=ValueArgument(
604
            'Valid network types are '
605
            'CUSTOM, IP_LESS_ROUTED, MAC_FILTERED (default), PHYSICAL_VLAN',
606
            '--with-type',
607
            default='MAC_FILTERED')
608
    )
609

    
610
    @errors.generic.all
611
    @errors.cyclades.connection
612
    @errors.cyclades.network_max
613
    def _run(self, name):
614
        self._print(self.client.create_network(
615
            name,
616
            cidr=self['cidr'],
617
            gateway=self['gateway'],
618
            dhcp=self['dhcp'],
619
            type=self['type']), print_dict)
620

    
621
    def main(self, name):
622
        super(self.__class__, self)._run()
623
        self._run(name)
624

    
625

    
626
@command(network_cmds)
627
class network_rename(_init_cyclades, _optional_output_cmd):
628
    """Set the name of a network"""
629

    
630
    @errors.generic.all
631
    @errors.cyclades.connection
632
    @errors.cyclades.network_id
633
    def _run(self, network_id, new_name):
634
        self._optional_output(
635
                self.client.update_network_name(int(network_id), new_name))
636

    
637
    def main(self, network_id, new_name):
638
        super(self.__class__, self)._run()
639
        self._run(network_id=network_id, new_name=new_name)
640

    
641

    
642
@command(network_cmds)
643
class network_delete(_init_cyclades, _optional_output_cmd):
644
    """Delete a network"""
645

    
646
    @errors.generic.all
647
    @errors.cyclades.connection
648
    @errors.cyclades.network_id
649
    @errors.cyclades.network_in_use
650
    def _run(self, network_id):
651
        self._optional_output(self.client.delete_network(int(network_id)))
652

    
653
    def main(self, network_id):
654
        super(self.__class__, self)._run()
655
        self._run(network_id=network_id)
656

    
657

    
658
@command(network_cmds)
659
class network_connect(_init_cyclades, _optional_output_cmd):
660
    """Connect a server to a network"""
661

    
662
    @errors.generic.all
663
    @errors.cyclades.connection
664
    @errors.cyclades.server_id
665
    @errors.cyclades.network_id
666
    def _run(self, server_id, network_id):
667
        self._optional_output(
668
                self.client.connect_server(int(server_id), int(network_id)))
669

    
670
    def main(self, server_id, network_id):
671
        super(self.__class__, self)._run()
672
        self._run(server_id=server_id, network_id=network_id)
673

    
674

    
675
@command(network_cmds)
676
class network_disconnect(_init_cyclades):
677
    """Disconnect a nic that connects a server to a network
678
    Nic ids are listed as "attachments" in detailed network information
679
    To get detailed network information: /network info <network id>
680
    """
681

    
682
    @errors.cyclades.nic_format
683
    def _server_id_from_nic(self, nic_id):
684
        return nic_id.split('-')[1]
685

    
686
    @errors.generic.all
687
    @errors.cyclades.connection
688
    @errors.cyclades.server_id
689
    @errors.cyclades.nic_id
690
    def _run(self, nic_id, server_id):
691
        num_of_disconnected = self.client.disconnect_server(server_id, nic_id)
692
        if not num_of_disconnected:
693
            raise ClientError(
694
                'Network Interface %s not found on server %s' % (
695
                    nic_id,
696
                    server_id),
697
                status=404)
698
        print('Disconnected %s connections' % num_of_disconnected)
699

    
700
    def main(self, nic_id):
701
        super(self.__class__, self)._run()
702
        server_id = self._server_id_from_nic(nic_id=nic_id)
703
        self._run(nic_id=nic_id, server_id=server_id)