aboutsummaryrefslogtreecommitdiffstats
path: root/vicn/resource/central.py
blob: 73a43478f691ffe37a4a0c73376d401ff60abfcd (plain)
1
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Cisco and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import networkx as nx
import logging

from netmodel.model.type                import String
from netmodel.util.misc                 import pairwise
from vicn.core.address_mgr              import AddressManager
from vicn.core.attribute                import Attribute
from vicn.core.exception                import ResourceNotFound
from vicn.core.resource                 import Resource
from vicn.core.task                     import async_task, inline_task
from vicn.core.task                     import EmptyTask, BashTask
from vicn.resource.channel              import Channel
from vicn.resource.ip.route             import IPRoute
from vicn.resource.icn.forwarder        import Forwarder
from vicn.resource.icn.face             import L2Face, L4Face, FaceProtocol
from vicn.resource.icn.producer         import Producer
from vicn.resource.icn.route            import Route as ICNRoute
from vicn.resource.linux.file           import TextFile
from vicn.resource.lxd.lxc_container    import LxcContainer
from vicn.resource.node                 import Node

log = logging.getLogger(__name__)

TMP_DEFAULT_PORT = 6363

CMD_CONTAINER_SET_DNS = 'echo "nameserver {ip_dns}" | ' \
                        'resolvconf -a {interface_name}'

# For host
CMD_NAT = '\n'.join([
    'iptables -t nat -A POSTROUTING -o {bridge_name} -s {network} ' \
    '! -d {network} -j MASQUERADE',
    'echo 1 > /proc/sys/net/ipv4/ip_forward'
])

# For containers
CMD_IP_FORWARD = 'echo 1 > /proc/sys/net/ipv4/ip_forward'

HOST_FILE_PATH = "/etc/hosts.vicn"

#------------------------------------------------------------------------------
# Routing strategies
#------------------------------------------------------------------------------

def routing_strategy_spt(G, origins, weight_key = None):
    """Routing strategy : Shortest path tree

    This routing strategy uses the Dijkstra algorithm on an undirected graph
    to build the shortest path tree towards all origin prefixes.

    NOTE: weights are currently unsupported by this strategy.

    Args:
        G (nx.Graph): network graph
        origins (dict): dictionary mapping nodes to the set of prefixes they
                        are origins for
        weight_key (str): key corresponding to weight key in edge data. None
                          assumes all weights have unit cost

    Returns:
        generator : returning triplets (source, prefix, next hop)
    """
    assert weight_key is None

    origin_nodes = origins.keys()
    seen = set()
    for dst_node in origin_nodes:
        sssp = nx.shortest_path(G, target = dst_node)
        # Notes from the documentation:
        #  - If only the target is specified, return a dictionary keyed by
        # sources with a list of nodes in a shortest path from one of the
        # sources to the target.
        #  - All returned paths include both the source and target in the
        # path.
        for _, path in sssp.items():
            if len(path) == 1:
                # Local prefix
                continue
            for s, d in pairwise(path):
                for prefix in origins[dst_node]:
                    t = (s, prefix, d)
                    if t in seen:
                        continue
                    seen.add(t)
                    yield t

def routing_strategy_max_flow(G, origins, weight_key = 'capacity'):
    """Routing strategy : Maximum Flow

    TODO

    Args:
        G (nx.Graph): network graph
        origins (dict): dictionary mapping nodes to the set of prefixes they
                        are origins for
        weight_key (str): key corresponding to weight key in edge data. None
                          assumes all weights have unit cost

    Returns:
        generator : returning triplets (source, prefix, next hop)
    """
    assert weight_key is None

    origin_nodes = origins.keys()
    for dst_node in origin_nodes:
        for src_node in G.nodes:
            if src_node == dst_node:
                continue
            _, flow_dict = nx.maximum_flow(G, src_node, dst_node,
                    capacity=weight_key)

            # Notes from the documentation:
            # https://networkx.github.io/documentation/networkx-1.10/reference/
            #       generated/networkx.algorithms.flow.maximum_flow.html
            #  - flow_dict (dict) – A dictionary containing the value of the
            # flow that went through each edge.
            for s, d_map in flow_dict.items():
                for d, flow in d_map.items():
                    if flow == 0:
                        continue
                    for prefix in origins[dst_node]:
                        yield s, prefix, d

MAP_ROUTING_STRATEGY = {
    'spt'       : routing_strategy_spt,
    'max_flow'  : routing_strategy_max_flow,
}

#------------------------------------------------------------------------------
# L2 and L4/ICN graphs
#------------------------------------------------------------------------------

