3 ############################################################################
4 # Copyright 2016 RIFT.io Inc #
6 # Licensed under the Apache License, Version 2.0 (the "License"); #
7 # you may not use this file except in compliance with the License. #
8 # You may obtain a copy of the License at #
10 # http://www.apache.org/licenses/LICENSE-2.0 #
12 # Unless required by applicable law or agreed to in writing, software #
13 # distributed under the License is distributed on an "AS IS" BASIS, #
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15 # See the License for the specific language governing permissions and #
16 # limitations under the License. #
17 ############################################################################
20 from contextlib import closing
31 class OnboardPkgError(Exception):
35 class OnboardPkgInputError(OnboardPkgError):
39 class OnboardPkgMissingPkg(OnboardPkgError):
43 class OnboardPkgFileError(OnboardPkgError):
47 class OnboardPkgMissingDescId(OnboardPkgError):
51 class OnboardPkgInvalidDescId(OnboardPkgError):
55 class OnboardPkgMissingAcct(OnboardPkgError):
59 class OnboardPkgSoConnError(OnboardPkgError):
63 class OnboardPkgCmdError(OnboardPkgError):
67 class OnboardPkgUploadError(OnboardPkgError):
71 class OnboardPkgRcConnError(OnboardPkgError):
75 class OnboardPkgDcError(OnboardPkgError):
79 class OnboardPkgAcctError(OnboardPkgError):
83 class OnboardPkgNsdError(OnboardPkgError):
87 class OnboardPkgInstError(OnboardPkgError):
91 class OnboardPkgInvalidPort(OnboardPkgError):
103 self._project = args.project
107 self._service_name = None
112 self._ip = args.so_ip
113 self._api_server_ip = "localhost"
115 self._uport = args.upload_port
116 self._onboard_port = args.onboard_port
117 self._rport = args.restconf_port
118 self._user = args.restconf_user
119 self._password = args.restconf_password
120 self._onboard_url = "curl -k --user \"{user}:{passwd}\" \"https://{ip}:{port}/composer/upload?api_server=https://{api_server_ip}&upload_server=https://{ip}\"". \
122 port=self._onboard_port,
124 passwd=self._password,
125 api_server_ip=self._api_server_ip)
127 self._upload_url = "curl -k https://{ip}:{port}/api/upload". \
131 self._headers = '-H "accept: application/json"' + \
132 ' -H "content-type: application/json"'
134 self._conf_url = "curl -k {header} --user \"{user}:{passwd}\" https://{ip}:{port}/api/config/project/{project}". \
135 format(header=self._headers,
137 passwd=self._password,
140 project=self._project)
142 self._oper_url = "curl -k {header} --user \"{user}:{passwd}\" https://{ip}:{port}/api/operational/project/{project}". \
143 format(header=self._headers,
145 passwd=self._password,
148 project=self._project)
154 def validate_args(self):
156 if args.upload_pkg is not None:
157 self._pkgs = args.upload_pkg
158 self.log.debug("Packages to upload: {}".format(self._pkgs))
159 if len(self._pkgs) == 0:
160 raise OnboardPkgMissingPkg('Need to specify atleast one package to upload')
162 for pkg in self._pkgs:
163 self.log.debug("Check pkg: {}".format(pkg))
164 if os.path.isfile(pkg) is False:
165 raise OnboardPkgFileError("Unable to access file: {}".format(pkg))
168 if args.nsd_id is None:
169 raise OnboardPkgMissingDescId("NS Descriptor ID required for instantiation")
172 self._dc = args.datacenter
174 if args.resource_orchestrator:
175 self._ro = args.resource_orchestrator
177 self._service_name = args.instantiate
178 self._nsd_id = args.nsd_id
180 self.log.debug("Instantiate NSD {} as {} on {}".format(self._nsd_id,
184 if (self._pkgs is None) and (self._nsd_id is None) and (not args.list_nsds):
185 raise OnboardPkgInputError("Need to specify either upload-pkg or instantiate or list options")
187 # Validate the port numbers are correct
188 def valid_port(port):
189 if 1 <= port <= 65535:
193 if not valid_port(self._uport):
194 raise OnboardPkgInvalidPort("Invalid upload port: {}".format(self._uport))
196 if not valid_port(self._rport):
197 raise OnboardPkgInvalidPort("Invalid Restconf port: {}".format(self._rport))
199 def _exec_cmd(self, cmd):
200 self.log.debug("Execute command: {}".format(cmd))
201 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
202 stderr=subprocess.PIPE, shell=True)
203 (output, err) = proc.communicate()
205 self.log.debug("Command exec status: {}\nSTDOUT: {}\nSTDERR: {}".
206 format(rc, output, err))
208 raise OnboardPkgCmdError("Command {} failed ({}): {}".
209 format(cmd, rc, err))
210 return output.decode("utf-8")
212 def validate_connectivity(self):
214 self.log.debug("Check connectivity to SO at {}:{}".
215 format(self._ip, self._uport))
217 with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
218 if sock.connect_ex((self._ip, self._uport)) != 0:
219 raise OnboardPkgSoConnError("Connection error to SO for upload at {}:{}".
220 format(self._ip, self._uport))
221 self.log.debug("Connection to SO upload port succeeded")
224 self.log.debug("Check connectivity to SO at {}:{}, with credentials {}:{}".
225 format(self._ip, self._rport, self._user, self._password))
227 rest_url = self._conf_url+"/ro-account"
229 output = self._exec_cmd(rest_url)
230 self.log.debug("Output of restconf validation: {}".
233 js = json.loads(output)
235 raise OnboardPkgRcConnError("SO Restconf connect error: {}".
238 self.log.debug("Connection to SO restconf port succeeded")
240 except OnboardPkgCmdError as e:
241 self.log.error("SO restconf connect failed: {}".format(e))
242 raise OnboardPkgRcConnError("SO Restconf connect error: {}".
246 def _upload_package(self, pkg):
247 upload_cmd = "{url} -F \"package=@{pkg}\" ". \
248 format(url=self._onboard_url,
250 self.log.debug("Upload pkg {} cmd: {}".format(pkg, upload_cmd))
252 output = self._exec_cmd(upload_cmd)
254 # Get the transaction id and wait for upload to complete
255 tx_id = json.loads(output)['transaction_id']
257 upload_status_url = "{url}/{id}/state". \
258 format(url=self._upload_url,
261 while status not in ['success', 'failure']:
262 output = self._exec_cmd(upload_status_url)
263 js = json.loads(output)
264 self.log.debug("Upload status of pkg {}: {}".format(pkg, js))
265 status = js['status']
267 if status != 'success':
268 raise OnboardPkgUploadError("Package {} upload failed: {}".
269 format(pkg, js['errors']))
271 self.log.info("Upload of package {} succeeded".format(pkg))
273 def upload_packages(self):
274 if self._pkgs is None:
275 self.log.debug("Upload packages not provided")
278 for pkg in self._pkgs:
279 self._upload_package(pkg)
281 def instantiate(self):
282 if self._nsd_id is None:
283 self.log.debug("No NSD ID provided for instantiation")
286 # Check id NSD ID is valid
287 nsd_url = "{url}/nsd-catalog/nsd/{nsd_id}". \
288 format(url=self._conf_url, nsd_id=self._nsd_id)
289 output = self._exec_cmd(nsd_url)
290 if (output is None) or (len(output) == 0):
292 raise OnboardPkgNsdError("NSD ID {} provided is not valid".
293 format(self._nsd_id))
295 js = json.loads(output)
297 raise OnboardPkgNsdError("NSD ID {} error: {}".
302 nsd = js['project-nsd:nsd']
303 except KeyError as e:
304 raise OnboardPkgNsdError("NSD ID {} provided is not valid".
305 format(self._nsd_id))
307 self.log.debug("NSD to instantiate: {}".format(nsd))
309 # Generate a UUID for NS
310 ns_id = str(uuid.uuid4())
311 self.log.debug("NS instance uuid: {}".format(ns_id))
313 # Build the nsr post data
315 'name': self._service_name,
318 nsr['datacenter'] = self._dc
321 nsr['resource-orchestrator'] = self._ro
323 data = {'nsr': [nsr]}
325 data_str = json.dumps(data)
326 self.log.debug("NSR post data: {}".format(data_str))
328 inst_url = "{url}/ns-instance-config -X POST -d '{data}'". \
329 format(url=self._conf_url, data=data_str)
330 output = self._exec_cmd(inst_url)
331 self.log.debug("Instantiate output: {}".format(output))
333 js = json.loads(output)
335 if "last-error" in js:
336 msg = "Error instantiating NS as {} with NSD {}: ". \
337 format(self._service_name, self._nsd_id,
340 raise OnboardPkgInstError(msg)
342 elif "rpc-reply" in js:
343 reply = js["rpc-reply"]
344 if "rpc-error" in reply:
345 msg = "Error instantiating NS as {} with NSD {}: ". \
346 format(self._service_name, self._nsd_id,
348 # self.log.error(msg)
349 raise OnboardPkgInstError(msg)
351 self.log.info("Successfully initiated instantiation of NS as {} ({})".
352 format(self._service_name, ns_id))
355 if self._args.list_nsds:
356 self.log.debug("Check NSDS at {}:{}, with credentials {}:{}".
357 format(self._ip, self._rport, self._user, self._password))
359 rest_url = self._conf_url+"/nsd-catalog/nsd"
361 output = self._exec_cmd(rest_url)
362 self.log.debug("Output of NSD list: {}".
365 js = json.loads(output)
367 raise OnboardPkgRcConnError("SO Restconf connect error: {}".
370 print("No NSDs found on SO")
373 self.log.debug("NSD list: {}".format(js))
374 print('List of NSDs on SO:\nName\tID')
375 for nsd in js['project-nsd:nsd']:
376 print('{}\t{}'.format(nsd['name'], nsd['id']))
378 except OnboardPkgCmdError as e:
379 self.log.error("SO restconf connect failed: {}".format(e))
380 raise OnboardPkgRcConnError("SO Restconf connect error: {}".
386 except Exception as e:
395 self.validate_connectivity()
396 self.upload_packages()
401 if __name__ == "__main__":
402 parser = argparse.ArgumentParser(description='Upload and instantiate NS')
403 parser.add_argument("-s", "--so-ip", default='localhost',
404 help="SO Launchpad IP")
406 parser.add_argument("-u", "--upload-pkg", action='append',
407 help="Descriptor packages to upload. " + \
408 "If multiple descriptors are provided, they are uploaded in the same sequence.")
410 parser.add_argument("-l", "--list-nsds", action='store_true',
411 help="List available network service descriptors")
413 parser.add_argument("-i", "--instantiate",
414 help="Instantiate a network service with the name")
415 parser.add_argument("-d", "--nsd-id",
416 help="Network descriptor ID to instantiate")
417 parser.add_argument("-D", "--datacenter",
418 help="OpenMano datacenter to instantiate on")
419 parser.add_argument("-r", "--resource-orchestrator",
420 help="RO account to instantiate on")
422 parser.add_argument("--project", default='default',
423 help="Project to use, default 'default'")
424 parser.add_argument("-o", "--onboard-port", default=8443, type=int,
425 help="Onboarding port number - node port number, default 8443")
426 parser.add_argument("-p", "--upload-port", default=4567, type=int,
427 help="Upload port number, default 4567")
428 parser.add_argument("-P", "--restconf-port", default=8008, type=int,
429 help="RESTconf port number, default 8008")
430 parser.add_argument("--restconf-user", default='admin',
431 help="RESTconf user name, default admin")
432 parser.add_argument("--restconf-password", default='admin',
433 help="RESTconf password, default admin")
435 parser.add_argument("-v", "--verbose", action='store_true',
436 help="Show more logs")
438 args = parser.parse_args()
440 fmt = logging.Formatter(
441 '%(asctime)-23s %(levelname)-5s (%(name)s@%(process)d:' \
442 '%(filename)s:%(lineno)d) - %(message)s')
443 log = logging.getLogger('onboard-pkg')
444 log.setLevel(logging.INFO)
446 log.setLevel(logging.DEBUG)
447 ch = logging.StreamHandler()
448 ch.setLevel(logging.DEBUG)
452 log.debug("Input arguments: {}".format(args))
455 ob = OnboardPackage(log, args)
457 except Exception as e: