1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 # Neither the name of the SONATA-NFV, Paderborn University
17 # nor the names of its contributors may be used to endorse or promote
18 # products derived from this software without specific prior written
21 # This work has been performed in the framework of the SONATA project,
22 # funded by the European Commission under Grant number 671517 through
23 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
24 # acknowledge the contributions of their colleagues of the SONATA
25 # partner consortium (www.sonata-nfv.eu).
27 from flask_restful
import Resource
28 from flask
import request
34 CORS_HEADER
= {'Access-Control-Allow-Origin': '*'}
36 # the dcs dict is set in the rest_api_endpoint.py upon datacenter init
40 class Compute(Resource
):
42 Start a new compute instance: A docker container (note: zerorpc does not support keyword arguments)
43 :param dc_label: name of the DC
44 :param compute_name: compute container name
45 :param image: image name
46 :param command: command to execute
47 :param network: list of all interface of the vnf, with their parameters (id=id1,ip=x.x.x.x/x),...
48 example networks list({"id":"input","ip": "10.0.0.254/8"}, {"id":"output","ip": "11.0.0.254/24"})
49 :return: docker inspect dict of deployed docker
54 def put(self
, dc_label
, compute_name
, resource
=None, value
=None):
56 # deploy new container
57 # check if json data is a dict
61 elif not isinstance(data
, dict):
62 data
= json
.loads(request
.json
)
64 network
= data
.get("network")
65 nw_list
= self
._parse
_network
(network
)
66 image
= data
.get("image")
67 command
= data
.get("docker_command")
70 if compute_name
is None or compute_name
== "None":
71 logging
.error("No compute name defined in request.")
72 return "No compute name defined in request.", 500, CORS_HEADER
73 if dc_label
is None or dcs
.get(dc_label
) is None:
74 logging
.error("No datacenter defined in request.")
75 return "No datacenter defined in request.", 500, CORS_HEADER
76 c
= dcs
.get(dc_label
).startCompute(
77 compute_name
, image
=image
, command
=command
, network
=nw_list
)
78 # (if available) trigger emu. entry point given in Dockerfile
80 config
= c
.dcinfo
.get("Config", dict())
81 env
= config
.get("Env", list())
82 legacy_command_execution
= False
84 var
, cmd
= map(str.strip
, map(str, env_var
.split('=', 1)))
85 logging
.debug("%r = %r" % (var
, cmd
))
86 if var
== "SON_EMU_CMD" or var
== "VIM_EMU_CMD":
87 logging
.info("Executing script in '{}': {}={}"
88 .format(compute_name
, var
, cmd
))
89 # execute command in new thread to ensure that API is
91 t
= threading
.Thread(target
=c
.cmdPrint
, args
=(cmd
,))
94 legacy_command_execution
= True
96 if not legacy_command_execution
:
98 except Exception as ex
:
99 logging
.warning("Couldn't run Docker entry point VIM_EMU_CMD")
101 "Exception: " + str(ex
) + "; " + str(type(ex
))
103 # return docker inspect dict
104 return c
.getStatus(), 200, CORS_HEADER
105 except Exception as ex
:
106 logging
.exception("API error.")
107 return ex
.message
, 500, CORS_HEADER
109 def get(self
, dc_label
, compute_name
):
111 logging
.debug("API CALL: compute status")
114 return dcs
.get(dc_label
).containers
.get(
115 compute_name
).getStatus(), 200, CORS_HEADER
116 except Exception as ex
:
117 logging
.exception("API error.")
118 return ex
.message
, 500, CORS_HEADER
120 def delete(self
, dc_label
, compute_name
):
121 logging
.debug("API CALL: compute stop")
123 return dcs
.get(dc_label
).stopCompute(
124 compute_name
), 200, CORS_HEADER
125 except Exception as ex
:
126 logging
.exception("API error.")
127 return ex
.message
, 500, CORS_HEADER
129 def _parse_network(self
, network_str
):
131 parse the options for all network interfaces of the vnf
132 :param network_str: (id=x,ip=x.x.x.x/x), ...
133 :return: list of dicts [{"id":x,"ip":"x.x.x.x/x"}, ...]
137 # TODO make this more robust with regex check
138 if network_str
is None:
141 networks
= network_str
[1:-1].split('),(')
143 nw_dict
= dict(tuple(e
.split('=')) for e
in nw
.split(','))
144 nw_list
.append(nw_dict
)
149 class ComputeList(Resource
):
152 def get(self
, dc_label
=None):
153 logging
.debug("API CALL: compute list")
155 if dc_label
is None or dc_label
== 'None':
156 # return list with all compute nodes in all DCs
158 for dc
in dcs
.values():
159 all_containers
+= dc
.listCompute()
160 container_list
= [(c
.name
, c
.getStatus())
161 for c
in all_containers
]
162 return container_list
, 200, CORS_HEADER
164 # return list of compute nodes for specified DC
165 container_list
= [(c
.name
, c
.getStatus())
166 for c
in dcs
.get(dc_label
).listCompute()]
167 return container_list
, 200, CORS_HEADER
168 except Exception as ex
:
169 logging
.exception("API error.")
170 return ex
.message
, 500, CORS_HEADER
173 class ComputeResources(Resource
):
175 Update the container's resources using the docker.update function
176 re-using the same parameters:
179 cpu_period, cpu_quota, cpu_shares
187 see https://docs.docker.com/engine/reference/commandline/update/
188 or API docs: https://docker-py.readthedocs.io/en/stable/api.html#module-docker.api.container
189 :param dc_label: name of the DC
190 :param compute_name: compute container name
192 :return: docker inspect dict of deployed docker
196 def put(self
, dc_label
, compute_name
):
197 logging
.debug("REST CALL: update container resources")
200 c
= self
._update
_resources
(dc_label
, compute_name
)
201 return c
.getStatus(), 200, CORS_HEADER
202 except Exception as ex
:
203 logging
.exception("API error.")
204 return ex
.message
, 500, CORS_HEADER
206 def _update_resources(self
, dc_label
, compute_name
):
209 params
= request
.args
214 "REST CALL: update container resources {0}".format(params
))
215 # check if container exists
216 d
= dcs
.get(dc_label
).net
.getNodeByName(compute_name
)
218 # general request of cpu percentage
219 # create a mutable copy
220 params
= params
.to_dict()
221 if 'cpu_bw' in params
:
222 cpu_period
= int(dcs
.get(dc_label
).net
.cpu_period
)
223 value
= params
.get('cpu_bw')
224 cpu_quota
= int(cpu_period
* float(value
))
225 # put default values back
226 if float(value
) <= 0:
229 params
['cpu_period'] = cpu_period
230 params
['cpu_quota'] = cpu_quota
231 # d.updateCpuLimit(cpu_period=cpu_period, cpu_quota=cpu_quota)
233 # only pass allowed keys to docker
234 allowed_keys
= ['blkio_weight', 'cpu_period', 'cpu_quota', 'cpu_shares', 'cpuset_cpus',
235 'cpuset_mems', 'mem_limit', 'mem_reservation', 'memswap_limit',
236 'kernel_memory', 'restart_policy']
237 filtered_params
= {key
: params
[key
]
238 for key
in allowed_keys
if key
in params
}
240 d
.update_resources(**filtered_params
)
245 class DatacenterList(Resource
):
249 logging
.debug("API CALL: datacenter list")
251 return [d
.getStatus() for d
in dcs
.values()], 200, CORS_HEADER
252 except Exception as ex
:
253 logging
.exception("API error.")
254 return ex
.message
, 500, CORS_HEADER
257 class DatacenterStatus(Resource
):
260 def get(self
, dc_label
):
261 logging
.debug("API CALL: datacenter status")
263 return dcs
.get(dc_label
).getStatus(), 200, CORS_HEADER
264 except Exception as ex
:
265 logging
.exception("API error.")
266 return ex
.message
, 500, CORS_HEADER