def _get_l2_graph(manager, with_managed = False):
    G = nx.Graph()
    for node in manager.by_type(Node):
        G.add_node(node._state.uuid)

    for channel in manager.by_type(Channel):
        if channel.has_type('emulatedchannel'):
            src = channel._ap_if
            for dst in channel._sta_ifs.values():
                if not with_managed and (not src.managed or not dst.managed):
                    continue
                if G.has_edge(src.node._state.uuid, dst.node._state.uuid):
                    continue

                map_node_interface = { src.node._state.uuid : src._state.uuid, 
                    dst.node._state.uuid: dst._state.uuid}
                G.add_edge(src.node._state.uuid, dst.node._state.uuid, 
                        map_node_interface = map_node_interface)
        else:
            # This is for a normal Channel
            for src_it in range(0,len(channel.interfaces)):
                src = channel.interfaces[src_it]

                # Iterate over the remaining interface to create all the 
                # possible combination
                for dst_it in range(src_it+1,len(channel.interfaces)):
                    dst = channel.interfaces[dst_it]
            
                    if not with_managed and (not src.managed or 
                            not dst.managed):
                        continue
                    if G.has_edge(src.node._state.uuid, dst.node._state.uuid):
                        continue
                    map_node_interface = { 
                        src.node._state.uuid : src._state.uuid, 
                        dst.node._state.uuid: dst._state.uuid}
                    G.add_edge(src.node._state.uuid, dst.node._state.uuid, 
                            map_node_interface = map_node_interface)
    return G

def _get_icn_graph(manager):
    G = nx.Graph()
    for forwarder in manager.by_type(Forwarder):
        node = forwarder.node
        G.add_node(node._state.uuid)
        for face in forwarder.faces:
            other_face = manager.by_uuid(face._internal_data['sibling_face'])
            other_node = other_face.node
            if G.has_edge(node._state.uuid, other_node._state.uuid):
                continue
            map_node_face = { node._state.uuid: face._state.uuid, 
                other_node._state.uuid: other_face._state.uuid }
            G.add_edge(node._state.uuid, other_node._state.uuid, 
                    map_node_face = map_node_face)

    return G

#-------------------------------------------------------------------------------

class IPAssignment(Resource):
    """
    Resource: IPAssignment
    """

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    def __after__(self):
        return ('Interface',)

    @inline_task
    def __get__(self):
        raise ResourceNotFound

    def __subresources__(self):
        self.host_file = TextFile(node = None, filename = HOST_FILE_PATH,
                overwrite = True)
        return self.host_file

    def __create__(self):
        """
        IP assignment strategy /32: assign /32 IP address to each interface

        We might use different subnets for resources involved in experiment,
        and supporting resources, and to minimize routing tables.
        """
        log.info('Assigment of IP addresses')
        tasks = EmptyTask()

        # We sort nodes by names for IP assignment. This code ensures that
        # interfaces on the same channel get consecutive IP addresses. That
        # way, we can assign /31 on p2p channels.
        channels = sorted(self._state.manager.by_type(Channel), 
                key = lambda x : x.get_sortable_name())
        channels.extend(sorted(self._state.manager.by_type(Node), 
                    key = lambda node : node.name))

        host_file_content = ""
       
        # Dummy code to start IP addressing on an even number for /31
        ip = AddressManager().get_ip(None)
        if int(ip[-1]) % 2 == 0:
            ip = AddressManager().get_ip("dummy object")

        for channel in channels:
            # Sort interfaces in a deterministic order to ensure consistent
            # addressing across restarts of the tool
            interfaces = sorted(channel.interfaces, 
                    key = lambda x : x.device_name)

            for interface in interfaces:
                if interface.ip_address is None:
                    ip = AddressManager().get_ip(interface)

                    @async_task
                    async def set_ip(interface, ip):
                        await interface.async_set('ip_address', ip)

                    if interface.managed:
                        tasks = tasks | set_ip(interface, ip)
                else:
                    ip = interface.ip_address

                # Note: interface.ip_address should still be None at this stage
                # since we have not made the assignment yet

                if isinstance(channel, Node):
                    host_file_content += '# {} {} {}\n'.format(
                            interface.node.name, interface.device_name, ip)
                    if interface == interface.node.host_interface:
                        host_file_content += '{} {}\n'.format(ip, 
                                interface.node.name)
        self.host_file.content = host_file_content

        return tasks

    __delete__ = None

#-------------------------------------------------------------------------------

