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

from io import StringIO
from pydoc import pager

from kamaki.cli import command
38
from kamaki.cli.cmdtree import CommandTree
39
from kamaki.cli.errors import CLIInvalidArgument, raiseCLIError
40
from kamaki.clients.cyclades import (
41
    CycladesNetworkClient, ClientError, CycladesComputeClient)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
42
from kamaki.cli.argument import (
43 44
    FlagArgument, ValueArgument, RepeatableArgument, IntArgument,
    StatusArgument)
45
from kamaki.cli.cmds import (
46
    CommandInit, OptionalOutput, NameFilter, IDFilter, errors, client_log)
47
from kamaki.cli.cmds import Wait
48 49


Stavros Sachtouris's avatar
Stavros Sachtouris committed
50 51 52 53
network_cmds = CommandTree('network', 'Network API network commands')
port_cmds = CommandTree('port', 'Network API port commands')
subnet_cmds = CommandTree('subnet', 'Network API subnet commands')
ip_cmds = CommandTree('ip', 'Network API floatingip commands')
54
namespaces = [network_cmds, port_cmds, subnet_cmds, ip_cmds]
55

56 57
port_states = ('BUILD', 'ACTIVE', 'DOWN', 'ERROR')

58

59
class _PortWait(Wait):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
60

61 62
    def wait(self, port_id, current_status, timeout=60):
        super(_PortWait, self).wait(
Stavros Sachtouris's avatar
Stavros Sachtouris committed
63 64 65 66
            'Port', port_id, self.client.wait_port, current_status,
            timeout=timeout)


67
class _NetworkInit(CommandInit):
68
    @errors.Generic.all
69
    @client_log
70 71
    def _run(self):
        self.client = self.get_client(CycladesNetworkClient, 'network')
72

73 74 75 76
    def _filter_by_user_id(self, nets):
        return [net for net in nets if net['user_id'] == self['user_id']] if (
            self['user_id']) else nets

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
    def _get_compute_client(self):
        compute = getattr(self, '_compute_client', None)
        if not compute:
            compute = self.get_client(CycladesComputeClient, 'cyclades')
            self._compute_client = compute
        return compute

    @errors.Cyclades.network_id
    def _network_exists(self, network_id):
        self.client.get_network_details(network_id)

    @errors.Cyclades.server_id
    def _server_exists(self, server_id):
        compute_client = self._get_compute_client()
        compute_client.get_server_details(server_id)

    def _ip_exists(self, ip, network_id, error):
        for ip_item in self.client.list_floatingips():
            if ip_item['floating_ip_address'] == ip:
                if network_id and ip_item['floating_network_id'] != network_id:
                    raiseCLIError(error, details=[
                        'Floating IP %s does not belong to network %s ,' % (
                            ip, network_id),
                        'To get information on IP %s' % ip,
                        '  kamaki ip info %s' % ip_item['id']])
                return
        raiseCLIError(error, details=[
            'Floating IP %s not found' % ip] + errors.Cyclades.about_ips)

106 107
    def main(self):
        self._run()
108 109 110


@command(network_cmds)
111
class network_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter):
112
    """List networks
113
    Use filtering arguments (e.g., --name-like) to manage long lists
114 115 116 117
    """

    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
118 119 120
        more=FlagArgument(
            'output results in pages (-n to set items per page, default 10)',
            '--more'),
121 122
        user_id=ValueArgument(
            'show only networks belonging to user with this id', '--user-id')
123 124
    )

125 126
    @errors.Generic.all
    @errors.Cyclades.connection
127
    def _run(self):
128
        nets = self.client.list_networks(detail=True)
129
        nets = self._filter_by_user_id(nets)
130 131
        nets = self._filter_by_name(nets)
        nets = self._filter_by_id(nets)
132
        if not self['detail']:
133
            nets = [dict(
134 135 136
                id=n['id'],
                name=n['name'],
                public='( %s )' % ('public' if (
137
                    n.get('public', None)) else 'private')) for n in nets]
138
            kwargs = dict(title=('id', 'name', 'public'))
139 140
        else:
            kwargs = dict()
Stavros Sachtouris's avatar
Stavros Sachtouris committed
141 142 143
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
144
        self.print_(nets, **kwargs)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
