1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # Neither the name of the SONATA-NFV, Paderborn University
17 # nor the names of its contributors may be used to endorse or promote
18 # products derived from this software without specific prior written
21 # This work has been performed in the framework of the SONATA project,
22 # funded by the European Commission under Grant number 671517 through
23 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
24 # acknowledge the contributions of their colleagues of the SONATA
25 # partner consortium (www.sonata-nfv.eu).
26 from mininet
.node
import Docker
27 from mininet
.link
import Link
28 from emuvim
.dcemulator
.resourcemodel
import NotEnoughResourcesAvailable
32 LOG
= logging
.getLogger("dcemulator.node")
33 LOG
.setLevel(logging
.DEBUG
)
36 DCDPID_BASE
= 1000 # start of switch dpid's used for data center switches
37 EXTSAPDPID_BASE
= 2000 # start of switch dpid's used for external SAP switches
40 class EmulatorCompute(Docker
):
42 Emulator specific compute node class.
43 Inherits from Containernet's Docker host class.
44 Represents a single container connected to a (logical)
46 We can add emulator specific helper functions to it.
50 self
, name
, dimage
, **kwargs
):
51 self
.datacenter
= kwargs
.get("datacenter") # pointer to current DC
52 self
.flavor_name
= kwargs
.get("flavor_name")
53 self
._network
_state
_cache
= None
54 LOG
.debug("Starting compute instance %r in data center %r" %
55 (name
, str(self
.datacenter
)))
56 # call original Docker.__init__
57 Docker
.__init
__(self
, name
, dimage
, **kwargs
)
59 def getNetworkStatus(self
):
61 Helper method to receive information about the virtual networks
62 this compute instance is connected to.
64 # get all links and find dc switch interface
65 networkStatusList
= []
66 for i
in self
.intfList():
68 vnf_interface
= str(i
)
69 dc_port_name
= self
.datacenter
.net
.find_connected_dc_interface(
70 vnf_name
, vnf_interface
)
71 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
72 intf_dict
= {'intf_name': str(i
), 'ip': "{0}/{1}".format(i
.IP(), i
.prefixLen
), 'netmask': i
.prefixLen
,
73 'mac': i
.MAC(), 'up': i
.isUp(), 'status': i
.status(), 'dc_portname': dc_port_name
}
74 networkStatusList
.append(intf_dict
)
75 return networkStatusList
79 Helper method to receive information about this compute instance.
82 cinspect
= self
.dcli
.inspect_container(self
.dc
)
83 # inspect networking (slow, so do only once)
84 if self
._network
_state
_cache
is None:
85 self
._network
_state
_cache
= self
.getNetworkStatus()
88 status
["name"] = self
.name
89 status
["network"] = self
._network
_state
_cache
90 status
["docker_network"] = self
.dcinfo
['NetworkSettings']['IPAddress']
91 status
["image"] = self
.dimage
92 status
["flavor_name"] = self
.flavor_name
93 status
["cpu_quota"] = self
.resources
.get('cpu_quota')
94 status
["cpu_period"] = self
.resources
.get('cpu_period')
95 status
["cpu_shares"] = self
.resources
.get('cpu_shares')
96 status
["cpuset"] = self
.resources
.get('cpuset_cpus')
97 status
["mem_limit"] = self
.resources
.get('mem_limit')
98 status
["memswap_limit"] = self
.resources
.get('memswap_limit')
99 status
["state"] = cinspect
["State"]
100 status
["id"] = cinspect
["Id"]
101 status
["short_id"] = cinspect
["Id"][:12]
102 status
["hostname"] = cinspect
["Config"]['Hostname']
103 status
["datacenter"] = (None if self
.datacenter
is None
104 else self
.datacenter
.label
)
109 class EmulatorExtSAP(object):
111 Emulator specific class that defines an external service access point (SAP) for the service.
112 Inherits from Containernet's OVSBridge class.
113 Represents a single OVS switch connected to a (logical)
115 We can add emulator specific helper functions to it.
118 def __init__(self
, sap_name
, sap_net
, datacenter
, **kwargs
):
120 self
.datacenter
= datacenter
# pointer to current DC
121 self
.net
= self
.datacenter
.net
124 LOG
.debug("Starting ext SAP instance %r in data center %r" %
125 (sap_name
, str(self
.datacenter
)))
127 # create SAP as separate OVS switch with an assigned ip address
128 self
.ip
= str(sap_net
[1]) + '/' + str(sap_net
.prefixlen
)
129 self
.subnet
= sap_net
130 # allow connection to the external internet through the host
131 params
= dict(NAT
=True)
132 self
.switch
= self
.net
.addExtSAP(sap_name
, self
.ip
, dpid
=hex(
133 self
._get
_next
_extSAP
_dpid
())[2:], **params
)
136 def _get_next_extSAP_dpid(self
):
137 global EXTSAPDPID_BASE
139 return EXTSAPDPID_BASE
141 def getNetworkStatus(self
):
143 Helper method to receive information about the virtual networks
144 this compute instance is connected to.
146 # get all links and find dc switch interface
147 networkStatusList
= []
148 for i
in self
.switch
.intfList():
150 vnf_interface
= str(i
)
151 if vnf_interface
== 'lo':
153 dc_port_name
= self
.datacenter
.net
.find_connected_dc_interface(
154 vnf_name
, vnf_interface
)
155 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
156 intf_dict
= {'intf_name': str(i
), 'ip': self
.ip
, 'netmask': i
.prefixLen
, 'mac': i
.MAC(
157 ), 'up': i
.isUp(), 'status': i
.status(), 'dc_portname': dc_port_name
}
158 networkStatusList
.append(intf_dict
)
160 return networkStatusList
164 "name": self
.switch
.name
,
165 "datacenter": self
.datacenter
.name
,
166 "network": self
.getNetworkStatus()
170 class Datacenter(object):
172 Represents a logical data center to which compute resources
173 (Docker containers) can be added at runtime.
175 Will also implement resource bookkeeping in later versions.
180 def __init__(self
, label
, metadata
={}, resource_log_path
=None):
181 self
.net
= None # DCNetwork to which we belong
182 # each node (DC) has a short internal name used by Mininet
183 # this is caused by Mininets naming limitations for swtiches etc.
184 self
.name
= "dc%d" % Datacenter
.DC_COUNTER
185 Datacenter
.DC_COUNTER
+= 1
186 # use this for user defined names that can be longer than self.name
188 # dict to store arbitrary metadata (e.g. latitude and longitude)
189 self
.metadata
= metadata
190 # path to which resource information should be logged (e.g. for
191 # experiments). None = no logging
192 self
.resource_log_path
= resource_log_path
193 # first prototype assumes one "bigswitch" per DC
195 # keep track of running containers
197 # keep track of attached external access points
199 # pointer to assigned resource model
200 self
._resource
_model
= None
205 def _get_next_dc_dpid(self
):
212 Each data center is represented by a single switch to which
213 compute resources can be connected at run time.
215 TODO: This will be changed in the future to support multiple networks
218 self
.switch
= self
.net
.addSwitch(
219 "%s.s1" % self
.name
, dpid
=hex(self
._get
_next
_dc
_dpid
())[2:])
220 LOG
.debug("created data center switch: %s" % str(self
.switch
))
225 def startCompute(self
, name
, image
=None, command
=None, network
=None,
226 flavor_name
="tiny", properties
=dict(), **params
):
228 Create a new container as compute resource and connect it to this
230 :param name: name (string)
231 :param image: image name (string)
232 :param command: command (string)
233 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
234 :param flavor_name: name of the flavor for this compute container
235 :param properties: dictionary of properties (key-value) that will be passed as environment variables
238 assert name
is not None
239 default_net
= {"id": "emu0"}
241 if name
in [c
.name
for c
in self
.net
.getAllContainers()]:
242 raise Exception("Container with name %s already exists." % name
)
243 # set default parameter
245 image
= "ubuntu:trusty"
248 if isinstance(network
, dict):
250 # create at least one default interface
251 network
= default_net
252 # if we have only one network, put it in a list
254 if isinstance(network
, list):
256 # create at least one default interface
257 network
.append(default_net
)
259 # apply hard-set resource limits=0
260 cpu_percentage
= params
.get('cpu_percent')
262 params
['cpu_period'] = self
.net
.cpu_period
263 params
['cpu_quota'] = self
.net
.cpu_period
* float(cpu_percentage
)
266 properties
['VNF_NAME'] = name
267 # create the container
268 d
= self
.net
.addDocker(
273 flavor_name
=flavor_name
,
278 # apply resource limits to container if a resource model is defined
279 if self
._resource
_model
is not None:
281 self
._resource
_model
.allocate(d
)
282 self
._resource
_model
.write_allocation_log(
283 d
, self
.resource_log_path
)
284 except NotEnoughResourcesAvailable
as ex
:
286 "Allocation of container %r was blocked by resource model." % name
)
288 # ensure that we remove the container
289 self
.net
.removeDocker(name
)
292 # connect all given networks
293 # if no --net option is given, network = [{}], so 1 empty dict in the list
294 # this results in 1 default interface with a default ip address
296 # clean up network configuration (e.g. RTNETLINK does not allow ':'
298 if nw
.get("id") is not None:
299 nw
["id"] = self
._clean
_ifname
(nw
["id"])
300 # TODO we cannot use TCLink here (see:
301 # https://github.com/mpeuster/containernet/issues/3)
302 self
.net
.addLink(d
, self
.switch
, params1
=nw
,
303 cls
=Link
, intfName1
=nw
.get('id'))
305 self
.containers
[name
] = d
306 return d
# we might use UUIDs for naming later on
308 def stopCompute(self
, name
):
310 Stop and remove a container from this data center.
312 assert name
is not None
313 if name
not in self
.containers
:
314 raise Exception("Container with name %s not found." % name
)
315 LOG
.debug("Stopping compute instance %r in data center %r" %
318 # stop the monitored metrics
319 if self
.net
.monitor_agent
is not None:
320 self
.net
.monitor_agent
.stop_metric(name
)
322 # call resource model and free resources
323 if self
._resource
_model
is not None:
324 self
._resource
_model
.free(self
.containers
[name
])
325 self
._resource
_model
.write_free_log(
326 self
.containers
[name
], self
.resource_log_path
)
330 link
=None, node1
=self
.containers
[name
], node2
=self
.switch
)
333 self
.net
.removeDocker("%s" % (name
))
334 del self
.containers
[name
]
338 def attachExternalSAP(self
, sap_name
, sap_net
, **params
):
339 extSAP
= EmulatorExtSAP(sap_name
, sap_net
, self
, **params
)
340 # link SAP to the DC switch
341 self
.net
.addLink(extSAP
.switch
, self
.switch
, cls
=Link
)
342 self
.extSAPs
[sap_name
] = extSAP
344 def removeExternalSAP(self
, sap_name
):
345 sap_switch
= self
.extSAPs
[sap_name
].switch
346 # sap_switch = self.net.getNodeByName(sap_name)
347 # remove link of SAP to the DC switch
348 self
.net
.removeLink(link
=None, node1
=sap_switch
, node2
=self
.switch
)
349 self
.net
.removeExtSAP(sap_name
)
350 del self
.extSAPs
[sap_name
]
352 def listCompute(self
):
354 Return a list of all running containers assigned to this
357 return list(self
.containers
.itervalues())
359 def listExtSAPs(self
):
361 Return a list of all external SAPs assigned to this
364 return list(self
.extSAPs
.itervalues())
368 Return a dict with status information about this DC.
370 container_list
= [name
for name
in self
.containers
]
371 ext_saplist
= [sap_name
for sap_name
in self
.extSAPs
]
374 "internalname": self
.name
,
375 "switch": self
.switch
.name
,
376 "n_running_containers": len(self
.containers
),
377 "metadata": self
.metadata
,
378 "vnf_list": container_list
,
379 "ext SAP list": ext_saplist
382 def assignResourceModel(self
, rm
):
384 Assign a resource model to this DC.
385 :param rm: a BaseResourceModel object
388 if self
._resource
_model
is not None:
390 "There is already an resource model assigned to this DC.")
391 self
._resource
_model
= rm
392 self
.net
.rm_registrar
.register(self
, rm
)
393 LOG
.info("Assigned RM: %r to DC: %r" % (rm
, self
))
396 def _clean_ifname(name
):
398 Cleans up given string to be a
399 RTNETLINK compatible interface name.
405 name
= name
.replace(":", "-")
406 name
= name
.replace(" ", "-")
407 name
= name
.replace(".", "-")
408 name
= name
.replace("_", "-")