blob: ba82e7edd016adfdae7734c1a9fdd23a3badcd21 [file] [log] [blame]
#!/usr/bin/env python3
############################################################################
# Copyright 2016 RIFT.io Inc #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
############################################################################
import argparse
from contextlib import closing
import logging
import os.path
import socket
import subprocess
import sys
import uuid
import json
class OnboardPkgError(Exception):
pass
class OnboardPkgInputError(OnboardPkgError):
pass
class OnboardPkgMissingPkg(OnboardPkgError):
pass
class OnboardPkgFileError(OnboardPkgError):
pass
class OnboardPkgMissingDescId(OnboardPkgError):
pass
class OnboardPkgInvalidDescId(OnboardPkgError):
pass
class OnboardPkgMissingAcct(OnboardPkgError):
pass
class OnboardPkgSoConnError(OnboardPkgError):
pass
class OnboardPkgCmdError(OnboardPkgError):
pass
class OnboardPkgUploadError(OnboardPkgError):
pass
class OnboardPkgRcConnError(OnboardPkgError):
pass
class OnboardPkgDcError(OnboardPkgError):
pass
class OnboardPkgAcctError(OnboardPkgError):
pass
class OnboardPkgNsdError(OnboardPkgError):
pass
class OnboardPkgInstError(OnboardPkgError):
pass
class OnboardPkgInvalidPort(OnboardPkgError):
pass
class OnboardPackage:
def __init__(self,
log,
args):
self._log = log
self._args = args
self._pkgs = None
self._service_name = None
self._nsd_id = None
self._dc = None
self._account = None
self._ip = args.so_ip
self._api_server_ip = "localhost"
self._uport = args.upload_port
self._onboard_port = args.onboard_port
self._rport = args.restconf_port
self._user = args.restconf_user
self._password = args.restconf_password
self._onboard_url = "curl -k --user \"{user}:{passwd}\" \"https://{ip}:{port}/composer/upload?api_server=https://{api_server_ip}&upload_server=https://{ip}\"". \
format(ip=self._ip,
port=self._onboard_port,
user=self._user,
passwd=self._password,
api_server_ip=self._api_server_ip)
self._upload_url = "curl -k https://{ip}:{port}/api/upload". \
format(ip=self._ip,
port=self._uport)
self._headers = '-H "accept: application/json"' + \
' -H "content-type: application/json"'
self._conf_url = "curl -k {header} --user \"{user}:{passwd}\" https://{ip}:{port}/api/config". \
format(header=self._headers,
user=self._user,
passwd=self._password,
ip=self._ip,
port=self._rport)
self._oper_url = "curl -k {header} --user \"{user}:{passwd}\" https://{ip}:{port}/api/operational". \
format(header=self._headers,
user=self._user,
passwd=self._password,
ip=self._ip,
port=self._rport)
@property
def log(self):
return self._log
def validate_args(self):
if args.upload_pkg is not None:
self._pkgs = args.upload_pkg
self.log.debug("Packages to upload: {}".format(self._pkgs))
if len(self._pkgs) == 0:
raise OnboardPkgMissingPkg('Need to specify atleast one package to upload')
for pkg in self._pkgs:
self.log.debug("Check pkg: {}".format(pkg))
if os.path.isfile(pkg) is False:
raise OnboardPkgFileError("Unable to access file: {}".format(pkg))
if args.instantiate:
if args.nsd_id is None:
raise OnboardPkgMissingDescId("NS Descriptor ID required for instantiation")
if args.datacenter:
try:
uuid.UUID(args.datacenter)
self._dc = args.datacenter
except ValueError as e:
raise OnboardPkgInvalidDescId("Invalid UUID for datacenter: {}".
format(args.datacenter))
elif args.vim_account:
self._account = args.vim_account
else:
raise OnboardPkgMissingAcct("Datacenter or VIM account required for instantiation")
self._service_name = args.instantiate
self._nsd_id = args.nsd_id
self.log.debug("Instantiate NSD {} as {} on {}".format(self._nsd_id,
self._service_name,
self._account))
if (self._pkgs is None) and (self._nsd_id is None):
raise OnboardPkgInputError("Need to specify either upload-pkg or instantiate options")
# Validate the port numbers are correct
def valid_port(port):
if 1 <= port <= 65535:
return True
return False
if not valid_port(self._uport):
raise OnboardPkgInvalidPort("Invalid upload port: {}".format(self._uport))
if not valid_port(self._rport):
raise OnboardPkgInvalidPort("Invalid Restconf port: {}".format(self._rport))
def _exec_cmd(self, cmd):
self.log.debug("Execute command: {}".format(cmd))
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=True)
(output, err) = proc.communicate()
rc = proc.returncode
self.log.debug("Command exec status: {}\nSTDOUT: {}\nSTDERR: {}".
format(rc, output, err))
if rc != 0:
raise OnboardPkgCmdError("Command {} failed ({}): {}".
format(cmd, rc, err))
return output.decode("utf-8")
def validate_connectivity(self):
if self._pkgs:
self.log.debug("Check connectivity to SO at {}:{}".
format(self._ip, self._uport))
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
if sock.connect_ex((self._ip, self._uport)) != 0:
raise OnboardPkgSoConnError("Connection error to SO for upload at {}:{}".
format(self._ip, self._uport))
self.log.debug("Connection to SO upload port succeeded")
if self._nsd_id:
self.log.debug("Check connectivity to SO at {}:{}, with credentials {}:{}".
format(self._ip, self._rport, self._user, self._password))
rest_url = self._conf_url+"/resource-orchestrator"
try:
output = self._exec_cmd(rest_url)
self.log.debug("Output of restconf validation: {}".
format(output))
if len(output) != 0:
js = json.loads(output)
if "error" in js:
raise OnboardPkgRcConnError("SO Restconf connect error: {}".
format(js["error"]))
self.log.debug("Connection to SO restconf port succeeded")
except OnboardPkgCmdError as e:
self.log.error("SO restconf connect failed: {}".format(e))
raise OnboardPkgRcConnError("SO Restconf connect error: {}".
format(e))
def _upload_package(self, pkg):
upload_cmd = "{url} -F \"package=@{pkg}\" ". \
format(url=self._onboard_url,
pkg=pkg)
self.log.debug("Upload pkg {} cmd: {}".format(pkg, upload_cmd))
output = self._exec_cmd(upload_cmd)
# Get the transaction id and wait for upload to complete
tx_id = json.loads(output)['transaction_id']
upload_status_url = "{url}/{id}/state". \
format(url=self._upload_url,
id=tx_id)
status = ""
while status not in ['success', 'failure']:
output = self._exec_cmd(upload_status_url)
js = json.loads(output)
self.log.debug("Upload status of pkg {}: {}".format(pkg, js))
status = js['status']
if status != 'success':
raise OnboardPkgUploadError("Package {} upload failed: {}".
format(pkg, js['errors']))
self.log.info("Upload of package {} succeeded".format(pkg))
def upload_packages(self):
if self._pkgs is None:
self.log.debug("Upload packages not provided")
return
for pkg in self._pkgs:
self._upload_package(pkg)
def instantiate(self):
if self._nsd_id is None:
self.log.debug("No NSD ID provided for instantiation")
return
# Check to see if datacenter is valid
if self._dc:
dc_url = "{url}/datacenters". format(url=self._oper_url)
output = self._exec_cmd(dc_url)
if (output is None) or (len(output) == 0):
# Account not found
raise OnboardPkgDcError("Datacenter {} provided is not valid".
format(self._dc))
found = False
js = json.loads(output)
if "ro-accounts" in js["rw-launchpad:datacenters"]:
for ro in js["rw-launchpad:datacenters"]["ro-accounts"]:
if "datacenters" in ro:
for dc in ro["datacenters"]:
if dc["uuid"] == self._dc:
self.log.debug("Found datacenter {}".format(dc))
found = True
break
if found:
break
if found is False:
raise OnboardPkgDcError("Datacenter {} provided is not valid".
format(self._dc))
# Check cloud account is valid, if provided
if self._account:
acct_url = "{url}/cloud/account/{acct}". \
format(url=self._conf_url, acct=self._account)
output = self._exec_cmd(acct_url)
if (output is None) or (len(output) == 0):
# Account not found
raise OnboardPkgAcctError("VIM/Cloud account {} provided is not valid".
format(self._account))
# Check id NSD ID is valid
nsd_url = "{url}/nsd-catalog/nsd/{nsd_id}". \
format(url=self._conf_url, nsd_id=self._nsd_id)
output = self._exec_cmd(nsd_url)
if (output is None) or (len(output) == 0):
# NSD not found
raise OnboardPkgNsdError("NSD ID {} provided is not valid".
format(self._nsd_id))
js = json.loads(output)
if "error" in js:
raise OnboardPkgNsdError("NSD ID {} error: {}".
format(self._nsd_id,
js['error']))
nsd = js['nsd:nsd']
self.log.debug("NSD to instantiate: {}".format(nsd))
# Generate a UUID for NS
ns_id = str(uuid.uuid4())
self.log.debug("NS instance uuid: {}".format(ns_id))
# Build the nsr post data
nsr = {"id": ns_id,
'name': self._service_name,
"nsd": nsd,}
if self._dc:
nsr['om-datacenter'] = self._dc
else:
nsr['cloud-account'] = self._account
data = {'nsr': [nsr]}
data_str = json.dumps(data)
self.log.debug("NSR post data: {}".format(data_str))
inst_url = "{url}/ns-instance-config -X POST -d '{data}'". \
format(url=self._conf_url, data=data_str)
output = self._exec_cmd(inst_url)
self.log.debug("Instantiate output: {}".format(output))
js = json.loads(output)
if "last-error" in js:
msg = "Error instantiating NS as {} with NSD {}: ". \
format(self._service_name, self._nsd_id,
js["last-error"])
self.log.error(msg)
raise OnboardPkgInstError(msg)
elif "rpc-reply" in js:
reply = js["rpc-reply"]
if "rpc-error" in reply:
msg = "Error instantiating NS as {} with NSD {}: ". \
format(self._service_name, self._nsd_id,
reply["rpc-error"])
# self.log.error(msg)
raise OnboardPkgInstError(msg)
self.log.info("Successfully initiated instantiation of NS as {} ({})".
format(self._service_name, ns_id))
def process(self):
try:
self.validate_args()
except Exception as e:
if args.verbose:
log.exception(e)
print("\nERROR:", e)
print("\n")
parser.print_help()
sys.exit(2)
self.validate_connectivity()
self.upload_packages()
self.instantiate()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Upload and instantiate NS')
parser.add_argument("-s", "--so-ip", default='localhost',
help="SO Launchpad IP")
parser.add_argument("-u", "--upload-pkg", action='append',
help="Descriptor packages to upload. " + \
"If multiple descriptors are provided, they are uploaded in the same sequence.")
parser.add_argument("-i", "--instantiate",
help="Instantiate a network service with the name")
parser.add_argument("-d", "--nsd-id",
help="Network descriptor ID to instantiate")
parser.add_argument("-D", "--datacenter",
help="OpenMano datacenter to instantiate on")
parser.add_argument("-c", "--vim-account",
help="Cloud/VIM account to instantiate on")
parser.add_argument("-o", "--onboard-port", default=8443, type=int,
help="Onboarding port number - node port number, default 8443")
parser.add_argument("-p", "--upload-port", default=4567, type=int,
help="Upload port number, default 4567")
parser.add_argument("-P", "--restconf-port", default=8008, type=int,
help="RESTconf port number, default 8008")
parser.add_argument("--restconf-user", default='admin',
help="RESTconf user name, default admin")
parser.add_argument("--restconf-password", default='admin',
help="RESTconf password, default admin")
parser.add_argument("-v", "--verbose", action='store_true',
help="Show more logs")
args = parser.parse_args()
fmt = logging.Formatter(
'%(asctime)-23s %(levelname)-5s (%(name)s@%(process)d:' \
'%(filename)s:%(lineno)d) - %(message)s')
log = logging.getLogger('onboard-pkg')
log.setLevel(logging.INFO)
if args.verbose:
log.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
ch.setFormatter(fmt)
log.addHandler(ch)
log.debug("Input arguments: {}".format(args))
try:
ob = OnboardPackage(log, args)
ob.process()
except Exception as e:
if args.verbose:
log.exception(e)
print("\nERROR:", e)
sys.exit(1)