145 146
        if self['more']:
            pager(kwargs['out'].getvalue())
147 148 149 150

    def main(self):
        super(self.__class__, self)._run()
        self._run()
Stavros Sachtouris's avatar
Stavros Sachtouris committed
151 152 153


@command(network_cmds)
154
class network_info(_NetworkInit, OptionalOutput):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
155 156
    """Get details about a network"""

157 158 159
    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.network_id
Stavros Sachtouris's avatar
Stavros Sachtouris committed
160 161
    def _run(self, network_id):
        net = self.client.get_network_details(network_id)
162
        self.print_(net, self.print_dict)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
163 164 165 166

    def main(self, network_id):
        super(self.__class__, self)._run()
        self._run(network_id=network_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
167 168


169 170
class NetworkTypeArgument(ValueArgument):

171
    types = ('MAC_FILTERED', 'CUSTOM', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
172 173 174

    @property
    def value(self):
175
        return getattr(self, '_value', self.types[0])
176 177 178 179 180 181 182 183 184 185 186

    @value.setter
    def value(self, new_value):
        if new_value and new_value.upper() in self.types:
            self._value = new_value.upper()
        elif new_value:
            raise CLIInvalidArgument(
                'Invalid network type %s' % new_value, details=[
                    'Valid types: %s' % ', '.join(self.types), ])


Stavros Sachtouris's avatar
Stavros Sachtouris committed
187
@command(network_cmds)
188
class network_create(_NetworkInit, OptionalOutput):
189
    """Create a new network (default type: MAC_FILTERED)"""
Stavros Sachtouris's avatar
Stavros Sachtouris committed
190

191 192 193
    arguments = dict(
        name=ValueArgument('Network name', '--name'),
        shared=FlagArgument(
194
            'Make network shared (special privileges required)', '--shared'),
195
        project_id=ValueArgument('Assign network to project', '--project-id'),
196 197
        network_type=NetworkTypeArgument(
            'Valid network types: %s' % (', '.join(NetworkTypeArgument.types)),
198
            '--type')
Stavros Sachtouris's avatar
Stavros Sachtouris committed
199 200
    )

201 202
    @errors.Generic.all
    @errors.Cyclades.connection
203
    def _run(self):
204 205 206 207 208 209 210 211 212
        try:
            net = self.client.create_network(
                self['network_type'],
                name=self['name'],
                shared=self['shared'],
                project_id=self['project_id'])
        except ClientError as ce:
            if self['project_id'] and ce.status in (400, 403, 404):
                self._project_id_exists(project_id=self['project_id'])
213
            raise
214
        self.print_(net, self.print_dict)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
215

216
    def main(self):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
217
        super(self.__class__, self)._run()
218
        self._run()
219 220


221
@command(network_cmds)
222
class network_reassign(_NetworkInit, OptionalOutput):
223 224 225 226 227 228
    """Assign a network to a different project"""

    arguments = dict(
        project_id=ValueArgument('Assign network to project', '--project-id'),
    )
    required = ('project_id', )
229

230 231
    @errors.Generic.all
    @errors.Cyclades.connection
232
    @errors.Cyclades.network_permissions
233
    @errors.Cyclades.network_id
234
    def _run(self, network_id):
235 236 237 238 239 240
        try:
            self.client.reassign_network(network_id, self['project_id'])
        except ClientError as ce:
            if ce.status in (400, 403, 404):
                self._project_id_exists(project_id=self['project_id'])
            raise
241

242
    def main(self, network_id):
243
        super(self.__class__, self)._run()
244
        self._run(network_id=network_id)
245 246


247
@command(network_cmds)
248
class network_delete(_NetworkInit):
249 250
    """Delete a network"""

251 252
    @errors.Generic.all
    @errors.Cyclades.connection
253 254
    @errors.Cyclades.network_permissions
    @errors.Cyclades.network_in_use
255
    @errors.Cyclades.network_id
256
    def _run(self, network_id):
257
        self.client.delete_network(network_id)
258 259 260 261

    def main(self, network_id):
        super(self.__class__, self)._run()
        self._run(network_id=network_id)
262 263 264


@command(network_cmds)
265
class network_modify(_NetworkInit, OptionalOutput):
266
    """Modify network attributes"""
267

268 269
    arguments = dict(new_name=ValueArgument('Rename the network', '--name'))
    required = ['new_name', ]
270

271 272
    @errors.Generic.all
    @errors.Cyclades.connection
273
    @errors.Cyclades.network_permissions
274
    @errors.Cyclades.network_id
275
    def _run(self, network_id):
276
        r = self.client.update_network(network_id, name=self['new_name'])
277
        self.print_(r, self.print_dict)
278 279 280 281

    def main(self, network_id):
        super(self.__class__, self)._run()
        self._run(network_id=network_id)
282 283 284


@command(subnet_cmds)
285
class subnet_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter):
286 287 288 289 290 291
    """List subnets
    Use filtering arguments (e.g., --name-like) to manage long server lists
    """

    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
292
        more=FlagArgument('output results in pages', '--more')
293 294
    )

