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.
35 from osm_ro_plugin
.sdnconn
import SdnConnectorBase
, SdnConnectorError
38 class DpbSshInterface
:
39 """ Communicate with the DPB via SSH """
41 __LOGGER_NAME_EXT
= ".ssh"
42 __FUNCTION_MAP_POS
= 1
45 self
, username
, password
, wim_url
, wim_port
, network
, auth_data
, logger_name
47 self
.logger
= logging
.getLogger(logger_name
+ self
.__LOGGER
_NAME
_EXT
)
48 self
.__username
= username
49 self
.__password
= password
51 self
.__port
= wim_port
52 self
.__network
= network
53 self
.__auth
_data
= auth_data
55 self
.__ssh
_client
= self
.__create
_client
()
58 self
.logger
.info("SSH connection to DPB defined")
60 def _check_connection(self
):
61 if not (self
.__stdin
and self
.__stdout
):
62 self
.__stdin
, self
.__stdout
= self
.__connect
()
64 def post(self
, function
, url_params
="", data
=None, get_response
=True):
65 """post request to dpb via ssh
68 - session_id need only be unique per ssh session, thus is currently safe if
71 self
._check
_connection
()
76 url_ext_info
= url_params
.split("/")
78 for i
in range(0, len(url_ext_info
)):
79 if url_ext_info
[i
] == "service":
80 data
["service-id"] = int(url_ext_info
[i
+ 1])
82 data
["type"] = function
[self
.__FUNCTION
_MAP
_POS
]
84 "session": self
.__session
_id
,
87 self
.__session
_id
+= 1
90 data
= json
.dumps(data
).encode("utf-8")
91 data_packed
= struct
.pack(">I" + str(len(data
)) + "s", len(data
), data
)
92 self
.__stdin
.write(data_packed
)
93 self
.logger
.debug("Data sent to DPB via SSH")
94 except Exception as e
:
95 raise SdnConnectorError("Failed to write via SSH | text: {}".format(e
), 500)
98 data_len
= struct
.unpack(">I", self
.__stdout
.read(4))[0]
99 data
= struct
.unpack(str(data_len
) + "s", self
.__stdout
.read(data_len
))[0]
101 return json
.loads(data
).get("content", {})
102 except Exception as e
:
103 raise SdnConnectorError(
104 "Could not get response from WIM | text: {}".format(e
), 500
107 def get(self
, function
, url_params
=""):
108 raise SdnConnectorError("SSH Get not implemented", 500)
110 def __create_client(self
):
111 ssh_client
= paramiko
.SSHClient()
112 ssh_client
.set_missing_host_key_policy(paramiko
.AutoAddPolicy())
120 if self
.__auth
_data
.get("auth_type", "PASS") == "KEY":
121 private_key
= self
.__build
_private
_key
_obj
()
123 if self
.__auth
_data
.get("auth_type", "PASS") == "PASS":
124 password
= self
.__password
127 self
.__ssh
_client
.connect(
130 username
=self
.__username
,
136 stdin
, stdout
, stderr
= self
.__ssh
_client
.exec_command(
137 command
=self
.__network
139 except paramiko
.BadHostKeyException
as e
:
140 raise SdnConnectorError(
141 "Could not add SSH host key | text: {}".format(e
), 500
143 except paramiko
.AuthenticationException
as e
:
144 raise SdnConnectorError(
145 "Could not authorize SSH connection | text: {}".format(e
), 400
147 except paramiko
.SSHException
as e
:
148 raise SdnConnectorError(
149 "Could not establish the SSH connection | text: {}".format(e
), 500
151 except Exception as e
:
152 raise SdnConnectorError(
153 "Unknown error occurred when connecting via SSH | text: {}".format(e
),
158 data_len
= struct
.unpack(">I", stdout
.read(4))[0]
160 struct
.unpack(str(data_len
) + "s", stdout
.read(data_len
))[0]
162 except Exception as e
:
163 raise SdnConnectorError(
164 "Failed to get response from DPB | text: {}".format(e
), 500
168 raise SdnConnectorError(data
.get("msg", data
.get("error", "ERROR")), 500)
170 self
.logger
.info("SSH connection to DPB established OK")
174 def __build_private_key_obj(self
):
176 with
open(self
.__auth
_data
.get("key_file"), "r") as key_file
:
177 if self
.__auth
_data
.get("key_type") == "RSA":
178 return paramiko
.RSAKey
.from_private_key(
179 key_file
, password
=self
.__auth
_data
.get("key_pass", None)
181 elif self
.__auth
_data
.get("key_type") == "ECDSA":
182 return paramiko
.ECDSAKey
.from_private_key(
183 key_file
, password
=self
.__auth
_data
.get("key_pass", None)
186 raise SdnConnectorError("Key type not supported", 400)
187 except Exception as e
:
188 raise SdnConnectorError(
189 "Could not load private SSH key | text: {}".format(e
), 500
193 class DpbRestInterface
:
194 """ Communicate with the DPB via the REST API """
196 __LOGGER_NAME_EXT
= ".rest"
197 __FUNCTION_MAP_POS
= 0
199 def __init__(self
, wim_url
, wim_port
, network
, logger_name
):
200 self
.logger
= logging
.getLogger(logger_name
+ self
.__LOGGER
_NAME
_EXT
)
201 self
.__base
_url
= "http://{}:{}/network/{}".format(
202 wim_url
, str(wim_port
), network
204 self
.logger
.info("REST defined OK")
206 def post(self
, function
, url_params
="", data
=None, get_response
=True):
207 url
= self
.__base
_url
+ url_params
+ "/" + function
[self
.__FUNCTION
_MAP
_POS
]
210 self
.logger
.info(data
)
211 response
= requests
.post(url
, json
=data
)
213 if response
.status_code
!= 200:
214 raise SdnConnectorError(
215 "REST request failed (status code: {})".format(response
.status_code
)
219 return response
.json()
220 except Exception as e
:
221 raise SdnConnectorError("REST request failed | text: {}".format(e
), 500)
223 def get(self
, function
, url_params
=""):
224 url
= self
.__base
_url
+ url_params
+ function
[self
.__FUNCTION
_MAP
_POS
]
227 return requests
.get(url
)
228 except Exception as e
:
229 raise SdnConnectorError("REST request failed | text: {}".format(e
), 500)
232 class DpbConnector(SdnConnectorBase
):
233 """ Use the DPB to establish multipoint connections """
235 __LOGGER_NAME
= "ro.sdn.dpb"
236 __SUPPORTED_SERV_TYPES
= ["ELAN (L2)", "ELINE (L2)"]
237 __SUPPORTED_CONNECTION_TYPES
= ["REST", "SSH"]
238 __SUPPORTED_SSH_AUTH_TYPES
= ["KEY", "PASS"]
239 __SUPPORTED_SSH_KEY_TYPES
= ["ECDSA", "RSA"]
240 __STATUS_MAP
= {"ACTIVE": "ACTIVE", "ACTIVATING": "BUILD", "FAILED": "ERROR"}
242 "CREATE": ("create-service", "new-service"),
243 "DEFINE": ("define", "define-service"),
244 "ACTIVATE": ("activate", "activate-service"),
245 "RELEASE": ("release", "release-service"),
246 "DEACTIVATE": ("deactivate", "deactivate-service"),
247 "CHECK": ("await-status", "await-service-status"),
248 "GET": ("services", "NOT IMPLEMENTED"),
249 "RESET": ("reset", "NOT IMPLEMENTED"),
252 def __init__(self
, wim
, wim_account
, config
):
253 self
.logger
= logging
.getLogger(self
.__LOGGER
_NAME
)
256 self
.__account
= wim_account
257 self
.__config
= config
258 self
.__cli
_config
= self
.__account
.pop("config", None)
260 self
.__url
= self
.__wim
.get("wim_url", "")
261 self
.__password
= self
.__account
.get("passwd", "")
262 self
.__username
= self
.__account
.get("user", "")
263 self
.__network
= self
.__cli
_config
.get("network", "")
264 self
.__connection
_type
= self
.__cli
_config
.get("connection_type", "REST")
265 self
.__port
= self
.__cli
_config
.get(
266 "port", (80 if self
.__connection
_type
== "REST" else 22)
268 self
.__ssh
_auth
= self
.__cli
_config
.get("ssh_auth", None)
270 if self
.__connection
_type
== "SSH":
271 interface
= DpbSshInterface(
280 elif self
.__connection
_type
== "REST":
281 interface
= DpbRestInterface(
282 self
.__url
, self
.__port
, self
.__network
, self
.__LOGGER
_NAME
285 raise SdnConnectorError(
286 "Connection type not supported (must be SSH or REST)", 400
289 self
.__post
= interface
.post
290 self
.__get
= interface
.get
291 self
.logger
.info("DPB WimConn Init OK")
293 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
294 self
.logger
.info("Creating a connectivity service")
297 response
= self
.__post
(self
.__ACTIONS
_MAP
.get("CREATE"))
299 if "service-id" in response
:
300 service_id
= int(response
.get("service-id"))
301 self
.logger
.debug("created service id {}".format(service_id
))
303 raise SdnConnectorError(
304 "Invalid create service response (could be an issue with the DPB)",
308 data
= {"segment": []}
310 for point
in connection_points
:
311 data
["segment"].append(
313 "terminal-name": point
.get("service_endpoint_id"),
315 (point
.get("service_endpoint_encapsulation_info")).get(
323 # "ingress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("ingress"),
324 # "egress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("egress")}
327 self
.__ACTIONS
_MAP
.get("DEFINE"),
328 "/service/" + str(service_id
),
333 self
.__ACTIONS
_MAP
.get("ACTIVATE"),
334 "/service/" + str(service_id
),
337 self
.logger
.debug("Created connectivity service id:{}".format(service_id
))
339 return (str(service_id
), None)
340 except Exception as e
:
341 raise SdnConnectorError(
342 "Connectivity service could not be made | text: {}".format(e
), 500
345 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
347 "Checking connectivity service status id:{}".format(service_uuid
)
349 data
= {"timeout-millis": 10000, "acceptable": ["ACTIVE", "FAILED"]}
352 response
= self
.__post
(
353 self
.__ACTIONS
_MAP
.get("CHECK"),
354 "/service/" + service_uuid
,
358 if "status" in response
:
359 status
= response
.get("status", None)
360 self
.logger
.info("CHECKED CONNECTIVITY SERVICE STATUS")
362 return {"wim_status": self
.__STATUS
_MAP
.get(status
)}
364 raise SdnConnectorError(
365 "Invalid status check response (could be an issue with the DPB)",
368 except Exception as e
:
369 raise SdnConnectorError(
370 "Failed to check service status | text: {}".format(e
), 500
373 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
374 self
.logger
.info("Deleting connectivity service id: {}".format(service_uuid
))
378 self
.__ACTIONS
_MAP
.get("RELEASE"),
379 "/service/" + service_uuid
,
382 except Exception as e
:
383 raise SdnConnectorError(
384 "Could not delete service id:{} (could be an issue with the DPB): {}".format(
390 self
.logger
.debug("Deleted connectivity service id:{}".format(service_uuid
))
394 def edit_connectivity_service(
395 self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
397 self
.logger
.info("Editing connectivity service id: {}".format(service_uuid
))
398 data
= {"timeout-millis": 10000, "acceptable": ["DORMANT"]}
402 self
.__ACTIONS
_MAP
.get("RESET"),
403 "/service/" + service_uuid
,
406 response
= self
.__post
(
407 self
.__ACTIONS
_MAP
.get("CHECK"),
408 "/service/" + service_uuid
,
412 if "status" in response
:
413 self
.logger
.debug("Connectivity service {} reset".format(service_uuid
))
415 raise SdnConnectorError(
416 "Invalid status check response (could be an issue with the DPB)",
419 except Exception as e
:
420 raise SdnConnectorError("Failed to reset service | text: {}".format(e
), 500)
423 data
= {"segment": []}
425 for point
in connection_points
:
426 data
["segment"].append(
428 "terminal-name": point
.get("service_endpoint_id"),
430 (point
.get("service_endpoint_encapsulation_info")).get(
438 # "ingress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("ingress"),
439 # "egress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("egress")}
442 self
.__ACTIONS
_MAP
.get("DEFINE"),
443 "/service/" + str(service_uuid
),
448 self
.__ACTIONS
_MAP
.get("ACTIVATE"),
449 "/service/" + str(service_uuid
),
452 except Exception as e
:
453 raise SdnConnectorError(
454 "Failed to edit connectivity service | text: {}".format(e
), 500
457 self
.logger
.debug("Edited connectivity service {}".format(service_uuid
))
461 def __check_service(self
, serv_type
, points
, kwargs
):
462 if serv_type
not in self
.__SUPPORTED
_SERV
_TYPES
:
463 raise SdnConnectorError("Service type no supported", 400)
464 # Future: BW Checks here