1 # -*- coding: utf-8 -*-
4 # Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U.
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may
8 # not use this file except in compliance with the License. You may obtain
9 # a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 # License for the specific language governing permissions and limitations
21 This is the thread for the http server North API.
22 Two thread will be launched, with normal and administrative permissions.
25 from uuid
import uuid4
26 from http
import HTTPStatus
28 __author__
= "Alfonso Tierno"
29 __date__
= "2019-10-22"
31 version_date
= "Oct 2019"
34 class SdnException(Exception):
35 def __init__(self
, message
, http_code
=HTTPStatus
.BAD_REQUEST
.value
):
36 self
.http_code
= http_code
37 Exception.__init
__(self
, message
)
42 def __init__(self
, db
, plugins
):
44 self
.plugins
= plugins
46 def start_service(self
):
47 pass # TODO py3 needed to load wims and plugins
49 def stop_service(self
):
52 def show_network(self
, uuid
):
55 def delete_network(self
, uuid
):
58 def new_network(self
, network
):
61 def get_openflow_rules(self
, network_id
=None):
63 Get openflow id from DB
64 :param network_id: Network id, if none all networks will be retrieved
65 :return: Return a list with Openflow rules per net
72 where_
= {"net_id": network_id
}
73 result
, content
= self
.db
.get_table(
74 SELECT
=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
75 WHERE
=where_
, FROM
='of_flows')
78 raise SdnException(str(content
), -result
)
81 def edit_openflow_rules(self
, network_id
=None):
83 To make actions over the net. The action is to reinstall the openflow rules
84 network_id can be 'all'
85 :param network_id: Network id, if none all networks will be retrieved
86 :return : Number of nets updated
93 where_
= {"uuid": network_id
}
94 result
, content
= self
.db
.get_table(SELECT
=("uuid", "type"), WHERE
=where_
, FROM
='nets')
97 raise SdnException(str(content
), -result
)
100 if net
["type"] != "ptp" and net
["type"] != "data":
105 self
.net_update_ofc_thread(net
['uuid'])
106 except SdnException
as e
:
107 raise SdnException("Error updating network'{}' {}".format(net
['uuid'], e
),
108 HTTPStatus
.INTERNAL_SERVER_ERROR
.value
)
109 except Exception as e
:
110 raise SdnException("Error updating network '{}' {}".format(net
['uuid'], e
),
111 HTTPStatus
.INTERNAL_SERVER_ERROR
.value
)
115 def delete_openflow_rules(self
, ofc_id
=None):
117 To make actions over the net. The action is to delete ALL openflow rules
118 :return: return operation result
122 if 'Default' in self
.config
['ofcs_thread']:
123 r
, c
= self
.config
['ofcs_thread']['Default'].insert_task("clear-all")
125 raise SdnException("Default Openflow controller not not running", HTTPStatus
.NOT_FOUND
.value
)
127 elif ofc_id
in self
.config
['ofcs_thread']:
128 r
, c
= self
.config
['ofcs_thread'][ofc_id
].insert_task("clear-all")
132 raise SdnException(str(c
), -r
)
134 raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id
),
135 HTTPStatus
.NOT_FOUND
.value
)
138 def get_openflow_ports(self
, ofc_id
=None):
140 Obtain switch ports names of openflow controller
141 :return: Return flow ports in DB
144 if 'Default' in self
.config
['ofcs_thread']:
145 conn
= self
.config
['ofcs_thread']['Default'].OF_connector
147 raise SdnException("Default Openflow controller not not running", HTTPStatus
.NOT_FOUND
.value
)
149 elif ofc_id
in self
.config
['ofcs_thread']:
150 conn
= self
.config
['ofcs_thread'][ofc_id
].OF_connector
152 raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id
),
153 HTTPStatus
.NOT_FOUND
.value
)
156 def new_of_controller(self
, ofc_data
):
158 Create a new openflow controller into DB
159 :param ofc_data: Dict openflow controller data
160 :return: openflow controller uuid
163 "uuid": str(uuid4()),
164 "name": ofc_data
["name"],
165 "description": ofc_data
.get("description"),
166 "type": ofc_data
["type"],
167 "wim_url": ofc_data
.get("url"),
169 if not db_wim
["wim_url"]:
170 if not ofc_data
.get("ip") or not ofc_data
.get("port"):
171 raise SdnException("Provide either 'url' or both 'ip' and 'port'")
172 db_wim
["wim_url"] = "{}:{}".format(ofc_data
["ip"], ofc_data
["port"])
175 "uuid": str(uuid4()),
176 "name": ofc_data
["name"],
177 "wim_id": db_wim
["uuid"],
179 "user": ofc_data
.get("user"),
180 "password": ofc_data
.get("password"),
182 db_wim_account_config
= ofc_data
.get("config", {})
183 if ofc_data
.get("dpid"):
184 db_wim_account_config
["dpid"] = ofc_data
["dpid"]
185 if ofc_data
.get("version"):
186 db_wim_account_config
["version"] = ofc_data
["version"]
188 db_wim_account
["config"] = yaml
.safe_dump(db_wim_account_config
, default_flow_style
=True, width
=256)
192 {"wim_accounts": db_wim_account
},
194 uuid_list
= [db_wim
["uuid"], db_wim_account
["uuid"]]
195 self
.db
.new_rows(db_tables
, uuid_list
)
196 return db_wim_account
["uuid"]
198 def edit_of_controller(self
, of_id
, ofc_data
):
200 Edit an openflow controller entry from DB
204 raise SdnException("No data received during uptade OF contorller",
205 http_code
=HTTPStatus
.INTERNAL_SERVER_ERROR
.value
)
207 # get database wim_accounts
208 wim_account
= self
._get
_of
_controller
(of_id
)
210 db_wim_update
= {x
: ofc_data
[x
] for x
in ("name", "description", "type", "wim_url") if x
in ofc_data
}
211 db_wim_account_update
= {x
: ofc_data
[x
] for x
in ("name", "user", "password") if x
in ofc_data
}
212 db_wim_account_config
= ofc_data
.get("config", {})
214 if ofc_data
.get("ip") or ofc_data
.get("port"):
215 if not ofc_data
.get("ip") or not ofc_data
.get("port"):
216 raise SdnException("Provide or both 'ip' and 'port'")
217 db_wim_update
["wim_url"] = "{}:{}".format(ofc_data
["ip"], ofc_data
["port"])
219 if ofc_data
.get("dpid"):
220 db_wim_account_config
["dpid"] = ofc_data
["dpid"]
221 if ofc_data
.get("version"):
222 db_wim_account_config
["version"] = ofc_data
["version"]
224 if db_wim_account_config
:
225 db_wim_account_update
["config"] = yaml
.load(wim_account
["config"], Loader
=yaml
.Loader
) or {}
226 db_wim_account_update
["config"].update(db_wim_account_config
)
227 db_wim_account_update
["config"] = yaml
.safe_dump(db_wim_account_update
["config"], default_flow_style
=True,
230 if db_wim_account_update
:
231 self
.db
.update_rows('wim_accounts', db_wim_account_update
, WHERE
={'uuid': of_id
})
233 self
.db
.update_rows('wims', db_wim_update
, WHERE
={'uuid': wim_account
["wim_id"]})
235 def _get_of_controller(self
, of_id
):
236 wim_accounts
= self
.db
.get_rows(FROM
='wim_accounts', WHERE
={"uuid": of_id
, "sdn": "true"})
239 raise SdnException("Cannot find sdn controller with id='{}'".format(of_id
),
240 http_code
=HTTPStatus
.NOT_FOUND
.value
)
241 elif len(wim_accounts
) > 1:
242 raise SdnException("Found more than one sdn controller with id='{}'".format(of_id
),
243 http_code
=HTTPStatus
.CONFLICT
.value
)
244 return wim_accounts
[0]
246 def delete_of_controller(self
, of_id
):
248 Delete an openflow controller from DB.
249 :param of_id: openflow controller dpid
252 wim_account
= self
._get
_of
_controller
(of_id
)
253 self
.db
.delete_row(FROM
='wim_accounts', WHERE
={"uuid": of_id
})
254 self
.db
.delete_row(FROM
='wims', WHERE
={"uuid": wim_account
["wim_id"]})
258 def _format_of_controller(wim_account
, wim
=None):
259 of_data
= {x
: wim_account
[x
] for x
in ("uuid", "name", "user")}
260 if isinstance(wim_account
["config"], str):
261 config
= yaml
.load(wim_account
["config"], Loader
=yaml
.Loader
)
262 of_data
["dpid"] = config
.get("switch_id") or config
.get("dpid")
263 of_data
["version"] = config
.get("version")
265 of_data
["url"] = wim
["wim_url"]
266 of_data
["type"] = wim
["type"]
269 def show_of_controller(self
, of_id
):
271 Show an openflow controller by dpid from DB.
272 :param db_filter: List with where query parameters
275 wim_account
= self
._get
_of
_controller
(of_id
)
276 wims
= self
.db
.get_rows(FROM
='wims', WHERE
={"uuid": wim_account
["wim_id"]})
277 return self
._format
_of
_controller
(wim_account
, wims
[0])
279 def get_of_controllers(self
, filter=None):
281 Show an openflow controllers from DB.
284 filter = filter or {}
285 filter["sdn"] = "true"
286 wim_accounts
= self
.db
.get_rows(FROM
='wim_accounts', WHERE
=filter)
287 return [self
._format
_of
_controller
(w
) for w
in wim_accounts
]
289 def set_of_port_mapping(self
, maps
, sdn_id
, switch_dpid
, vim_id
):
291 Create new port mapping entry
292 :param of_maps: List with port mapping information
293 # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
294 "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
295 :param sdn_id: ofc id
296 :param switch_dpid: switch dpid
297 :param vim_id: datacenter
300 # get wim from wim_account
301 wim_account
= self
._get
_of
_controller
(sdn_id
)
302 wim_id
= wim_account
["wim_id"]
303 db_wim_port_mappings
= []
305 _switch_dpid
= map.get("switch_id") or map.get("switch_dpid") or switch_dpid
308 'switch_dpid': _switch_dpid
,
309 "switch_port": map.get("switch_port"),
310 'datacenter_id': vim_id
,
311 "device_id": map.get("compute_node"),
312 "service_endpoint_id": _switch_dpid
+ "-" + str(uuid4())
315 new_map
["device_interface_id"] = map["pci"].lower()
317 if map.get("switch_mac"):
318 config
["switch_mac"] = map["switch_mac"]
320 new_map
["service_mapping_info"] = yaml
.safe_dump(config
, default_flow_style
=True, width
=256)
321 db_wim_port_mappings
.append(new_map
)
324 {"wim_port_mappings": db_wim_port_mappings
},
326 self
.db
.new_rows(db_tables
, [])
327 return db_wim_port_mappings
329 def clear_of_port_mapping(self
, db_filter
=None):
331 Clear port mapping filtering using db_filter dict
332 :param db_filter: Parameter to filter during remove process
335 return self
.db
.delete_row(FROM
='wim_port_mappings', WHERE
=db_filter
)
337 def get_of_port_mappings(self
, db_filter
=None):
339 Retrive port mapping from DB
343 maps
= self
.db
.get_rows(WHERE
=db_filter
, FROM
='wim_port_mappings')
345 if map.get("service_mapping_info"):
346 map["service_mapping_info"] = yaml
.load(map["service_mapping_info"], Loader
=yaml
.Loader
)
348 map["service_mapping_info"] = {}
351 def get_ports(self
, instance_wim_net_id
):
353 instance_wim_net
= self
.db
.get_rows(FROM
='instance_wim_nets', WHERE
={"uuid": instance_wim_net_id
})
354 wim_id
= instance_wim_net
[0]["wim_id"]
356 ports
= self
.db
.get_rows(FROM
='instance_interfaces', WHERE
={"instance_wim_net_id": instance_wim_net_id
})
357 maps
= self
.get_of_port_mappings(db_filter
={"wim_id": wim_id
})
359 map_
= next((x
for x
in maps
if x
.get("device_id") == port
["compute_node"] and
360 x
.get("device_interface_id") == port
["pci"]), None)
362 switch_port
= {'switch_dpid': map_
.get('switch_dpid') or map_
.get('switch_id'),
363 'switch_port': map_
.get('switch_port')}
364 if switch_port
not in switch_ports
:
365 switch_ports
.append(switch_port
)