blob: ae658f0b4ee2b85683a4428b0fddd2d6c7fb0667 [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"""
peusterm7aae6852016-01-12 14:53:18 +010028from mininet.node import Docker
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
peusterm60bf8b82016-04-06 14:12:35 +020032import time
33import json
peustermcbcd4c22015-12-28 11:33:42 +010034
peustermf9a817d2016-07-18 09:06:04 +020035LOG = logging.getLogger("dcemulator.node")
peusterm3444ae42016-03-16 20:46:41 +010036LOG.setLevel(logging.DEBUG)
37
peustermcbcd4c22015-12-28 11:33:42 +010038
39DCDPID_BASE = 1000 # start of switch dpid's used for data center switches
40
41
peusterm7aae6852016-01-12 14:53:18 +010042class EmulatorCompute(Docker):
43 """
44 Emulator specific compute node class.
peusterm5877ea22016-05-11 13:44:59 +020045 Inherits from Containernet's Docker host class.
peusterm7aae6852016-01-12 14:53:18 +010046 Represents a single container connected to a (logical)
47 data center.
48 We can add emulator specific helper functions to it.
49 """
50
51 def __init__(
52 self, name, dimage, **kwargs):
peusterm42f08be2016-03-10 21:56:34 +010053 self.datacenter = kwargs.get("datacenter") # pointer to current DC
54 self.flavor_name = kwargs.get("flavor_name")
peusterm3444ae42016-03-16 20:46:41 +010055 LOG.debug("Starting compute instance %r in data center %r" % (name, str(self.datacenter)))
peusterm7aae6852016-01-12 14:53:18 +010056 # call original Docker.__init__
57 Docker.__init__(self, name, dimage, **kwargs)
58
59 def getNetworkStatus(self):
60 """
61 Helper method to receive information about the virtual networks
62 this compute instance is connected to.
63 """
stevenvanrossembecc7c52016-11-07 05:52:01 +010064
peusterm056fd452016-01-12 15:32:25 +010065 # format list of tuples (name, Ip, MAC, isUp, status)
stevenvanrossem307aa1f2016-05-06 10:35:15 +020066 return [{'intf_name':str(i), 'ip':i.IP(), 'mac':i.MAC(), 'up':i.isUp(), 'status':i.status()}
peusterm056fd452016-01-12 15:32:25 +010067 for i in self.intfList()]
peusterm7aae6852016-01-12 14:53:18 +010068
69 def getStatus(self):
70 """
71 Helper method to receive information about this compute instance.
72 """
peusterm056fd452016-01-12 15:32:25 +010073 status = {}
74 status["name"] = self.name
75 status["network"] = self.getNetworkStatus()
stevenvanrosseme66ef122016-05-03 11:22:54 +020076 status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
peusterm056fd452016-01-12 15:32:25 +010077 status["image"] = self.dimage
peusterm36c070c2016-04-16 17:39:01 +020078 status["flavor_name"] = self.flavor_name
peusterm056fd452016-01-12 15:32:25 +010079 status["cpu_quota"] = self.cpu_quota
80 status["cpu_period"] = self.cpu_period
81 status["cpu_shares"] = self.cpu_shares
82 status["cpuset"] = self.cpuset
83 status["mem_limit"] = self.mem_limit
84 status["memswap_limit"] = self.memswap_limit
85 status["state"] = self.dcli.inspect_container(self.dc)["State"]
86 status["id"] = self.dcli.inspect_container(self.dc)["Id"]
peusterm2ec74e12016-01-13 11:17:53 +010087 status["datacenter"] = (None if self.datacenter is None
peusterma47db032016-02-04 14:55:29 +010088 else self.datacenter.label)
peusterm056fd452016-01-12 15:32:25 +010089 return status
peusterm7aae6852016-01-12 14:53:18 +010090
91
peustermcbcd4c22015-12-28 11:33:42 +010092class Datacenter(object):
93 """
94 Represents a logical data center to which compute resources
95 (Docker containers) can be added at runtime.
peusterme4e89d32016-01-07 09:14:54 +010096
97 Will also implement resource bookkeeping in later versions.
peustermcbcd4c22015-12-28 11:33:42 +010098 """
99
peusterma47db032016-02-04 14:55:29 +0100100 DC_COUNTER = 1
101
peusterm60bf8b82016-04-06 14:12:35 +0200102 def __init__(self, label, metadata={}, resource_log_path=None):
peustermcbcd4c22015-12-28 11:33:42 +0100103 self.net = None # DCNetwork to which we belong
peusterma47db032016-02-04 14:55:29 +0100104 # each node (DC) has a short internal name used by Mininet
105 # this is caused by Mininets naming limitations for swtiches etc.
106 self.name = "dc%d" % Datacenter.DC_COUNTER
107 Datacenter.DC_COUNTER += 1
peusterm53504942016-02-04 16:09:28 +0100108 # use this for user defined names that can be longer than self.name
109 self.label = label
110 # dict to store arbitrary metadata (e.g. latitude and longitude)
111 self.metadata = metadata
peusterm60bf8b82016-04-06 14:12:35 +0200112 # path to which resource information should be logged (e.g. for experiments). None = no logging
113 self.resource_log_path = resource_log_path
peusterm42f08be2016-03-10 21:56:34 +0100114 # first prototype assumes one "bigswitch" per DC
115 self.switch = None
116 # keep track of running containers
117 self.containers = {}
118 # pointer to assigned resource model
119 self._resource_model = None
peustermcbcd4c22015-12-28 11:33:42 +0100120
peusterme26487b2016-03-08 14:00:21 +0100121 def __repr__(self):
122 return self.label
123
peustermcbcd4c22015-12-28 11:33:42 +0100124 def _get_next_dc_dpid(self):
125 global DCDPID_BASE
126 DCDPID_BASE += 1
127 return DCDPID_BASE
128
129 def create(self):
130 """
131 Each data center is represented by a single switch to which
132 compute resources can be connected at run time.
133
peusterm9c252b62016-01-06 16:59:53 +0100134 TODO: This will be changed in the future to support multiple networks
peustermcbcd4c22015-12-28 11:33:42 +0100135 per data center
136 """
peusterm293cbc32016-01-13 17:05:28 +0100137 self.switch = self.net.addSwitch(
peustermcbcd4c22015-12-28 11:33:42 +0100138 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
peusterm3444ae42016-03-16 20:46:41 +0100139 LOG.debug("created data center switch: %s" % str(self.switch))
peustermcbcd4c22015-12-28 11:33:42 +0100140
141 def start(self):
142 pass
143
peusterm42f08be2016-03-10 21:56:34 +0100144 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny"):
peusterme4e89d32016-01-07 09:14:54 +0100145 """
146 Create a new container as compute resource and connect it to this
147 data center.
peusterm7f8e8402016-02-28 18:38:10 +0100148 :param name: name (string)
149 :param image: image name (string)
150 :param command: command (string)
151 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
peusterm42f08be2016-03-10 21:56:34 +0100152 :param flavor_name: name of the flavor for this compute container
peusterm7f8e8402016-02-28 18:38:10 +0100153 :return:
peusterme4e89d32016-01-07 09:14:54 +0100154 """
peusterm4e98b632016-01-12 14:08:07 +0100155 assert name is not None
peusterm9165ef92016-01-13 13:50:39 +0100156 # no duplications
peustermbd44f4a2016-01-13 14:53:30 +0100157 if name in [c.name for c in self.net.getAllContainers()]:
peusterm9165ef92016-01-13 13:50:39 +0100158 raise Exception("Container with name %s already exists." % name)
peusterm4e98b632016-01-12 14:08:07 +0100159 # set default parameter
160 if image is None:
peusterm0dc3ae02016-04-27 09:33:28 +0200161 image = "ubuntu:trusty"
peusterm4e98b632016-01-12 14:08:07 +0100162 if network is None:
163 network = {} # {"ip": "10.0.0.254/8"}
peusterm7f8e8402016-02-28 18:38:10 +0100164 if isinstance(network, dict):
165 network = [network] # if we have only one network, put it in a list
166 if isinstance(network, list):
167 if len(network) < 1:
168 network.append({})
169
170 # create the container
peusterm42f08be2016-03-10 21:56:34 +0100171 d = self.net.addDocker(
172 "%s" % (name),
173 dimage=image,
174 dcmd=command,
175 datacenter=self,
peustermd18559d2016-04-16 04:59:23 +0200176 flavor_name=flavor_name
peusterm71b3a2f2016-03-19 12:56:11 +0100177 )
peustermd18559d2016-04-16 04:59:23 +0200178
179 # apply resource limits to container if a resource model is defined
180 if self._resource_model is not None:
peusterma41f7862016-04-27 11:01:24 +0200181 try:
182 self._resource_model.allocate(d)
183 self._resource_model.write_allocation_log(d, self.resource_log_path)
184 except NotEnoughResourcesAvailable as ex:
185 LOG.warning("Allocation of container %r was blocked by resource model." % name)
186 LOG.info(ex.message)
187 # ensure that we remove the container
188 self.net.removeDocker(name)
189 return None
peustermd18559d2016-04-16 04:59:23 +0200190
peusterm7f8e8402016-02-28 18:38:10 +0100191 # connect all given networks
stevenvanrossem14c89052016-04-10 23:49:59 +0200192 # if no --net option is given, network = [{}], so 1 empty dict in the list
193 # this results in 1 default interface with a default ip address
peusterm7f8e8402016-02-28 18:38:10 +0100194 for nw in network:
peusterm761c14d2016-07-19 09:31:19 +0200195 # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
196 if nw.get("id") is not None:
197 nw["id"] = self._clean_ifname(nw["id"])
peusterm5877ea22016-05-11 13:44:59 +0200198 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
stevenvanrossem5b376412016-05-04 15:34:49 +0200199 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
peusterm2ec74e12016-01-13 11:17:53 +0100200 # do bookkeeping
peusterma2ad9ff2016-01-11 17:10:07 +0100201 self.containers[name] = d
peustermfa4bcc72016-01-15 11:08:09 +0100202 return d # we might use UUIDs for naming later on
peustermcbcd4c22015-12-28 11:33:42 +0100203
peusterm7aae6852016-01-12 14:53:18 +0100204 def stopCompute(self, name):
peusterma2ad9ff2016-01-11 17:10:07 +0100205 """
206 Stop and remove a container from this data center.
207 """
peusterm9165ef92016-01-13 13:50:39 +0100208 assert name is not None
209 if name not in self.containers:
210 raise Exception("Container with name %s not found." % name)
peusterm60bf8b82016-04-06 14:12:35 +0200211 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
peustermd18559d2016-04-16 04:59:23 +0200212
stevenvanrossem461941c2016-05-10 11:41:29 +0200213 # stop the monitored metrics
214 if self.net.monitor_agent is not None:
215 self.net.monitor_agent.stop_metric(name)
216
peusterm42f08be2016-03-10 21:56:34 +0100217 # call resource model and free resources
218 if self._resource_model is not None:
peustermd18559d2016-04-16 04:59:23 +0200219 self._resource_model.free(self.containers[name])
peusterm36c070c2016-04-16 17:39:01 +0200220 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
peusterm60bf8b82016-04-06 14:12:35 +0200221
peustermd18559d2016-04-16 04:59:23 +0200222 # remove links
223 self.net.removeLink(
224 link=None, node1=self.containers[name], node2=self.switch)
225
226 # remove container
227 self.net.removeDocker("%s" % (name))
228 del self.containers[name]
229
peusterm4e98b632016-01-12 14:08:07 +0100230 return True
231
232 def listCompute(self):
233 """
234 Return a list of all running containers assigned to this
235 data center.
236 """
peusterm5aa8cf22016-01-15 11:12:17 +0100237 return list(self.containers.itervalues())
peustermd313dc12016-02-04 15:36:02 +0100238
239 def getStatus(self):
240 """
241 Return a dict with status information about this DC.
242 """
peusterm53504942016-02-04 16:09:28 +0100243 return {
244 "label": self.label,
245 "internalname": self.name,
246 "switch": self.switch.name,
247 "n_running_containers": len(self.containers),
248 "metadata": self.metadata
249 }
peusterm42f08be2016-03-10 21:56:34 +0100250
251 def assignResourceModel(self, rm):
peusterm279565d2016-03-19 10:36:52 +0100252 """
253 Assign a resource model to this DC.
254 :param rm: a BaseResourceModel object
255 :return:
256 """
peusterm42f08be2016-03-10 21:56:34 +0100257 if self._resource_model is not None:
258 raise Exception("There is already an resource model assigned to this DC.")
259 self._resource_model = rm
260 self.net.rm_registrar.register(self, rm)
peusterm3444ae42016-03-16 20:46:41 +0100261 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
peusterm42f08be2016-03-10 21:56:34 +0100262
peusterm761c14d2016-07-19 09:31:19 +0200263 @staticmethod
264 def _clean_ifname(name):
265 """
266 Cleans up given string to be a
267 RTNETLINK compatible interface name.
268 :param name: string
269 :return: string
270 """
271 if name is None:
272 return "if0"
273 name = name.replace(":", "-")
274 name = name.replace(" ", "-")
275 name = name.replace(".", "-")
276 name = name.replace("_", "-")
277 return name
278