295 296
    @errors.Generic.all
    @errors.Cyclades.connection
297 298 299 300
    def _run(self):
        nets = self.client.list_subnets()
        nets = self._filter_by_name(nets)
        nets = self._filter_by_id(nets)
301
        if not self['detail']:
302
            nets = [dict(
303 304 305 306
                id=n['id'],
                name=n['name'],
                net='( of network %s )' % n['network_id']) for n in nets]
            kwargs = dict(title=('id', 'name', 'net'))
307 308
        else:
            kwargs = dict()
309 310 311
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
312
        self.print_(nets, **kwargs)
313
        if self['more']:
314
            pager('%s' % kwargs['out'].getvalue())
315 316 317 318 319 320 321

    def main(self):
        super(self.__class__, self)._run()
        self._run()


@command(subnet_cmds)
322
class subnet_info(_NetworkInit, OptionalOutput):
323 324
    """Get details about a subnet"""

325 326
    @errors.Generic.all
    @errors.Cyclades.connection
327
    @errors.Cyclades.subnet_id
328 329
    def _run(self, subnet_id):
        net = self.client.get_subnet_details(subnet_id)
330
        self.print_(net, self.print_dict)
331 332 333 334 335 336 337 338 339 340 341 342 343 344

    def main(self, subnet_id):
        super(self.__class__, self)._run()
        self._run(subnet_id=subnet_id)


class AllocationPoolArgument(RepeatableArgument):

    @property
    def value(self):
        return super(AllocationPoolArgument, self).value or []

    @value.setter
    def value(self, new_pools):
345 346
        if not new_pools:
            return
347 348 349 350 351 352 353 354 355 356 357 358 359
        new_list = []
        for pool in new_pools:
            start, comma, end = pool.partition(',')
            if not (start and comma and end):
                raise CLIInvalidArgument(
                    'Invalid allocation pool argument %s' % pool, details=[
                    'Allocation values must be of the form:',
                    '  <start address>,<end address>'])
            new_list.append(dict(start=start, end=end))
        self._value = new_list


@command(subnet_cmds)
360
class subnet_create(_NetworkInit, OptionalOutput):
361
    """Create a new subnet"""
362 363 364 365 366 367 368 369 370 371

    arguments = dict(
        name=ValueArgument('Subnet name', '--name'),
        allocation_pools=AllocationPoolArgument(
            'start_address,end_address of allocation pool (can be repeated)'
            ' e.g., --alloc-pool=123.45.67.1,123.45.67.8',
            '--alloc-pool'),
        gateway=ValueArgument('Gateway IP', '--gateway'),
        subnet_id=ValueArgument('The id for the subnet', '--id'),
        ipv6=FlagArgument('If set, IP version is set to 6, else 4', '--ipv6'),
372 373 374
        enable_dhcp=FlagArgument('Enable dhcp (default: off)', '--with-dhcp'),
        network_id=ValueArgument('Set the network ID', '--network-id'),
        cidr=ValueArgument('Set the CIDR', '--cidr')
375
    )
376
    required = ('network_id', 'cidr')
377

378 379
    @errors.Generic.all
    @errors.Cyclades.connection
380 381 382 383 384 385 386 387 388 389
    def _run(self):
        try:
            net = self.client.create_subnet(
                self['network_id'], self['cidr'],
                self['name'], self['allocation_pools'], self['gateway'],
                self['subnet_id'], self['ipv6'], self['enable_dhcp'])
        except ClientError as ce:
            if ce.status in (404, 400):
                self._network_exists(network_id=self['network_id'])
            raise
