blob: 8612096585ec61193890c9ae66c77785110d5fbc [file] [log] [blame]
peustermcbcd4c22015-12-28 11:33:42 +01001"""
peusterm79ef6ae2016-07-08 13:53:57 +02002Copyright (c) 2015 SONATA-NFV and Paderborn University
3ALL RIGHTS RESERVED.
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18nor the names of its contributors may be used to endorse or promote
19products derived from this software without specific prior written
20permission.
21
22This work has been performed in the framework of the SONATA project,
23funded by the European Commission under Grant number 671517 through
24the Horizon 2020 and 5G-PPP programmes. The authors would like to
25acknowledge the contributions of their colleagues of the SONATA
26partner consortium (www.sonata-nfv.eu).
peustermcbcd4c22015-12-28 11:33:42 +010027"""
stevenvanrossem86e64a02017-04-13 02:21:45 +020028from mininet.node import Docker, OVSBridge
peustermea8db832016-03-08 10:25:58 +010029from mininet.link import Link
peusterma41f7862016-04-27 11:01:24 +020030from emuvim.dcemulator.resourcemodel import NotEnoughResourcesAvailable
peustermcbcd4c22015-12-28 11:33:42 +010031import logging
stevenvanrossem86e64a02017-04-13 02:21:45 +020032
peustermcbcd4c22015-12-28 11:33:42 +010033
peustermf9a817d2016-07-18 09:06:04 +020034LOG = logging.getLogger("dcemulator.node")
peusterm3444ae42016-03-16 20:46:41 +010035LOG.setLevel(logging.DEBUG)
36
peustermcbcd4c22015-12-28 11:33:42 +010037
38DCDPID_BASE = 1000 # start of switch dpid's used for data center switches
stevenvanrossem86e64a02017-04-13 02:21:45 +020039EXTSAPDPID_BASE = 2000 # start of switch dpid's used for external SAP switches
peustermcbcd4c22015-12-28 11:33:42 +010040
peusterm7aae6852016-01-12 14:53:18 +010041class EmulatorCompute(Docker):
42 """
43 Emulator specific compute node class.
peusterm5877ea22016-05-11 13:44:59 +020044 Inherits from Containernet's Docker host class.
peusterm7aae6852016-01-12 14:53:18 +010045 Represents a single container connected to a (logical)
46 data center.
47 We can add emulator specific helper functions to it.
48 """
49
50 def __init__(
51 self, name, dimage, **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010052 self.datacenter = kwargs.get("datacenter") # pointer to current DC
53 self.flavor_name = kwargs.get("flavor_name")
peusterm3444ae42016-03-16 20:46:41 +010054 LOG.debug("Starting compute instance %r in data center %r" % (name, str(self.datacenter)))
peusterm7aae6852016-01-12 14:53:18 +010055 # call original Docker.__init__
56 Docker.__init__(self, name, dimage, **kwargs)
57
58 def getNetworkStatus(self):
59 """
60 Helper method to receive information about the virtual networks
61 this compute instance is connected to.
62 """
stevenvanrossem566779d2016-11-07 06:33:44 +010063 # get all links and find dc switch interface
64 networkStatusList = []
65 for i in self.intfList():
66 vnf_name = self.name
67 vnf_interface = str(i)
68 dc_port_name = self.datacenter.net.find_connected_dc_interface(vnf_name, vnf_interface)
69 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
stevenvanrossem17b6e882017-05-04 16:51:34 +020070 intf_dict = {'intf_name': str(i), 'ip': "{0}/{1}".format(i.IP(), i.prefixLen), 'netmask': i.prefixLen, 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name}
stevenvanrossem566779d2016-11-07 06:33:44 +010071 networkStatusList.append(intf_dict)
72
73 return networkStatusList
peusterm7aae6852016-01-12 14:53:18 +010074
75 def getStatus(self):
76 """
77 Helper method to receive information about this compute instance.
78 """
peusterm056fd452016-01-12 15:32:25 +010079 status = {}
80 status["name"] = self.name
81 status["network"] = self.getNetworkStatus()
stevenvanrosseme66ef122016-05-03 11:22:54 +020082 status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
peusterm056fd452016-01-12 15:32:25 +010083 status["image"] = self.dimage
peusterm36c070c2016-04-16 17:39:01 +020084 status["flavor_name"] = self.flavor_name
stevenvanrossem08272492017-02-10 17:18:44 +010085 status["cpu_quota"] = self.resources.get('cpu_quota')
86 status["cpu_period"] = self.resources.get('cpu_period')
87 status["cpu_shares"] = self.resources.get('cpu_shares')
88 status["cpuset"] = self.resources.get('cpuset_cpus')
89 status["mem_limit"] = self.resources.get('mem_limit')
90 status["memswap_limit"] = self.resources.get('memswap_limit')
peusterm056fd452016-01-12 15:32:25 +010091 status["state"] = self.dcli.inspect_container(self.dc)["State"]
92 status["id"] = self.dcli.inspect_container(self.dc)["Id"]
stevenvanrossem8a9df3f2017-01-27 22:35:04 +010093 status["short_id"] = self.dcli.inspect_container(self.dc)["Id"][:12]
stevenvanrossem18165082017-04-07 17:20:50 +020094 status["hostname"] = self.dcli.inspect_container(self.dc)["Config"]['Hostname']
peusterm2ec74e12016-01-13 11:17:53 +010095 status["datacenter"] = (None if self.datacenter is None
peusterma47db032016-02-04 14:55:29 +010096 else self.datacenter.label)
stevenvanrossem18165082017-04-07 17:20:50 +020097
peusterm056fd452016-01-12 15:32:25 +010098 return status
peusterm7aae6852016-01-12 14:53:18 +010099
100
stevenvanrossem17b6e882017-05-04 16:51:34 +0200101class EmulatorExtSAP(object):
102 """
103 Emulator specific class that defines an external service access point (SAP) for the service.
104 Inherits from Containernet's OVSBridge class.
105 Represents a single OVS switch connected to a (logical)
106 data center.
107 We can add emulator specific helper functions to it.
108 """
109
110 def __init__(self, sap_name, sap_net, datacenter, **kwargs):
111
112 self.datacenter = datacenter # pointer to current DC
113 self.net = self.datacenter.net
114 self.name = sap_name
115
116 LOG.debug("Starting ext SAP instance %r in data center %r" % (sap_name, str(self.datacenter)))
117
118 # create SAP as separate OVS switch with an assigned ip address
119 self.ip = str(sap_net[1]) + '/' + str(sap_net.prefixlen)
120 self.subnet = sap_net
121 # allow connection to the external internet through the host
122 params = dict(NAT=True)
123 self.switch = self.net.addExtSAP(sap_name, self.ip, dpid=hex(self._get_next_extSAP_dpid())[2:], **params)
124 self.switch.start()
125
126 def _get_next_extSAP_dpid(self):
127 global EXTSAPDPID_BASE
128 EXTSAPDPID_BASE += 1
129 return EXTSAPDPID_BASE
130
131 def getNetworkStatus(self):
132 """
133 Helper method to receive information about the virtual networks
134 this compute instance is connected to.
135 """
136 # get all links and find dc switch interface
137 networkStatusList = []
138 for i in self.switch.intfList():
139 vnf_name = self.name
140 vnf_interface = str(i)
141 if vnf_interface == 'lo':
142 continue
143 dc_port_name = self.datacenter.net.find_connected_dc_interface(vnf_name, vnf_interface)
144 # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
145 intf_dict = {'intf_name': str(i), 'ip': self.ip, 'netmask': i.prefixLen, 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name}
146 networkStatusList.append(intf_dict)
147
148 return networkStatusList
149
150 def getStatus(self):
151 return {
152 "name": self.switch.name,
153 "datacenter": self.datacenter.name,
154 "network": self.getNetworkStatus()
155 }
156
peustermcbcd4c22015-12-28 11:33:42 +0100157class Datacenter(object):
158 """
159 Represents a logical data center to which compute resources
160 (Docker containers) can be added at runtime.
peusterme4e89d32016-01-07 09:14:54 +0100161
162 Will also implement resource bookkeeping in later versions.
peustermcbcd4c22015-12-28 11:33:42 +0100163 """
164
peusterma47db032016-02-04 14:55:29 +0100165 DC_COUNTER = 1
166
peusterm60bf8b82016-04-06 14:12:35 +0200167 def __init__(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100168 self.net = None # DCNetwork to which we belong
peusterma47db032016-02-04 14:55:29 +0100169 # each node (DC) has a short internal name used by Mininet
170 # this is caused by Mininets naming limitations for swtiches etc.
171 self.name = "dc%d" % Datacenter.DC_COUNTER
172 Datacenter.DC_COUNTER += 1
peusterm53504942016-02-04 16:09:28 +0100173 # use this for user defined names that can be longer than self.name
edmaas7e084ea2016-11-28 13:50:23 +0100174 self.label = label
peusterm53504942016-02-04 16:09:28 +0100175 # dict to store arbitrary metadata (e.g. latitude and longitude)
176 self.metadata = metadata
peusterm60bf8b82016-04-06 14:12:35 +0200177 # path to which resource information should be logged (e.g. for experiments). None = no logging
178 self.resource_log_path = resource_log_path
peusterm42f08be2016-03-10 21:56:34 +0100179 # first prototype assumes one "bigswitch" per DC
180 self.switch = None
181 # keep track of running containers
182 self.containers = {}
stevenvanrossem17b6e882017-05-04 16:51:34 +0200183 # keep track of attached external access points
184 self.extSAPs = {}
peusterm42f08be2016-03-10 21:56:34 +0100185 # pointer to assigned resource model
186 self._resource_model = None
peustermcbcd4c22015-12-28 11:33:42 +0100187
peusterme26487b2016-03-08 14:00:21 +0100188 def __repr__(self):
189 return self.label
190
peustermcbcd4c22015-12-28 11:33:42 +0100191 def _get_next_dc_dpid(self):
192 global DCDPID_BASE
193 DCDPID_BASE += 1
194 return DCDPID_BASE
195
196 def create(self):
197 """
198 Each data center is represented by a single switch to which
199 compute resources can be connected at run time.
200
peusterm9c252b62016-01-06 16:59:53 +0100201 TODO: This will be changed in the future to support multiple networks
peustermcbcd4c22015-12-28 11:33:42 +0100202 per data center
203 """
peusterm293cbc32016-01-13 17:05:28 +0100204 self.switch = self.net.addSwitch(
peustermcbcd4c22015-12-28 11:33:42 +0100205 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
peusterm3444ae42016-03-16 20:46:41 +0100206 LOG.debug("created data center switch: %s" % str(self.switch))
peustermcbcd4c22015-12-28 11:33:42 +0100207
208 def start(self):
209 pass
210
splietker7b38ee12017-06-28 17:24:01 +0200211 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", properties=dict(), **params):
peusterme4e89d32016-01-07 09:14:54 +0100212 """
213 Create a new container as compute resource and connect it to this
214 data center.
peusterm7f8e8402016-02-28 18:38:10 +0100215 :param name: name (string)
216 :param image: image name (string)
217 :param command: command (string)
218 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
peusterm42f08be2016-03-10 21:56:34 +0100219 :param flavor_name: name of the flavor for this compute container
splietker7b38ee12017-06-28 17:24:01 +0200220 :param properties: dictionary of properties (key-value) that will be passed as environment variables
peusterm7f8e8402016-02-28 18:38:10 +0100221 :return:
peusterme4e89d32016-01-07 09:14:54 +0100222 """
peusterm4e98b632016-01-12 14:08:07 +0100223 assert name is not None
peusterm9165ef92016-01-13 13:50:39 +0100224 # no duplications
peustermbd44f4a2016-01-13 14:53:30 +0100225 if name in [c.name for c in self.net.getAllContainers()]:
peusterm9165ef92016-01-13 13:50:39 +0100226 raise Exception("Container with name %s already exists." % name)
peusterm4e98b632016-01-12 14:08:07 +0100227 # set default parameter
228 if image is None:
peusterm0dc3ae02016-04-27 09:33:28 +0200229 image = "ubuntu:trusty"
peusterm4e98b632016-01-12 14:08:07 +0100230 if network is None:
231 network = {} # {"ip": "10.0.0.254/8"}
peusterm7f8e8402016-02-28 18:38:10 +0100232 if isinstance(network, dict):
233 network = [network] # if we have only one network, put it in a list
234 if isinstance(network, list):
235 if len(network) < 1:
236 network.append({})
237
stevenvanrossemb3f34172016-11-16 23:30:57 +0100238 # apply hard-set resource limits=0
stevenvanrosseme8d86282017-01-28 00:52:22 +0100239 cpu_percentage = params.get('cpu_percent')
stevenvanrossemb3f34172016-11-16 23:30:57 +0100240 if cpu_percentage:
stevenvanrosseme8d86282017-01-28 00:52:22 +0100241 params['cpu_period'] = self.net.cpu_period
242 params['cpu_quota'] = self.net.cpu_period * float(cpu_percentage)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100243
splietker7b38ee12017-06-28 17:24:01 +0200244 env = properties
245 properties['VNF_NAME'] = name
peusterm7f8e8402016-02-28 18:38:10 +0100246 # create the container
peusterm42f08be2016-03-10 21:56:34 +0100247 d = self.net.addDocker(
248 "%s" % (name),
249 dimage=image,
250 dcmd=command,
251 datacenter=self,
stevenvanrossemb3f34172016-11-16 23:30:57 +0100252 flavor_name=flavor_name,
splietker7b38ee12017-06-28 17:24:01 +0200253 environment = env,
edmaas7e084ea2016-11-28 13:50:23 +0100254 **params
peusterm71b3a2f2016-03-19 12:56:11 +0100255 )
peustermd18559d2016-04-16 04:59:23 +0200256
stevenvanrossemb3f34172016-11-16 23:30:57 +0100257
258
peustermd18559d2016-04-16 04:59:23 +0200259 # apply resource limits to container if a resource model is defined
260 if self._resource_model is not None:
peusterma41f7862016-04-27 11:01:24 +0200261 try:
262 self._resource_model.allocate(d)
263 self._resource_model.write_allocation_log(d, self.resource_log_path)
264 except NotEnoughResourcesAvailable as ex:
265 LOG.warning("Allocation of container %r was blocked by resource model." % name)
266 LOG.info(ex.message)
267 # ensure that we remove the container
268 self.net.removeDocker(name)
269 return None
peustermd18559d2016-04-16 04:59:23 +0200270
peusterm7f8e8402016-02-28 18:38:10 +0100271 # connect all given networks
stevenvanrossem14c89052016-04-10 23:49:59 +0200272 # if no --net option is given, network = [{}], so 1 empty dict in the list
273 # this results in 1 default interface with a default ip address
peusterm7f8e8402016-02-28 18:38:10 +0100274 for nw in network:
peusterm761c14d2016-07-19 09:31:19 +0200275 # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
276 if nw.get("id") is not None:
277 nw["id"] = self._clean_ifname(nw["id"])
peusterm5877ea22016-05-11 13:44:59 +0200278 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
stevenvanrossem5b376412016-05-04 15:34:49 +0200279 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
peusterm2ec74e12016-01-13 11:17:53 +0100280 # do bookkeeping
peusterma2ad9ff2016-01-11 17:10:07 +0100281 self.containers[name] = d
stevenvanrossemba51a812017-04-23 01:22:59 +0200282
peustermfa4bcc72016-01-15 11:08:09 +0100283 return d # we might use UUIDs for naming later on
peustermcbcd4c22015-12-28 11:33:42 +0100284
peusterm7aae6852016-01-12 14:53:18 +0100285 def stopCompute(self, name):
peusterma2ad9ff2016-01-11 17:10:07 +0100286 """
287 Stop and remove a container from this data center.
288 """
peusterm9165ef92016-01-13 13:50:39 +0100289 assert name is not None
290 if name not in self.containers:
291 raise Exception("Container with name %s not found." % name)
peusterm60bf8b82016-04-06 14:12:35 +0200292 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
peustermd18559d2016-04-16 04:59:23 +0200293
stevenvanrossem461941c2016-05-10 11:41:29 +0200294 # stop the monitored metrics
295 if self.net.monitor_agent is not None:
296 self.net.monitor_agent.stop_metric(name)
297
peusterm42f08be2016-03-10 21:56:34 +0100298 # call resource model and free resources
299 if self._resource_model is not None:
peustermd18559d2016-04-16 04:59:23 +0200300 self._resource_model.free(self.containers[name])
peusterm36c070c2016-04-16 17:39:01 +0200301 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
peusterm60bf8b82016-04-06 14:12:35 +0200302
peustermd18559d2016-04-16 04:59:23 +0200303 # remove links
304 self.net.removeLink(
305 link=None, node1=self.containers[name], node2=self.switch)
306
307 # remove container
308 self.net.removeDocker("%s" % (name))
309 del self.containers[name]
310
peusterm4e98b632016-01-12 14:08:07 +0100311 return True
312
stevenvanrossem86e64a02017-04-13 02:21:45 +0200313 def attachExternalSAP(self, sap_name, sap_net, **params):
stevenvanrossem17b6e882017-05-04 16:51:34 +0200314 extSAP = EmulatorExtSAP(sap_name, sap_net, self, **params)
stevenvanrossem86e64a02017-04-13 02:21:45 +0200315 # link SAP to the DC switch
stevenvanrossem17b6e882017-05-04 16:51:34 +0200316 self.net.addLink(extSAP.switch, self.switch, cls=Link)
317 self.extSAPs[sap_name] = extSAP
stevenvanrossemce032e12017-04-05 17:31:20 +0200318
stevenvanrossem68a0ba92017-05-03 21:48:26 +0200319 def removeExternalSAP(self, sap_name):
stevenvanrossem17b6e882017-05-04 16:51:34 +0200320 sap_switch = self.extSAPs[sap_name].switch
321 #sap_switch = self.net.getNodeByName(sap_name)
322 # remove link of SAP to the DC switch
stevenvanrossem00e65b92017-04-18 16:57:40 +0200323 self.net.removeLink(link=None, node1=sap_switch, node2=self.switch)
stevenvanrossem68a0ba92017-05-03 21:48:26 +0200324 self.net.removeExtSAP(sap_name)
stevenvanrossem17b6e882017-05-04 16:51:34 +0200325 del self.extSAPs[sap_name]
stevenvanrossemce032e12017-04-05 17:31:20 +0200326
peusterm4e98b632016-01-12 14:08:07 +0100327 def listCompute(self):
328 """
329 Return a list of all running containers assigned to this
330 data center.
331 """
peusterm5aa8cf22016-01-15 11:12:17 +0100332 return list(self.containers.itervalues())
peustermd313dc12016-02-04 15:36:02 +0100333
stevenvanrossem17b6e882017-05-04 16:51:34 +0200334 def listExtSAPs(self):
335 """
336 Return a list of all external SAPs assigned to this
337 data center.
338 """
339 return list(self.extSAPs.itervalues())
340
peustermd313dc12016-02-04 15:36:02 +0100341 def getStatus(self):
342 """
343 Return a dict with status information about this DC.
344 """
stevenvanrossemba51a812017-04-23 01:22:59 +0200345 container_list = [name for name in self.containers]
stevenvanrossem17b6e882017-05-04 16:51:34 +0200346 ext_saplist = [sap_name for sap_name in self.extSAPs]
peusterm53504942016-02-04 16:09:28 +0100347 return {
348 "label": self.label,
349 "internalname": self.name,
350 "switch": self.switch.name,
351 "n_running_containers": len(self.containers),
stevenvanrossemba51a812017-04-23 01:22:59 +0200352 "metadata": self.metadata,
stevenvanrossem17b6e882017-05-04 16:51:34 +0200353 "vnf_list" : container_list,
354 "ext SAP list" : ext_saplist
peusterm53504942016-02-04 16:09:28 +0100355 }
peusterm42f08be2016-03-10 21:56:34 +0100356
357 def assignResourceModel(self, rm):
peusterm279565d2016-03-19 10:36:52 +0100358 """
359 Assign a resource model to this DC.
360 :param rm: a BaseResourceModel object
361 :return:
362 """
peusterm42f08be2016-03-10 21:56:34 +0100363 if self._resource_model is not None:
364 raise Exception("There is already an resource model assigned to this DC.")
365 self._resource_model = rm
366 self.net.rm_registrar.register(self, rm)
peusterm3444ae42016-03-16 20:46:41 +0100367 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
peusterm42f08be2016-03-10 21:56:34 +0100368
peusterm761c14d2016-07-19 09:31:19 +0200369 @staticmethod
370 def _clean_ifname(name):
371 """
372 Cleans up given string to be a
373 RTNETLINK compatible interface name.
374 :param name: string
375 :return: string
376 """
377 if name is None:
378 return "if0"
379 name = name.replace(":", "-")
380 name = name.replace(" ", "-")
381 name = name.replace(".", "-")
382 name = name.replace("_", "-")
383 return name
384