blob: 9439eeb3945ec3bc8d0d51b32b1787c107043e04 [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)
stevenvanrossem99d1f612017-05-03 16:28:38 +020070 intf_dict = {'intf_name': str(i), 'ip': i.IP(), '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
peustermcbcd4c22015-12-28 11:33:42 +0100101class Datacenter(object):
102 """
103 Represents a logical data center to which compute resources
104 (Docker containers) can be added at runtime.
peusterme4e89d32016-01-07 09:14:54 +0100105
106 Will also implement resource bookkeeping in later versions.
peustermcbcd4c22015-12-28 11:33:42 +0100107 """
108
peusterma47db032016-02-04 14:55:29 +0100109 DC_COUNTER = 1
110
peusterm60bf8b82016-04-06 14:12:35 +0200111 def __init__(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100112 self.net = None # DCNetwork to which we belong
peusterma47db032016-02-04 14:55:29 +0100113 # each node (DC) has a short internal name used by Mininet
114 # this is caused by Mininets naming limitations for swtiches etc.
115 self.name = "dc%d" % Datacenter.DC_COUNTER
116 Datacenter.DC_COUNTER += 1
peusterm53504942016-02-04 16:09:28 +0100117 # use this for user defined names that can be longer than self.name
edmaas7e084ea2016-11-28 13:50:23 +0100118 self.label = label
peusterm53504942016-02-04 16:09:28 +0100119 # dict to store arbitrary metadata (e.g. latitude and longitude)
120 self.metadata = metadata
peusterm60bf8b82016-04-06 14:12:35 +0200121 # path to which resource information should be logged (e.g. for experiments). None = no logging
122 self.resource_log_path = resource_log_path
peusterm42f08be2016-03-10 21:56:34 +0100123 # first prototype assumes one "bigswitch" per DC
124 self.switch = None
125 # keep track of running containers
126 self.containers = {}
127 # pointer to assigned resource model
128 self._resource_model = None
peustermcbcd4c22015-12-28 11:33:42 +0100129
peusterme26487b2016-03-08 14:00:21 +0100130 def __repr__(self):
131 return self.label
132
peustermcbcd4c22015-12-28 11:33:42 +0100133 def _get_next_dc_dpid(self):
134 global DCDPID_BASE
135 DCDPID_BASE += 1
136 return DCDPID_BASE
137
stevenvanrossem86e64a02017-04-13 02:21:45 +0200138 def _get_next_extSAP_dpid(self):
139 global EXTSAPDPID_BASE
140 EXTSAPDPID_BASE += 1
141 return EXTSAPDPID_BASE
142
peustermcbcd4c22015-12-28 11:33:42 +0100143 def create(self):
144 """
145 Each data center is represented by a single switch to which
146 compute resources can be connected at run time.
147
peusterm9c252b62016-01-06 16:59:53 +0100148 TODO: This will be changed in the future to support multiple networks
peustermcbcd4c22015-12-28 11:33:42 +0100149 per data center
150 """
peusterm293cbc32016-01-13 17:05:28 +0100151 self.switch = self.net.addSwitch(
peustermcbcd4c22015-12-28 11:33:42 +0100152 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
peusterm3444ae42016-03-16 20:46:41 +0100153 LOG.debug("created data center switch: %s" % str(self.switch))
peustermcbcd4c22015-12-28 11:33:42 +0100154
155 def start(self):
156 pass
157
edmaas7e084ea2016-11-28 13:50:23 +0100158 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", **params):
peusterme4e89d32016-01-07 09:14:54 +0100159 """
160 Create a new container as compute resource and connect it to this
161 data center.
peusterm7f8e8402016-02-28 18:38:10 +0100162 :param name: name (string)
163 :param image: image name (string)
164 :param command: command (string)
165 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
peusterm42f08be2016-03-10 21:56:34 +0100166 :param flavor_name: name of the flavor for this compute container
peusterm7f8e8402016-02-28 18:38:10 +0100167 :return:
peusterme4e89d32016-01-07 09:14:54 +0100168 """
peusterm4e98b632016-01-12 14:08:07 +0100169 assert name is not None
peusterm9165ef92016-01-13 13:50:39 +0100170 # no duplications
peustermbd44f4a2016-01-13 14:53:30 +0100171 if name in [c.name for c in self.net.getAllContainers()]:
peusterm9165ef92016-01-13 13:50:39 +0100172 raise Exception("Container with name %s already exists." % name)
peusterm4e98b632016-01-12 14:08:07 +0100173 # set default parameter
174 if image is None:
peusterm0dc3ae02016-04-27 09:33:28 +0200175 image = "ubuntu:trusty"
peusterm4e98b632016-01-12 14:08:07 +0100176 if network is None:
177 network = {} # {"ip": "10.0.0.254/8"}
peusterm7f8e8402016-02-28 18:38:10 +0100178 if isinstance(network, dict):
179 network = [network] # if we have only one network, put it in a list
180 if isinstance(network, list):
181 if len(network) < 1:
182 network.append({})
183
stevenvanrossemb3f34172016-11-16 23:30:57 +0100184 # apply hard-set resource limits=0
stevenvanrosseme8d86282017-01-28 00:52:22 +0100185 cpu_percentage = params.get('cpu_percent')
stevenvanrossemb3f34172016-11-16 23:30:57 +0100186 if cpu_percentage:
stevenvanrosseme8d86282017-01-28 00:52:22 +0100187 params['cpu_period'] = self.net.cpu_period
188 params['cpu_quota'] = self.net.cpu_period * float(cpu_percentage)
stevenvanrossemb3f34172016-11-16 23:30:57 +0100189
peusterm7f8e8402016-02-28 18:38:10 +0100190 # create the container
peusterm42f08be2016-03-10 21:56:34 +0100191 d = self.net.addDocker(
192 "%s" % (name),
193 dimage=image,
194 dcmd=command,
195 datacenter=self,
stevenvanrossemb3f34172016-11-16 23:30:57 +0100196 flavor_name=flavor_name,
stevenvanrosseme8d86282017-01-28 00:52:22 +0100197 environment = {'VNF_NAME':name},
edmaas7e084ea2016-11-28 13:50:23 +0100198 **params
peusterm71b3a2f2016-03-19 12:56:11 +0100199 )
peustermd18559d2016-04-16 04:59:23 +0200200
stevenvanrossemb3f34172016-11-16 23:30:57 +0100201
202
peustermd18559d2016-04-16 04:59:23 +0200203 # apply resource limits to container if a resource model is defined
204 if self._resource_model is not None:
peusterma41f7862016-04-27 11:01:24 +0200205 try:
206 self._resource_model.allocate(d)
207 self._resource_model.write_allocation_log(d, self.resource_log_path)
208 except NotEnoughResourcesAvailable as ex:
209 LOG.warning("Allocation of container %r was blocked by resource model." % name)
210 LOG.info(ex.message)
211 # ensure that we remove the container
212 self.net.removeDocker(name)
213 return None
peustermd18559d2016-04-16 04:59:23 +0200214
peusterm7f8e8402016-02-28 18:38:10 +0100215 # connect all given networks
stevenvanrossem14c89052016-04-10 23:49:59 +0200216 # if no --net option is given, network = [{}], so 1 empty dict in the list
217 # this results in 1 default interface with a default ip address
peusterm7f8e8402016-02-28 18:38:10 +0100218 for nw in network:
peusterm761c14d2016-07-19 09:31:19 +0200219 # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
220 if nw.get("id") is not None:
221 nw["id"] = self._clean_ifname(nw["id"])
peusterm5877ea22016-05-11 13:44:59 +0200222 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
stevenvanrossem5b376412016-05-04 15:34:49 +0200223 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
peusterm2ec74e12016-01-13 11:17:53 +0100224 # do bookkeeping
peusterma2ad9ff2016-01-11 17:10:07 +0100225 self.containers[name] = d
stevenvanrossemba51a812017-04-23 01:22:59 +0200226
peustermfa4bcc72016-01-15 11:08:09 +0100227 return d # we might use UUIDs for naming later on
peustermcbcd4c22015-12-28 11:33:42 +0100228
peusterm7aae6852016-01-12 14:53:18 +0100229 def stopCompute(self, name):
peusterma2ad9ff2016-01-11 17:10:07 +0100230 """
231 Stop and remove a container from this data center.
232 """
peusterm9165ef92016-01-13 13:50:39 +0100233 assert name is not None
234 if name not in self.containers:
235 raise Exception("Container with name %s not found." % name)
peusterm60bf8b82016-04-06 14:12:35 +0200236 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
peustermd18559d2016-04-16 04:59:23 +0200237
stevenvanrossem461941c2016-05-10 11:41:29 +0200238 # stop the monitored metrics
239 if self.net.monitor_agent is not None:
240 self.net.monitor_agent.stop_metric(name)
241
peusterm42f08be2016-03-10 21:56:34 +0100242 # call resource model and free resources
243 if self._resource_model is not None:
peustermd18559d2016-04-16 04:59:23 +0200244 self._resource_model.free(self.containers[name])
peusterm36c070c2016-04-16 17:39:01 +0200245 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
peusterm60bf8b82016-04-06 14:12:35 +0200246
peustermd18559d2016-04-16 04:59:23 +0200247 # remove links
248 self.net.removeLink(
249 link=None, node1=self.containers[name], node2=self.switch)
250
251 # remove container
252 self.net.removeDocker("%s" % (name))
253 del self.containers[name]
254
peusterm4e98b632016-01-12 14:08:07 +0100255 return True
256
stevenvanrossem86e64a02017-04-13 02:21:45 +0200257 def attachExternalSAP(self, sap_name, sap_net, **params):
258 # create SAP as separate OVS switch with an assigned ip address
259 sap_ip = str(sap_net[1]) + '/' + str(sap_net.prefixlen)
stevenvanrossem00e65b92017-04-18 16:57:40 +0200260 # allow connection to the external internet through the host
261 params = dict(NAT=True, SAPNet=str(sap_net))
stevenvanrossem86e64a02017-04-13 02:21:45 +0200262 sap_switch = self.net.addExtSAP(sap_name, sap_ip, dpid=hex(self._get_next_extSAP_dpid())[2:], **params)
263 sap_switch.start()
stevenvanrossemce032e12017-04-05 17:31:20 +0200264
stevenvanrossem86e64a02017-04-13 02:21:45 +0200265 # link SAP to the DC switch
266 self.net.addLink(sap_switch, self.switch, cls=Link)
stevenvanrossemce032e12017-04-05 17:31:20 +0200267
stevenvanrossem00e65b92017-04-18 16:57:40 +0200268 def removeExternalSAP(self, sap_name, sap_net):
269 sap_switch = self.net.getNodeByName(sap_name)
270 # link SAP to the DC switch
271 self.net.removeLink(link=None, node1=sap_switch, node2=self.switch)
272 self.net.removeExtSAP(sap_name, str(sap_net))
stevenvanrossemce032e12017-04-05 17:31:20 +0200273
peusterm4e98b632016-01-12 14:08:07 +0100274 def listCompute(self):
275 """
276 Return a list of all running containers assigned to this
277 data center.
278 """
peusterm5aa8cf22016-01-15 11:12:17 +0100279 return list(self.containers.itervalues())
peustermd313dc12016-02-04 15:36:02 +0100280
281 def getStatus(self):
282 """
283 Return a dict with status information about this DC.
284 """
stevenvanrossemba51a812017-04-23 01:22:59 +0200285 container_list = [name for name in self.containers]
peusterm53504942016-02-04 16:09:28 +0100286 return {
287 "label": self.label,
288 "internalname": self.name,
289 "switch": self.switch.name,
290 "n_running_containers": len(self.containers),
stevenvanrossemba51a812017-04-23 01:22:59 +0200291 "metadata": self.metadata,
292 "vnf_list" : container_list
peusterm53504942016-02-04 16:09:28 +0100293 }
peusterm42f08be2016-03-10 21:56:34 +0100294
295 def assignResourceModel(self, rm):
peusterm279565d2016-03-19 10:36:52 +0100296 """
297 Assign a resource model to this DC.
298 :param rm: a BaseResourceModel object
299 :return:
300 """
peusterm42f08be2016-03-10 21:56:34 +0100301 if self._resource_model is not None:
302 raise Exception("There is already an resource model assigned to this DC.")
303 self._resource_model = rm
304 self.net.rm_registrar.register(self, rm)
peusterm3444ae42016-03-16 20:46:41 +0100305 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
peusterm42f08be2016-03-10 21:56:34 +0100306
peusterm761c14d2016-07-19 09:31:19 +0200307 @staticmethod
308 def _clean_ifname(name):
309 """
310 Cleans up given string to be a
311 RTNETLINK compatible interface name.
312 :param name: string
313 :return: string
314 """
315 if name is None:
316 return "if0"
317 name = name.replace(":", "-")
318 name = name.replace(" ", "-")
319 name = name.replace(".", "-")
320 name = name.replace("_", "-")
321 return name
322