390
        self.print_(net, self.print_dict)
391

392
    def main(self):
393
        super(self.__class__, self)._run()
394
        self._run()
395 396 397


@command(subnet_cmds)
398
class subnet_modify(_NetworkInit, OptionalOutput):
399
    """Modify the attributes of a subnet"""
400

401 402 403 404
    arguments = dict(
        new_name=ValueArgument('New name of the subnet', '--name')
    )
    required = ['new_name']
405

406 407
    @errors.Generic.all
    @errors.Cyclades.connection
408 409
    @errors.Cyclades.subnet_permissions
    @errors.Cyclades.subnet_id
410
    def _run(self, subnet_id):
411
        r = self.client.update_subnet(subnet_id, name=self['new_name'])
412
        self.print_(r, self.print_dict)
413 414 415 416

    def main(self, subnet_id):
        super(self.__class__, self)._run()
        self._run(subnet_id=subnet_id)
417 418 419


@command(port_cmds)
420
class port_list(_NetworkInit, OptionalOutput, NameFilter, IDFilter):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
421
    """List all ports"""
422

423 424
    arguments = dict(
        detail=FlagArgument('show detailed output', ('-l', '--details')),
425
        more=FlagArgument('output results in pages', '--more'),
426 427 428 429
        user_id=ValueArgument(
            'show only networks belonging to user with this id', '--user-id')
    )

430 431
    @errors.Generic.all
    @errors.Cyclades.connection
Stavros Sachtouris's avatar
Stavros Sachtouris committed
432
    def _run(self):
433
        ports = self.client.list_ports()
434 435 436
        ports = self._filter_by_user_id(ports)
        ports = self._filter_by_name(ports)
        ports = self._filter_by_id(ports)
437 438
        if not self['detail']:
            ports = [dict(id=p['id'], name=p['name']) for p in ports]
439 440 441 442
        kwargs = dict()
        if self['more']:
            kwargs['out'] = StringIO()
            kwargs['title'] = ()
443
        self.print_(ports, **kwargs)
444 445
        if self['more']:
            pager(kwargs['out'].getvalue())
446

Stavros Sachtouris's avatar
Stavros Sachtouris committed
447
    def main(self):
448
        super(self.__class__, self)._run()
Stavros Sachtouris's avatar
Stavros Sachtouris committed
449
        self._run()
450 451 452


@command(port_cmds)
453
class port_info(_NetworkInit, OptionalOutput):
454 455
    """Get details about a port"""

456 457
    @errors.Generic.all
    @errors.Cyclades.connection
458
    @errors.Cyclades.port_id
459
    def _run(self, port_id):
460
        port = self.client.get_port_details(port_id)
461
        self.print_(port, self.print_dict)
462 463 464 465 466 467 468

    def main(self, port_id):
        super(self.__class__, self)._run()
        self._run(port_id=port_id)


@command(port_cmds)
469
class port_delete(_NetworkInit, _PortWait):
470
    """Delete a port (== disconnect server from network)"""
471

472
    arguments = dict(
473
        wait=FlagArgument('Wait port to be deleted', ('-w', '--wait'))
474 475
    )

476 477
    @errors.Generic.all
    @errors.Cyclades.connection
478
    @errors.Cyclades.port_id
479
    def _run(self, port_id):
480 481
        if self['wait']:
            status = self.client.get_port_details(port_id)['status']
482
        self.client.delete_port(port_id)
483
        if self['wait']:
484
            try:
485
                self.wait(port_id, status)
486 487 488 489
            except ClientError as ce:
                if ce.status not in (404, ):
                    raise
                self.error('Port %s is deleted' % port_id)
490 491 492 493 494 495 496

    def main(self, port_id):
        super(self.__class__, self)._run()
        self._run(port_id=port_id)


@command(port_cmds)
497
class port_modify(_NetworkInit, OptionalOutput):
498
    """Modify the attributes of a port"""
499

500 501
    arguments = dict(new_name=ValueArgument('New name of the port', '--name'))
    required = ['new_name', ]
502

