Added Dockerfile-based entry points to REST API.
[osm/vim-emu.git] / src / emuvim / api / rest / compute.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, Paderborn University
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 import logging
29 from flask_restful import Resource
30 from flask import request
31 import json
32 import threading
33 from copy import deepcopy
34
35 logging.basicConfig()
36
37 CORS_HEADER = {'Access-Control-Allow-Origin': '*'}
38
39 # the dcs dict is set in the rest_api_endpoint.py upon datacenter init
40 dcs = {}
41
42
43 class Compute(Resource):
44 """
45 Start a new compute instance: A docker container (note: zerorpc does not support keyword arguments)
46 :param dc_label: name of the DC
47 :param compute_name: compute container name
48 :param image: image name
49 :param command: command to execute
50 :param network: list of all interface of the vnf, with their parameters (id=id1,ip=x.x.x.x/x),...
51 example networks list({"id":"input","ip": "10.0.0.254/8"}, {"id":"output","ip": "11.0.0.254/24"})
52 :return: docker inspect dict of deployed docker
53 """
54
55 global dcs
56
57 def put(self, dc_label, compute_name, resource=None, value=None):
58
59 # deploy new container
60 # check if json data is a dict
61 data = request.json
62 if data is None:
63 data = {}
64 elif type(data) is not dict:
65 data = json.loads(request.json)
66
67 network = data.get("network")
68 nw_list = self._parse_network(network)
69 image = data.get("image")
70 command = data.get("docker_command")
71
72 try:
73 if compute_name is None or compute_name == "None":
74 logging.error("No compute name defined in request.")
75 return "No compute name defined in request.", 500, CORS_HEADER
76 if dc_label is None or dcs.get(dc_label) is None:
77 logging.error("No datacenter defined in request.")
78 return "No datacenter defined in request.", 500, CORS_HEADER
79 c = dcs.get(dc_label).startCompute(
80 compute_name, image=image, command=command, network=nw_list)
81 # (if available) trigger emu. entry point given in Dockerfile
82 try:
83 config = c.dcinfo.get("Config", dict())
84 env = config.get("Env", list())
85 for env_var in env:
86 var, cmd = map(str.strip, map(str, env_var.split('=', 1)))
87 logging.debug("%r = %r" % (var , cmd))
88 if var=="SON_EMU_CMD" or var=="VIM_EMU_CMD":
89 logging.info("Executing entry point script in %r: %r" % (c.name, cmd))
90 # execute command in new thread to ensure that API is not blocked by VNF
91 t = threading.Thread(target=c.cmdPrint, args=(cmd,))
92 t.daemon = True
93 t.start()
94 except Exception as ex:
95 logging.warning("Couldn't run Docker entry point VIM_EMU_CMD")
96 logging.exception("Exception:")
97 # return docker inspect dict
98 return c.getStatus(), 200, CORS_HEADER
99 except Exception as ex:
100 logging.exception("API error.")
101 return ex.message, 500, CORS_HEADER
102
103 def get(self, dc_label, compute_name):
104
105 logging.debug("API CALL: compute status")
106
107 try:
108 return dcs.get(dc_label).containers.get(compute_name).getStatus(), 200, CORS_HEADER
109 except Exception as ex:
110 logging.exception("API error.")
111 return ex.message, 500, CORS_HEADER
112
113 def delete(self, dc_label, compute_name):
114 logging.debug("API CALL: compute stop")
115 try:
116 return dcs.get(dc_label).stopCompute(compute_name), 200, CORS_HEADER
117 except Exception as ex:
118 logging.exception("API error.")
119 return ex.message, 500, CORS_HEADER
120
121 def _parse_network(self, network_str):
122 '''
123 parse the options for all network interfaces of the vnf
124 :param network_str: (id=x,ip=x.x.x.x/x), ...
125 :return: list of dicts [{"id":x,"ip":"x.x.x.x/x"}, ...]
126 '''
127 nw_list = list()
128
129 # TODO make this more robust with regex check
130 if network_str is None:
131 return nw_list
132
133 networks = network_str[1:-1].split('),(')
134 for nw in networks:
135 nw_dict = dict(tuple(e.split('=')) for e in nw.split(','))
136 nw_list.append(nw_dict)
137
138 return nw_list
139
140
141 class ComputeList(Resource):
142 global dcs
143
144 def get(self, dc_label=None):
145 logging.debug("API CALL: compute list")
146 try:
147 if dc_label is None or dc_label == 'None':
148 # return list with all compute nodes in all DCs
149 all_containers = []
150 all_extSAPs = []
151 for dc in dcs.itervalues():
152 all_containers += dc.listCompute()
153 all_extSAPs += dc.listExtSAPs()
154
155 extSAP_list = [(sap.name, sap.getStatus()) for sap in all_extSAPs]
156 container_list = [(c.name, c.getStatus()) for c in all_containers]
157 total_list = container_list + extSAP_list
158 return total_list, 200, CORS_HEADER
159 else:
160 # return list of compute nodes for specified DC
161 container_list = [(c.name, c.getStatus()) for c in dcs.get(dc_label).listCompute()]
162 extSAP_list = [(sap.name, sap.getStatus()) for sap in dcs.get(dc_label).listExtSAPs()]
163 total_list = container_list + extSAP_list
164 return total_list, 200, CORS_HEADER
165 except Exception as ex:
166 logging.exception("API error.")
167 return ex.message, 500, CORS_HEADER
168
169 class ComputeResources(Resource):
170 """
171 Update the container's resources using the docker.update function
172 re-using the same parameters:
173 url params:
174 blkio_weight
175 cpu_period, cpu_quota, cpu_shares
176 cpuset_cpus
177 cpuset_mems
178 mem_limit
179 mem_reservation
180 memswap_limit
181 kernel_memory
182 restart_policy
183 see https://docs.docker.com/engine/reference/commandline/update/
184 or API docs: https://docker-py.readthedocs.io/en/stable/api.html#module-docker.api.container
185 :param dc_label: name of the DC
186 :param compute_name: compute container name
187
188 :return: docker inspect dict of deployed docker
189 """
190 global dcs
191
192 def put(self, dc_label, compute_name):
193 logging.debug("REST CALL: update container resources")
194
195 try:
196 c = self._update_resources(dc_label, compute_name)
197 return c.getStatus(), 200, CORS_HEADER
198 except Exception as ex:
199 logging.exception("API error.")
200 return ex.message, 500, CORS_HEADER
201
202 def _update_resources(self, dc_label, compute_name):
203
204 # get URL parameters
205 params = request.args
206 # then no data
207 if params is None:
208 params = {}
209 logging.debug("REST CALL: update container resources {0}".format(params))
210 #check if container exists
211 d = dcs.get(dc_label).net.getNodeByName(compute_name)
212
213 # general request of cpu percentage
214 # create a mutable copy
215 params = params.to_dict()
216 if 'cpu_bw' in params:
217 cpu_period = int(dcs.get(dc_label).net.cpu_period)
218 value = params.get('cpu_bw')
219 cpu_quota = int(cpu_period * float(value))
220 #put default values back
221 if float(value) <= 0:
222 cpu_period = 100000
223 cpu_quota = -1
224 params['cpu_period'] = cpu_period
225 params['cpu_quota'] = cpu_quota
226 #d.updateCpuLimit(cpu_period=cpu_period, cpu_quota=cpu_quota)
227
228 # only pass allowed keys to docker
229 allowed_keys = ['blkio_weight', 'cpu_period', 'cpu_quota', 'cpu_shares', 'cpuset_cpus',
230 'cpuset_mems', 'mem_limit', 'mem_reservation', 'memswap_limit',
231 'kernel_memory', 'restart_policy']
232 filtered_params = {key:params[key] for key in allowed_keys if key in params}
233
234 d.update_resources(**filtered_params)
235
236 return d
237
238 class DatacenterList(Resource):
239 global dcs
240
241 def get(self):
242 logging.debug("API CALL: datacenter list")
243 try:
244 return [d.getStatus() for d in dcs.itervalues()], 200, CORS_HEADER
245 except Exception as ex:
246 logging.exception("API error.")
247 return ex.message, 500, CORS_HEADER
248
249
250 class DatacenterStatus(Resource):
251 global dcs
252
253 def get(self, dc_label):
254 logging.debug("API CALL: datacenter status")
255 try:
256 return dcs.get(dc_label).getStatus(), 200, CORS_HEADER
257 except Exception as ex:
258 logging.exception("API error.")
259 return ex.message, 500, CORS_HEADER