Merge remote-tracking branch 'upstream/master'
[osm/vim-emu.git] / src / emuvim / dcemulator / node.py
1 """
2 Copyright (c) 2015 SONATA-NFV and Paderborn University
3 ALL RIGHTS RESERVED.
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18 nor the names of its contributors may be used to endorse or promote
19 products derived from this software without specific prior written
20 permission.
21
22 This work has been performed in the framework of the SONATA project,
23 funded by the European Commission under Grant number 671517 through
24 the Horizon 2020 and 5G-PPP programmes. The authors would like to
25 acknowledge the contributions of their colleagues of the SONATA
26 partner consortium (www.sonata-nfv.eu).
27 """
28 from mininet.node import Docker
29 from mininet.link import Link
30 from emuvim.dcemulator.resourcemodel import NotEnoughResourcesAvailable
31 import logging
32 import time
33 import json
34
35 LOG = logging.getLogger("dcemulator.node")
36 LOG.setLevel(logging.DEBUG)
37
38
39 DCDPID_BASE = 1000 # start of switch dpid's used for data center switches
40
41
42 class EmulatorCompute(Docker):
43 """
44 Emulator specific compute node class.
45 Inherits from Containernet's Docker host class.
46 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):
53 self.datacenter = kwargs.get("datacenter") # pointer to current DC
54 self.flavor_name = kwargs.get("flavor_name")
55 LOG.debug("Starting compute instance %r in data center %r" % (name, str(self.datacenter)))
56 # 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 """
64 # format list of tuples (name, Ip, MAC, isUp, status)
65 return [{'intf_name':str(i), 'ip':i.IP(), 'mac':i.MAC(), 'up':i.isUp(), 'status':i.status()}
66 for i in self.intfList()]
67
68 def getStatus(self):
69 """
70 Helper method to receive information about this compute instance.
71 """
72 status = {}
73 status["name"] = self.name
74 status["network"] = self.getNetworkStatus()
75 status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
76 status["image"] = self.dimage
77 status["flavor_name"] = self.flavor_name
78 status["cpu_quota"] = self.cpu_quota
79 status["cpu_period"] = self.cpu_period
80 status["cpu_shares"] = self.cpu_shares
81 status["cpuset"] = self.cpuset
82 status["mem_limit"] = self.mem_limit
83 status["memswap_limit"] = self.memswap_limit
84 status["state"] = self.dcli.inspect_container(self.dc)["State"]
85 status["id"] = self.dcli.inspect_container(self.dc)["Id"]
86 status["datacenter"] = (None if self.datacenter is None
87 else self.datacenter.label)
88 return status
89
90
91 class Datacenter(object):
92 """
93 Represents a logical data center to which compute resources
94 (Docker containers) can be added at runtime.
95
96 Will also implement resource bookkeeping in later versions.
97 """
98
99 DC_COUNTER = 1
100
101 def __init__(self, label, metadata={}, resource_log_path=None):
102 self.net = None # DCNetwork to which we belong
103 # each node (DC) has a short internal name used by Mininet
104 # this is caused by Mininets naming limitations for swtiches etc.
105 self.name = "dc%d" % Datacenter.DC_COUNTER
106 Datacenter.DC_COUNTER += 1
107 # use this for user defined names that can be longer than self.name
108 self.label = label
109 # dict to store arbitrary metadata (e.g. latitude and longitude)
110 self.metadata = metadata
111 # path to which resource information should be logged (e.g. for experiments). None = no logging
112 self.resource_log_path = resource_log_path
113 # first prototype assumes one "bigswitch" per DC
114 self.switch = None
115 # keep track of running containers
116 self.containers = {}
117 # pointer to assigned resource model
118 self._resource_model = None
119
120 def __repr__(self):
121 return self.label
122
123 def _get_next_dc_dpid(self):
124 global DCDPID_BASE
125 DCDPID_BASE += 1
126 return DCDPID_BASE
127
128 def create(self):
129 """
130 Each data center is represented by a single switch to which
131 compute resources can be connected at run time.
132
133 TODO: This will be changed in the future to support multiple networks
134 per data center
135 """
136 self.switch = self.net.addSwitch(
137 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
138 LOG.debug("created data center switch: %s" % str(self.switch))
139
140 def start(self):
141 pass
142
143 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny"):
144 """
145 Create a new container as compute resource and connect it to this
146 data center.
147 :param name: name (string)
148 :param image: image name (string)
149 :param command: command (string)
150 :param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
151 :param flavor_name: name of the flavor for this compute container
152 :return:
153 """
154 assert name is not None
155 # no duplications
156 if name in [c.name for c in self.net.getAllContainers()]:
157 raise Exception("Container with name %s already exists." % name)
158 # set default parameter
159 if image is None:
160 image = "ubuntu:trusty"
161 if network is None:
162 network = {} # {"ip": "10.0.0.254/8"}
163 if isinstance(network, dict):
164 network = [network] # if we have only one network, put it in a list
165 if isinstance(network, list):
166 if len(network) < 1:
167 network.append({})
168
169 # create the container
170 d = self.net.addDocker(
171 "%s" % (name),
172 dimage=image,
173 dcmd=command,
174 datacenter=self,
175 flavor_name=flavor_name
176 )
177
178 # apply resource limits to container if a resource model is defined
179 if self._resource_model is not None:
180 try:
181 self._resource_model.allocate(d)
182 self._resource_model.write_allocation_log(d, self.resource_log_path)
183 except NotEnoughResourcesAvailable as ex:
184 LOG.warning("Allocation of container %r was blocked by resource model." % name)
185 LOG.info(ex.message)
186 # ensure that we remove the container
187 self.net.removeDocker(name)
188 return None
189
190 # connect all given networks
191 # if no --net option is given, network = [{}], so 1 empty dict in the list
192 # this results in 1 default interface with a default ip address
193 for nw in network:
194 # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
195 if nw.get("id") is not None:
196 nw["id"] = self._clean_ifname(nw["id"])
197 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
198 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
199 # do bookkeeping
200 self.containers[name] = d
201 return d # we might use UUIDs for naming later on
202
203 def stopCompute(self, name):
204 """
205 Stop and remove a container from this data center.
206 """
207 assert name is not None
208 if name not in self.containers:
209 raise Exception("Container with name %s not found." % name)
210 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
211
212 # stop the monitored metrics
213 if self.net.monitor_agent is not None:
214 self.net.monitor_agent.stop_metric(name)
215
216 # call resource model and free resources
217 if self._resource_model is not None:
218 self._resource_model.free(self.containers[name])
219 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
220
221 # remove links
222 self.net.removeLink(
223 link=None, node1=self.containers[name], node2=self.switch)
224
225 # remove container
226 self.net.removeDocker("%s" % (name))
227 del self.containers[name]
228
229 return True
230
231 def listCompute(self):
232 """
233 Return a list of all running containers assigned to this
234 data center.
235 """
236 return list(self.containers.itervalues())
237
238 def getStatus(self):
239 """
240 Return a dict with status information about this DC.
241 """
242 return {
243 "label": self.label,
244 "internalname": self.name,
245 "switch": self.switch.name,
246 "n_running_containers": len(self.containers),
247 "metadata": self.metadata
248 }
249
250 def assignResourceModel(self, rm):
251 """
252 Assign a resource model to this DC.
253 :param rm: a BaseResourceModel object
254 :return:
255 """
256 if self._resource_model is not None:
257 raise Exception("There is already an resource model assigned to this DC.")
258 self._resource_model = rm
259 self.net.rm_registrar.register(self, rm)
260 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
261
262 @staticmethod
263 def _clean_ifname(name):
264 """
265 Cleans up given string to be a
266 RTNETLINK compatible interface name.
267 :param name: string
268 :return: string
269 """
270 if name is None:
271 return "if0"
272 name = name.replace(":", "-")
273 name = name.replace(" ", "-")
274 name = name.replace(".", "-")
275 name = name.replace("_", "-")
276 return name
277