503 504
    @errors.Generic.all
    @errors.Cyclades.connection
505
    @errors.Cyclades.port_id
506 507 508
    def _run(self, port_id):
        r = self.client.get_port_details(port_id)
        r = self.client.update_port(
509
            port_id, r['network_id'], name=self['new_name'])
510
        self.print_(r, self.print_dict)
511 512 513 514 515 516

    def main(self, port_id):
        super(self.__class__, self)._run()
        self._run(port_id=port_id)


517
class _port_create(_NetworkInit, OptionalOutput, _PortWait):
518

519 520 521 522
    @errors.Cyclades.subnet_id
    def _subnet_exists(self, subnet_id):
        self.client.get_subnet_details(subnet_id)

523
    def connect(self, network_id, device_id):
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
        subnet_id, ip = self['subnet_id'], self['ip_address']
        fixed_ips = [dict(ip_address=ip)] if (ip) else None
        if fixed_ips and subnet_id:
            fixed_ips[0]['subnet_id'] = subnet_id
        try:
            r = self.client.create_port(
                network_id, device_id,
                name=self['name'],
                security_groups=self['security_group_id'],
                fixed_ips=fixed_ips)
        except ClientError as ce:
            if ce.status in (400, 404):
                self._network_exists(network_id=network_id)
                self._server_exists(server_id=device_id)
                if subnet_id:
                    self._subnet_exists(subnet_id=subnet_id)
                if self['ip_address']:
                    self._ip_exists(ip=ip, network_id=network_id, error=ce)
            raise
543
        if self['wait']:
544
            self.wait(r['id'], r['status'])
545
            r = self.client.get_port_details(r['id'])
546
        self.print_([r])
547 548


549
@command(port_cmds)
550
class port_create(_port_create):
551
    """Create a new port (== connect server to network)"""
552 553

    arguments = dict(
554
        name=ValueArgument('A human readable name', '--name'),
555 556
        security_group_id=RepeatableArgument(
            'Add a security group id (can be repeated)',
557 558 559 560
            ('-g', '--security-group')),
        subnet_id=ValueArgument(
            'Subnet id for fixed ips (used with --ip-address)',
            '--subnet-id'),
561
        ip_address=ValueArgument('IP address for subnet id', '--ip-address'),
562 563 564
        network_id=ValueArgument('Set the network ID', '--network-id'),
        device_id=ValueArgument(
            'The device is either a virtual server or a virtual router',
Stavros Sachtouris's avatar
Stavros Sachtouris committed
565 566
            '--device-id'),
        wait=FlagArgument('Wait port to be established', ('-w', '--wait')),
567
    )
568
    required = ('network_id', 'device_id')
569

570 571
    @errors.Generic.all
    @errors.Cyclades.connection
572 573
    def _run(self):
        self.connect(self['network_id'], self['device_id'])
574

575
    def main(self):
576
        super(self.__class__, self)._run()
577
        self._run()
578 579


Stavros Sachtouris's avatar
Stavros Sachtouris committed
580
@command(port_cmds)
581
class port_wait(_NetworkInit, _PortWait):
582
    """Wait for port to finish (default: BUILD)"""
Stavros Sachtouris's avatar
Stavros Sachtouris committed
583 584

    arguments = dict(
585 586 587 588 589
        port_status=StatusArgument(
            'Wait while in this status (%s, default: %s)' % (
                ', '.join(port_states), port_states[0]),
            '--status',
            valid_states=port_states),
Stavros Sachtouris's avatar
Stavros Sachtouris committed
590 591 592 593
        timeout=IntArgument(
            'Wait limit in seconds (default: 60)', '--timeout', default=60)
    )

594 595
    @errors.Generic.all
    @errors.Cyclades.connection
596
    def _run(self, port_id, port_status):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
597
        port = self.client.get_port_details(port_id)
598
        if port['status'].lower() == port_status.lower():
599
            self.wait(port_id, port_status, timeout=self['timeout'])
Stavros Sachtouris's avatar
Stavros Sachtouris committed
600 601 602 603
        else:
            self.error(
                'Port %s: Cannot wait for status %s, '
                'status is already %s' % (
604
                    port_id, port_status, port['status']))
Stavros Sachtouris's avatar
Stavros Sachtouris committed
605

