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