class IPRoutes(Resource):
    """
    Resource: IPRoutes

    Centralized IP route computation.
    """
    routing_strategy = Attribute(String)

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    @inline_task
    def __get__(self):
        raise ResourceNotFound

    @inline_task
    def __create__(self):
        routes = list()
        pre_routes, routes = self._get_ip_routes()
        routes.extend(pre_routes)
        routes.extend(routes)
        for route in routes:
            route.node.routing_table.routes << route

    def __delete__(self):
        raise NotImplementedError

    #--------------------------------------------------------------------------
    # Internal methods
    #--------------------------------------------------------------------------

    def _get_ip_origins(self):
        origins = dict()
        for node in self._state.manager.by_type(Node):
            node_uuid = node._state.uuid
            if not node_uuid in origins:
                origins[node_uuid] = list()
            for interface in node.interfaces:
                origins[node_uuid].append(interface.ip_address)
        return origins

    def _get_ip_routes(self):
        if self.routing_strategy == 'pair':
            return [], self._get_pair_ip_routes()

        strategy = MAP_ROUTING_STRATEGY.get(self.routing_strategy)

        G = _get_l2_graph(self._state.manager)
        origins = self._get_ip_origins()
        
        # node -> list(origins for which we have routes)
        ip_routes = dict()

        pre_routes = list()
        routes = list()
        for src, prefix, dst in strategy(G, origins):
            data = G.get_edge_data(src, dst)
	    
            map_ = data['map_node_interface']
            next_hop_interface = map_[src]


            next_hop_ingress    = self._state.manager.by_uuid(map_[dst])
            src_node = self._state.manager.by_uuid(src)

            mac_addr = None
            if ((hasattr(next_hop_ingress, 'vpp') and 
                        next_hop_ingress.vpp is not None) or
                    (hasattr(src_node, 'vpp') and src_node.vpp is not None)):
                mac_addr = next_hop_ingress.mac_address

            # Avoid duplicate routes due to multiple paths in the network
            if not src_node in ip_routes:
                ip_routes[src_node] = list()
            if prefix in ip_routes[src_node]:
                continue
            
            if prefix == next_hop_ingress.ip_address:
                # Direct route on src_node.name :
                # route add [prefix] dev [next_hop_interface_.device_name]
                route = IPRoute(node     = src_node, 
                                managed    = False,
                                owner      = self,
                                ip_address = prefix,
                                mac_address = mac_addr, 
                                interface  = next_hop_interface)
            else:
                # We need to be sure we have a route to the gw from the node
                if not next_hop_ingress.ip_address in ip_routes[src_node]:
                    pre_route = IPRoute(node = src_node,
                                    managed    = False,
                                    owner      = self,
                                    ip_address = next_hop_ingress.ip_address,
                                    mac_address = mac_addr,
                                    interface  = next_hop_interface)
                    ip_routes[src_node].append(next_hop_ingress.ip_address)
                    pre_routes.append(pre_route)
                    
                # Route on src_node.name: 
                # route add [prefix] dev [next_hop_interface_.device_name]
                #    via [next_hop_ingress.ip_address]
                route = IPRoute(node     = src_node, 
                                managed    = False,
                                owner      = self,
                                ip_address = prefix,
                                interface  = next_hop_interface,
                                mac_address = mac_addr,
                                gateway    = next_hop_ingress.ip_address)
            ip_routes[src_node].append(prefix)
            routes.append(route)
        return pre_routes, routes

    def _get_pair_ip_routes(self):
        """
        IP routing strategy : direct routes only
        """
        routes = list()
        G = _get_l2_graph(self._state.manager) 
        for src_node_uuid, dst_node_uuid, data in G.edges_iter(data = True):
            src_node = self._state.manager.by_uuid(src_node_uuid)
            dst_node = self._state.manager.by_uuid(dst_node_uuid)

            map_ = data['map_node_interface']
            src = self._state.manager.by_uuid(map_[src_node_uuid])
            dst = self._state.manager.by_uuid(map_[dst_node_uuid])

            log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
                        src_node.name, src.device_name, src.ip_address,
                        dst_node.name, dst.device_name, dst.ip_address))
            log.debug('[IP ROUTE] NODES {}/{}/{} -> {}/{}/{}'.format(
                        dst_node.name, dst.device_name, dst.ip_address,
                        src_node.name, src.device_name, src.ip_address))

            route = IPRoute(node        = src_node, 
                            managed     = False,
                            owner       = self,
                            ip_address  = dst.ip_address,
                            mac_address = dst.mac_address,
                            interface   = src)
            routes.append(route)

            route = IPRoute(node       = dst_node, 
                            managed    = False,
                            owner      = self,
                            ip_address = src.ip_address,
                            mac_address = src.mac_address,
                            interface  = dst)
            routes.append(route)

        return routes