Stavros Sachtouris's avatar
Stavros Sachtouris committed
606
    def main(self, port_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
607
        super(self.__class__, self)._run()
608 609
        port_status = self['port_status'] or port_states[0]
        self._run(port_id=port_id, port_status=port_status)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
610 611


612
@command(ip_cmds)
613
class ip_list(_NetworkInit, OptionalOutput):
614 615
    """List reserved floating IPs"""

616 617
    @errors.Generic.all
    @errors.Cyclades.connection
618
    def _run(self):
619
        self.print_(self.client.list_floatingips())
620 621 622 623 624 625 626

    def main(self):
        super(self.__class__, self)._run()
        self._run()


@command(ip_cmds)
627
class ip_info(_NetworkInit, OptionalOutput):
628 629
    """Get details on a floating IP"""

630 631
    @errors.Generic.all
    @errors.Cyclades.connection
632
    @errors.Cyclades.ip_id
633
    def _run(self, ip_id):
634
        self.print_(self.client.get_floatingip_details(ip_id), self.print_dict)
635 636 637 638 639 640 641

    def main(self, ip_id):
        super(self.__class__, self)._run()
        self._run(ip_id=ip_id)


@command(ip_cmds)
642
class ip_create(_NetworkInit, OptionalOutput):
643
    """Reserve an IP on a network"""
644

645 646 647
    arguments = dict(
        network_id=ValueArgument(
            'The network to preserve the IP on', '--network-id'),
648 649
        ip_address=ValueArgument('Allocate an IP address', '--address'),
        project_id=ValueArgument('Assign the IP to project', '--project-id'),
650 651
    )

652 653
    @errors.Generic.all
    @errors.Cyclades.connection
654
    def _run(self):
655 656 657 658 659 660 661 662 663 664 665 666 667
        try:
            self.print_(
                self.client.create_floatingip(
                    self['network_id'],
                    floating_ip_address=self['ip_address'],
                    project_id=self['project_id']),
                self.print_dict)
        except ClientError as ce:
            if ce.status in (400, 404):
                network_id, ip = self['network_id'], self['ip_address']
                self._network_exists(network_id=network_id)
                if ip:
                    self._ip_exists(ip, network_id, ce)
668 669
            if self['project_id'] and ce.status in (400, 403, 404):
                self._project_id_exists(project_id=self['project_id'])
670
            raise
671 672 673

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


677
@command(ip_cmds)
678
class ip_reassign(_NetworkInit):
679 680 681 682 683 684 685
    """Assign a floating IP to a different project"""

    arguments = dict(
        project_id=ValueArgument('Assign the IP to project', '--project-id'),
    )
    required = ('project_id', )

686 687
    @errors.Generic.all
    @errors.Cyclades.connection
688
    def _run(self, ip):
689 690 691 692 693 694
        try:
            self.client.reassign_floating_ip(ip, self['project_id'])
        except ClientError as ce:
            if ce.status in (400, 404):
                self._ip_exists(ip=ip, network_id=None, error=ce)
            raise
695

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


701
@command(ip_cmds)
702
class ip_delete(_NetworkInit):
703 704
    """Unreserve an IP (also delete the port, if attached)"""

705 706 707
    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.ip_id
708
    def _run(self, ip_id):
709
        self.client.delete_floatingip(ip_id)
710 711 712 713 714 715

    def main(self, ip_id):
        super(self.__class__, self)._run()
        self._run(ip_id=ip_id)


Stavros Sachtouris's avatar
Stavros Sachtouris committed
716 717 718 719 720 721 722 723 724 725 726
@command(ip_cmds)
class ip_attach(_port_create):
    """Attach an IP on a virtual server"""

    arguments = dict(
        name=ValueArgument('A human readable name for the port', '--name'),
        security_group_id=RepeatableArgument(
            'Add a security group id (can be repeated)',
            ('-g', '--security-group')),
        subnet_id=ValueArgument('Subnet id', '--subnet-id'),
        wait=FlagArgument('Wait IP to be attached', ('-w', '--wait')),
727
        server_id=ValueArgument('Server to attach to this IP', '--server-id')
Stavros Sachtouris's avatar
Stavros Sachtouris committed
728 729 730
    )
    required = ('server_id', )

731 732
    @errors.Generic.all
    @errors.Cyclades.connection
733
    def _run(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
734 735
        netid = None
        for ip in self.client.list_floatingips():
736
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
737 738
                netid = ip['floating_network_id']
                iparg = ValueArgument(parsed_name='--ip')
739
                iparg.value = ip['floating_ip_address']
Stavros Sachtouris's avatar
Stavros Sachtouris committed
740 741 742
                self.arguments['ip_address'] = iparg
                break
        if netid:
743
            server_id = self['server_id']
Stavros Sachtouris's avatar
Stavros Sachtouris committed
744
            self.error('Creating a port to attach IP %s to server %s' % (
745
                ip_or_ip_id, server_id))
746 747 748 749 750 751 752 753 754
            try:
                self.connect(netid, server_id)
            except ClientError as ce:
                self.error('Failed to connect network %s with server %s' % (
                    netid, server_id))
                if ce.status in (400, 404):
                    self._server_exists(server_id=server_id)
                    self._network_exists(network_id=netid)
                raise
Stavros Sachtouris's avatar
Stavros Sachtouris committed
755 756
        else:
            raiseCLIError(
757
                '%s does not match any reserved IPs or IP ids' % ip_or_ip_id,
758
                details=errors.Cyclades.about_ips)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
759

760
    def main(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
761
        super(self.__class__, self)._run()
762
        self._run(ip_or_ip_id=ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
763 764 765


@command(ip_cmds)
766
class ip_detach(_NetworkInit, _PortWait, OptionalOutput):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
767 768 769 770 771 772
    """Detach an IP from a virtual server"""

    arguments = dict(
        wait=FlagArgument('Wait network to disconnect', ('-w', '--wait')),
    )

773 774
    @errors.Generic.all
    @errors.Cyclades.connection
775
    def _run(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
776
        for ip in self.client.list_floatingips():
777
            if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
778
                if not ip['port_id']:
779
                    raiseCLIError('IP %s is not attached' % ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
780 781 782 783 784 785
                self.error('Deleting port %s:' % ip['port_id'])
                self.client.delete_port(ip['port_id'])
                if self['wait']:
                    port_status = self.client.get_port_details(ip['port_id'])[
                        'status']
                    try:
786
                        self.wait(ip['port_id'], port_status)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
787 788 789 790 791
                    except ClientError as ce:
                        if ce.status not in (404, ):
                            raise
                        self.error('Port %s is deleted' % ip['port_id'])
                return
792
        raiseCLIError('IP or IP id %s not found' % ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
793

794
    def main(self, ip_or_ip_id):
Stavros Sachtouris's avatar
Stavros Sachtouris committed
795
        super(self.__class__, self)._run()
796
        self._run(ip_or_ip_id)
Stavros Sachtouris's avatar
Stavros Sachtouris committed
797 798


799
@command(network_cmds)
800 801
class network_connect(_port_create):
    """Connect a network with a device (server or router)"""
802

803
    arguments = dict(
804
        name=ValueArgument('A human readable name for the port', '--name'),
805 806 807 808 809 810 811 812
        security_group_id=RepeatableArgument(
            'Add a security group id (can be repeated)',
            ('-g', '--security-group')),
        subnet_id=ValueArgument(
            'Subnet id for fixed ips (used with --ip-address)',
            '--subnet-id'),
        ip_address=ValueArgument(
            'IP address for subnet id (used with --subnet-id', '--ip-address'),
813
        wait=FlagArgument('Wait network to connect', ('-w', '--wait')),
814 815 816
        device_id=RepeatableArgument(
            'Connect this device to the network (can be repeated)',
            '--device-id')
817
    )
818
    required = ('device_id', )
819

820 821 822
    @errors.Generic.all
    @errors.Cyclades.connection
    @errors.Cyclades.network_id
823
    def _run(self, network_id, server_id):
824 825
        self.error('Creating a port to connect network %s with device %s' % (
            network_id, server_id))
826 827 828 829 830 831
        try:
            self.connect(network_id, server_id)
        except ClientError as ce:
            if ce.status in (400, 404):
                self._server_exists(server_id=server_id)
            raise
832

833
    def main(self, network_id):
834
        super(self.__class__, self)._run()
835 836