aboutsummaryrefslogtreecommitdiffstats
path: root/vicn/resource/ns3/emulated_channel.py
blob: 5f61960c08ff0c9cf0fc924dd30c670f854a91ab (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
#!/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 logging
import random

from netmodel.model.type                import Integer
from netmodel.util.socket		import check_port
from vicn.core.address_mgr              import AddressManager
from vicn.core.attribute      	        import Attribute, Multiplicity
from vicn.core.exception                import ResourceNotFound
from vicn.core.requirement              import Requirement
from vicn.core.resource                 import BaseResource
from vicn.core.resource_mgr             import wait_resources
from vicn.core.task                     import inline_task, async_task, task
from vicn.core.task                     import BashTask, run_task
from vicn.resource.channel              import Channel
from vicn.resource.linux.application    import LinuxApplication as Application
from vicn.resource.linux.net_device     import NetDevice
from vicn.resource.linux.tap_device     import TapDevice
from vicn.resource.linux.veth_pair      import VethPair
from vicn.resource.lxd.lxc_container    import LxcContainer
from vicn.resource.node                 import Node

log = logging.getLogger(__name__)

class EmulatedChannel(Channel, Application):
    """EmulatedChannel resource

    This resources serves as a base class for wireless channels emulated by
    means of ns3 simulation.

    Attributes:
        ap (Reference[node]): Reference to the AP node
        stations (Reference[node]): Reference to the list of stations.
        control_port (int): Port used to communicate with the management
            interface of the simulation.

    Implementation notes:
      - Both AP and stations are allocated a separate VLAN to isolate broadcast
        traffic and prevent loops on the bridge.
      - We also need that all interfaces related to ap and stations are created
        before we run the commandline (currently, dynamically adding/removing
        AP and stations is not supported by the emulator). This is made 
        possible thanks to the key=True parameter, which makes sure the
        attributes are processed before the __create__ is called.

    Todo:
      - Retrieve the process PID to kill it during __delete__
    """

    __resource_type__ = BaseResource

    ap = Attribute(Node, description = 'AP', key = True)
    stations = Attribute(Node, description = 'List of stations',
            multiplicity = Multiplicity.OneToMany, key = True)
    control_port = Attribute(Integer,
            description = 'Control port for the simulation')

    # Overloaded attributes
    node = Attribute(requirements = [
            Requirement('bridge')
        ])

    #--------------------------------------------------------------------------
    # Constructor
    #--------------------------------------------------------------------------

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # AP (resp. stations) interfaces (for external connectivity) and
        # tap_devices (for connection to emulator)
        self._ap_if = None
        self._ap_tap = None
        self._sta_ifs = dict()
        self._sta_taps = dict()

        # Device names to be attached to the bridge (differs according to the
        # node type, Physical or LxcContainer, and eventually None for an
        # unmanaged stations)
        self._ap_bridged = None
        self._sta_bridged = dict()

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

    @inline_task
    def __get__(self):
        raise ResourceNotFound

    def __create__(self):
        # NOTE: http://stackoverflow.com/questions/21141352/python-subprocess-
        #       calling-a-script-which-runs-a-background-process-hanging
        # The output of the background scripts is still going to the same file
        # descriptor as the child script, thus the parent script waits for it
        # to finish.
        cmd = '(' + self.__app_name__ + ' ' + self._get_cmdline_params() + \
            '>/dev/null 2>&1) &'
        return BashTask(self.node, cmd)

    def __delete__(self):
        raise NotImplementedError

    #--------------------------------------------------------------------------
    # Attribute handlers
    #--------------------------------------------------------------------------

    @async_task
    async def _set_ap(self, ap=None):
        if ap is None:
            ap = self.ap
        if ap is None:
            log.info('Ignored setting ap to None...')
            return

        # Add a WiFi interface for the AP...
        interfaces = list()
        if isinstance(ap, LxcContainer):
            # Ideally, We need to create a VethPair for each station
            # This should be monitored for the total channel bw
            host = NetDevice(node = ap.node,
                device_name='vhh-' + ap.name + '-' + self.name,
                monitored = False,
                managed = False)
            self._ap_if = VethPair(node = self.ap,
                    name          = 'vh-' + ap.name + '-' + self.name,
                    device_name   = 'vh-' + ap.name + '-' + self.name,
                    host          = host,
                    owner = self)
            self._ap_bridged = self._ap_if.host
        else:
            raise NotImplementedError
        self._state.manager.commit_resource(self._ap_if)

        interfaces.append(self._ap_if)

        # Add a tap interface for the AP...
        self._ap_tap = TapDevice(node = self.node,
                owner = self,
                device_name = 'tap-' + ap.name + '-' + self.name,
                up = True,
                promiscuous = True,
                monitored = False)
        self._state.manager.commit_resource(self._ap_tap)
        interfaces.append(self._ap_tap)

        # Wait for interfaces to be setup
        await wait_resources(interfaces)

        # NOTE: only set channel after the resource is created or it might
        # create loops which, at this time, are not handled
        self._ap_if.set('channel', self)

        # Add interfaces to bridge
        vlan = AddressManager().get('vlan', self, tag='ap')

        # AS the container has created the VethPair already without Vlan, we
        # need to delete and recreate it
        task = self.node.bridge._remove_interface(self._ap_bridged)
        await run_task(task, self._state.manager)
        task = self.node.bridge._add_interface(self._ap_bridged, vlan = vlan)
        await run_task(task, self._state.manager)

        task = self.node.bridge._add_interface(self._ap_tap, vlan = vlan)
        await run_task(task, self._state.manager)

    @inline_task
    def _get_ap(self):
        return {'ap': None}

    @inline_task
    def _get_stations(self):
        return {'stations': list()}

    @async_task
    async def _set_stations(self, stations=None):
        print('adding stations...')
        if stations is None:
            stations = self.stations

        for station in stations:
            await self._add_station(station)

    def _add_stations(self, stations):
        raise NotImplementedError

    @inline_task
    def _remove_stations(self, station):
        raise NotImplementedError