#-------------------------------------------------------------------------------

class ICNFaces(Resource):
    """
    Resource: ICNFaces

    Centralized ICN face creation.
    """
    protocol_name = Attribute(String)

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    @inline_task
    def __get__(self):
        raise ResourceNotFound # always create faces

    @inline_task
    def __create__(self):
        icn_faces = self._get_faces()
        for face in icn_faces:
            face.node.forwarder.faces << face

    def __delete__(self):
        raise NotImplementedError

    #--------------------------------------------------------------------------
    # Internal methods
    #--------------------------------------------------------------------------

    def _get_faces(self):
        """
        Face creation (heuristic: facemgr)

        Requires: at least direct IP links
        """
        protocol = FaceProtocol.from_string(self.protocol_name)

        faces = list()
        G = _get_l2_graph(self._state.manager)
        for src_node_uuid, dst_node_uuid, data in G.edges_iter(data = True):
            src_node = self._state.manager.by_uuid(src_node_uuid)
            dst_node = self._state.manager.by_uuid(dst_node_uuid)

            map_ = data['map_node_interface']
            src = self._state.manager.by_uuid(map_[src_node_uuid])
            dst = self._state.manager.by_uuid(map_[dst_node_uuid])

            log.debug('{} -> {} ({} -> {})'.format(src_node_uuid,
                        dst_node_uuid, src.device_name, dst.device_name))

            if protocol == FaceProtocol.ether:
                src_face = L2Face(node        = src_node,
                                  owner      = self,
                                  protocol    = protocol,
                                  src_nic     = src,
                                  dst_mac     = dst.mac_address) 
                dst_face = L2Face(node        = dst_node,
                                  owner      = self,
                                  protocol    = protocol,
                                  src_nic     = dst,
                                  dst_mac     = src.mac_address)

            elif protocol in (FaceProtocol.tcp4, FaceProtocol.tcp6, 
                    FaceProtocol.udp4, FaceProtocol.udp6):
                src_face = L4Face(node        = src_node,
                                  owner      = self,
                                  protocol    = protocol,
                                  src_ip      = src.ip_address,
                                  dst_ip      = dst.ip_address,
                                  src_port    = TMP_DEFAULT_PORT,
                                  dst_port    = TMP_DEFAULT_PORT) 
                dst_face = L4Face(node        = dst_node,
                                  owner      = self,
                                  protocol    = protocol,
                                  src_ip      = dst.ip_address,
                                  dst_ip      = src.ip_address,
                                  src_port    = TMP_DEFAULT_PORT,
                                  dst_port    = TMP_DEFAULT_PORT) 
            else:
                raise NotImplementedError

            # We key the sibling face for easier building of the ICN graph
            src_face._internal_data['sibling_face'] = dst_face._state.uuid
            dst_face._internal_data['sibling_face'] = src_face._state.uuid

            faces.append(src_face)
            faces.append(dst_face)

        return faces

#------------------------------------------------------------------------------

class ICNRoutes(Resource):
    """
    Resource: ICNRoutes

    Centralized ICN route computation.
    """

    routing_strategy = Attribute(String)

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    @inline_task
    def __get__(self):
        raise ResourceNotFound # always create routes

    @inline_task
    def __create__(self):
        icn_routes = self._get_icn_routes()
        for route in icn_routes:
            route.node.forwarder.routes << route

    def __delete__(self):
        raise NotImplementedError

    #--------------------------------------------------------------------------
    # Internal methods
    #--------------------------------------------------------------------------

    def _get_prefix_origins(self):
        origins = dict()
        for producer in self._state.manager.by_type(Producer):
            node_uuid = producer.node._state.uuid
            if not node_uuid in origins:
                origins[node_uuid] = list()
            origins[node_uuid].extend(producer.prefixes)
        return origins

    def _get_icn_routes(self):
        strategy = MAP_ROUTING_STRATEGY.get(self.routing_strategy)

        G = _get_icn_graph(self._state.manager)
        origins = self._get_prefix_origins()

        routes = list()
        for src, prefix, dst in strategy(G, origins):
            data = G.get_edge_data(src, dst)
	    
            map_ = data['map_node_face']
            next_hop_face = map_[src]

            route = ICNRoute(node   = src,
                             owner  = self,
                             prefix = prefix,
                             face   = next_hop_face)
            routes.append(route)
        return routes

