2 # Copyright 2020 University of Lancaster - High Performance Networks Research
6 # Contributors: Will Fantom, Paul McCherry
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
12 # http://www.apache.org/licenses/LICENSE-2.0
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
20 # products derived from this software without specific prior written permission.
22 # This work has been performed in the context of DCMS UK 5G Testbeds
23 # & Trials Programme and in the framework of the Metro-Haul project -
24 # funded by the European Commission under Grant number 761727 through the
25 # Horizon 2020 and 5G-PPP programmes.
32 from osm_ro_plugin
.sdnconn
import SdnConnectorBase
, SdnConnectorError
37 class DpbSshInterface
:
38 """Communicate with the DPB via SSH"""
40 __LOGGER_NAME_EXT
= ".ssh"
41 __FUNCTION_MAP_POS
= 1
44 self
, username
, password
, wim_url
, wim_port
, network
, auth_data
, logger_name
46 self
.logger
= logging
.getLogger(logger_name
+ self
.__LOGGER
_NAME
_EXT
)
47 self
.__username
= username
48 self
.__password
= password
50 self
.__port
= wim_port
51 self
.__network
= network
52 self
.__auth
_data
= auth_data
54 self
.__ssh
_client
= self
.__create
_client
()
57 self
.logger
.info("SSH connection to DPB defined")
59 def _check_connection(self
):
60 if not (self
.__stdin
and self
.__stdout
):
61 self
.__stdin
, self
.__stdout
= self
.__connect
()
63 def post(self
, function
, url_params
="", data
=None, get_response
=True):
64 """post request to dpb via ssh
67 - session_id need only be unique per ssh session, thus is currently safe if
70 self
._check
_connection
()
75 url_ext_info
= url_params
.split("/")
77 for i
in range(0, len(url_ext_info
)):
78 if url_ext_info
[i
] == "service":
79 data
["service-id"] = int(url_ext_info
[i
+ 1])
81 data
["type"] = function
[self
.__FUNCTION
_MAP
_POS
]
83 "session": self
.__session
_id
,
86 self
.__session
_id
+= 1
89 data
= json
.dumps(data
).encode("utf-8")
90 data_packed
= struct
.pack(">I" + str(len(data
)) + "s", len(data
), data
)
91 self
.__stdin
.write(data_packed
)
92 self
.logger
.debug("Data sent to DPB via SSH")
93 except Exception as e
:
94 raise SdnConnectorError("Failed to write via SSH | text: {}".format(e
), 500)
97 data_len
= struct
.unpack(">I", self
.__stdout
.read(4))[0]
98 data
= struct
.unpack(str(data_len
) + "s", self
.__stdout
.read(data_len
))[0]
100 return json
.loads(data
).get("content", {})
101 except Exception as e
:
102 raise SdnConnectorError(
103 "Could not get response from WIM | text: {}".format(e
), 500
106 def get(self
, function
, url_params
=""):
107 raise SdnConnectorError("SSH Get not implemented", 500)
109 def __create_client(self
):
110 ssh_client
= paramiko
.SSHClient()
111 ssh_client
.set_missing_host_key_policy(paramiko
.AutoAddPolicy())
119 if self
.__auth
_data
.get("auth_type", "PASS") == "KEY":
120 private_key
= self
.__build
_private
_key
_obj
()
122 if self
.__auth
_data
.get("auth_type", "PASS") == "PASS":
123 password
= self
.__password
126 self
.__ssh
_client
.connect(
129 username
=self
.__username
,
135 stdin
, stdout
, stderr
= self
.__ssh
_client
.exec_command(
136 command
=self
.__network
138 except paramiko
.BadHostKeyException
as e
:
139 raise SdnConnectorError(
140 "Could not add SSH host key | text: {}".format(e
), 500
142 except paramiko
.AuthenticationException
as e
:
143 raise SdnConnectorError(
144 "Could not authorize SSH connection | text: {}".format(e
), 400
146 except paramiko
.SSHException
as e
:
147 raise SdnConnectorError(
148 "Could not establish the SSH connection | text: {}".format(e
), 500
150 except Exception as e
:
151 raise SdnConnectorError(
152 "Unknown error occurred when connecting via SSH | text: {}".format(e
),
157 data_len
= struct
.unpack(">I", stdout
.read(4))[0]
159 struct
.unpack(str(data_len
) + "s", stdout
.read(data_len
))[0]
161 except Exception as e
:
162 raise SdnConnectorError(
163 "Failed to get response from DPB | text: {}".format(e
), 500
167 raise SdnConnectorError(data
.get("msg", data
.get("error", "ERROR")), 500)
169 self
.logger
.info("SSH connection to DPB established OK")
173 def __build_private_key_obj(self
):
175 with
open(self
.__auth
_data
.get("key_file"), "r") as key_file
:
176 if self
.__auth
_data
.get("key_type") == "RSA":
177 return paramiko
.RSAKey
.from_private_key(
178 key_file
, password
=self
.__auth
_data
.get("key_pass", None)
180 elif self
.__auth
_data
.get("key_type") == "ECDSA":
181 return paramiko
.ECDSAKey
.from_private_key(
182 key_file
, password
=self
.__auth
_data
.get("key_pass", None)
185 raise SdnConnectorError("Key type not supported", 400)
186 except Exception as e
:
187 raise SdnConnectorError(
188 "Could not load private SSH key | text: {}".format(e
), 500
192 class DpbRestInterface
:
193 """Communicate with the DPB via the REST API"""
195 __LOGGER_NAME_EXT
= ".rest"
196 __FUNCTION_MAP_POS
= 0
198 def __init__(self
, wim_url
, wim_port
, network
, logger_name
):
199 self
.logger
= logging
.getLogger(logger_name
+ self
.__LOGGER
_NAME
_EXT
)
200 self
.__base
_url
= "http://{}:{}/network/{}".format(
201 wim_url
, str(wim_port
), network
203 self
.logger
.info("REST defined OK")
205 def post(self
, function
, url_params
="", data
=None, get_response
=True):
206 url
= self
.__base
_url
+ url_params
+ "/" + function
[self
.__FUNCTION
_MAP
_POS
]
209 self
.logger
.info(data
)
210 response
= requests
.post(url
, json
=data
)
212 if response
.status_code
!= 200:
213 raise SdnConnectorError(
214 "REST request failed (status code: {})".format(response
.status_code
)
218 return response
.json()
219 except Exception as e
:
220 raise SdnConnectorError("REST request failed | text: {}".format(e
), 500)
222 def get(self
, function
, url_params
=""):
223 url
= self
.__base
_url
+ url_params
+ function
[self
.__FUNCTION
_MAP
_POS
]
226 return requests
.get(url
)
227 except Exception as e
:
228 raise SdnConnectorError("REST request failed | text: {}".format(e
), 500)
231 class DpbConnector(SdnConnectorBase
):
232 """Use the DPB to establish multipoint connections"""
234 __LOGGER_NAME
= "ro.sdn.dpb"
235 __SUPPORTED_SERV_TYPES
= ["ELAN (L2)", "ELINE (L2)"]
236 __SUPPORTED_CONNECTION_TYPES
= ["REST", "SSH"]
237 __SUPPORTED_SSH_AUTH_TYPES
= ["KEY", "PASS"]
238 __SUPPORTED_SSH_KEY_TYPES
= ["ECDSA", "RSA"]
239 __STATUS_MAP
= {"ACTIVE": "ACTIVE", "ACTIVATING": "BUILD", "FAILED": "ERROR"}
241 "CREATE": ("create-service", "new-service"),
242 "DEFINE": ("define", "define-service"),
243 "ACTIVATE": ("activate", "activate-service"),
244 "RELEASE": ("release", "release-service"),
245 "DEACTIVATE": ("deactivate", "deactivate-service"),
246 "CHECK": ("await-status", "await-service-status"),
247 "GET": ("services", "NOT IMPLEMENTED"),
248 "RESET": ("reset", "NOT IMPLEMENTED"),
251 def __init__(self
, wim
, wim_account
, config
):
252 self
.logger
= logging
.getLogger(self
.__LOGGER
_NAME
)
255 self
.__account
= wim_account
256 self
.__config
= config
257 self
.__cli
_config
= self
.__account
.pop("config", None)
259 self
.__url
= self
.__wim
.get("wim_url", "")
260 self
.__password
= self
.__account
.get("passwd", "")
261 self
.__username
= self
.__account
.get("user", "")
262 self
.__network
= self
.__cli
_config
.get("network", "")
263 self
.__connection
_type
= self
.__cli
_config
.get("connection_type", "REST")
264 self
.__port
= self
.__cli
_config
.get(
265 "port", (80 if self
.__connection
_type
== "REST" else 22)
267 self
.__ssh
_auth
= self
.__cli
_config
.get("ssh_auth", None)
269 if self
.__connection
_type
== "SSH":
270 interface
= DpbSshInterface(
279 elif self
.__connection
_type
== "REST":
280 interface
= DpbRestInterface(
281 self
.__url
, self
.__port
, self
.__network
, self
.__LOGGER
_NAME
284 raise SdnConnectorError(
285 "Connection type not supported (must be SSH or REST)", 400
288 self
.__post
= interface
.post
289 self
.__get
= interface
.get
290 self
.logger
.info("DPB WimConn Init OK")
292 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
293 self
.logger
.info("Creating a connectivity service")
296 response
= self
.__post
(self
.__ACTIONS
_MAP
.get("CREATE"))
298 if "service-id" in response
:
299 service_id
= int(response
.get("service-id"))
300 self
.logger
.debug("created service id {}".format(service_id
))
302 raise SdnConnectorError(
303 "Invalid create service response (could be an issue with the DPB)",
307 data
= {"segment": []}
309 for point
in connection_points
:
310 data
["segment"].append(
312 "terminal-name": point
.get("service_endpoint_id"),
314 (point
.get("service_endpoint_encapsulation_info")).get(
322 # "ingress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("ingress"),
323 # "egress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("egress")}
326 self
.__ACTIONS
_MAP
.get("DEFINE"),
327 "/service/" + str(service_id
),
332 self
.__ACTIONS
_MAP
.get("ACTIVATE"),
333 "/service/" + str(service_id
),
336 self
.logger
.debug("Created connectivity service id:{}".format(service_id
))
338 return (str(service_id
), None)
339 except Exception as e
:
340 raise SdnConnectorError(
341 "Connectivity service could not be made | text: {}".format(e
), 500
344 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
346 "Checking connectivity service status id:{}".format(service_uuid
)
348 data
= {"timeout-millis": 10000, "acceptable": ["ACTIVE", "FAILED"]}
351 response
= self
.__post
(
352 self
.__ACTIONS
_MAP
.get("CHECK"),
353 "/service/" + service_uuid
,
357 if "status" in response
:
358 status
= response
.get("status", None)
359 self
.logger
.info("CHECKED CONNECTIVITY SERVICE STATUS")
361 return {"wim_status": self
.__STATUS
_MAP
.get(status
)}
363 raise SdnConnectorError(
364 "Invalid status check response (could be an issue with the DPB)",
367 except Exception as e
:
368 raise SdnConnectorError(
369 "Failed to check service status | text: {}".format(e
), 500
372 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
373 self
.logger
.info("Deleting connectivity service id: {}".format(service_uuid
))
377 self
.__ACTIONS
_MAP
.get("RELEASE"),
378 "/service/" + service_uuid
,
381 except Exception as e
:
382 raise SdnConnectorError(
383 "Could not delete service id:{} (could be an issue with the DPB): {}".format(
389 self
.logger
.debug("Deleted connectivity service id:{}".format(service_uuid
))
393 def edit_connectivity_service(
394 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
396 self
.logger
.info("Editing connectivity service id: {}".format(service_uuid
))
397 data
= {"timeout-millis": 10000, "acceptable": ["DORMANT"]}
401 self
.__ACTIONS
_MAP
.get("RESET"),
402 "/service/" + service_uuid
,
405 response
= self
.__post
(
406 self
.__ACTIONS
_MAP
.get("CHECK"),
407 "/service/" + service_uuid
,
411 if "status" in response
:
412 self
.logger
.debug("Connectivity service {} reset".format(service_uuid
))
414 raise SdnConnectorError(
415 "Invalid status check response (could be an issue with the DPB)",
418 except Exception as e
:
419 raise SdnConnectorError("Failed to reset service | text: {}".format(e
), 500)
422 data
= {"segment": []}
424 for point
in connection_points
:
425 data
["segment"].append(
427 "terminal-name": point
.get("service_endpoint_id"),
429 (point
.get("service_endpoint_encapsulation_info")).get(
437 # "ingress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("ingress"),
438 # "egress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("egress")}
441 self
.__ACTIONS
_MAP
.get("DEFINE"),
442 "/service/" + str(service_uuid
),
447 self
.__ACTIONS
_MAP
.get("ACTIVATE"),
448 "/service/" + str(service_uuid
),
451 except Exception as e
:
452 raise SdnConnectorError(
453 "Failed to edit connectivity service | text: {}".format(e
), 500
456 self
.logger
.debug("Edited connectivity service {}".format(service_uuid
))
460 def __check_service(self
, serv_type
, points
, kwargs
):
461 if serv_type
not in self
.__SUPPORTED
_SERV
_TYPES
:
462 raise SdnConnectorError("Service type no supported", 400)
463 # Future: BW Checks here