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.
34 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
43 def __init__(self
, username
, password
, wim_url
, wim_port
, network
, auth_data
, logger_name
):
44 self
.logger
= logging
.getLogger(logger_name
+ self
.__LOGGER
_NAME
_EXT
)
45 self
.__username
= username
46 self
.__password
= password
48 self
.__port
= wim_port
49 self
.__network
= network
50 self
.__auth
_data
= auth_data
52 self
.__ssh
_client
= self
.__create
_client
()
55 self
.logger
.info("SSH connection to DPB defined")
57 def _check_connection(self
):
58 if not (self
.__stdin
and self
.__stdout
):
59 self
.__stdin
, self
.__stdout
= self
.__connect
()
61 def post(self
, function
, url_params
="", data
=None, get_response
=True):
62 """post request to dpb via ssh
65 - session_id need only be unique per ssh session, thus is currently safe if
68 self
._check
_connection
()
71 url_ext_info
= url_params
.split('/')
72 for i
in range(0, len(url_ext_info
)):
73 if url_ext_info
[i
] == "service":
74 data
["service-id"] = int(url_ext_info
[i
+1])
75 data
["type"] = function
[self
.__FUNCTION
_MAP
_POS
]
77 "session": self
.__session
_id
,
80 self
.__session
_id
+= 1
83 data
= json
.dumps(data
).encode("utf-8")
84 data_packed
= struct
.pack(
85 ">I" + str(len(data
)) + "s", len(data
), data
)
86 self
.__stdin
.write(data_packed
)
87 self
.logger
.debug("Data sent to DPB via SSH")
88 except Exception as e
:
89 raise SdnConnectorError(
90 "Failed to write via SSH | text: {}".format(e
), 500)
93 data_len
= struct
.unpack(">I", self
.__stdout
.read(4))[0]
94 data
= struct
.unpack(str(data_len
) + "s",
95 self
.__stdout
.read(data_len
))[0]
96 return json
.loads(data
).get("content", {})
97 except Exception as e
:
98 raise SdnConnectorError(
99 "Could not get response from WIM | text: {}".format(e
), 500)
101 def get(self
, function
, url_params
=""):
102 raise SdnConnectorError("SSH Get not implemented", 500)
104 def __create_client(self
):
105 ssh_client
= paramiko
.SSHClient()
106 ssh_client
.set_missing_host_key_policy(paramiko
.AutoAddPolicy())
112 if self
.__auth
_data
.get("auth_type", "PASS") == "KEY":
113 private_key
= self
.__build
_private
_key
_obj
()
114 if self
.__auth
_data
.get("auth_type", "PASS") == "PASS":
115 password
= self
.__password
118 self
.__ssh
_client
.connect(hostname
=self
.__url
,
120 username
=self
.__username
,
125 stdin
, stdout
, stderr
= self
.__ssh
_client
.exec_command(
126 command
=self
.__network
)
127 except paramiko
.BadHostKeyException
as e
:
128 raise SdnConnectorError(
129 "Could not add SSH host key | text: {}".format(e
), 500)
130 except paramiko
.AuthenticationException
as e
:
131 raise SdnConnectorError(
132 "Could not authorize SSH connection | text: {}".format(e
), 400)
133 except paramiko
.SSHException
as e
:
134 raise SdnConnectorError(
135 "Could not establish the SSH connection | text: {}".format(e
), 500)
136 except Exception as e
:
137 raise SdnConnectorError(
138 "Unknown error occurred when connecting via SSH | text: {}".format(e
), 500)
141 data_len
= struct
.unpack(">I", stdout
.read(4))[0]
142 data
= json
.loads(struct
.unpack(
143 str(data_len
) + "s", stdout
.read(data_len
))[0])
144 except Exception as e
:
145 raise SdnConnectorError(
146 "Failed to get response from DPB | text: {}".format(e
), 500)
148 raise SdnConnectorError(
149 data
.get("msg", data
.get("error", "ERROR")), 500)
150 self
.logger
.info("SSH connection to DPB established OK")
153 def __build_private_key_obj(self
):
155 with
open(self
.__auth
_data
.get("key_file"), 'r') as key_file
:
156 if self
.__auth
_data
.get("key_type") == "RSA":
157 return paramiko
.RSAKey
.from_private_key(key_file
,
158 password
=self
.__auth
_data
.get("key_pass", None))
159 elif self
.__auth
_data
.get("key_type") == "ECDSA":
160 return paramiko
.ECDSAKey
.from_private_key(key_file
,
161 password
=self
.__auth
_data
.get("key_pass", None))
163 raise SdnConnectorError("Key type not supported", 400)
164 except Exception as e
:
165 raise SdnConnectorError(
166 "Could not load private SSH key | text: {}".format(e
), 500)
169 class DpbRestInterface():
170 """ Communicate with the DPB via the REST API """
172 __LOGGER_NAME_EXT
= ".rest"
173 __FUNCTION_MAP_POS
= 0
175 def __init__(self
, wim_url
, wim_port
, network
, logger_name
):
176 self
.logger
= logging
.getLogger(logger_name
+ self
.__LOGGER
_NAME
_EXT
)
177 self
.__base
_url
= "http://{}:{}/network/{}".format(
178 wim_url
, str(wim_port
), network
)
179 self
.logger
.info("REST defined OK")
181 def post(self
, function
, url_params
="", data
=None, get_response
=True):
182 url
= self
.__base
_url
+ url_params
+ \
183 "/" + function
[self
.__FUNCTION
_MAP
_POS
]
185 self
.logger
.info(data
)
186 response
= requests
.post(url
, json
=data
)
187 if response
.status_code
!= 200:
188 raise SdnConnectorError(
189 "REST request failed (status code: {})".format(response
.status_code
))
191 return response
.json()
192 except Exception as e
:
193 raise SdnConnectorError(
194 "REST request failed | text: {}".format(e
), 500)
196 def get(self
, function
, url_params
=""):
197 url
= self
.__base
_url
+ url_params
+ function
[self
.__FUNCTION
_MAP
_POS
]
199 return requests
.get(url
)
200 except Exception as e
:
201 raise SdnConnectorError(
202 "REST request failed | text: {}".format(e
), 500)
205 class DpbConnector(SdnConnectorBase
):
206 """ Use the DPB to establish multipoint connections """
208 __LOGGER_NAME
= "openmano.rosdnconn.dpb"
209 __SUPPORTED_SERV_TYPES
= ["ELAN (L2)", "ELINE (L2)"]
210 __SUPPORTED_CONNECTION_TYPES
= ["REST", "SSH"]
211 __SUPPORTED_SSH_AUTH_TYPES
= ["KEY", "PASS"]
212 __SUPPORTED_SSH_KEY_TYPES
= ["ECDSA", "RSA"]
215 "ACTIVATING": "BUILD",
218 "CREATE": ("create-service", "new-service"),
219 "DEFINE": ("define", "define-service"),
220 "ACTIVATE": ("activate", "activate-service"),
221 "RELEASE": ("release", "release-service"),
222 "DEACTIVATE": ("deactivate", "deactivate-service"),
223 "CHECK": ("await-status", "await-service-status"),
224 "GET": ("services", "NOT IMPLEMENTED"),
225 "RESET": ("reset", "NOT IMPLEMENTED")
228 def __init__(self
, wim
, wim_account
, config
):
229 self
.logger
= logging
.getLogger(self
.__LOGGER
_NAME
)
232 self
.__account
= wim_account
233 self
.__config
= config
234 self
.__cli
_config
= self
.__account
.pop("config", None)
236 self
.__url
= self
.__wim
.get("wim_url", "")
237 self
.__password
= self
.__account
.get("passwd", "")
238 self
.__username
= self
.__account
.get("user", "")
239 self
.__network
= self
.__cli
_config
.get("network", "")
240 self
.__connection
_type
= self
.__cli
_config
.get(
241 "connection_type", "REST")
242 self
.__port
= self
.__cli
_config
.get(
243 "port", (80 if self
.__connection
_type
== "REST" else 22))
244 self
.__ssh
_auth
= self
.__cli
_config
.get("ssh_auth", None)
246 if self
.__connection
_type
== "SSH":
247 interface
= DpbSshInterface(self
.__username
,
254 elif self
.__connection
_type
== "REST":
255 interface
= DpbRestInterface(self
.__url
,
260 raise SdnConnectorError(
261 "Connection type not supported (must be SSH or REST)", 400)
262 self
.__post
= interface
.post
263 self
.__get
= interface
.get
264 self
.logger
.info("DPB WimConn Init OK")
266 def create_connectivity_service(self
, service_type
, connection_points
, **kwargs
):
267 self
.logger
.info("Creating a connectivity service")
269 response
= self
.__post
(self
.__ACTIONS
_MAP
.get("CREATE"))
270 if "service-id" in response
:
271 service_id
= int(response
.get("service-id"))
272 self
.logger
.debug("created service id {}".format(service_id
))
274 raise SdnConnectorError(
275 "Invalid create service response (could be an issue with the DPB)", 500)
276 data
= {"segment": []}
277 for point
in connection_points
:
278 data
["segment"].append({
279 "terminal-name": point
.get("service_endpoint_id"),
280 "label": int((point
.get("service_endpoint_encapsulation_info")).get("vlan")),
283 # "ingress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("ingress"),
284 # "egress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("egress")}
285 self
.__post
(self
.__ACTIONS
_MAP
.get("DEFINE"),
286 "/service/"+str(service_id
), data
, get_response
=False)
288 self
.__post
(self
.__ACTIONS
_MAP
.get("ACTIVATE"),
289 "/service/"+str(service_id
), get_response
=False)
291 "Created connectivity service id:{}".format(service_id
))
292 return (str(service_id
), None)
293 except Exception as e
:
294 raise SdnConnectorError(
295 "Connectivity service could not be made | text: {}".format(e
), 500)
297 def get_connectivity_service_status(self
, service_uuid
, conn_info
=None):
299 "Checking connectivity service status id:{}".format(service_uuid
))
301 "timeout-millis": 10000,
302 "acceptable": ["ACTIVE", "FAILED"]
305 response
= self
.__post
(self
.__ACTIONS
_MAP
.get(
306 "CHECK"), "/service/"+service_uuid
, data
)
307 if "status" in response
:
308 status
= response
.get("status", None)
309 self
.logger
.info("CHECKED CONNECTIVITY SERVICE STATUS")
310 return {"wim_status": self
.__STATUS
_MAP
.get(status
)}
312 raise SdnConnectorError(
313 "Invalid status check response (could be an issue with the DPB)", 500)
314 except Exception as e
:
315 raise SdnConnectorError(
316 "Failed to check service status | text: {}".format(e
), 500)
318 def delete_connectivity_service(self
, service_uuid
, conn_info
=None):
320 "Deleting connectivity service id: {}".format(service_uuid
))
322 self
.__post
(self
.__ACTIONS
_MAP
.get("RELEASE"),
323 "/service/"+service_uuid
, get_response
=False)
325 raise SdnConnectorError(
326 "Could not delete service id:{} (could be an issue with the DPB)".format(service_uuid
), 500)
328 "Deleted connectivity service id:{}".format(service_uuid
))
331 def edit_connectivity_service(self
, service_uuid
, conn_info
=None, connection_points
=None, **kwargs
):
333 "Editing connectivity service id: {}".format(service_uuid
))
335 "timeout-millis": 10000,
336 "acceptable": ["DORMANT"]
339 self
.__post
(self
.__ACTIONS
_MAP
.get("RESET"),
340 "/service/"+service_uuid
, get_response
=False)
341 response
= self
.__post
(self
.__ACTIONS
_MAP
.get(
342 "CHECK"), "/service/"+service_uuid
, data
)
343 if "status" in response
:
345 "Connectivity service {} reset".format(service_uuid
))
347 raise SdnConnectorError(
348 "Invalid status check response (could be an issue with the DPB)", 500)
349 except Exception as e
:
350 raise SdnConnectorError(
351 "Failed to reset service | text: {}".format(e
), 500)
353 data
= {"segment": []}
354 for point
in connection_points
:
355 data
["segment"].append({
356 "terminal-name": point
.get("service_endpoint_id"),
357 "label": int((point
.get("service_endpoint_encapsulation_info")).get("vlan")),
360 # "ingress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("ingress"),
361 # "egress-bw": (bandwidth.get(point.get("service_endpoint_id"))).get("egress")}
362 self
.__post
(self
.__ACTIONS
_MAP
.get("DEFINE"), "/service/" +
363 str(service_uuid
), data
, get_response
=False)
364 self
.__post
(self
.__ACTIONS
_MAP
.get("ACTIVATE"),
365 "/service/"+str(service_uuid
), get_response
=False)
366 except Exception as e
:
367 raise SdnConnectorError(
368 "Failed to edit connectivity service | text: {}".format(e
), 500)
370 "Edited connectivity service {}".format(service_uuid
))
373 def __check_service(self
, serv_type
, points
, kwargs
):
374 if not serv_type
in self
.__SUPPORTED
_SERV
_TYPES
:
375 raise SdnConnectorError("Service type no supported", 400)
376 # Future: BW Checks here