properly delete the External SAPs
[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, OVSBridge
29 from mininet.link import Link
30 from emuvim.dcemulator.resourcemodel import NotEnoughResourcesAvailable
31 import logging
32
33
34 LOG = logging.getLogger("dcemulator.node")
35 LOG.setLevel(logging.DEBUG)
36
37
38 DCDPID_BASE = 1000 # start of switch dpid's used for data center switches
39 EXTSAPDPID_BASE = 2000 # start of switch dpid's used for external SAP switches
40
41 class EmulatorCompute(Docker):
42 """
43 Emulator specific compute node class.
44 Inherits from Containernet's Docker host class.
45 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):
52 self.datacenter = kwargs.get("datacenter") # pointer to current DC
53 self.flavor_name = kwargs.get("flavor_name")
54 LOG.debug("Starting compute instance %r in data center %r" % (name, str(self.datacenter)))
55 # 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 """
63 # 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)
70 intf_dict = {'intf_name': str(i), 'ip': i.IP(), 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name}
71 networkStatusList.append(intf_dict)
72
73 return networkStatusList
74
75 def getStatus(self):
76 """
77 Helper method to receive information about this compute instance.
78 """
79 status = {}
80 status["name"] = self.name
81 status["network"] = self.getNetworkStatus()
82 status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
83 status["image"] = self.dimage
84 status["flavor_name"] = self.flavor_name
85 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')
91 status["state"] = self.dcli.inspect_container(self.dc)["State"]
92 status["id"] = self.dcli.inspect_container(self.dc)["Id"]
93 status["short_id"] = self.dcli.inspect_container(self.dc)["Id"][:12]
94 status["hostname"] = self.dcli.inspect_container(self.dc)["Config"]['Hostname']
95 status["datacenter"] = (None if self.datacenter is None
96 else self.datacenter.label)
97
98 return status
99
100
101 class Datacenter(object):
102 """
103 Represents a logical data center to which compute resources
104 (Docker containers) can be added at runtime.
105
106 Will also implement resource bookkeeping in later versions.
107 """
108
109 DC_COUNTER = 1
110
111 def __init__(self, label, metadata={}, resource_log_path=None):
112 self.net = None # DCNetwork to which we belong
113 # 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
117 # use this for user defined names that can be longer than self.name
118 self.label = label
119 # dict to store arbitrary metadata (e.g. latitude and longitude)
120 self.metadata = metadata
121 # path to which resource information should be logged (e.g. for experiments). None = no logging
122 self.resource_log_path = resource_log_path
123 # 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
129
130 def __repr__(self):
131 return self.label
132
133 def _get_next_dc_dpid(self):
134 global DCDPID_BASE
135 DCDPID_BASE += 1
136 return DCDPID_BASE
137
138 def _get_next_extSAP_dpid(self):
139 global EXTSAPDPID_BASE
140 EXTSAPDPID_BASE += 1
141 return EXTSAPDPID_BASE
142
143 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
148 TODO: This will be changed in the future to support multiple networks
149 per data center
150 """
151 self.switch = self.net.addSwitch(
152 "%s.s1" % self.name, dpid=hex(self._get_next_dc_dpid())[2:])
153 LOG.debug("created data center switch: %s" % str(self.switch))
154
155 def start(self):
156 pass
157
158 def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", **params):
159 """
160 Create a new container as compute resource and connect it to this
161 data center.
162 :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"})
166 :param flavor_name: name of the flavor for this compute container
167 :return:
168 """
169 assert name is not None
170 # no duplications
171 if name in [c.name for c in self.net.getAllContainers()]:
172 raise Exception("Container with name %s already exists." % name)
173 # set default parameter
174 if image is None:
175 image = "ubuntu:trusty"
176 if network is None:
177 network = {} # {"ip": "10.0.0.254/8"}
178 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
184 # apply hard-set resource limits=0
185 cpu_percentage = params.get('cpu_percent')
186 if cpu_percentage:
187 params['cpu_period'] = self.net.cpu_period
188 params['cpu_quota'] = self.net.cpu_period * float(cpu_percentage)
189
190 # create the container
191 d = self.net.addDocker(
192 "%s" % (name),
193 dimage=image,
194 dcmd=command,
195 datacenter=self,
196 flavor_name=flavor_name,
197 environment = {'VNF_NAME':name},
198 **params
199 )
200
201
202
203 # apply resource limits to container if a resource model is defined
204 if self._resource_model is not None:
205 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
214
215 # connect all given networks
216 # 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
218 for nw in network:
219 # 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"])
222 # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
223 self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
224 # do bookkeeping
225 self.containers[name] = d
226 return d # we might use UUIDs for naming later on
227
228 def stopCompute(self, name):
229 """
230 Stop and remove a container from this data center.
231 """
232 assert name is not None
233 if name not in self.containers:
234 raise Exception("Container with name %s not found." % name)
235 LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
236
237 # stop the monitored metrics
238 if self.net.monitor_agent is not None:
239 self.net.monitor_agent.stop_metric(name)
240
241 # call resource model and free resources
242 if self._resource_model is not None:
243 self._resource_model.free(self.containers[name])
244 self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
245
246 # remove links
247 self.net.removeLink(
248 link=None, node1=self.containers[name], node2=self.switch)
249
250 # remove container
251 self.net.removeDocker("%s" % (name))
252 del self.containers[name]
253
254 return True
255
256 def attachExternalSAP(self, sap_name, sap_net, **params):
257 # create SAP as separate OVS switch with an assigned ip address
258 sap_ip = str(sap_net[1]) + '/' + str(sap_net.prefixlen)
259 # allow connection to the external internet through the host
260 params = dict(NAT=True, SAPNet=str(sap_net))
261 sap_switch = self.net.addExtSAP(sap_name, sap_ip, dpid=hex(self._get_next_extSAP_dpid())[2:], **params)
262 sap_switch.start()
263
264 # link SAP to the DC switch
265 self.net.addLink(sap_switch, self.switch, cls=Link)
266
267 def removeExternalSAP(self, sap_name, sap_net):
268 sap_switch = self.net.getNodeByName(sap_name)
269 # link SAP to the DC switch
270 self.net.removeLink(link=None, node1=sap_switch, node2=self.switch)
271 self.net.removeExtSAP(sap_name, str(sap_net))
272
273 def listCompute(self):
274 """
275 Return a list of all running containers assigned to this
276 data center.
277 """
278 return list(self.containers.itervalues())
279
280 def getStatus(self):
281 """
282 Return a dict with status information about this DC.
283 """
284 return {
285 "label": self.label,
286 "internalname": self.name,
287 "switch": self.switch.name,
288 "n_running_containers": len(self.containers),
289 "metadata": self.metadata
290 }
291
292 def assignResourceModel(self, rm):
293 """
294 Assign a resource model to this DC.
295 :param rm: a BaseResourceModel object
296 :return:
297 """
298 if self._resource_model is not None:
299 raise Exception("There is already an resource model assigned to this DC.")
300 self._resource_model = rm
301 self.net.rm_registrar.register(self, rm)
302 LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
303
304 @staticmethod
305 def _clean_ifname(name):
306 """
307 Cleans up given string to be a
308 RTNETLINK compatible interface name.
309 :param name: string
310 :return: string
311 """
312 if name is None:
313 return "if0"
314 name = name.replace(":", "-")
315 name = name.replace(" ", "-")
316 name = name.replace(".", "-")
317 name = name.replace("_", "-")
318 return name
319