#------------------------------------------------------------------------------

class DnsServerEntry(Resource):
    """
    Resource: DnsServerEntry

    Setup of DNS resolver for LxcContainers

    Todo:
      - This should be merged into the LxcContainer resource
    """

    node = Attribute(String)
    ip_address = Attribute(String)
    interface_name = Attribute(String)

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    @inline_task
    def __get__(self):
        raise ResourceNotFound

    def __create__(self):
        return BashTask(self.node, CMD_CONTAINER_SET_DNS,
                {'ip_dns': self.ip_address,
                 'interface_name': self.interface_name})

    def __delete__(self):
        raise NotImplementedError

#------------------------------------------------------------------------------

class ContainerSetup(Resource):
    """
    Resource: ContainerSetup

    Setup of container networking

    Todo:
      - This should be merged into the LxcContainer resource
    """

    container = Attribute(LxcContainer)

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    def __subresources__(self):

        # a) routes: host -> container
        #   . container interfaces
        #   . container host (main) interface
        # route add -host {ip_address} dev {bridge_name}
        route = IPRoute(node       = self.container.node,
                        managed    = False,
                        owner      = self,
                        ip_address = self.container.host_interface.ip_address, 
                        interface  = self.container.node.bridge)
        route.node.routing_table.routes << route

        # b) route: container -> host
        # route add {ip_gateway} dev {interface_name}
        # route add default gw {ip_gateway} dev {interface_name}
        route = IPRoute(node       = self.container,
                        owner      = self,
                        managed    = False,
                        ip_address = self.container.node.bridge.ip_address,
                        interface  = self.container.host_interface)
        route.node.routing_table.routes << route
        route_gw = IPRoute(node       = self.container,
                           managed    = False,
                           owner      = self,
                           ip_address = 'default',
                           interface  = self.container.host_interface,
                           gateway    = self.container.node.bridge.ip_address)
        route_gw.node.routing_table.routes << route_gw

        # c) dns
        dns_server_entry = DnsServerEntry(node = self.container, 
                owner      = self,
                ip_address = self.container.node.bridge.ip_address, 
                interface_name = self.container.host_interface.device_name)

        return dns_server_entry

    @inline_task
    def __get__(self):
        raise ResourceNotFound

    def __create__(self):
        return BashTask(self.container.node, CMD_IP_FORWARD)

#------------------------------------------------------------------------------

class ContainersSetup(Resource):
    """
    Resource: ContainersSetup

    Setup of LxcContainers (main resource)

    Todo:
      - This should be merged into the LxcContainer resource
    """

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    def __subresources__(self):
        containers  = self._state.manager.by_type(LxcContainer)
        if len(containers) == 0:
            return None

        container_resources = [ContainerSetup(owner = self, container = c) 
            for c in containers]

        return Resource.__concurrent__(*container_resources)

#------------------------------------------------------------------------------

class CentralIP(Resource):
    """
    Resource: CentralIP

    Central IP management (main resource)
    """

    ip_routing_strategy = Attribute(String, description = 'IP routing strategy',
            default = 'pair') # spt, pair

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    def __after_init__(self):
        return ('Node', 'Channel', 'Interface')

    def __subresources__(self):
        ip_assign = IPAssignment(owner=self)
        containers_setup = ContainersSetup(owner=self)
        ip_routes = IPRoutes(owner = self, 
                routing_strategy = self.ip_routing_strategy)

        return ip_assign > (ip_routes | containers_setup)

    @inline_task
    def __get__(self):
        raise ResourceNotFound

    __delete__ = None

#------------------------------------------------------------------------------

class CentralICN(Resource):
    """
    Resource: CentralICN

    Central ICN management (main resource)
    """

    # Choices: spt, max_flow
    icn_routing_strategy = Attribute(String, 
            description = 'ICN routing strategy',
            default = 'spt') 
    face_protocol = Attribute(String, 
            description = 'Protocol used to create faces',
            default = 'ether')

    #--------------------------------------------------------------------------
    # Resource lifecycle
    #--------------------------------------------------------------------------

    def __after__(self):
        """
        We need to wait for IP configuration in order to be able to build
        overload ICN faces, and producers for prefix origins.
        """
        return ('CentralIP',)

    def __subresources__(self):
        icn_faces = ICNFaces(owner = self, protocol_name = self.face_protocol) 
        icn_routes = ICNRoutes(owner = self, 
                routing_strategy = self.icn_routing_strategy)
        return icn_faces > icn_routes

    @inline_task
    def __get__(self):
        raise ResourceNotFound

    __delete__ = None